mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-16 23:09:28 +02:00
Compare commits
25 Commits
@mermaid-j
...
xychart
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4f9cf4f9fc | ||
![]() |
90be8dedf6 | ||
![]() |
f1490ff679 | ||
![]() |
25160d9688 | ||
![]() |
533a921ef5 | ||
![]() |
3bef03f568 | ||
![]() |
737f4f0cf3 | ||
![]() |
71e5a2b3a3 | ||
![]() |
54f1435839 | ||
![]() |
48a20c5cb8 | ||
![]() |
5f4f1cc08d | ||
![]() |
da0a4ae37d | ||
![]() |
2972012059 | ||
![]() |
cf641ba4fd | ||
![]() |
39e5064019 | ||
![]() |
7830d0c4bf | ||
![]() |
60d34bdc72 | ||
![]() |
3262a06a8e | ||
![]() |
506314598e | ||
![]() |
25a9479acf | ||
![]() |
1273f440a8 | ||
![]() |
e1d085925e | ||
![]() |
39d175314c | ||
![]() |
17426f0a97 | ||
![]() |
a36fa7cd2f |
@@ -1,6 +1,7 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('XY Chart', () => {
|
||||
describe('single dataset', () => {
|
||||
it('should render the simplest possible chart', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -69,20 +70,6 @@ describe('XY Chart', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('Multiple plots can be rendered', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart-beta
|
||||
line [23, 46, 77, 34]
|
||||
line [45, 32, 33, 12]
|
||||
bar [87, 54, 99, 85]
|
||||
line [78, 88, 22, 4]
|
||||
line [22, 29, 75, 33]
|
||||
bar [52, 96, 35, 10]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('Decimals and negative numbers are supported', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -303,16 +290,107 @@ describe('XY Chart', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should use the correct distances between data points', () => {
|
||||
});
|
||||
|
||||
describe('multiple datasets', () => {
|
||||
describe('vertical', () => {
|
||||
it('should render bar diagram for 3 datasets', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart-beta
|
||||
x-axis 0 --> 2
|
||||
line [0, 1, 0, 1]
|
||||
bar [1, 0, 1, 0]
|
||||
title "Basic xychart with multiple datasets"
|
||||
x-axis "Relevant categories" [category1, "category 2", category3, category4]
|
||||
y-axis Animals 0 --> 160
|
||||
bar "dogs" [0, 20, 40, 30]
|
||||
bar "cats" [20, 40, 0, 30]
|
||||
bar "birds" [30, 60, 50, 30]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render line diagram for 3 datasets', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart-beta
|
||||
title "Basic xychart with multiple datasets"
|
||||
x-axis "Relevant categories" [category1, "category 2", category3, category4]
|
||||
y-axis Animals 0 --> 160
|
||||
line "dogs" [0, 20, 40, 30]
|
||||
line "cats" [20, 40, 0, 30]
|
||||
line "birds" [30, 60, 50, 30]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a mix of multiple bar and line plots', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart-beta
|
||||
line [23, 46, 77, 34]
|
||||
line [45, 32, 33, 12]
|
||||
bar [87, 54, 99, 85]
|
||||
line [78, 88, 22, 4]
|
||||
line [22, 29, 75, 33]
|
||||
bar [52, 96, 35, 10]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('horizontal', () => {
|
||||
it('should render bar diagram for 3 datasets', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart-beta horizontal
|
||||
title "Basic xychart with multiple datasets"
|
||||
x-axis "Relevant categories" [category1, "category 2", category3, category4]
|
||||
y-axis Animals 0 --> 160
|
||||
bar "dogs" [0, 20, 40, 30]
|
||||
bar "cats" [20, 40, 0, 30]
|
||||
bar "birds" [30, 60, 50, 30]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render line diagram for 3 datasets', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart-beta horizontal
|
||||
title "Basic xychart with multiple datasets"
|
||||
x-axis "Relevant categories" [category1, "category 2", category3, category4]
|
||||
y-axis Animals 0 --> 160
|
||||
line "dogs" [0, 20, 40, 30]
|
||||
line "cats" [20, 40, 0, 30]
|
||||
line "birds" [30, 60, 50, 30]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a mix of multiple bar and line plots', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart-beta horizontal
|
||||
line [23, 46, 77, 34]
|
||||
line [45, 32, 33, 12]
|
||||
bar [87, 54, 99, 85]
|
||||
line [78, 88, 22, 4]
|
||||
line [22, 29, 75, 33]
|
||||
bar [52, 96, 35, 10]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -52,7 +52,7 @@
|
||||
line [+1.3, .6, 2.4, -.34]
|
||||
</pre>
|
||||
|
||||
<h1>XY Charts Bar with multiple category</h1>
|
||||
<h1>XY Charts bar with single dataset</h1>
|
||||
<pre class="mermaid">
|
||||
xychart-beta
|
||||
title "Basic xychart with many categories"
|
||||
@@ -61,7 +61,28 @@
|
||||
bar "sample bar" [52, 96, 35, 10, 87, 34, 67, 99]
|
||||
</pre>
|
||||
|
||||
<h1>XY Charts line with multiple category</h1>
|
||||
<h1>XY Charts bar with multiple datasets</h1>
|
||||
<pre class="mermaid">
|
||||
xychart-beta
|
||||
title "Basic xychart with multiple datasets"
|
||||
x-axis "Relevant categories" [category1, "category 2", category3, category4]
|
||||
bar "dogs" [0, 60, 40, 30]
|
||||
bar "cats" [20, 40, 50, 30]
|
||||
bar "birds" [30, 60, 50, 30]
|
||||
</pre>
|
||||
|
||||
<h1>XY Charts bar horizontal with multiple datasets</h1>
|
||||
<pre class="mermaid">
|
||||
xychart-beta horizontal
|
||||
title "Basic xychart with multiple datasets"
|
||||
x-axis "Relevant categories" [category1, "category 2", category3, category4]
|
||||
y-axis Animals 0 --> 160
|
||||
bar "dogs" [0, 60, 40, 30]
|
||||
bar "cats" [20, 40, 50, 30]
|
||||
bar "birds" [30, 60, 50, 30]
|
||||
</pre>
|
||||
|
||||
<h1>XY Charts line single dataset</h1>
|
||||
<pre class="mermaid">
|
||||
xychart-beta
|
||||
title "Line chart with many category"
|
||||
@@ -70,7 +91,7 @@
|
||||
line "sample line" [52, 96, 35, 10, 87, 34, 67, 99]
|
||||
</pre>
|
||||
|
||||
<h1>XY Charts category with large text</h1>
|
||||
<h1>XY Charts bar with large text</h1>
|
||||
<pre class="mermaid">
|
||||
xychart-beta
|
||||
title "Basic xychart with many categories with category overlap"
|
||||
|
@@ -6,12 +6,54 @@
|
||||
|
||||
# XY Chart
|
||||
|
||||
> In the context of mermaid-js, the XY chart is a comprehensive charting module that encompasses various types of charts that utilize both x-axis and y-axis for data representation. Presently, it includes two fundamental chart types: the bar chart and the line chart. These charts are designed to visually display and analyze data that involve two numerical variables.
|
||||
> In the context of mermaid-js, the XY chart is a comprehensive charting module that encompasses various types of charts that utilize both x-axis and y-axis for data representation. Presently, it includes two fundamental chart types: the bar chart and the line chart. These charts are designed to display one or more datasets containing categories of data.
|
||||
|
||||
> It's important to note that while the current implementation of mermaid-js includes these two chart types, the framework is designed to be dynamic and adaptable. Therefore, it has the capacity for expansion and the inclusion of additional chart types in the future. This means that users can expect an evolving suite of charting options within the XY chart module, catering to various data visualization needs as new chart types are introduced over time.
|
||||
|
||||
## Example
|
||||
|
||||
### bar chart displaying single dataset
|
||||
|
||||
```mermaid-example
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
```
|
||||
|
||||
### bar chart displaying 3 datasets
|
||||
|
||||
```mermaid-example
|
||||
xychart-beta
|
||||
title "Basic xychart with multiple datasets"
|
||||
x-axis "Relevant categories" [category1, "category 2", category3, category4]
|
||||
y-axis Animals 0 --> 160
|
||||
bar "dogs" [40, 20, 40, 30]
|
||||
bar "cats" [20, 40, 50, 30]
|
||||
bar "birds" [30, 60, 50, 30]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
xychart-beta
|
||||
title "Basic xychart with multiple datasets"
|
||||
x-axis "Relevant categories" [category1, "category 2", category3, category4]
|
||||
y-axis Animals 0 --> 160
|
||||
bar "dogs" [40, 20, 40, 30]
|
||||
bar "cats" [20, 40, 50, 30]
|
||||
bar "birds" [30, 60, 50, 30]
|
||||
```
|
||||
|
||||
### combined bar/line chart displaying 2 datasets
|
||||
|
||||
```mermaid-example
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
|
@@ -0,0 +1,5 @@
|
||||
/*eslint-disable no-restricted-syntax */
|
||||
export enum PlotType {
|
||||
BAR = 'bar',
|
||||
LINE = 'line',
|
||||
}
|
@@ -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,7 +12,10 @@ export class BarPlot {
|
||||
) {}
|
||||
|
||||
getDrawableElement(): DrawableElem[] {
|
||||
const finalData: [number, number][] = this.barData.data.map((d) => [
|
||||
const offset = new Array(this.barData[0].data.length).fill(0);
|
||||
const enlarge = 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]),
|
||||
]);
|
||||
@@ -25,36 +28,69 @@ export class BarPlot {
|
||||
const barWidthHalf = barWidth / 2;
|
||||
|
||||
if (this.orientation === 'horizontal') {
|
||||
return [
|
||||
{
|
||||
groupTexts: ['plot', `bar-plot-${this.plotIndex}`],
|
||||
return {
|
||||
groupTexts: ['plot', `bar-plot-${this.plotIndex}-${dataIndex}`],
|
||||
type: 'rect',
|
||||
data: finalData.map((data) => ({
|
||||
x: this.boundingRect.x,
|
||||
data: finalData.map((data, index) => {
|
||||
const adjustForAxisOuterPadding = dataIndex > 0 ? this.yAxis.getAxisOuterPadding() : 0;
|
||||
let x = offset[index] + this.boundingRect.x;
|
||||
let width = data[1] - this.boundingRect.x - adjustForAxisOuterPadding;
|
||||
if (enlarge[index] > 0) {
|
||||
x -= enlarge[index];
|
||||
width += enlarge[index];
|
||||
enlarge[index] = 0;
|
||||
offset[index] -= adjustForAxisOuterPadding;
|
||||
}
|
||||
offset[index] += width;
|
||||
if (barData.data[index][1] === 0 && enlarge[index] === 0) {
|
||||
enlarge[index] = width;
|
||||
}
|
||||
if (barData.data[index][1] === 0) {
|
||||
width = 0;
|
||||
}
|
||||
return {
|
||||
x,
|
||||
y: data[0] - barWidthHalf,
|
||||
height: barWidth,
|
||||
width: data[1] - this.boundingRect.x,
|
||||
fill: this.barData.fill,
|
||||
width,
|
||||
fill: barData.fill,
|
||||
strokeWidth: 0,
|
||||
strokeFill: this.barData.fill,
|
||||
})),
|
||||
},
|
||||
];
|
||||
strokeFill: barData.fill,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
return [
|
||||
{
|
||||
groupTexts: ['plot', `bar-plot-${this.plotIndex}`],
|
||||
return {
|
||||
groupTexts: ['plot', `bar-plot-${this.plotIndex}-${dataIndex}`],
|
||||
type: 'rect',
|
||||
data: finalData.map((data) => ({
|
||||
data: finalData.map((data, index) => {
|
||||
const adjustForAxisOuterPadding = dataIndex > 0 ? this.yAxis.getAxisOuterPadding() : 0;
|
||||
const y = data[1] - offset[index] + adjustForAxisOuterPadding;
|
||||
let height =
|
||||
this.boundingRect.y + this.boundingRect.height - data[1] - adjustForAxisOuterPadding;
|
||||
if (enlarge[index] > 0) {
|
||||
height += enlarge[index];
|
||||
enlarge[index] = 0;
|
||||
offset[index] -= adjustForAxisOuterPadding;
|
||||
}
|
||||
offset[index] += height;
|
||||
if (barData.data[index][1] === 0 && enlarge[index] === 0) {
|
||||
enlarge[index] = height;
|
||||
}
|
||||
if (barData.data[index][1] === 0) {
|
||||
height = 0;
|
||||
}
|
||||
return {
|
||||
x: data[0] - barWidthHalf,
|
||||
y: data[1],
|
||||
y,
|
||||
width: barWidth,
|
||||
height: this.boundingRect.y + this.boundingRect.height - data[1],
|
||||
fill: this.barData.fill,
|
||||
height,
|
||||
fill: barData.fill,
|
||||
strokeWidth: 0,
|
||||
strokeFill: this.barData.fill,
|
||||
})),
|
||||
},
|
||||
];
|
||||
strokeFill: barData.fill,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -6,11 +6,14 @@ import type {
|
||||
Point,
|
||||
XYChartThemeConfig,
|
||||
XYChartConfig,
|
||||
BarPlotData,
|
||||
LinePlotData,
|
||||
} from '../../interfaces.js';
|
||||
import type { Axis } from '../axis/index.js';
|
||||
import type { ChartComponent } from '../../interfaces.js';
|
||||
import { LinePlot } from './linePlot.js';
|
||||
import { BarPlot } from './barPlot.js';
|
||||
import { PlotType } from './PlotType.js';
|
||||
|
||||
export interface Plot extends ChartComponent {
|
||||
setAxes(xAxis: Axis, yAxis: Axis): void;
|
||||
@@ -55,34 +58,35 @@ 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 linePlots = this.chartData.plots.filter(
|
||||
(plot) => plot.type === PlotType.LINE
|
||||
) as LinePlotData[];
|
||||
const barPlots = this.chartData.plots.filter(
|
||||
(plot) => plot.type === PlotType.BAR
|
||||
) as BarPlotData[];
|
||||
|
||||
let plotIndex = 0;
|
||||
if (linePlots.length) {
|
||||
const linePlot = new LinePlot(
|
||||
plot,
|
||||
linePlots,
|
||||
this.xAxis,
|
||||
this.yAxis,
|
||||
this.chartConfig.chartOrientation,
|
||||
i
|
||||
plotIndex
|
||||
);
|
||||
drawableElem.push(...linePlot.getDrawableElement());
|
||||
}
|
||||
break;
|
||||
case 'bar':
|
||||
{
|
||||
if (barPlots.length) {
|
||||
const barPlot = new BarPlot(
|
||||
plot,
|
||||
barPlots,
|
||||
this.boundingRect,
|
||||
this.xAxis,
|
||||
this.yAxis,
|
||||
this.chartConfig.chartOrientation,
|
||||
i
|
||||
plotIndex
|
||||
);
|
||||
drawableElem.push(...barPlot.getDrawableElement());
|
||||
}
|
||||
break;
|
||||
}
|
||||
plotIndex++;
|
||||
}
|
||||
return drawableElem;
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import type { Axis } from '../axis/index.js';
|
||||
|
||||
export class LinePlot {
|
||||
constructor(
|
||||
private plotData: LinePlotData,
|
||||
private plotData: LinePlotData[],
|
||||
private xAxis: Axis,
|
||||
private yAxis: Axis,
|
||||
private orientation: XYChartConfig['chartOrientation'],
|
||||
@@ -12,7 +12,10 @@ export class LinePlot {
|
||||
) {}
|
||||
|
||||
getDrawableElement(): DrawableElem[] {
|
||||
const finalData: [number, number][] = this.plotData.data.map((d) => [
|
||||
const drawables: DrawableElem[] = [];
|
||||
this.plotData.forEach((plotData, dataIndex) => {
|
||||
{
|
||||
const finalData: [number, number][] = plotData.data.map((d) => [
|
||||
this.xAxis.getScaleValue(d[0]),
|
||||
this.yAxis.getScaleValue(d[1]),
|
||||
]);
|
||||
@@ -30,18 +33,19 @@ export class LinePlot {
|
||||
if (!path) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
groupTexts: ['plot', `line-plot-${this.plotIndex}`],
|
||||
drawables.push({
|
||||
groupTexts: ['plot', `line-plot-${this.plotIndex}-${dataIndex}`],
|
||||
type: 'path',
|
||||
data: [
|
||||
{
|
||||
path,
|
||||
strokeFill: this.plotData.strokeFill,
|
||||
strokeWidth: this.plotData.strokeWidth,
|
||||
strokeFill: plotData.strokeFill,
|
||||
strokeWidth: plotData.strokeWidth,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
});
|
||||
return drawables;
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { PlotType } from './components/plot/PlotType.js';
|
||||
|
||||
export interface XYChartAxisThemeConfig {
|
||||
titleColor: string;
|
||||
labelColor: string;
|
||||
@@ -28,14 +30,14 @@ export interface ChartComponent {
|
||||
export type SimplePlotDataType = [string, number][];
|
||||
|
||||
export interface LinePlotData {
|
||||
type: 'line';
|
||||
type: PlotType.LINE;
|
||||
strokeFill: string;
|
||||
strokeWidth: number;
|
||||
data: SimplePlotDataType;
|
||||
}
|
||||
|
||||
export interface BarPlotData {
|
||||
type: 'bar';
|
||||
type: PlotType.BAR;
|
||||
fill: string;
|
||||
data: SimplePlotDataType;
|
||||
}
|
||||
@@ -43,7 +45,7 @@ export interface BarPlotData {
|
||||
export type PlotData = LinePlotData | BarPlotData;
|
||||
|
||||
export function isBarPlot(data: PlotData): data is BarPlotData {
|
||||
return data.type === 'bar';
|
||||
return data.type === PlotType.BAR;
|
||||
}
|
||||
|
||||
export interface BandAxisDataType {
|
||||
|
@@ -33,6 +33,7 @@ describe('Testing xychart jison file', () => {
|
||||
clearMocks();
|
||||
});
|
||||
|
||||
describe('single dataset', () => {
|
||||
it('should throw error if xychart-beta text is not there', () => {
|
||||
const str = 'xychart-beta-1';
|
||||
expect(parserFnConstructor(str)).toThrow();
|
||||
@@ -271,7 +272,8 @@ describe('Testing xychart jison file', () => {
|
||||
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
|
||||
});
|
||||
it('parse line Data', () => {
|
||||
const str = 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line lineTitle [23, 45, 56.6]';
|
||||
const str =
|
||||
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line lineTitle [23, 45, 56.6]';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setLineData).toHaveBeenCalledWith(
|
||||
{ text: 'lineTitle', type: 'text' },
|
||||
@@ -311,7 +313,8 @@ describe('Testing xychart jison file', () => {
|
||||
expect(parserFnConstructor(str)).toThrow();
|
||||
});
|
||||
it('parse line Data throws error if data is not provided', () => {
|
||||
const str = 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" ';
|
||||
const str =
|
||||
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" ';
|
||||
expect(parserFnConstructor(str)).toThrow();
|
||||
});
|
||||
it('parse line Data throws error if data is empty', () => {
|
||||
@@ -368,7 +371,8 @@ describe('Testing xychart jison file', () => {
|
||||
expect(parserFnConstructor(str)).toThrow();
|
||||
});
|
||||
it('parse bar should throw error if data is not provided', () => {
|
||||
const str = 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" ';
|
||||
const str =
|
||||
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" ';
|
||||
expect(parserFnConstructor(str)).toThrow();
|
||||
});
|
||||
it('parse bar should throw error if data is empty', () => {
|
||||
@@ -445,4 +449,55 @@ describe('Testing xychart jison file', () => {
|
||||
[45, 99, 12]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple datasets', () => {
|
||||
it('parse 2 datasets', () => {
|
||||
const str = `xychart-beta
|
||||
x-axis xAxisName
|
||||
y-axis yAxisName
|
||||
bar "barTitle1" [23, 45, 56.6]
|
||||
bar "barTitle2" [13, 42, 56.89]`;
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
|
||||
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
|
||||
expect(mockDB.setBarData).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
{ text: 'barTitle1', type: 'text' },
|
||||
[23, 45, 56.6]
|
||||
);
|
||||
expect(mockDB.setBarData).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
{ text: 'barTitle2', type: 'text' },
|
||||
[13, 42, 56.89]
|
||||
);
|
||||
});
|
||||
|
||||
it('parse 3 datasets', () => {
|
||||
const str = `xychart-beta
|
||||
x-axis xAxisName
|
||||
y-axis yAxisName
|
||||
bar "barTitle1" [23, 45, 56.6]
|
||||
bar "barTitle2" [13, 42, 56.89]
|
||||
bar "barTitle3" [18, 37, 56.1]`;
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
|
||||
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
|
||||
expect(mockDB.setBarData).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
{ text: 'barTitle1', type: 'text' },
|
||||
[23, 45, 56.6]
|
||||
);
|
||||
expect(mockDB.setBarData).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
{ text: 'barTitle2', type: 'text' },
|
||||
[13, 42, 56.89]
|
||||
);
|
||||
expect(mockDB.setBarData).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
{ text: 'barTitle3', type: 'text' },
|
||||
[18, 37, 56.1]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -22,6 +22,7 @@ import type {
|
||||
} from './chartBuilder/interfaces.js';
|
||||
import { isBandAxisData, isLinearAxisData } from './chartBuilder/interfaces.js';
|
||||
import type { Group } from '../../diagram-api/types.js';
|
||||
import { PlotType } from './chartBuilder/components/plot/PlotType.js';
|
||||
|
||||
let plotIndex = 0;
|
||||
|
||||
@@ -34,6 +35,8 @@ let plotColorPalette = xyChartThemeConfig.plotColorPalette.split(',').map((color
|
||||
let hasSetXAxis = false;
|
||||
let hasSetYAxis = false;
|
||||
|
||||
let dataSets: number[][] = [];
|
||||
|
||||
interface NormalTextType {
|
||||
type: 'text';
|
||||
text: string;
|
||||
@@ -57,7 +60,7 @@ function getChartDefaultData(): XYChartData {
|
||||
yAxis: {
|
||||
type: 'linear',
|
||||
title: '',
|
||||
min: Infinity,
|
||||
min: 0,
|
||||
max: -Infinity,
|
||||
},
|
||||
xAxis: {
|
||||
@@ -109,20 +112,26 @@ function setYAxisRangeData(min: number, max: number) {
|
||||
}
|
||||
|
||||
// this function does not set `hasSetYAxis` as there can be multiple data so we should calculate the range accordingly
|
||||
function setYAxisRangeFromPlotData(data: number[]) {
|
||||
const minValue = Math.min(...data);
|
||||
const maxValue = Math.max(...data);
|
||||
const prevMinValue = isLinearAxisData(xyChartData.yAxis) ? xyChartData.yAxis.min : Infinity;
|
||||
const prevMaxValue = isLinearAxisData(xyChartData.yAxis) ? xyChartData.yAxis.max : -Infinity;
|
||||
function setYAxisRangeFromPlotData(data: number[], plotType: PlotType) {
|
||||
const sum = new Array(data.length).fill(0);
|
||||
if (plotType === PlotType.BAR) {
|
||||
dataSets.push(data);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
for (const entry of dataSets) {
|
||||
sum[i] += entry[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xyChartData.yAxis = {
|
||||
type: 'linear',
|
||||
title: xyChartData.yAxis.title,
|
||||
min: Math.min(prevMinValue, minValue),
|
||||
max: Math.max(prevMaxValue, maxValue),
|
||||
min: isLinearAxisData(xyChartData.yAxis) ? xyChartData.yAxis.min : Math.min(...sum),
|
||||
max: Math.max(...sum),
|
||||
};
|
||||
}
|
||||
|
||||
function transformDataWithoutCategory(data: number[]): SimplePlotDataType {
|
||||
function transformDataWithoutCategory(data: number[], plotType: PlotType): SimplePlotDataType {
|
||||
let retData: SimplePlotDataType = [];
|
||||
if (data.length === 0) {
|
||||
return retData;
|
||||
@@ -133,11 +142,11 @@ function transformDataWithoutCategory(data: number[]): SimplePlotDataType {
|
||||
setXAxisRangeData(Math.min(prevMinValue, 1), Math.max(prevMaxValue, data.length));
|
||||
}
|
||||
if (!hasSetYAxis) {
|
||||
setYAxisRangeFromPlotData(data);
|
||||
setYAxisRangeFromPlotData(data, plotType);
|
||||
}
|
||||
|
||||
if (isBandAxisData(xyChartData.xAxis)) {
|
||||
retData = xyChartData.xAxis.categories.map((c, i) => [c, data[i]]);
|
||||
retData = xyChartData.xAxis.categories.map((c, i) => [c, data[i] ?? 0]);
|
||||
}
|
||||
|
||||
if (isLinearAxisData(xyChartData.xAxis)) {
|
||||
@@ -148,7 +157,7 @@ function transformDataWithoutCategory(data: number[]): SimplePlotDataType {
|
||||
for (let i = min; i <= max; i += step) {
|
||||
categories.push(`${i}`);
|
||||
}
|
||||
retData = categories.map((c, i) => [c, data[i]]);
|
||||
retData = categories.map((c, i) => [c, data[i] ?? 0]);
|
||||
}
|
||||
|
||||
return retData;
|
||||
@@ -159,9 +168,9 @@ function getPlotColorFromPalette(plotIndex: number): string {
|
||||
}
|
||||
|
||||
function setLineData(title: NormalTextType, data: number[]) {
|
||||
const plotData = transformDataWithoutCategory(data);
|
||||
const plotData = transformDataWithoutCategory(data, PlotType.LINE);
|
||||
xyChartData.plots.push({
|
||||
type: 'line',
|
||||
type: PlotType.LINE,
|
||||
strokeFill: getPlotColorFromPalette(plotIndex),
|
||||
strokeWidth: 2,
|
||||
data: plotData,
|
||||
@@ -170,9 +179,9 @@ function setLineData(title: NormalTextType, data: number[]) {
|
||||
}
|
||||
|
||||
function setBarData(title: NormalTextType, data: number[]) {
|
||||
const plotData = transformDataWithoutCategory(data);
|
||||
const plotData = transformDataWithoutCategory(data, PlotType.BAR);
|
||||
xyChartData.plots.push({
|
||||
type: 'bar',
|
||||
type: PlotType.BAR,
|
||||
fill: getPlotColorFromPalette(plotIndex),
|
||||
data: plotData,
|
||||
});
|
||||
@@ -204,6 +213,7 @@ const clear = function () {
|
||||
plotColorPalette = xyChartThemeConfig.plotColorPalette.split(',').map((color) => color.trim());
|
||||
hasSetXAxis = false;
|
||||
hasSetYAxis = false;
|
||||
dataSets = [];
|
||||
};
|
||||
|
||||
export default {
|
||||
|
@@ -1,11 +1,35 @@
|
||||
# XY Chart
|
||||
|
||||
> In the context of mermaid-js, the XY chart is a comprehensive charting module that encompasses various types of charts that utilize both x-axis and y-axis for data representation. Presently, it includes two fundamental chart types: the bar chart and the line chart. These charts are designed to visually display and analyze data that involve two numerical variables.
|
||||
> In the context of mermaid-js, the XY chart is a comprehensive charting module that encompasses various types of charts that utilize both x-axis and y-axis for data representation. Presently, it includes two fundamental chart types: the bar chart and the line chart. These charts are designed to display one or more datasets containing categories of data.
|
||||
|
||||
> It's important to note that while the current implementation of mermaid-js includes these two chart types, the framework is designed to be dynamic and adaptable. Therefore, it has the capacity for expansion and the inclusion of additional chart types in the future. This means that users can expect an evolving suite of charting options within the XY chart module, catering to various data visualization needs as new chart types are introduced over time.
|
||||
|
||||
## Example
|
||||
|
||||
### bar chart displaying single dataset
|
||||
|
||||
```mermaid-example
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
```
|
||||
|
||||
### bar chart displaying 3 datasets
|
||||
|
||||
```mermaid-example
|
||||
xychart-beta
|
||||
title "Basic xychart with multiple datasets"
|
||||
x-axis "Relevant categories" [category1, "category 2", category3, category4]
|
||||
y-axis Animals 0 --> 160
|
||||
bar "dogs" [40, 20, 40, 30]
|
||||
bar "cats" [20, 40, 50, 30]
|
||||
bar "birds" [30, 60, 50, 30]
|
||||
```
|
||||
|
||||
### combined bar/line chart displaying 2 datasets
|
||||
|
||||
```mermaid-example
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
|
Reference in New Issue
Block a user