From a8b1b40e9ba5063cf1892f7c029babb37c1c147a Mon Sep 17 00:00:00 2001 From: kevinlee Date: Thu, 16 Jan 2025 17:04:17 +1100 Subject: [PATCH 1/7] try customised component --- package-lock.json | 18 ++--- package.json | 2 +- .../BasicPieComponent.emb.ts | 55 +++++++++++++ .../BasicPieComponent/BasicPieComponent.tsx | 77 +++++++++++++++++++ .../BasicPieComponent/index.ts | 1 + yarn.lock | 18 ++--- 6 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 src/components/ailo-components/BasicPieComponent/BasicPieComponent.emb.ts create mode 100644 src/components/ailo-components/BasicPieComponent/BasicPieComponent.tsx create mode 100644 src/components/ailo-components/BasicPieComponent/index.ts diff --git a/package-lock.json b/package-lock.json index a2495613..9055a5b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ }, "devDependencies": { "@embeddable.com/core": "^2.8.1", - "@embeddable.com/react": "^2.9.1", + "@embeddable.com/react": "^2.9.2", "@embeddable.com/sdk-core": "^3.12.4", "@embeddable.com/sdk-react": "^3.10.4", "@trivago/prettier-plugin-sort-imports": "^4.3.0", @@ -595,23 +595,21 @@ "license": "MIT" }, "node_modules/@embeddable.com/core": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/@embeddable.com/core/-/core-2.8.1.tgz", - "integrity": "sha512-px0Z9B1p2Wr6f/5eeOnXcEtv5/WUweznupBIYC7YfkvADApAWxg7XzHjwUhLCCL0DhZJcOT5UtNF9e9cgNPBGQ==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@embeddable.com/core/-/core-2.8.2.tgz", + "integrity": "sha512-5fGgPtbvIq5K+VemRDzRp2FEjVcfh7YqLrpqcMU7VQFsxR2A3tYOlt/ywZTN0gLjUOi3h+izbQK+7fjIujGk8g==", "dev": true, - "license": "MIT", "dependencies": { "jsdom": "^24.1.1" } }, "node_modules/@embeddable.com/react": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@embeddable.com/react/-/react-2.9.1.tgz", - "integrity": "sha512-P8IenI4AbRC9j9P//qZjtbruEv0ZfloScDHOX4HJF7eMrry2XobYO5vohhFo1DMxW77ipG8K7nF8k/3VgcfLCA==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@embeddable.com/react/-/react-2.9.2.tgz", + "integrity": "sha512-H/fMUjXmNZljB9ZjWvvoSIipSBjCxtmWOfFxSHJLmnjpNgxnd249uBdaSQj769OfDl5h3oXmCbG954Qd51lk1w==", "dev": true, - "license": "MIT", "dependencies": { - "@embeddable.com/core": "2.8.1" + "@embeddable.com/core": "2.8.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" diff --git a/package.json b/package.json index aa6621a0..a8274527 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ }, "devDependencies": { "@embeddable.com/core": "^2.8.1", - "@embeddable.com/react": "^2.9.1", + "@embeddable.com/react": "^2.9.2", "@embeddable.com/sdk-core": "^3.12.4", "@embeddable.com/sdk-react": "^3.10.4", "@trivago/prettier-plugin-sort-imports": "^4.3.0", diff --git a/src/components/ailo-components/BasicPieComponent/BasicPieComponent.emb.ts b/src/components/ailo-components/BasicPieComponent/BasicPieComponent.emb.ts new file mode 100644 index 00000000..fca43a2a --- /dev/null +++ b/src/components/ailo-components/BasicPieComponent/BasicPieComponent.emb.ts @@ -0,0 +1,55 @@ +import {defineComponent, EmbeddedComponentMeta, Inputs} from '@embeddable.com/react'; +import { loadData } from '@embeddable.com/core'; + +import Component from './BasicPieComponent'; +import {Dataset, Dimension, Measure} from "@embeddable.com/core/lib/loadData"; + +export const meta = { + name: 'BasicPieComponent', // unique name for this component (must match file name) + label: 'Basic Pie', + inputs: [ // the inputs the no-code builder user will be asked to enter + { + name: "ds", // unique name for this input + type: "dataset", // tells Embeddable to render a dropdown containing available datasets + label: "Dataset to display", // human readable name for this input (shown in UI above) + }, + { + name: "slice", + type: "dimension", // renders a dropdown containing available dimensions + label: "Slice", + config: { + dataset: "ds", // only show dimensions from dataset "ds" (defined above) + }, + }, + { + name: "metric", + type: "measure", // renders a dropdown containing available measures + label: "Metric", + config: { + dataset: "ds", //only show measures from dataset "ds" (defined above) + }, + }, + { + name: 'showLegend', + type: 'boolean', + label: 'Turn on the legend', + defaultValue: true, + category: 'Chart settings', + }, + ] +} as const satisfies EmbeddedComponentMeta; + +export default defineComponent(Component, meta, { + props: (inputs: Inputs) => { + return { + // pass ds, slice and metric directly to the React component + ...inputs, + // request data to populate our chart + results: loadData({ + from: inputs.ds, + dimensions: [inputs.slice], + measures: [inputs.metric], + }) + }; + } +}); \ No newline at end of file diff --git a/src/components/ailo-components/BasicPieComponent/BasicPieComponent.tsx b/src/components/ailo-components/BasicPieComponent/BasicPieComponent.tsx new file mode 100644 index 00000000..043dd0ef --- /dev/null +++ b/src/components/ailo-components/BasicPieComponent/BasicPieComponent.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { Pie } from 'react-chartjs-2'; +import {Dimension, Measure, Dataset, DataResponse} from "@embeddable.com/core"; +import Loading from "../../util/Loading"; +import Error from "../../util/Error"; + + +type Props = { + ds: Dataset; + slice: Dimension; // { name, title } + metric: Measure; // [{ name, title }] + results: DataResponse; // { isLoading, error, data: [{ : , ... }] } + showLegend: boolean; +}; + +const COLORS = [ + '#A9DBB0', + '#F59E54', + '#F77A5F', + '#8FCBCF', + '#C3B0EA', +]; + +const chartOptions = (showLegend: boolean) => ({ + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: showLegend + } + }, +}); + +const chartData = (labels: string[] | undefined, counts: number[] | undefined) => { + return { + labels, + datasets: [ + { + data: counts, + backgroundColor: COLORS, + borderColor: COLORS, + } + ] + }; +} + +export default (props: Props) => { + const { slice, metric, results, showLegend } = props; + const { isLoading, data, error } = results; + + if(isLoading) { + return + } + if(error) { + return ; + } + + /* + E.g: + data = [ + { country: "US", count: 23 }, + { country: "UK", count: 10 }, + { country: "Germany", count: 5 }, + ] + slice = { name: 'country' } + metric = { name: 'count' } + */ + + // Chart.js pie expects labels like so: ['US', 'UK', 'Germany'] + const labels: string[] |undefined = data?.map(d => d[slice.name] as string); + + // Chart.js pie expects counts like so: [23, 10, 5] + const counts = data?.map(d => d[metric.name] as number); + + return +}; \ No newline at end of file diff --git a/src/components/ailo-components/BasicPieComponent/index.ts b/src/components/ailo-components/BasicPieComponent/index.ts new file mode 100644 index 00000000..c48dd55b --- /dev/null +++ b/src/components/ailo-components/BasicPieComponent/index.ts @@ -0,0 +1 @@ +export * as BasicPieComponent from "./BasicPieComponent.emb"; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 17cf064b..07883362 100644 --- a/yarn.lock +++ b/yarn.lock @@ -323,19 +323,19 @@ resolved "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz" integrity sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg== -"@embeddable.com/core@^2.8.1", "@embeddable.com/core@2.8.1": - version "2.8.1" - resolved "https://registry.npmjs.org/@embeddable.com/core/-/core-2.8.1.tgz" - integrity sha512-px0Z9B1p2Wr6f/5eeOnXcEtv5/WUweznupBIYC7YfkvADApAWxg7XzHjwUhLCCL0DhZJcOT5UtNF9e9cgNPBGQ== +"@embeddable.com/core@^2.8.1", "@embeddable.com/core@2.8.2": + version "2.8.2" + resolved "https://registry.npmjs.org/@embeddable.com/core/-/core-2.8.2.tgz" + integrity sha512-5fGgPtbvIq5K+VemRDzRp2FEjVcfh7YqLrpqcMU7VQFsxR2A3tYOlt/ywZTN0gLjUOi3h+izbQK+7fjIujGk8g== dependencies: jsdom "^24.1.1" -"@embeddable.com/react@^2.9.1": - version "2.9.1" - resolved "https://registry.npmjs.org/@embeddable.com/react/-/react-2.9.1.tgz" - integrity sha512-P8IenI4AbRC9j9P//qZjtbruEv0ZfloScDHOX4HJF7eMrry2XobYO5vohhFo1DMxW77ipG8K7nF8k/3VgcfLCA== +"@embeddable.com/react@^2.9.2": + version "2.9.2" + resolved "https://registry.npmjs.org/@embeddable.com/react/-/react-2.9.2.tgz" + integrity sha512-H/fMUjXmNZljB9ZjWvvoSIipSBjCxtmWOfFxSHJLmnjpNgxnd249uBdaSQj769OfDl5h3oXmCbG954Qd51lk1w== dependencies: - "@embeddable.com/core" "2.8.1" + "@embeddable.com/core" "2.8.2" "@embeddable.com/sdk-core@^3.12.4", "@embeddable.com/sdk-core@3.12.4": version "3.12.4" From f17288d625677ffdf9f590761e45239639f5bb77 Mon Sep 17 00:00:00 2001 From: kevinlee Date: Fri, 17 Jan 2025 12:29:34 +1100 Subject: [PATCH 2/7] add basic stacked bar chart and make it support customised legends layout --- .../BasicPieComponent.emb.ts | 2 +- .../BasicStackedBarChart.emb.ts | 169 ++++++++++++++++++ .../BasicStackedBarChart.tsx | 93 ++++++++++ .../BasicStackedBarChart/index.ts | 1 + src/components/util/getBarChartOptions.ts | 5 +- src/components/util/getStackedChartData.ts | 2 + 6 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts create mode 100644 src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx create mode 100644 src/components/ailo-components/BasicStackedBarChart/index.ts diff --git a/src/components/ailo-components/BasicPieComponent/BasicPieComponent.emb.ts b/src/components/ailo-components/BasicPieComponent/BasicPieComponent.emb.ts index fca43a2a..6d158a3d 100644 --- a/src/components/ailo-components/BasicPieComponent/BasicPieComponent.emb.ts +++ b/src/components/ailo-components/BasicPieComponent/BasicPieComponent.emb.ts @@ -2,11 +2,11 @@ import {defineComponent, EmbeddedComponentMeta, Inputs} from '@embeddable.com/re import { loadData } from '@embeddable.com/core'; import Component from './BasicPieComponent'; -import {Dataset, Dimension, Measure} from "@embeddable.com/core/lib/loadData"; export const meta = { name: 'BasicPieComponent', // unique name for this component (must match file name) label: 'Basic Pie', + category: 'Ailo: components', inputs: [ // the inputs the no-code builder user will be asked to enter { name: "ds", // unique name for this input diff --git a/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts new file mode 100644 index 00000000..a5104b53 --- /dev/null +++ b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts @@ -0,0 +1,169 @@ +import { OrderBy, loadData } from '@embeddable.com/core'; +import { EmbeddedComponentMeta, Inputs, defineComponent } from '@embeddable.com/react'; + +import Component from './BasicStackedBarChart'; + +export const meta = { + name: 'BasicStackedBarChart', + label: 'Basica stacked bar chart', + classNames: ['inside-card'], + category: 'Ailo: components', + inputs: [ + { + name: 'ds', + type: 'dataset', + label: 'Dataset to display', + category: 'Chart data', + }, + { + name: 'xAxis', + type: 'dimension', + label: 'X-Axis', + config: { + dataset: 'ds', + }, + category: 'Chart data', + }, + { + name: 'segment', + type: 'dimension', + label: 'Grouping', + config: { + dataset: 'ds', + }, + category: 'Chart data', + }, + { + name: 'metric', + type: 'measure', + label: 'Metric', + config: { + dataset: 'ds', + }, + category: 'Chart data', + }, + { + name: 'sortBy', + type: 'dimensionOrMeasure', + label: 'Sort by (optional)', + config: { + dataset: 'ds', + }, + category: 'Chart data', + }, + { + name: 'title', + type: 'string', + label: 'Title', + description: 'The title for the chart', + category: 'Chart settings', + }, + { + name: 'description', + type: 'string', + label: 'Description', + description: 'The description for the chart', + category: 'Chart settings', + }, + { + name: 'stackBars', + type: 'boolean', + label: 'Stack bars', + defaultValue: true, + category: 'Chart settings', + }, + { + name: 'showLegend', + type: 'boolean', + label: 'Show legend', + defaultValue: true, + category: 'Chart settings', + }, + { + name: 'maxSegments', + type: 'number', + label: 'Max Legend Items', + defaultValue: 8, + category: 'Chart settings', + }, + { + name: 'showLabels', + type: 'boolean', + label: 'Show Labels', + defaultValue: false, + category: 'Chart settings', + }, + { + name: 'showTotals', + type: 'boolean', + label: 'Show Totals Above Stacked Bars', + defaultValue: false, + category: 'Chart settings', + }, + { + name: 'displayHorizontally', + type: 'boolean', + label: 'Display Horizontally', + defaultValue: false, + category: 'Chart settings', + }, + { + name: 'reverseXAxis', + type: 'boolean', + label: 'Reverse X Axis', + category: 'Chart settings', + defaultValue: false, + }, + { + name: 'displayAsPercentage', + type: 'boolean', + label: 'Display as Percentages', + defaultValue: false, + category: 'Chart settings', + }, + { + name: 'dps', + type: 'number', + label: 'Decimal Places', + category: 'Formatting', + }, + { + name: 'enableDownloadAsCSV', + type: 'boolean', + label: 'Show download as CSV', + category: 'Export options', + defaultValue: true, + }, + { + name: 'enableDownloadAsPNG', + type: 'boolean', + label: 'Show download as PNG', + category: 'Export options', + defaultValue: true, + }, + ], +} as const satisfies EmbeddedComponentMeta; + +export default defineComponent(Component, meta, { + props: (inputs: Inputs) => { + const orderProp: OrderBy[] = []; + + if (inputs.sortBy) { + orderProp.push({ + property: inputs.sortBy, + direction: inputs.sortBy.nativeType == 'string' ? 'asc' : 'desc', + }); + } + + return { + ...inputs, + isGroupedBar: true, + results: loadData({ + from: inputs.ds, + dimensions: [inputs.xAxis, inputs.segment], + measures: [inputs.metric], + orderBy: orderProp, + }), + }; + }, +}); diff --git a/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx new file mode 100644 index 00000000..40a31e7d --- /dev/null +++ b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx @@ -0,0 +1,93 @@ +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend, ChartData, +} from 'chart.js'; +import { Bar } from 'react-chartjs-2'; +import getStackedChartData, {Props} from "../../util/getStackedChartData"; +import Container from "../../vanilla/Container"; +import getBarChartOptions from "../../util/getBarChartOptions"; +import React from "react"; + +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend +); + +type Totals = { + [xAxis: string]: { + total: number; + lastSegment: number | null; + }; +}; + + +export const options = { + plugins: { + title: { + display: true, + text: 'Ailo Bar Chart - Stacked', + }, + }, + responsive: true, + scales: { + x: { + stacked: true, + }, + y: { + stacked: true, + }, + }, +}; + +export default (props: Props) => { + const datasetsMeta = { + barPercentage: 0.6, + barThickness: 'flex', + maxBarThickness: 25, + minBarLength: 0, + borderRadius: 3, + }; + + if (props.showTotals) { + const totals: Totals = {}; + const { data } = props.results; + const { metric, xAxis, segment } = props; + if (data && data.length > 0) { + data?.forEach((d: { [key: string]: any }) => { + const x = d[xAxis.name]; + const y = parseFloat(d[metric.name]); + if (totals[x]) { + totals[x].total += y; + totals[x].lastSegment = null; + } else { + totals[x] = { + total: y, + lastSegment: null, // we'll fill this in later + }; + } + }); + props.totals = totals; + } + } + + return ( + + + } + /> + +); +} \ No newline at end of file diff --git a/src/components/ailo-components/BasicStackedBarChart/index.ts b/src/components/ailo-components/BasicStackedBarChart/index.ts new file mode 100644 index 00000000..dd99d3ec --- /dev/null +++ b/src/components/ailo-components/BasicStackedBarChart/index.ts @@ -0,0 +1 @@ +export * as BasicStackedBarChart from "./BasicStackedBarChart.emb"; \ No newline at end of file diff --git a/src/components/util/getBarChartOptions.ts b/src/components/util/getBarChartOptions.ts index a2f3d433..e08a4285 100644 --- a/src/components/util/getBarChartOptions.ts +++ b/src/components/util/getBarChartOptions.ts @@ -4,6 +4,7 @@ import { ChartDataset, ChartOptions } from 'chart.js'; import formatValue from '../util/format'; import { setYAxisStepSize } from './chartjs/common'; import { Props } from './getStackedChartData'; +import { LayoutPosition } from "chart.js/dist/types/layout"; // We're adding a few properties to use when showing totals on the chart type ExtendedChartDataset = ChartDataset<'bar' | 'line'> & { @@ -51,6 +52,7 @@ export default function getBarChartOptions({ segment, showLabels = false, showLegend = false, + legendPosition = 'bottom', showSecondYAxis = false, showTotals = false, stackMetrics = false, @@ -69,6 +71,7 @@ export default function getBarChartOptions({ reverseXAxis?: boolean; secondAxisTitle?: string; showSecondYAxis?: boolean; + legendPosition?: LayoutPosition; stackMetrics?: boolean; stacked?: boolean; xAxisTitle?: string; @@ -180,7 +183,7 @@ export default function getBarChartOptions({ plugins: { legend: { display: showLegend, - position: 'bottom', + position: legendPosition, labels: { usePointStyle: true, boxHeight: 8, diff --git a/src/components/util/getStackedChartData.ts b/src/components/util/getStackedChartData.ts index ea4ed985..9f82af6c 100644 --- a/src/components/util/getStackedChartData.ts +++ b/src/components/util/getStackedChartData.ts @@ -3,6 +3,7 @@ import { ChartData } from 'chart.js'; import { COLORS, DATE_DISPLAY_FORMATS } from '../constants'; import formatValue from '../util/format'; +import { LayoutPosition } from "chart.js/dist/types/layout"; type DatasetsMeta = { [key: string]: boolean | string | number; @@ -21,6 +22,7 @@ export type Props = { segment: Dimension; showLabels?: boolean; showLegend?: boolean; + legendPosition?: LayoutPosition; showTotals?: boolean; title?: string; totals?: { [key: string]: { total: number; lastSegment: number | null } }; From 112ad6de01c870b445295662655b213b1b92d8fd Mon Sep 17 00:00:00 2001 From: kevinlee Date: Fri, 17 Jan 2025 13:27:55 +1100 Subject: [PATCH 3/7] add maxLabelsToShow chart config --- .../BasicStackedBarChart.emb.ts | 7 ++++ src/components/util/getStackedChartData.ts | 32 ++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts index a5104b53..fc2efc6a 100644 --- a/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts +++ b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts @@ -86,6 +86,13 @@ export const meta = { defaultValue: 8, category: 'Chart settings', }, + { + name: 'maxLabelsToShow', + type: 'number', + label: 'Max number of labels to show', + defaultValue: 8, + category: 'Chart settings', + }, { name: 'showLabels', type: 'boolean', diff --git a/src/components/util/getStackedChartData.ts b/src/components/util/getStackedChartData.ts index 9f82af6c..14d118e4 100644 --- a/src/components/util/getStackedChartData.ts +++ b/src/components/util/getStackedChartData.ts @@ -33,6 +33,7 @@ export type Props = { yAxisTitle?: string; isGroupedBar?: boolean; stackBars?: boolean; + maxLabelsToShow?: number; }; type Options = { @@ -58,9 +59,11 @@ export default function getStackedChartData( showTotals, totals, useCustomDateFormat, - xAxis + xAxis, + maxLabelsToShow } = props; - const labels = [...new Set(results?.data?.map((d: Record) => d[xAxis?.name || '']))] as string[]; + // const labels = [...new Set(results?.data?.map((d: Record) => d[xAxis?.name || '']))] as string[]; + const labels = labelsToInclude(); const segments = segmentsToInclude(); const resultMap: { [key: string]: LabelRef } = {}; @@ -87,11 +90,20 @@ export default function getStackedChartData( const axis = d[xAxis?.name || '']; const met = d[metric?.name || '']; - if (segments.includes(seg)) { - resultMap[axis][seg] = parseFloat(met); + if(labels.includes(axis)){ + if (segments.includes(seg)) { + resultMap[axis][seg] = parseFloat(met); + } else { + resultMap[axis]['Other'] = (resultMap[axis]['Other'] || 0) + parseFloat(met); + } } else { - resultMap[axis]['Other'] = (resultMap[axis]['Other'] || 0) + parseFloat(met); + if (segments.includes(seg)) { + resultMap['Other'][seg] = parseFloat(met); + } else { + resultMap['Other']['Other'] = (resultMap['Other']['Other'] || 0) + parseFloat(met); + } } + }); const dateFormat = @@ -166,4 +178,14 @@ export default function getStackedChartData( return segmentsToInclude; } + + function labelsToInclude(): string[] { + const uniqueLabels = [...new Set(results?.data?.map((d: Record) => d[xAxis?.name || '']))] as string[] + if(!maxLabelsToShow || maxLabelsToShow < 1){ + return uniqueLabels; + } + const labelsToInclude = uniqueLabels.slice(0, maxLabelsToShow); + labelsToInclude.push('Other'); + return labelsToInclude; + } } From 5aa8e6c2583fcf27f098a3ab329efa997b79683f Mon Sep 17 00:00:00 2001 From: kevinlee Date: Fri, 17 Jan 2025 14:02:54 +1100 Subject: [PATCH 4/7] make other segement and other labels name customisable --- .../BasicStackedBarChart.emb.ts | 14 ++++++++++++++ src/components/util/getStackedChartData.ts | 18 ++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts index fc2efc6a..62f6f354 100644 --- a/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts +++ b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.emb.ts @@ -86,6 +86,13 @@ export const meta = { defaultValue: 8, category: 'Chart settings', }, + { + name: 'otherSegmentsName', + type: 'string', + label: 'Other segments grouped name', + defaultValue: 'Other', + category: 'Chart settings', + }, { name: 'maxLabelsToShow', type: 'number', @@ -93,6 +100,13 @@ export const meta = { defaultValue: 8, category: 'Chart settings', }, + { + name: 'otherLabelsName', + type: 'string', + label: 'Other labels grouped name', + defaultValue: 'Other', + category: 'Chart settings', + }, { name: 'showLabels', type: 'boolean', diff --git a/src/components/util/getStackedChartData.ts b/src/components/util/getStackedChartData.ts index 14d118e4..0b5f1b3c 100644 --- a/src/components/util/getStackedChartData.ts +++ b/src/components/util/getStackedChartData.ts @@ -17,6 +17,7 @@ export type Props = { granularity?: Granularity; isTSGroupedBarChart?: boolean; maxSegments?: number; + otherSegmentsName?: string; metric: Measure; results: DataResponse; segment: Dimension; @@ -34,6 +35,7 @@ export type Props = { isGroupedBar?: boolean; stackBars?: boolean; maxLabelsToShow?: number; + otherLabelsName?: string; }; type Options = { @@ -53,6 +55,7 @@ export default function getStackedChartData( displayAsPercentage, granularity, maxSegments, + otherSegmentsName, metric, results, segment, @@ -60,9 +63,12 @@ export default function getStackedChartData( totals, useCustomDateFormat, xAxis, - maxLabelsToShow + maxLabelsToShow, + otherLabelsName } = props; // const labels = [...new Set(results?.data?.map((d: Record) => d[xAxis?.name || '']))] as string[]; + const otherSegmentsGroupedName = otherSegmentsName ?? 'Other' + const otherLabelsGroupedName = otherLabelsName ?? 'Other' const labels = labelsToInclude(); const segments = segmentsToInclude(); const resultMap: { [key: string]: LabelRef } = {}; @@ -94,13 +100,13 @@ export default function getStackedChartData( if (segments.includes(seg)) { resultMap[axis][seg] = parseFloat(met); } else { - resultMap[axis]['Other'] = (resultMap[axis]['Other'] || 0) + parseFloat(met); + resultMap[axis][otherSegmentsGroupedName] = (resultMap[axis][otherSegmentsGroupedName] || 0) + parseFloat(met); } } else { if (segments.includes(seg)) { - resultMap['Other'][seg] = parseFloat(met); + resultMap[otherLabelsGroupedName][seg] = parseFloat(met); } else { - resultMap['Other']['Other'] = (resultMap['Other']['Other'] || 0) + parseFloat(met); + resultMap[otherLabelsGroupedName][otherSegmentsGroupedName] = (resultMap[otherLabelsGroupedName][otherSegmentsGroupedName] || 0) + parseFloat(met); } } @@ -174,7 +180,7 @@ export default function getStackedChartData( const segmentsToInclude = summedSegments.slice(0, maxSegments).map((s) => s.name); - segmentsToInclude.push('Other'); + segmentsToInclude.push(otherSegmentsGroupedName); return segmentsToInclude; } @@ -185,7 +191,7 @@ export default function getStackedChartData( return uniqueLabels; } const labelsToInclude = uniqueLabels.slice(0, maxLabelsToShow); - labelsToInclude.push('Other'); + labelsToInclude.push(otherLabelsGroupedName); return labelsToInclude; } } From 65bde8f3c9c2934253ff766731f4ebd1db65b017 Mon Sep 17 00:00:00 2001 From: kevinlee Date: Fri, 17 Jan 2025 14:50:43 +1100 Subject: [PATCH 5/7] increase width and radius --- .../BasicStackedBarChart/BasicStackedBarChart.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx index 40a31e7d..2345bb9e 100644 --- a/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx +++ b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx @@ -50,11 +50,11 @@ export const options = { export default (props: Props) => { const datasetsMeta = { - barPercentage: 0.6, + barPercentage: 0.8, + categoryPercentage: 0.8, barThickness: 'flex', - maxBarThickness: 25, minBarLength: 0, - borderRadius: 3, + borderRadius: 5, }; if (props.showTotals) { From 96a20aeda1c57769969854729b1d1eb2acf1873d Mon Sep 17 00:00:00 2001 From: kevinlee Date: Fri, 17 Jan 2025 15:00:20 +1100 Subject: [PATCH 6/7] fix null tax categories name --- src/components/util/getStackedChartData.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/util/getStackedChartData.ts b/src/components/util/getStackedChartData.ts index 0b5f1b3c..53b4b8db 100644 --- a/src/components/util/getStackedChartData.ts +++ b/src/components/util/getStackedChartData.ts @@ -64,7 +64,7 @@ export default function getStackedChartData( useCustomDateFormat, xAxis, maxLabelsToShow, - otherLabelsName + otherLabelsName, } = props; // const labels = [...new Set(results?.data?.map((d: Record) => d[xAxis?.name || '']))] as string[]; const otherSegmentsGroupedName = otherSegmentsName ?? 'Other' @@ -93,7 +93,7 @@ export default function getStackedChartData( results?.data?.forEach((d) => { const seg = d[segment?.name || '']; - const axis = d[xAxis?.name || '']; + const axis = d[xAxis?.name || ''] ?? 'Uncategorised'; const met = d[metric?.name || '']; if(labels.includes(axis)){ @@ -186,7 +186,7 @@ export default function getStackedChartData( } function labelsToInclude(): string[] { - const uniqueLabels = [...new Set(results?.data?.map((d: Record) => d[xAxis?.name || '']))] as string[] + const uniqueLabels = [...new Set(results?.data?.map((d: Record) => d[xAxis?.name || ''] ?? 'Uncategorised' ))] as string[] if(!maxLabelsToShow || maxLabelsToShow < 1){ return uniqueLabels; } From 01e9ec58bdb5a74a836cba0d97c30dc4eac327f6 Mon Sep 17 00:00:00 2001 From: kevinlee Date: Fri, 17 Jan 2025 16:54:44 +1100 Subject: [PATCH 7/7] customise color --- .../BasicStackedBarChart/BasicStackedBarChart.tsx | 3 +++ src/components/ailo-components/colors.ts | 8 ++++++++ src/components/util/getStackedChartData.ts | 8 ++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src/components/ailo-components/colors.ts diff --git a/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx index 2345bb9e..674c197e 100644 --- a/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx +++ b/src/components/ailo-components/BasicStackedBarChart/BasicStackedBarChart.tsx @@ -12,6 +12,7 @@ import getStackedChartData, {Props} from "../../util/getStackedChartData"; import Container from "../../vanilla/Container"; import getBarChartOptions from "../../util/getBarChartOptions"; import React from "react"; +import {AILO_BAR_CHART_SEGMENT_COLORS} from "../colors"; ChartJS.register( CategoryScale, @@ -57,6 +58,8 @@ export default (props: Props) => { borderRadius: 5, }; + props.customisedSegmentColors = AILO_BAR_CHART_SEGMENT_COLORS; + if (props.showTotals) { const totals: Totals = {}; const { data } = props.results; diff --git a/src/components/ailo-components/colors.ts b/src/components/ailo-components/colors.ts new file mode 100644 index 00000000..4a96cb02 --- /dev/null +++ b/src/components/ailo-components/colors.ts @@ -0,0 +1,8 @@ +export const AILO_BAR_CHART_SEGMENT_COLORS = [ + '#1B4480', + '#696EE0', + '#F5A623', + '#EB6E47', + '#9E0E4E', + '#1C1E26' +] \ No newline at end of file diff --git a/src/components/util/getStackedChartData.ts b/src/components/util/getStackedChartData.ts index 53b4b8db..2b2691c5 100644 --- a/src/components/util/getStackedChartData.ts +++ b/src/components/util/getStackedChartData.ts @@ -36,6 +36,7 @@ export type Props = { stackBars?: boolean; maxLabelsToShow?: number; otherLabelsName?: string; + customisedSegmentColors?: string[]; }; type Options = { @@ -65,6 +66,7 @@ export default function getStackedChartData( xAxis, maxLabelsToShow, otherLabelsName, + customisedSegmentColors } = props; // const labels = [...new Set(results?.data?.map((d: Record) => d[xAxis?.name || '']))] as string[]; const otherSegmentsGroupedName = otherSegmentsName ?? 'Other' @@ -115,13 +117,15 @@ export default function getStackedChartData( const dateFormat = useCustomDateFormat && granularity ? DATE_DISPLAY_FORMATS[granularity] : undefined; + const segmentColors = customisedSegmentColors ?? COLORS; + return { labels: labels.map((l) => formatValue(l, { meta: xAxis?.meta, dateFormat: dateFormat })), datasets: segments.map((s, i) => { const dataset = { ...datasetsMeta, - backgroundColor: COLORS[i % COLORS.length], - borderColor: COLORS[i % COLORS.length], + backgroundColor: segmentColors[i % segmentColors.length], + borderColor: segmentColors[i % segmentColors.length], label: s, // this is actually segment name, not label, but chart.js wants "label" here data: labels.map((label) => { const segmentValue = resultMap[label][s];