Struggling with QuickChart Generation

Describe the problem/error/question

Hi everyone,
I have been struggling for many days trying to get QuickChart to work with complex multi-axis charts. I have an AI Agent that translates human language into chart instructions, which gets passed to a code node to generate a chart json format Chart.js. This is then passed on to an HTTP node to call quickchart API to generate the image url.
This works for many simple charts of all types, i.e. Bar, Line, Scatter, etc. But when the agent decides to create a multi-axis chart for a complex analysis, the generated url breaks on all browsers, and I get an error.
I have been working with ChatGPT for a week now trying to troubleshoot this annoying issue, and I am now running in circles. If anyone can get this particular chart (attached workflow) to work in a way that also supports simple charts, I will be grateful for life.

What is the error message (if any)?

On browsers I get the error: Chart error: TypeError: Cannot read properties of undefined (reading ‘options’)

Please share your workflow

Share the output returned by the last node

Information on your n8n setup

  • n8n 1.99.1 Cloud
  • Database (default: SQLite):
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • Running n8n via (Docker, npm, n8n cloud, desktop app):
  • Operating system:

Is this the result you were looking for?

1 Like

Oh man! That is EXACTLY what I, and ChatGPT have been struggling to get right for a week :slight_smile:
What were “we” doing wrong?

I set up the HTTP node exactly as you did, with no changes to my code node (I am assuming you didn’t change that code), and I got a weird binary output from the HTTP node with an error. Check screenshot

Im not sure, I asked Cursor IDE to fix it :sweat_smile:

TL;DR: It seems you messed up the data points and the way the chart was configured.
PS, Claude is much better at coding in my opinion and this is the model I used in Cursor. Try using Claude next time you’re trying to produce some chart config, it might do a better job (https://claude.ai/new).

For the error you received it said this:

Main Issues Fixed:

  1. Removed the wrapper chart object: Chart.js expects the configuration object directly, not wrapped in a chart property.

  2. Added explicit yAxisID to the first dataset: Both datasets should specify which axis they belong to for clarity.

  3. Added type: ‘linear’ to both y-axes: This is required for proper dual-axis functionality in Chart.js v3+.

  4. Added position: ‘left’ to the primary y-axis: Makes the configuration explicit.

  5. Added grid: { drawOnChartArea: false } to the secondary axis: This prevents grid lines from overlapping, making the chart cleaner.

Key improvements for dual-axis charts:

  • Clear axis association: Each dataset is explicitly tied to its axis

  • Proper axis positioning: Left and right axes are clearly defined

  • Clean grid display: Only the primary axis shows grid lines

  • Explicit axis types: Both axes are defined as linear scales

The configuration now follows the Chart.js line chart documentation standards and should render your dual-axis line chart correctly!

Then that produced a blank chart meaning there was some issue with the actual data points so it fixed it and said this:

Common causes of blank charts:

  1. Missing display: true - I’ve added this explicitly to all scales

  2. Invalid color formats - I’ve changed to proper RGB format

  3. Missing x-axis configuration - Added explicit x-axis setup

  4. Complex dual-axis setup - Simplified to single axis first

Key changes made:

  • :white_check_mark: Proper color formats: rgb(75, 192, 192) instead of “blue”

  • :white_check_mark: Added display: true to all scales

  • :white_check_mark: Added x-axis configuration

  • :white_check_mark: Simplified to single Y-axis initially

  • :white_check_mark: Added backgroundColor for better visibility

It then provided me with a duel axis example I hace not tried but here it is:

return {
  chart: {
    type: "line",
    data: {
      labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
      datasets: [
        { 
          label: "Count", 
          data: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120], 
          borderColor: "rgb(75, 192, 192)",
          backgroundColor: "rgba(75, 192, 192, 0.2)",
          fill: false,
          yAxisID: "y"
        },
        { 
          label: "Severity", 
          data: [100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210], 
          borderColor: "rgb(255, 99, 132)",
          backgroundColor: "rgba(255, 99, 132, 0.2)",
          fill: false, 
          yAxisID: "y1" 
        }
      ]
    },
    options: {
      responsive: true,
      interaction: {
        mode: 'index',
        intersect: false,
      },
      plugins: { 
        title: { 
          display: true, 
          text: "Test Dual-Axis" 
        },
        legend: { 
          display: true 
        } 
      },
      scales: {
        x: {
          display: true,
          title: {
            display: true,
            text: 'Month'
          }
        },
        y: { 
          type: 'linear',
          display: true,
          position: 'left',
          beginAtZero: true,
          title: { 
            display: true, 
            text: "Count" 
          }
        },
        y1: { 
          type: 'linear',
          display: true,
          position: 'right',
          beginAtZero: true,
          title: { 
            display: true, 
            text: "Severity" 
          },
          grid: {
            drawOnChartArea: false
          }
        }
      }
    }
  }
};

