From 441595dd6d900ecaac1dc115168762ee33d7f141 Mon Sep 17 00:00:00 2001 From: Gerard Burns Date: Thu, 18 Apr 2024 10:04:01 +0100 Subject: [PATCH] line chart --- packages/client/manifest.json | 2 +- .../components/app/charts/ApexChart.svelte | 3 + .../src/components/app/charts/BarChart.svelte | 1 - .../components/app/charts/LineChart.svelte | 214 +++++++++--------- 4 files changed, 112 insertions(+), 108 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 008496a8e9..96cdcf4927 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -1859,7 +1859,7 @@ "type": "select", "label": "Colors", "key": "palette", - "defaultValue": "Palette 1", + "defaultValue": "palette1", "options": [ { "label": "Custom", "value": "Custom" }, { "label": "Palette 1", "value": "palette1" }, diff --git a/packages/client/src/components/app/charts/ApexChart.svelte b/packages/client/src/components/app/charts/ApexChart.svelte index 55e37a44f2..db99a00834 100644 --- a/packages/client/src/components/app/charts/ApexChart.svelte +++ b/packages/client/src/components/app/charts/ApexChart.svelte @@ -16,6 +16,9 @@ let chart; const updateChart = async (newOptions) => { + // Line charts won't transition from category to datetime types properly without + // calling this with an empty object first; I don't know why this works. + await chart?.updateOptions({}) await chart?.updateOptions(newOptions) } diff --git a/packages/client/src/components/app/charts/BarChart.svelte b/packages/client/src/components/app/charts/BarChart.svelte index 40ae3960a3..dc71792053 100644 --- a/packages/client/src/components/app/charts/BarChart.svelte +++ b/packages/client/src/components/app/charts/BarChart.svelte @@ -33,7 +33,6 @@ $: labelType = dataProvider?.schema?.[labelColumn]?.type === 'datetime' ? "datetime" : "category" - $: formatter = getFormatter(labelType, valueUnits) $: xAxisFormatter = getFormatter(labelType, valueUnits, horizontal, "x") $: yAxisFormatter = getFormatter(labelType, valueUnits, horizontal, "y") diff --git a/packages/client/src/components/app/charts/LineChart.svelte b/packages/client/src/components/app/charts/LineChart.svelte index 599d150d7b..c91bb56e07 100644 --- a/packages/client/src/components/app/charts/LineChart.svelte +++ b/packages/client/src/components/app/charts/LineChart.svelte @@ -16,7 +16,7 @@ export let dataLabels export let curve export let legend - export let yAxisUnits + export let valueUnits export let palette export let c1, c2, c3, c4, c5 @@ -25,122 +25,124 @@ export let stacked export let gradient - $: options = setUpChart( - title, - dataProvider, - labelColumn, - valueColumns, - xAxisLabel, - yAxisLabel, - height, - width, - animate, - dataLabels, - curve, - legend, - yAxisUnits, - palette, - area, - stacked, - gradient, - c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null, - customColor - ) + $: { + console.log(palette); + } - $: customColor = palette === "Custom" + const formatters = { + ["Default"]: val => val, + ["Thousands"]: val => `${Math.round(val / 1000)}K`, + ["Millions"]: val => `${Math.round(val / 1000000)}M`, + ["Datetime"]: val => (new Date(val)).toLocaleString() + } - const setUpChart = ( - title, - dataProvider, - labelColumn, - valueColumns, - xAxisLabel, - yAxisLabel, - height, - width, - animate, - dataLabels, - curve, - legend, - yAxisUnits, - palette, - area, - stacked, - gradient, - colors, - customColor - ) => { - const allCols = [labelColumn, ...(valueColumns || [null])] - if ( - !dataProvider || - !dataProvider.rows?.length || - allCols.find(x => x == null) - ) { - return null + $: series = getSeries(dataProvider, valueColumns) + $: categories = getCategories(dataProvider, labelColumn); + + $: labelType = dataProvider?.schema?.[labelColumn]?.type === 'datetime' ? + "datetime" : "category" + $: xAxisFormatter = getFormatter(labelType, valueUnits, "x") + $: yAxisFormatter = getFormatter(labelType, valueUnits, "y") + + $: options = { + series, + stroke: { + curve: curve.toLowerCase() + }, + colors: palette === "Custom" ? [c1, c2, c3, c4, c5] : [], + theme: { + palette: palette === "Custom" ? null : palette + }, + legend: { + show: legend, + position: "top", + horizontalAlign: "right", + showForSingleSeries: true, + showForNullSeries: true, + showForZeroSeries: true, + }, + title: { + text: title, + }, + dataLabels: { + enabled: dataLabels + }, + chart: { + height: height == null || height === "" ? "auto" : height, + width: width == null || width === "" ? "100%" : width, + type: "line", + stacked, + animations: { + enabled: animate + }, + toolbar: { + show: false, + }, + zoom: { + enabled: false, + }, + }, + xaxis: { + type: labelType, + categories, + labels: { + formatter: xAxisFormatter + }, + title: { + text: xAxisLabel + } + }, + yaxis: { + labels: { + formatter: yAxisFormatter + }, + title: { + text: yAxisLabel + } } + } - // Fetch, filter and sort data - const { schema, rows } = dataProvider - const data = rows - if (!schema || !data.length) { - return null - } + const getSeries = (datasource, valueColumns = []) => { + const rows = datasource.rows ?? []; - // Initialise default chart - let builder = new ApexOptionsBuilder() - .title(title) - .type(area ? "area" : "line") - .width(width) - .height(height) - .xLabel(xAxisLabel) - .yLabel(yAxisLabel) - .dataLabels(dataLabels) - .animate(animate) - .curve(curve.toLowerCase()) - .gradient(gradient) - .stacked(stacked) - .legend(legend) - .yUnits(yAxisUnits) - .palette(palette) - .colors(customColor ? colors : null) - - // Add data - let useDates = false - if (schema[labelColumn]) { - const labelFieldType = schema[labelColumn].type - builder = builder.xType(labelFieldType) - useDates = labelFieldType === "datetime" - } - const series = (valueColumns ?? []).map(column => ({ + return valueColumns.map(column => ({ name: column, - data: data.map(row => { - if (!useDates) { - const value = get(row, column); - - if (Array.isArray(value)) { - return null; - } - - if (Number.isNaN(parseInt(value, 10))) { - return null; - } - - return value; - } else { - return [row[labelColumn], row[column]] - } + data: rows.map(row => { + return row?.[column] }), })) - builder = builder.series(series) - if (!useDates) { - builder = builder.xCategories(data.map(row => row[labelColumn])) - } else { - builder = builder.clearXFormatter() + } + + const getCategories = (datasource, labelColumn) => { + const rows = datasource.rows ?? []; + + return rows.map(row => { + const value = row?.[labelColumn] + + // If a nullish or non-scalar type, replace it with an empty string + if (!["string", "number", "boolean"].includes(typeof value)) { + return "" + } + + return value; + }) + } + + const getFormatter = (labelType, valueUnits, axis) => { + const isLabelAxis = axis === "x" + + if (labelType === "datetime" && isLabelAxis) { + return formatters["Datetime"] } - // Build chart options - return builder.getOptions() + if (isLabelAxis) { + return formatters["Default"] + } + + return formatters[valueUnits] } + + $: console.log("opt", options);