first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,19 @@
{
"node": "n8n-nodes-base.quickChart",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Marketing"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/quickchart/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.quickchart/"
}
]
},
"alias": ["image", "graph", "report", "chart", "diagram", "data", "visualize"]
}
@@ -0,0 +1,432 @@
import type {
IDataObject,
IExecuteFunctions,
IHttpRequestOptions,
IN8nHttpFullResponse,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { jsonParse, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import {
CHART_TYPE_OPTIONS,
Fill_CHARTS,
HORIZONTAL_CHARTS,
ITEM_STYLE_CHARTS,
POINT_STYLE_CHARTS,
} from './constants';
import type { IDataset } from './types';
export class QuickChart implements INodeType {
description: INodeTypeDescription = {
displayName: 'QuickChart',
name: 'quickChart',
icon: 'file:quickChart.svg',
group: ['output'],
description: 'Create a chart via QuickChart',
version: 1,
defaults: {
name: 'QuickChart',
},
usableAsTool: true,
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
properties: [
{
displayName: 'Chart Type',
name: 'chartType',
type: 'options',
default: 'bar',
options: CHART_TYPE_OPTIONS,
description: 'The type of chart to create',
},
{
displayName: 'Add Labels',
name: 'labelsMode',
type: 'options',
options: [
{
name: 'Manually',
value: 'manually',
},
{
name: 'From Array',
value: 'array',
},
],
default: 'manually',
},
{
displayName: 'Labels',
name: 'labelsUi',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
sortable: true,
},
default: {},
required: true,
description: 'Labels to use in the chart',
placeholder: 'Add Label',
options: [
{
name: 'labelsValues',
displayName: 'Labels',
values: [
{
displayName: 'Label',
name: 'label',
type: 'string',
default: '',
},
],
},
],
displayOptions: {
show: {
labelsMode: ['manually'],
},
},
},
{
displayName: 'Labels Array',
name: 'labelsArray',
type: 'string',
required: true,
default: '',
placeholder: 'e.g. ["Berlin", "Paris", "Rome", "New York"]',
displayOptions: {
show: {
labelsMode: ['array'],
},
},
description: 'The array of labels to be used in the chart',
},
{
displayName: 'Data',
name: 'data',
type: 'json',
default: '',
description:
'Data to use for the dataset, documentation and examples <a href="https://quickchart.io/documentation/chart-types/" target="_blank">here</a>',
placeholder: 'e.g. [60, 10, 12, 20]',
required: true,
},
{
displayName: 'Put Output In Field',
name: 'output',
type: 'string',
default: 'data',
required: true,
description:
'The binary data will be displayed in the Output panel on the right, under the Binary tab',
hint: 'The name of the output field to put the binary file data in',
},
{
displayName: 'Chart Options',
name: 'chartOptions',
type: 'collection',
placeholder: 'Add option',
default: {},
options: [
{
displayName: 'Background Color',
name: 'backgroundColor',
type: 'color',
typeOptions: {
showAlpha: true,
},
default: '',
description: 'Background color of the chart',
},
{
displayName: 'Device Pixel Ratio',
name: 'devicePixelRatio',
type: 'number',
default: 2,
typeOptions: {
minValue: 1,
maxValue: 2,
},
description: 'Pixel ratio of the chart',
},
{
displayName: 'Format',
name: 'format',
type: 'options',
default: 'png',
description: 'File format of the resulting chart',
options: [
{
name: 'PNG',
value: 'png',
},
{
name: 'PDF',
value: 'pdf',
},
{
name: 'SVG',
value: 'svg',
},
{
name: 'WebP',
value: 'webp',
},
],
},
{
displayName: 'Height',
name: 'height',
type: 'number',
default: 300,
description: 'Height of the chart',
},
{
displayName: 'Horizontal',
name: 'horizontal',
type: 'boolean',
default: false,
description: 'Whether the chart should use its Y axis horizontal',
displayOptions: {
show: {
'/chartType': HORIZONTAL_CHARTS,
},
},
},
{
displayName: 'Width',
name: 'width',
type: 'number',
default: 500,
description: 'Width of the chart',
},
],
},
{
displayName: 'Dataset Options',
name: 'datasetOptions',
type: 'collection',
placeholder: 'Add option',
default: {},
options: [
{
displayName: 'Background Color',
name: 'backgroundColor',
type: 'color',
default: '',
typeOptions: {
showAlpha: true,
},
description:
'Color used for the background the dataset (area of a line graph, fill of a bar chart, etc.)',
},
{
displayName: 'Border Color',
name: 'borderColor',
type: 'color',
typeOptions: {
showAlpha: true,
},
default: '',
description: 'Color used for lines of the dataset',
},
{
displayName: 'Fill',
name: 'fill',
type: 'boolean',
default: true,
description: 'Whether to fill area of the dataset',
displayOptions: {
show: {
'/chartType': Fill_CHARTS,
},
},
},
{
displayName: 'Label',
name: 'label',
type: 'string',
default: '',
description: 'The label of the dataset',
},
{
displayName: 'Point Style',
name: 'pointStyle',
type: 'options',
default: 'circle',
description: 'Style to use for points of the dataset',
options: [
{
name: 'Circle',
value: 'circle',
},
{
name: 'Cross',
value: 'cross',
},
{
name: 'CrossRot',
value: 'crossRot',
},
{
name: 'Dash',
value: 'dash',
},
{
name: 'Line',
value: 'line',
},
{
name: 'Rect',
value: 'rect',
},
{
name: 'Rect Rot',
value: 'rectRot',
},
{
name: 'Rect Rounded',
value: 'rectRounded',
},
{
name: 'Star',
value: 'star',
},
{
name: 'Triangle',
value: 'triangle',
},
],
displayOptions: {
show: {
'/chartType': POINT_STYLE_CHARTS,
},
},
},
],
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const datasets: IDataset[] = [];
let chartType = '';
const labels: string[] = [];
const labelsMode = this.getNodeParameter('labelsMode', 0) as string;
if (labelsMode === 'manually') {
const labelsUi = this.getNodeParameter('labelsUi.labelsValues', 0, []) as IDataObject[];
if (labelsUi.length) {
for (const labelValue of labelsUi as [{ label: string[] | string }]) {
if (Array.isArray(labelValue.label)) {
labels?.push(...labelValue.label);
} else {
labels?.push(labelValue.label);
}
}
}
} else {
const labelsArray = this.getNodeParameter('labelsArray', 0, '') as string;
const errorMessage =
'Labels Array is not a valid array, use valid JSON format, or specify it by expressions';
if (Array.isArray(labelsArray)) {
labels.push(...labelsArray);
} else {
const labelsArrayParsed = jsonParse<string[]>(labelsArray, {
errorMessage,
});
if (!Array.isArray(labelsArrayParsed)) {
throw new NodeOperationError(this.getNode(), errorMessage);
}
labels.push(...labelsArrayParsed);
}
}
for (let i = 0; i < items.length; i++) {
const data = this.getNodeParameter('data', i) as string;
const datasetOptions = this.getNodeParameter('datasetOptions', i) as IDataObject;
const backgroundColor = datasetOptions.backgroundColor as string;
const borderColor = datasetOptions.borderColor as string | undefined;
const fill = datasetOptions.fill as boolean | undefined;
const label = (datasetOptions.label as string) || 'Chart';
const pointStyle = datasetOptions.pointStyle as string | undefined;
chartType = this.getNodeParameter('chartType', i) as string;
if (HORIZONTAL_CHARTS.includes(chartType)) {
const horizontal = this.getNodeParameter('chartOptions.horizontal', i, false) as boolean;
if (horizontal) {
chartType =
'horizontal' + chartType[0].toUpperCase() + chartType.substring(1, chartType.length);
}
}
// Boxplots and Violins are an addon that uses the name 'itemStyle'
// instead of 'pointStyle'.
let pointStyleName = 'pointStyle';
if (ITEM_STYLE_CHARTS.includes(chartType)) {
pointStyleName = 'itemStyle';
}
datasets.push({
label,
data,
backgroundColor,
borderColor,
type: chartType,
fill,
[pointStyleName]: pointStyle,
});
}
const output = this.getNodeParameter('output', 0) as string;
const chartOptions = this.getNodeParameter('chartOptions', 0) as IDataObject;
const chart = {
type: chartType,
data: {
labels,
datasets,
},
};
const options: IHttpRequestOptions = {
method: 'GET',
url: 'https://quickchart.io/chart',
qs: {
chart: JSON.stringify(chart),
...chartOptions,
},
returnFullResponse: true,
encoding: 'arraybuffer',
json: false,
};
const response = (await this.helpers.httpRequest(options)) as IN8nHttpFullResponse;
let mimeType = response.headers['content-type'] as string | undefined;
mimeType = mimeType ? mimeType.split(';').find((value) => value.includes('/')) : undefined;
return [
[
{
binary: {
[output]: await this.helpers.prepareBinaryData(
response.body as Buffer,
undefined,
mimeType,
),
},
json: { chart },
},
],
];
}
}
@@ -0,0 +1,30 @@
import type { INodePropertyOptions } from 'n8n-workflow';
// Disable some charts that use different datasets for now
export const CHART_TYPE_OPTIONS: INodePropertyOptions[] = [
{
name: 'Bar Chart',
value: 'bar',
},
{
name: 'Doughnut Chart',
value: 'doughnut',
},
{
name: 'Line Chart',
value: 'line',
},
{
name: 'Pie Chart',
value: 'pie',
},
{
name: 'Polar Chart',
value: 'polarArea',
},
];
export const HORIZONTAL_CHARTS = ['bar', 'boxplot', 'violin'];
export const ITEM_STYLE_CHARTS = ['boxplot', 'horizontalBoxplot', 'violin', 'horizontalViolin'];
export const Fill_CHARTS = ['line'];
export const POINT_STYLE_CHARTS = ['line'];
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="445" height="445"><g style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;fill:none;fill-rule:nonzero;opacity:1"><path d="M40.135 90h-8.782a2.75 2.75 0 0 1-2.75-2.75V45.381a2.75 2.75 0 0 1 2.75-2.75h8.782a2.75 2.75 0 0 1 2.75 2.75V87.25a2.75 2.75 0 0 1-2.75 2.75" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;fill:#ffc36e;fill-rule:nonzero;opacity:1" transform="translate(-2.472 -2.472)scale(4.94)"/><path d="M58.647 90h-8.782a2.75 2.75 0 0 1-2.75-2.75V31.449a2.75 2.75 0 0 1 2.75-2.75h8.782a2.75 2.75 0 0 1 2.75 2.75V87.25a2.75 2.75 0 0 1-2.75 2.75" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;fill:#a5d76e;fill-rule:nonzero;opacity:1" transform="translate(-2.472 -2.472)scale(4.94)"/><path d="M21.624 90h-8.782a2.75 2.75 0 0 1-2.75-2.75V59.313a2.75 2.75 0 0 1 2.75-2.75h8.782a2.75 2.75 0 0 1 2.75 2.75V87.25a2.75 2.75 0 0 1-2.75 2.75" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;fill:#d2555a;fill-rule:nonzero;opacity:1" transform="translate(-2.472 -2.472)scale(4.94)"/><path d="M77.158 90h-8.782a2.75 2.75 0 0 1-2.75-2.75V17.517a2.75 2.75 0 0 1 2.75-2.75h8.782a2.75 2.75 0 0 1 2.75 2.75V87.25a2.75 2.75 0 0 1-2.75 2.75" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;fill:#78d2be;fill-rule:nonzero;opacity:1" transform="translate(-2.472 -2.472)scale(4.94)"/><path d="M67.773 17.977h18.183c1.787 0 2.682-2.258 1.418-3.578L74.185.614a1.944 1.944 0 0 0-2.836 0L58.16 14.398c-1.264 1.321-.369 3.578 1.418 3.578z" style="stroke:none;stroke-width:1;stroke-dasharray:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;fill:#78d2be;fill-rule:nonzero;opacity:1" transform="translate(-2.472 -2.472)scale(4.94)"/></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,90 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import type { WorkflowTestData } from 'n8n-workflow';
import nock from 'nock';
describe('Test QuickChart Node', () => {
beforeEach(async () => {
nock('https://quickchart.io')
.persist()
.get(/chart.*/)
.reply(200, { success: true });
});
const testHarness = new NodeTestHarness();
const tests: WorkflowTestData[] = [
{
description: 'nodes/QuickChart/test/QuickChart.workflow.json',
input: {
workflowData: testHarness.readWorkflowJSON('QuickChart.workflow.json'),
},
output: {
nodeData: {
BarChart: [
[
{
json: {
chart: {
type: 'horizontalBar',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [
{
label: 'Free Users',
data: [50, 60, 70, 180],
backgroundColor: '#121d6d77',
borderColor: '#e81010',
type: 'horizontalBar',
},
{
label: 'Paid Users',
data: [30, 10, 14, 25],
backgroundColor: '#0c0d0d96',
borderColor: '#e81010',
type: 'horizontalBar',
},
],
},
},
},
},
],
],
Doughnut: [
[
{
json: {
chart: {
type: 'doughnut',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [
{
label: 'Free Users',
data: [50, 60, 70, 180],
backgroundColor: '#121d6d77',
borderColor: '#e81010',
type: 'doughnut',
},
{
label: 'Paid Users',
data: [30, 10, 14, 25],
backgroundColor: '#0c0d0d96',
borderColor: '#e81010',
type: 'doughnut',
},
],
},
},
},
},
],
],
},
},
},
];
for (const testData of tests) {
testHarness.setupTest(testData);
}
});
@@ -0,0 +1,147 @@
{
"meta": {
"instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c"
},
"nodes": [
{
"parameters": {},
"id": "5ebe2f65-45db-4b22-bd2b-43993c20806f",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [740, 320]
},
{
"parameters": {
"data": [
{
"label": "Free Users",
"labels": ["Berlin", "Paris", "Rome", "New York"],
"data": [50, 60, 70, 180],
"backgroundColor": "#121d6d77",
"chartType": "line"
},
{
"label": "Paid Users",
"labels": ["Berlin", "Paris", "Rome", "New York"],
"data": [30, 10, 14, 25],
"backgroundColor": "#0c0d0d96",
"chartType": "bar"
}
]
},
"id": "2e81f78c-41a5-48de-80c4-74abf163cd57",
"name": "Code",
"type": "n8n-nodes-testing.testData",
"typeVersion": 1,
"position": [980, 320]
},
{
"parameters": {
"labelsUi": {
"labelsValues": [
{
"label": "Q1"
},
{
"label": "Q2"
},
{
"label": "Q3"
},
{
"label": "Q4"
}
]
},
"data": "={{ $json.data }}",
"chartOptions": {
"backgroundColor": "#f93636ff",
"devicePixelRatio": 2,
"format": "png",
"height": 300,
"horizontal": true,
"width": 500
},
"datasetOptions": {
"backgroundColor": "={{ $json[\"backgroundColor\"] }}",
"borderColor": "#e81010",
"label": "={{ $json[\"label\"] }}"
}
},
"name": "BarChart",
"type": "n8n-nodes-base.quickChart",
"typeVersion": 1,
"position": [1220, 200],
"id": "9f6c9d1c-2732-473f-a357-5766265cd0db"
},
{
"parameters": {
"chartType": "doughnut",
"labelsUi": {
"labelsValues": [
{
"label": "Q1"
},
{
"label": "Q2"
},
{
"label": "Q3"
},
{
"label": "Q4"
}
]
},
"data": "={{ $json.data }}",
"chartOptions": {
"backgroundColor": "#f93636ff",
"devicePixelRatio": 2,
"format": "png",
"height": 300,
"width": 500
},
"datasetOptions": {
"backgroundColor": "={{ $json[\"backgroundColor\"] }}",
"borderColor": "#e81010",
"label": "={{ $json[\"label\"] }}"
}
},
"name": "Doughnut",
"type": "n8n-nodes-base.quickChart",
"typeVersion": 1,
"position": [1220, 400],
"id": "6c8e1463-c384-4f5c-9de3-d7e052b02b0a"
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "BarChart",
"type": "main",
"index": 0
},
{
"node": "Doughnut",
"type": "main",
"index": 0
}
]
]
}
}
}
@@ -0,0 +1,12 @@
import type { IDataObject } from 'n8n-workflow';
export interface IDataset {
label?: string;
data: string | IDataObject;
backgroundColor?: string;
borderColor?: string;
color?: string;
type?: string;
fill?: boolean;
pointStyle?: string;
}