Share your workflow again here in a code block lemme see. It’s working on my side.
image

What version of n8n are you using?

I struggled for so long with Claude, and I get tired with its max msg length after a long chat, that’s why I went to ChatGPT. Anyways, I tried with Claude for a while until it ran out of msg length, and here is the latest code node logic it gave me.

{
“nodes”: [
{
“parameters”: {
“method”: “POST”,
“url”: “https://quickchart.io/chart”,
“sendBody”: true,
“specifyBody”: “json”,
“jsonBody”: “={\n "version": "2",\n "backgroundColor": "transparent",\n "width": 500,\n "height": 300,\n "devicePixelRatio": 1.0,\n "format": "png",\n "chart": {{ JSON.stringify($json.chart) }}\n}”,
“options”: {}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.2,
“position”: [
520,
20
],
“id”: “41f29b32-a509-4268-b1ec-5cca74dae5a7”,
“name”: “HTTP Request”
},
{
“parameters”: {
“jsCode”: “const raw = $json.output;\nlet rawStr = ‘’;\nif (typeof raw === ‘string’) {\n rawStr = raw;\n} else if (raw !== undefined && raw !== null) {\n rawStr = JSON.stringify(raw);\n} else {\n throw new Error(‘The value of $json.output is empty or not defined’);\n}\nconst stripped = rawStr\n .trim()\n .replace(/^json\\n?/, '')\n .replace(/$/, ‘’);\n\nlet parsed;\ntry {\n parsed = JSON.parse(stripped);\n} catch (err) {\n throw new Error('JSON parse failed: ’ + err.message);\n}\n\n// Build the chart configuration\nconst chartConfig = {\n type: parsed.chart_type,\n data: parsed.chart_data,\n options: parsed.chart_options || {}\n};\n\n// Detect if this is a dual-axis chart\nconst hasDualAxis = chartConfig.data.datasets && \n chartConfig.data.datasets.some(dataset => dataset.yAxisID === ‘y1’);\n\nif (hasDualAxis) {\n // Ensure proper dual-axis configuration\n \n // Fix datasets - ensure all have explicit yAxisID\n chartConfig.data.datasets.forEach((dataset, index) => {\n if (!dataset.yAxisID) {\n dataset.yAxisID = index === 0 ? ‘y’ : ‘y1’;\n }\n \n // Fix invalid colors - convert named colors to valid CSS colors\n if (dataset.borderColor === ‘blue’ || !dataset.borderColor) {\n dataset.borderColor = index === 0 ? ‘rgb(75, 192, 192)’ : ‘rgb(255, 99, 132)’;\n }\n if (dataset.backgroundColor === ‘blue’ || dataset.backgroundColor === ‘red’ || !dataset.backgroundColor) {\n dataset.backgroundColor = index === 0 ? ‘rgba(75, 192, 192, 0.2)’ : ‘rgba(255, 99, 132, 0.2)’;\n }\n \n // Remove problematic point properties that QuickChart doesn’t handle well\n delete dataset.pointBackgroundColor;\n delete dataset.pointRadius;\n delete dataset.pointHoverRadius;\n });\n \n // Ensure proper scale configuration\n if (!chartConfig.options.scales) {\n chartConfig.options.scales = {};\n }\n \n // Configure primary Y-axis\n if (!chartConfig.options.scales.y) {\n chartConfig.options.scales.y = {};\n }\n chartConfig.options.scales.y.type = ‘linear’;\n chartConfig.options.scales.y.position = ‘left’;\n if (!chartConfig.options.scales.y.hasOwnProperty(‘beginAtZero’)) {\n chartConfig.options.scales.y.beginAtZero = true;\n }\n \n // Configure secondary Y-axis \n if (!chartConfig.options.scales.y1) {\n chartConfig.options.scales.y1 = {};\n }\n chartConfig.options.scales.y1.type = ‘linear’;\n chartConfig.options.scales.y1.position = ‘right’;\n if (!chartConfig.options.scales.y1.hasOwnProperty(‘beginAtZero’)) {\n chartConfig.options.scales.y1.beginAtZero = true;\n }\n \n // Prevent grid overlap\n chartConfig.options.scales.y1.grid = {\n drawOnChartArea: false\n };\n \n} else {\n // Single-axis chart - ensure basic scale configuration\n if (!chartConfig.options.scales) {\n chartConfig.options.scales = {};\n }\n \n if (!chartConfig.options.scales.y) {\n chartConfig.options.scales.y = {};\n }\n \n if (!chartConfig.options.scales.y.hasOwnProperty(‘beginAtZero’)) {\n chartConfig.options.scales.y.beginAtZero = true;\n }\n}\n\n// Ensure basic chart options\nif (!chartConfig.options.responsive) {\n chartConfig.options.responsive = true;\n}\n\n// Return the complete QuickChart request body as a plain object\nreturn {\n version: "2",\n backgroundColor: "transparent",\n width: 500,\n height: 300,\n devicePixelRatio: 1.0,\n format: "png",\n chart: chartConfig\n};”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
240,
20
],
“id”: “c7ae0c90-fd89-4330-903c-96f96d707866”,
“name”: “Code1”
},
{
“parameters”: {},
“type”: “n8n-nodes-base.manualTrigger”,
“typeVersion”: 1,
“position”: [
-60,
20
],
“id”: “dccdc793-274c-469f-8401-e031dff5ba5c”,
“name”: “When clicking ‘Execute workflow’”
}
],
“connections”: {
“HTTP Request”: {
“main”: [

]
},
“Code1”: {
“main”: [
[
{
“node”: “HTTP Request”,
“type”: “main”,
“index”: 0
}
]
]
},
“When clicking ‘Execute workflow’”: {
“main”: [
[
{
“node”: “Code1”,
“type”: “main”,
“index”: 0
}
]
]
}
},
“pinData”: {
“When clicking ‘Execute workflow’”: [
{
“output”: {
“chart_type”: “line”,
“chart_data”: {
“labels”: [
“January”,
“February”,
“March”,
“April”,
“May”,
“June”,
“July”,
“August”,
“September”,
“October”,
“November”,
“December”
],
“datasets”: [
{
“label”: “Average Claims Count”,
“data”: [
18.18,
17.73,
18.73,
15.82,
16.1,
17.5,
18.1,
21.4,
17.4,
19.2,
15.5,
17.6
],
“borderColor”: “blue”,
“backgroundColor”: “blue”,
“fill”: false,
“yAxisID”: “y”,
“pointRadius”: 3,
“pointHoverRadius”: 6,
“pointBackgroundColor”: [
“blue”,
“blue”,
“blue”,
“blue”,
“blue”,
“blue”,
“blue”,
“blue”,
“blue”,
“blue”,
“blue”,
“blue”
]
},
{
“label”: “Average Claim Severity”,
“data”: [
1442.1,
945.3,
1273.4,
1092.33,
1280.36,
973.6,
901.08,
1297.67,
1171.02,
1564.81,
1141.09,
1002.95
],
“borderColor”: “red”,
“backgroundColor”: “red”,
“fill”: false,
“yAxisID”: “y1”,
“pointRadius”: 3,
“pointHoverRadius”: 6,
“pointBackgroundColor”: [
“red”,
“red”,
“red”,
“red”,
“red”,
“red”,
“red”,
“red”,
“red”,
“red”,
“red”,
“red”
]
}
]
},
“chart_options”: {
“responsive”: true,
“plugins”: {
“title”: {
“display”: true,
“text”: “Monthly Claims Patterns - Seasonality Analysis”
},
“legend”: {
“display”: true,
“position”: “top”
}
},
“scales”: {
“y”: {
“type”: “linear”,
“position”: “left”,
“beginAtZero”: false,
“title”: {
“display”: true,
“text”: “Average Claims Count”
},
“grid”: {
“display”: true
}
},
“y1”: {
“type”: “linear”,
“position”: “right”,
“beginAtZero”: false,
“title”: {
“display”: true,
“text”: “Average Claim Severity”
},
“grid”: {
“drawOnChartArea”: false,
“display”: true
}
}
}
}
}
}
]
},
“meta”: {
“instanceId”: “a046b6bda08e9c36a7dd21992f0c74569028f78589ca22f2792845226c8c5d28”
}
}

I really just want that chart JSON to be created successfully. Those AIs like GPT or Claude end up in circles, trying the same things over and over. If you can manage to generate a good chart with the exact data I pasted here, that will be great. Right now I get a Bad request in my HTTP node. No matter how many options Claude gave me, I always end up with this error.

Can you please share your code in a code block? I cant use the above as it messes it up if its not in a code block

Oh your code node is very different. I see you’re trying to set the data in the manual trigger and then process it through the code node? Why are you trying to do that? Im suspecting you’re breaking the structure of the chart input again.

What exactly is your use case so we can find a better solution. What will trigger your workflow, what data are you sending in or retrieving etc so we can build a robust solution.

In short, you’ll need to make sure the code node returns the exact structure I gave you in my example from earlier

The mock data in the Manual Trigger node is a typical output from my AI Agent node. It is literally copied from one real execution, and it is the type of complex chart that I am struggling with. So it is a real scenario.

The Code node is different I agree, and it is what Claude finally gave me, but it isn’t working. My requirement now is this: How can I translate the chart JSON in the manual trigger node to a real chart on QuickChart?

What I have been doing so far is that I’ve been changing the AI Agent system prompt to generate a chart JSON that respects QuickChart’s requirements for multi-axis charts. The agent created the data you see on the manual trigger node.
The logic was that we need a code node to make sure that the output from the agent complies with QuickChart requirements for this type of chart.

What I have not tested so far is that, do I even need the code node with the output from the agent? I will test that soon, because I am now thinking that after so many modifications to the agent’s prompt, it might be that I don’t need to process the JSON any further. You may also try it out, straight from the manual trigger to the HTTP node and see if a chart gets created.

The code node was a result of my countless interactions with ChatGPT and Claude, but it might be redundant after the many agent prompt modifications.

So, I just really want the chart JSON from the agent to result in a chart! Whichever way that can be achieved will work for me.

Thanks for sticking around and supporting me, I really appreciate that a lot…
Ashraf

One more thing…

I initially tried using the new n8n community node for QuickChart, but after days of chatting with ChatGPT it finally concluded that the node was not setup for complex charts! I am not sure if you agree with this, but it is worth testing out also. I am really out of things to try…

Ok so if you’re trying to produce charts straight from an AI Agent, then you dont need the code block not my fixed data object I gave you. You’ll to possibly implement a RAG system so that the agent has a knowledge base of the quickchart and chart js documentation. Dont assume these LLMs know everything out of the box. Also trying to bake the chartjs knowledge into a single prompt is likely to fail and produce garbage which will break like it has been.

There is a few ways to approach this problem, but a RAG would be a good start I would say and it will involve a bit of preparation before things will start working.

Your other option is to try and train an LLM with the knowledge base of the documentation from quickchart and chart.js but involve a bit more complex steps to get working quickly.

Can you share the current prompt you’re using or an example prompt you’re using as input. I’ll need to go build something which will work but I need to have an example. What is the data based on, what are you asking the agent for etc etc

I can share that with you, and I have thought about the complex approach of RAG, but the more I dig into this the more I see that it is a simple problem..

For example, the workflow you sent me initially worked fine with your code node, which produced a particular “structure” for quickchart, but didn’t work for me with my agent-generated structure I guess!
the error I get in the browser when I try the chart url that is generated by my various workflow versions almost always is: Chart error: TypeError: Cannot read properties of undefined (reading ‘options’)

According to ChatGPT and Claude, this is a failure to parse the chart JSON by QuickChart, because of a malformed structure. I was thinking that you might be able to use my exact use-case JSON and make it structured in the way you used the first time.

I am using an AI Agent connected to a Postgres tool to analyze data in a Postgres database. The data is about vehicle insurance policies. The agent is doing a great job analyzing the data, drawing insights, and creating nice line, bar, scatter and area charts, when the chart is simple and does not include multi-axis. But complex, multi-axis charts create an issue.

So as you can see, any working example, like the one you shared initially, but modified to work with the mock data I sent last, will work for me.

THanks
ashraf

Yes, so hence Im saying if you have an agent which understand all of the chartjs and quickchart documentation, it should be able to understand how to create the multi axis charts based on the data you pull from your db. Currently it seems this is where the shortfall is.

1 Like

Thank very much Wouter! I was actually able to resolve it late yesterday . I just took time reviewing the QuickChart documentation, and how they expect chart JSONs to look like and I was able to adjust the agent and the code node to output the exact same format. one big issue was that the quickchart API actually didn’t need outer { “chart”: {} } object in the JSON, it works fine with just starting from the chart parameters, such as { “type”: “line”, …, …}

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.