diff --git a/demos/xychart.html b/demos/xychart.html index 1d8bad78b..4f2711016 100644 --- a/demos/xychart.html +++ b/demos/xychart.html @@ -52,7 +52,7 @@ line [+1.3, .6, 2.4, -.34] -
xychart-beta title "Basic xychart with many categories" @@ -61,7 +61,25 @@ bar "sample bar" [52, 96, 35, 10, 87, 34, 67, 99]-
+ xychart-beta + title "Basic xychart with multiple datasets" + x-axis "Relevant categories" [category1, "category 2", category3, category4, category5, category6, category7] + y-axis Animals 10 --> 200 + bar [["dogs" [52, 96, 35, 10, 87, 34, 67, 99]],["cats" [15, 7, 23, 55, 11, 41, 26, 3]]] ++ +
+ xychart-beta horizontal + title "Basic xychart with multiple datasets" + x-axis "Relevant categories" [category1, "category 2", category3, category4, category5, category6, category7] + y-axis Animals 10 --> 200 + bar [["dogs" [52, 96, 35, 10, 87, 34, 67, 99]],["cats" [15, 7, 23, 55, 11, 41, 26, 3]]] ++ +
xychart-beta title "Line chart with many category" @@ -70,7 +88,7 @@ line "sample line" [52, 96, 35, 10, 87, 34, 67, 99]-
xychart-beta title "Basic xychart with many categories with category overlap" diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts index cf7d4e516..8d4c3aa1e 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts @@ -3,7 +3,7 @@ import type { Axis } from '../axis/index.js'; export class BarPlot { constructor( - private barData: BarPlotData, + private barData: BarPlotData[], private boundingRect: BoundingRect, private xAxis: Axis, private yAxis: Axis, @@ -12,49 +12,58 @@ export class BarPlot { ) {} getDrawableElement(): DrawableElem[] { - const finalData: [number, number][] = this.barData.data.map((d) => [ - this.xAxis.getScaleValue(d[0]), - this.yAxis.getScaleValue(d[1]), - ]); + const offset = new Array(this.barData[0].data.length).fill(0); + return this.barData.map((barData, dataIndex) => { + const finalData: [number, number][] = barData.data.map((d) => [ + this.xAxis.getScaleValue(d[0]), + this.yAxis.getScaleValue(d[1]), + ]); + + const barPaddingPercent = 0.05; - const barPaddingPercent = 0.05; - - const barWidth = - Math.min(this.xAxis.getAxisOuterPadding() * 2, this.xAxis.getTickDistance()) * - (1 - barPaddingPercent); - const barWidthHalf = barWidth / 2; - - if (this.orientation === 'horizontal') { - return [ - { - groupTexts: ['plot', `bar-plot-${this.plotIndex}`], + const barWidth = + Math.min(this.xAxis.getAxisOuterPadding() * 2, this.xAxis.getTickDistance()) * + (1 - barPaddingPercent); + const barWidthHalf = barWidth / 2; + + if (this.orientation === 'horizontal') { + return { + groupTexts: ['plot', `bar-plot-${this.plotIndex}-${dataIndex}`], + type: 'rect', + data: finalData.map((data, index) => { + const x = offset[index] + this.boundingRect.x; + const width = data[1] - this.boundingRect.x; + offset[index] += width; + return { + x, + y: data[0] - barWidthHalf, + height: barWidth, + width, + fill: barData.fill, + strokeWidth: 0, + strokeFill: barData.fill, + } + }), + }; + } + return { + groupTexts: ['plot', `bar-plot-${this.plotIndex}-${dataIndex}`], type: 'rect', - data: finalData.map((data) => ({ - x: this.boundingRect.x, - y: data[0] - barWidthHalf, - height: barWidth, - width: data[1] - this.boundingRect.x, - fill: this.barData.fill, - strokeWidth: 0, - strokeFill: this.barData.fill, - })), - }, - ]; - } - return [ - { - groupTexts: ['plot', `bar-plot-${this.plotIndex}`], - type: 'rect', - data: finalData.map((data) => ({ - x: data[0] - barWidthHalf, - y: data[1], - width: barWidth, - height: this.boundingRect.y + this.boundingRect.height - data[1], - fill: this.barData.fill, - strokeWidth: 0, - strokeFill: this.barData.fill, - })), - }, - ]; + data: finalData.map((data, index) => { + const y = data[1] - offset[index]; + const height = this.boundingRect.y + this.boundingRect.height - data[1]; + offset[index] += height; + return { + x: data[0] - barWidthHalf, + y, + width: barWidth, + height, + fill: barData.fill, + strokeWidth: 0, + strokeFill: barData.fill, + }; + }), + }; + }); } } diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts index 2a7b4a283..1eea56436 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts @@ -6,6 +6,8 @@ import type { Point, XYChartThemeConfig, XYChartConfig, + BarPlotData, + LinePlotData, } from '../../interfaces.js'; import type { Axis } from '../axis/index.js'; import type { ChartComponent } from '../../interfaces.js'; @@ -55,34 +57,31 @@ export class BasePlot implements Plot { throw Error('Axes must be passed to render Plots'); } const drawableElem: DrawableElem[] = []; - for (const [i, plot] of this.chartData.plots.entries()) { - switch (plot.type) { - case 'line': - { - const linePlot = new LinePlot( - plot, - this.xAxis, - this.yAxis, - this.chartConfig.chartOrientation, - i - ); - drawableElem.push(...linePlot.getDrawableElement()); - } - break; - case 'bar': - { - const barPlot = new BarPlot( - plot, - this.boundingRect, - this.xAxis, - this.yAxis, - this.chartConfig.chartOrientation, - i - ); - drawableElem.push(...barPlot.getDrawableElement()); - } - break; - } + const linePlots = this.chartData.plots.filter(plot => plot.type === 'line') as LinePlotData[]; + const barPlots = this.chartData.plots.filter(plot => plot.type === 'bar') as BarPlotData[]; + + let plotIndex = 0; + if(linePlots.length) { + const linePlot = new LinePlot( + linePlots[0], + this.xAxis, + this.yAxis, + this.chartConfig.chartOrientation, + plotIndex + ); + drawableElem.push(...linePlot.getDrawableElement()); + } + if(barPlots.length) { + const barPlot = new BarPlot( + barPlots, + this.boundingRect, + this.xAxis, + this.yAxis, + this.chartConfig.chartOrientation, + plotIndex + ); + drawableElem.push(...barPlot.getDrawableElement()); + plotIndex++; } return drawableElem; } diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison index 987132d17..d44dbd170 100644 --- a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison +++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison @@ -102,13 +102,28 @@ statement | Y_AXIS parseYAxis | LINE plotData { yy.setLineData({text: '', type: 'text'}, $plotData); } | LINE text plotData { yy.setLineData($text, $plotData); } - | BAR plotData { yy.setBarData({text: '', type: 'text'}, $plotData); } - | BAR text plotData { yy.setBarData($text, $plotData); } + | BAR datasets { yy.setBarData($datasets); } | acc_title acc_title_value { $$=$acc_title_value.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$acc_descr_value.trim();yy.setAccDescription($$); } | acc_descr_multiline_value { $$=$acc_descr_multiline_value.trim();yy.setAccDescription($$); } ; +datasets + : SQUARE_BRACES_START datasetBraced COMMA datasets SQUARE_BRACES_END { $$ = [$datasetBraced, ...$datasets] } + | SQUARE_BRACES_START datasetBraced SQUARE_BRACES_END { $$ = [$datasetBraced] } + | datasetBraced { $$ = [$datasetBraced] } + | dataset { $$ = [$dataset] } + ; + +datasetBraced + : SQUARE_BRACES_START dataset SQUARE_BRACES_END { $$ = $dataset } + ; + +dataset + : plotData { $$ = ['', $plotData] } + | text plotData { $$ = [$text, $plotData] } + ; + plotData : SQUARE_BRACES_START commaSeparatedNumbers SQUARE_BRACES_END { $$ = $commaSeparatedNumbers } ; diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts index 637477f28..65b7d3b66 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartDb.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts @@ -169,14 +169,18 @@ function setLineData(title: NormalTextType, data: number[]) { plotIndex++; } -function setBarData(title: NormalTextType, data: number[]) { - const plotData = transformDataWithoutCategory(data); - xyChartData.plots.push({ - type: 'bar', - fill: getPlotColorFromPalette(plotIndex), - data: plotData, +type NamedDataset = [title: NormalTextType, data: number[]]; + +function setBarData(datasets: NamedDataset[]) { + datasets.forEach(dataset => { + const plotData = transformDataWithoutCategory(dataset[1]); + xyChartData.plots.push({ + type: 'bar', + fill: getPlotColorFromPalette(plotIndex), + data: plotData, + }); + plotIndex++; }); - plotIndex++; } function getDrawableElem(): DrawableElem[] {