From 02741eb56bca97633ab1f9114b113889c2eec8da Mon Sep 17 00:00:00 2001 From: Yuichiro Take Date: Sun, 31 Mar 2024 09:46:35 +0900 Subject: [PATCH] fix #100 --- examples/demo/index.ts | 46 +++++++++++++++++++++++++++ src/plugin.ts | 70 +++++++++++++++++++++++++++-------------- src/test/plugin.test.ts | 34 +++++++++++--------- src/utils.ts | 2 +- 4 files changed, 114 insertions(+), 38 deletions(-) diff --git a/examples/demo/index.ts b/examples/demo/index.ts index a611616..b0b6c11 100644 --- a/examples/demo/index.ts +++ b/examples/demo/index.ts @@ -426,3 +426,49 @@ createChart("Case.14 horizontal bar chart with parsing option", { plugins: { stacked100: { enable: true } }, }, }); + +createChart("Case.15 Complex parsing options", { + type: "bar", + data: { + labels: ["Alice", "Bob"], + datasets: [ + { + label: "bad", + data: [{ count: 1, user: "Alice" }], + backgroundColor: COLORS.red, + parsing: { + xAxisKey: "user", + yAxisKey: "count", + }, + }, + { + label: "better", + data: [{ count: 2, user: "Bob" }], + backgroundColor: COLORS.yellow, + parsing: { + xAxisKey: "user", + yAxisKey: "count", + }, + }, + { + label: "good", + data: [ + { count: 3, user: "Alice" }, + { count: 1, user: "Bob" }, + ], + backgroundColor: COLORS.blue, + parsing: { + xAxisKey: "user", + yAxisKey: "count", + }, + }, + ], + }, + options: { + plugins: { + stacked100: { + enable: true, + }, + }, + }, +} as any); diff --git a/src/plugin.ts b/src/plugin.ts index dadaf58..18907db 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -7,32 +7,50 @@ import { ParsingOptions, } from "chart.js"; -import { dataValue, setOriginalData, round, getPrecision } from "./utils"; +import { dataValue, setOriginalData, round, getPrecision, isObject } from "./utils"; import { ExtendedChartData, ExtendedPlugin } from "./types"; export const defaultStackKey = Symbol(); +const getDataIndex = ( + labels: unknown[], + data: unknown, + parsing: ParsingOptions["parsing"], + isHorizontal: boolean, + srcIndex: number, +) => { + if (!isObject(data)) return srcIndex; + + const axis = isHorizontal ? "y" : "x"; + const parseKey = parsing && parsing[`${axis}AxisKey`]; + if (!parseKey) return srcIndex; + const label = data[parseKey]; + if (!label) return srcIndex; + const labelIndex = labels.findIndex((l) => l === label); + + return labelIndex < 0 ? srcIndex : labelIndex; +}; + export const summarizeValues = ( - datasets: ChartData["datasets"], + chartData: ChartData, visibles: number[], isHorizontal: boolean, individual: boolean, parsing?: ParsingOptions["parsing"], ) => { - const datasetDataLength = - datasets?.reduce((longestLength, dataset) => { - const length = dataset?.data?.length || 0; - return length > longestLength ? length : longestLength; - }, 0) || 0; + const { labels, datasets } = chartData; + const datasetDataLength = labels.length; const isStack = datasets?.[0]?.stack; const values = [...new Array(datasetDataLength)].map((el, i) => { return datasets.reduce((sum, dataset, j) => { + const parsingOptions = dataset.parsing || parsing; const key = dataset.stack || defaultStackKey; + const rec = dataset.data.find((ds, index) => { + return getDataIndex(labels, ds, parsingOptions, isHorizontal, index) == i; + }); if (!sum[key]) sum[key] = 0; - const value = - Math.abs(dataValue(dataset.data[i], isHorizontal, dataset.parsing || parsing) || 0) * - visibles[j]; + const value = Math.abs(dataValue(rec, isHorizontal, parsingOptions) || 0) * visibles[j]; if (individual && !isStack) { if (sum[key] < value) sum[key] = value; } else { @@ -68,19 +86,26 @@ const calculateRate = ( targetAxisId?: string, parsing?: ParsingOptions["parsing"], ) => { - const totals = summarizeValues(data?.datasets, visibles, isHorizontal, individual, parsing); + const totals = summarizeValues(data, visibles, isHorizontal, individual, parsing); return data.datasets.map((dataset) => { const isTarget = isTargetDataset(dataset, targetAxisId); - return dataset.data.map((val, j) => { - const dv = dataValue(val, isHorizontal, dataset.parsing || parsing); - if (!isTarget) return dv; + const ret = new Array(data.labels.length); + dataset.data.forEach((val, j) => { + const parsingOptions = dataset.parsing || parsing; + const dv = dataValue(val, isHorizontal, parsingOptions); + const dataIndex = getDataIndex(data.labels, val, parsingOptions, isHorizontal, j); + if (isTarget) { + const key = dataset.stack || defaultStackKey; + const total = totals[dataIndex][key]; - const total = totals[j][dataset.stack || defaultStackKey]; - - return dv && total ? round(dv / total, precision) : 0; + ret[dataIndex] = dv && total ? round(dv / total, precision) : 0; + } else { + ret[dataIndex] = dv; + } }); + return ret; }); }; @@ -93,13 +118,12 @@ const tooltipLabel = ( const datasetIndex = tooltipItem.datasetIndex; const index = tooltipItem.dataIndex; const datasetLabel = data.datasets[datasetIndex].label || ""; - const originalValue = data.originalData[datasetIndex][index]; - const rateValue = data.calculatedData[datasetIndex][index]; - const value = dataValue( - originalValue, - isHorizontal, - data.datasets[datasetIndex].parsing || tooltipItem.chart.options.parsing, + const parsing = data.datasets[datasetIndex].parsing || tooltipItem.chart.options.parsing; + const originalValue = data.originalData[datasetIndex].find( + (rec, i) => getDataIndex(data.labels, rec, parsing, isHorizontal, i) == index, ); + const rateValue = data.calculatedData[datasetIndex][index]; + const value = dataValue(originalValue, isHorizontal, parsing); if (!isTargetDataset(data.datasets[datasetIndex], targetAxisId)) { return `${datasetLabel}: ${rateValue}`; diff --git a/src/test/plugin.test.ts b/src/test/plugin.test.ts index 226a617..346b10e 100644 --- a/src/test/plugin.test.ts +++ b/src/test/plugin.test.ts @@ -4,20 +4,23 @@ const allVisibles = [1, 1, 1, 1]; describe("summarizeValues", () => { describe("no stack group", () => { - const datasets = [ - { data: [1, 5], label: "dummy1" }, - { data: [4, 2], label: "dummy2" }, - ]; + const data = { + labels: ["l1", "l2"], + datasets: [ + { data: [1, 5], label: "dummy1" }, + { data: [4, 2], label: "dummy2" }, + ], + }; test("relative", () => { - expect(summarizeValues(datasets, allVisibles, true, false)).toEqual([ + expect(summarizeValues(data, allVisibles, true, false)).toEqual([ { [defaultStackKey]: 1 + 4 }, { [defaultStackKey]: 5 + 2 }, ]); }); test("individual", () => { - expect(summarizeValues(datasets, allVisibles, true, true)).toEqual([ + expect(summarizeValues(data, allVisibles, true, true)).toEqual([ { [defaultStackKey]: Math.max(1, 4) }, { [defaultStackKey]: Math.max(5, 2) }, ]); @@ -25,15 +28,18 @@ describe("summarizeValues", () => { }); describe("stack group", () => { - const datasets = [ - { stack: "a", data: [1, 8] }, - { stack: "a", data: [7, 2] }, - { stack: "b", data: [3, 6] }, - { stack: "b", data: [4, 9] }, - ]; + const data = { + labels: ["l1", "l2"], + datasets: [ + { stack: "a", data: [1, 8] }, + { stack: "a", data: [7, 2] }, + { stack: "b", data: [3, 6] }, + { stack: "b", data: [4, 9] }, + ], + }; test("relative", () => { - expect(summarizeValues(datasets, allVisibles, true, false)).toEqual([ + expect(summarizeValues(data, allVisibles, true, false)).toEqual([ { a: 1 + 7, b: 3 + 4 }, { a: 8 + 2, b: 6 + 9 }, ]); @@ -43,7 +49,7 @@ describe("summarizeValues", () => { test("individual", () => { const group1Max = Math.max(1 + 7, 3 + 4); const group2Max = Math.max(8 + 2, 6 + 9); - expect(summarizeValues(datasets, allVisibles, true, true)).toEqual([ + expect(summarizeValues(data, allVisibles, true, true)).toEqual([ { a: group1Max, b: group1Max }, { a: group2Max, b: group2Max }, ]); diff --git a/src/utils.ts b/src/utils.ts index 511702f..16eb8fb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,7 @@ import { ParsingOptions } from "chart.js"; import { ExtendedChartData, PluginOptions } from "./types"; -export const isObject = (obj: any) => { +export const isObject = (obj: any): obj is Record => { const type = typeof obj; return type === "object" && !!obj; };