Compare commits

...

25 Commits

Author SHA1 Message Date
Sidharth Vinod
4f9cf4f9fc Merge branch 'develop' of https://github.com/mermaid-js/mermaid into xychart
* 'develop' of https://github.com/mermaid-js/mermaid: (546 commits)
  rebuild
  chore: Use string templates
  chore: Update docs
  chore: Fix docs
  chore: Add argos token to cypress config.
  chore: Remove cy.get('svg') calls
  chore: fix Argos parallel
  chore: Support local screenshot testing without applitools or argos
  chore: Skip checking drupal links
  chore: Remove reference screenshot generation from E2E
  chore: Remove cytoscape patch.
  chore(argos): disable matchImageSnapshot
  chore(argos): put parallel mode only when necessary
  chore(argos): setup parallel mode
  chore: setup Argos Visual Testing on E2E
  Fixed linters
  Added more specs for elk detector
  Fixed wrong elk detector, check only beginning of the line for diagram keywords
  Removed unused patch
  Fix cytoscape patch
  ...
2024-06-21 10:24:58 +05:30
Sidharth Vinod
90be8dedf6 Merge pull request #5167 from camueller/feature/xychart_multiple_datasets
xychart: support for multiple datasets added
2024-02-05 13:06:45 +05:30
Sidharth Vinod
f1490ff679 Merge branch 'develop' into feature/xychart_multiple_datasets 2024-01-31 17:09:56 +05:30
Axel Müller
25160d9688 axis calculation fixed for mix of line and bar charts 2024-01-27 17:11:40 +01:00
Axel Müller
533a921ef5 axis calculation fixed 2024-01-27 15:21:27 +01:00
Axel Müller
3bef03f568 Merge branch 'develop' into feature/xychart_multiple_datasets 2024-01-27 14:07:51 +01:00
Axel Müller
737f4f0cf3 axis calculation fixed 2024-01-27 14:06:30 +01:00
Axel Müller
71e5a2b3a3 Merge branch 'develop' into feature/xychart_multiple_datasets 2024-01-24 19:13:49 +01:00
Axel Müller
54f1435839 axis calculation fixed 2024-01-24 19:07:00 +01:00
Axel Müller
48a20c5cb8 more tests added 2024-01-22 15:59:33 +01:00
Axel Müller
5f4f1cc08d Merge branch 'develop' into feature/xychart_multiple_datasets 2024-01-20 10:25:55 +01:00
Axel Müller
da0a4ae37d multiple line charts fixed 2024-01-20 08:46:39 +01:00
Axel Müller
2972012059 use edge case for better coverage 2024-01-16 18:41:30 +01:00
Axel Müller
cf641ba4fd Merge branch 'develop' into feature/xychart_multiple_datasets 2024-01-16 18:17:32 +01:00
Axel Müller
39e5064019 Merge branch 'develop' into feature/xychart_multiple_datasets 2024-01-13 13:47:26 +01:00
Axel Müller
7830d0c4bf fix 0 values 2024-01-13 13:39:55 +01:00
Axel Müller
60d34bdc72 fix missing values 2024-01-13 13:30:18 +01:00
Axel Müller
3262a06a8e fix 0 values 2024-01-13 11:54:14 +01:00
Axel Müller
506314598e review findings 2024-01-13 07:39:58 +01:00
Axel Müller
25a9479acf documentation updated 2024-01-08 17:13:59 +01:00
Axel Müller
1273f440a8 e2e tests added 2024-01-08 16:41:53 +01:00
Axel Müller
e1d085925e parser fixed for > 2 datasets
some unit tests added
2024-01-07 17:10:36 +01:00
Axel Müller
39d175314c formatting fixed 2024-01-07 07:33:32 +01:00
Axel Müller
17426f0a97 unit tests fixed 2024-01-07 07:21:04 +01:00
Axel Müller
a36fa7cd2f support for multiple datasets added 2023-12-27 12:13:30 +01:00
11 changed files with 909 additions and 628 deletions

View File

@@ -1,18 +1,19 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
describe('XY Chart', () => {
it('should render the simplest possible chart', () => {
imgSnapshotTest(
`
describe('single dataset', () => {
it('should render the simplest possible chart', () => {
imgSnapshotTest(
`
xychart-beta
line [10, 30, 20]
`,
{}
);
});
it('Should render a complete chart', () => {
imgSnapshotTest(
`
{}
);
});
it('Should render a complete chart', () => {
imgSnapshotTest(
`
xychart-beta
title "Sales Revenue"
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
@@ -20,82 +21,68 @@ describe('XY Chart', () => {
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('Should render a chart without title', () => {
imgSnapshotTest(
`
{}
);
});
it('Should render a chart without title', () => {
imgSnapshotTest(
`
xychart-beta
x-axis Months [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]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('y-axis title not required', () => {
imgSnapshotTest(
`
{}
);
});
it('y-axis title not required', () => {
imgSnapshotTest(
`
xychart-beta
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('Should render a chart without y-axis with different range', () => {
imgSnapshotTest(
`
{}
);
});
it('Should render a chart without y-axis with different range', () => {
imgSnapshotTest(
`
xychart-beta
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
bar [5000, 6000, 7500, 8200, 9500, 10500, 14000, 3200, 9200, 9900, 3400, 6000]
line [2000, 7000, 6500, 9200, 9500, 7500, 11000, 10200, 3200, 8500, 7000, 8800]
`,
{}
);
});
it('x axis title not required', () => {
imgSnapshotTest(
`
{}
);
});
it('x axis title not required', () => {
imgSnapshotTest(
`
xychart-beta
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
bar [5000, 6000, 7500, 8200, 9500, 10500, 14000, 3200, 9200, 9900, 3400, 6000]
line [2000, 7000, 6500, 9200, 9500, 7500, 11000, 10200, 3200, 8500, 7000, 8800]
`,
{}
);
});
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(
`
{}
);
});
it('Decimals and negative numbers are supported', () => {
imgSnapshotTest(
`
xychart-beta
y-axis -2.4 --> 3.5
line [+1.3, .6, 2.4, -.34]
`,
{}
);
});
it('Render spark line with "plotReservedSpacePercent"', () => {
imgSnapshotTest(
`
{}
);
});
it('Render spark line with "plotReservedSpacePercent"', () => {
imgSnapshotTest(
`
---
config:
theme: dark
@@ -107,12 +94,12 @@ describe('XY Chart', () => {
xychart-beta
line [5000, 9000, 7500, 6200, 9500, 5500, 11000, 8200, 9200, 9500, 7000, 8800]
`,
{}
);
});
it('Render spark bar without displaying other property', () => {
imgSnapshotTest(
`
{}
);
});
it('Render spark bar without displaying other property', () => {
imgSnapshotTest(
`
---
config:
theme: dark
@@ -133,12 +120,12 @@ describe('XY Chart', () => {
xychart-beta
bar [5000, 9000, 7500, 6200, 9500, 5500, 11000, 8200, 9200, 9500, 7000, 8800]
`,
{}
);
});
it('Should use all the config from directive', () => {
imgSnapshotTest(
`
{}
);
});
it('Should use all the config from directive', () => {
imgSnapshotTest(
`
%%{init: {"xyChart": {"width": 1000, "height": 600, "titlePadding": 5, "titleFontSize": 10, "xAxis": {"labelFontSize": "20", "labelPadding": 10, "titleFontSize": 30, "titlePadding": 20, "tickLength": 10, "tickWidth": 5}, "yAxis": {"labelFontSize": "20", "labelPadding": 10, "titleFontSize": 30, "titlePadding": 20, "tickLength": 10, "tickWidth": 5}, "plotBorderWidth": 5, "chartOrientation": "horizontal", "plotReservedSpacePercent": 60 }}}%%
xychart-beta
title "Sales Revenue"
@@ -147,12 +134,12 @@ describe('XY Chart', () => {
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('Should use all the config from yaml', () => {
imgSnapshotTest(
`
{}
);
});
it('Should use all the config from yaml', () => {
imgSnapshotTest(
`
---
config:
theme: forest
@@ -187,12 +174,12 @@ describe('XY Chart', () => {
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('Render with show axis title false', () => {
imgSnapshotTest(
`
{}
);
});
it('Render with show axis title false', () => {
imgSnapshotTest(
`
---
config:
xyChart:
@@ -208,12 +195,12 @@ describe('XY Chart', () => {
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('Render with show axis label false', () => {
imgSnapshotTest(
`
{}
);
});
it('Render with show axis label false', () => {
imgSnapshotTest(
`
---
config:
xyChart:
@@ -229,12 +216,12 @@ describe('XY Chart', () => {
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('Render with show axis tick false', () => {
imgSnapshotTest(
`
{}
);
});
it('Render with show axis tick false', () => {
imgSnapshotTest(
`
---
config:
xyChart:
@@ -250,12 +237,12 @@ describe('XY Chart', () => {
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('Render with show axis line false', () => {
imgSnapshotTest(
`
{}
);
});
it('Render with show axis line false', () => {
imgSnapshotTest(
`
---
config:
xyChart:
@@ -271,12 +258,12 @@ describe('XY Chart', () => {
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('Render all the theme color', () => {
imgSnapshotTest(
`
{}
);
});
it('Render all the theme color', () => {
imgSnapshotTest(
`
---
config:
themeVariables:
@@ -300,19 +287,110 @@ describe('XY Chart', () => {
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
{}
);
});
});
it('should use the correct distances between data points', () => {
imgSnapshotTest(
`
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');
{}
);
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');
});
});
});
});

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
/*eslint-disable no-restricted-syntax */
export enum PlotType {
BAR = 'bar',
LINE = 'line',
}

View File

@@ -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,85 @@ 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);
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]),
]);
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;
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}`],
if (this.orientation === 'horizontal') {
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}`],
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,
fill: barData.fill,
strokeWidth: 0,
strokeFill: barData.fill,
};
}),
};
}
return {
groupTexts: ['plot', `bar-plot-${this.plotIndex}-${dataIndex}`],
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 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,
width: barWidth,
height,
fill: barData.fill,
strokeWidth: 0,
strokeFill: barData.fill,
};
}),
};
});
}
}

View File

@@ -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 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 === 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(
linePlots,
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;
}

View File

@@ -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,36 +12,40 @@ export class LinePlot {
) {}
getDrawableElement(): DrawableElem[] {
const finalData: [number, number][] = this.plotData.data.map((d) => [
this.xAxis.getScaleValue(d[0]),
this.yAxis.getScaleValue(d[1]),
]);
let path: string | null;
if (this.orientation === 'horizontal') {
path = line()
.y((d) => d[0])
.x((d) => d[1])(finalData);
} else {
path = line()
.x((d) => d[0])
.y((d) => d[1])(finalData);
}
if (!path) {
return [];
}
return [
const drawables: DrawableElem[] = [];
this.plotData.forEach((plotData, dataIndex) => {
{
groupTexts: ['plot', `line-plot-${this.plotIndex}`],
type: 'path',
data: [
{
path,
strokeFill: this.plotData.strokeFill,
strokeWidth: this.plotData.strokeWidth,
},
],
},
];
const finalData: [number, number][] = plotData.data.map((d) => [
this.xAxis.getScaleValue(d[0]),
this.yAxis.getScaleValue(d[1]),
]);
let path: string | null;
if (this.orientation === 'horizontal') {
path = line()
.y((d) => d[0])
.x((d) => d[1])(finalData);
} else {
path = line()
.x((d) => d[0])
.y((d) => d[1])(finalData);
}
if (!path) {
return [];
}
drawables.push({
groupTexts: ['plot', `line-plot-${this.plotIndex}-${dataIndex}`],
type: 'path',
data: [
{
path,
strokeFill: plotData.strokeFill,
strokeWidth: plotData.strokeWidth,
},
],
});
}
});
return drawables;
}
}

View File

@@ -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 {

View File

@@ -33,384 +33,388 @@ describe('Testing xychart jison file', () => {
clearMocks();
});
it('should throw error if xychart-beta text is not there', () => {
const str = 'xychart-beta-1';
expect(parserFnConstructor(str)).toThrow();
});
it('should not throw error if only xychart is there', () => {
const str = 'xychart-beta';
expect(parserFnConstructor(str)).not.toThrow();
});
it('parse title of the chart within "', () => {
const str = 'xychart-beta \n title "This is a title"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('This is a title');
});
it('parse title of the chart without "', () => {
const str = 'xychart-beta \n title oneLinertitle';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('oneLinertitle');
});
it('parse chart orientation', () => {
const str = 'xychart-beta vertical';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setOrientation).toHaveBeenCalledWith('vertical');
});
it('parse chart orientation with spaces', () => {
let str = 'xychart-beta horizontal ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setOrientation).toHaveBeenCalledWith('horizontal');
str = 'xychart-beta abc';
expect(parserFnConstructor(str)).toThrow();
});
it('parse x-axis', () => {
const str = 'xychart-beta \nx-axis xAxisName\n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName',
type: 'text',
describe('single dataset', () => {
it('should throw error if xychart-beta text is not there', () => {
const str = 'xychart-beta-1';
expect(parserFnConstructor(str)).toThrow();
});
});
it('parse x-axis with axis name without "', () => {
const str = 'xychart-beta \nx-axis xAxisName \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName',
type: 'text',
it('should not throw error if only xychart is there', () => {
const str = 'xychart-beta';
expect(parserFnConstructor(str)).not.toThrow();
});
});
it('parse x-axis with axis name with "', () => {
const str = 'xychart-beta \n x-axis "xAxisName has space"\n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName has space',
type: 'text',
it('parse title of the chart within "', () => {
const str = 'xychart-beta \n title "This is a title"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('This is a title');
});
it('parse title of the chart without "', () => {
const str = 'xychart-beta \n title oneLinertitle';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('oneLinertitle');
});
});
it('parse x-axis with axis name with " with spaces', () => {
const str = 'xychart-beta \n x-axis " xAxisName has space " \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: ' xAxisName has space ',
type: 'text',
it('parse chart orientation', () => {
const str = 'xychart-beta vertical';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setOrientation).toHaveBeenCalledWith('vertical');
});
});
it('parse x-axis with axis name and range data', () => {
const str = 'xychart-beta \nx-axis xAxisName 45.5 --> 33 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName',
type: 'text',
});
expect(mockDB.setXAxisRangeData).toHaveBeenCalledWith(45.5, 33);
});
it('parse x-axis throw error for invalid range data', () => {
const str = 'xychart-beta \nx-axis xAxisName aaa --> 33 \n';
expect(parserFnConstructor(str)).toThrow();
});
it('parse x-axis with axis name and range data with only decimal part', () => {
const str = 'xychart-beta \nx-axis xAxisName 45.5 --> .34 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName',
type: 'text',
});
expect(mockDB.setXAxisRangeData).toHaveBeenCalledWith(45.5, 0.34);
});
it('parse chart orientation with spaces', () => {
let str = 'xychart-beta horizontal ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setOrientation).toHaveBeenCalledWith('horizontal');
it('parse x-axis without axisname and range data', () => {
const str = 'xychart-beta \nx-axis 45.5 --> 1.34 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: '',
type: 'text',
str = 'xychart-beta abc';
expect(parserFnConstructor(str)).toThrow();
});
expect(mockDB.setXAxisRangeData).toHaveBeenCalledWith(45.5, 1.34);
});
it('parse x-axis with axis name and category data', () => {
const str = 'xychart-beta \nx-axis xAxisName [ "cat1" , cat2a ] \n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName',
type: 'text',
});
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{
text: 'cat1',
it('parse x-axis', () => {
const str = 'xychart-beta \nx-axis xAxisName\n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName',
type: 'text',
},
{ text: 'cat2a', type: 'text' },
]);
});
it('parse x-axis without axisname and category data', () => {
const str = 'xychart-beta \nx-axis [ "cat1" , cat2a ] \n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: '',
type: 'text',
});
});
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{
text: 'cat1',
it('parse x-axis with axis name without "', () => {
const str = 'xychart-beta \nx-axis xAxisName \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName',
type: 'text',
},
{ text: 'cat2a', type: 'text' },
]);
});
it('parse x-axis throw error if unbalanced bracket', () => {
let str = 'xychart-beta \nx-axis xAxisName [ "cat1" [ cat2a ] \n ';
expect(parserFnConstructor(str)).toThrow();
str = 'xychart-beta \nx-axis xAxisName [ "cat1" , cat2a ] ] \n ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse x-axis complete variant 1', () => {
const str = `xychart-beta \n x-axis "this is x axis" [category1, "category 2", category3]\n`;
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'this is x axis', type: 'text' });
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{ text: 'category1', type: 'text' },
{ text: 'category 2', type: 'text' },
{ text: 'category3', type: 'text' },
]);
});
it('parse x-axis complete variant 2', () => {
const str =
'xychart-beta \nx-axis xAxisName [ "cat1 with space" , cat2 , cat3] \n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{ text: 'cat1 with space', type: 'text' },
{ text: 'cat2', type: 'text' },
{ text: 'cat3', type: 'text' },
]);
});
it('parse x-axis complete variant 3', () => {
const str =
'xychart-beta \nx-axis xAxisName [ "cat1 with space" , cat2 asdf , cat3] \n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{ text: 'cat1 with space', type: 'text' },
{ text: 'cat2asdf', type: 'text' },
{ text: 'cat3', type: 'text' },
]);
});
it('parse y-axis with axis name', () => {
const str = 'xychart-beta \ny-axis yAxisName\n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
});
it('parse y-axis with axis name with spaces', () => {
const str = 'xychart-beta \ny-axis yAxisName \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
});
it('parse y-axis with axis name with "', () => {
const str = 'xychart-beta \n y-axis "yAxisName has space"\n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({
text: 'yAxisName has space',
type: 'text',
});
});
});
it('parse y-axis with axis name with " and spaces', () => {
const str = 'xychart-beta \n y-axis " yAxisName has space " \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({
text: ' yAxisName has space ',
type: 'text',
it('parse x-axis with axis name with "', () => {
const str = 'xychart-beta \n x-axis "xAxisName has space"\n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName has space',
type: 'text',
});
});
});
it('parse y-axis with axis name with range data', () => {
const str = 'xychart-beta \ny-axis yAxisName 45.5 --> 33 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setYAxisRangeData).toHaveBeenCalledWith(45.5, 33);
});
it('parse y-axis without axisname with range data', () => {
const str = 'xychart-beta \ny-axis 45.5 --> 33 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: '', type: 'text' });
expect(mockDB.setYAxisRangeData).toHaveBeenCalledWith(45.5, 33);
});
it('parse y-axis with axis name with range data with only decimal part', () => {
const str = 'xychart-beta \ny-axis yAxisName 45.5 --> .33 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setYAxisRangeData).toHaveBeenCalledWith(45.5, 0.33);
});
it('parse y-axis throw error for invalid number in range data', () => {
const str = 'xychart-beta \ny-axis yAxisName 45.5 --> abc \n';
expect(parserFnConstructor(str)).toThrow();
});
it('parse y-axis throws error if range data is passed', () => {
const str = 'xychart-beta \ny-axis yAxisName [ 45.3, 33 ] \n';
expect(parserFnConstructor(str)).toThrow();
});
it('parse both axis at once', () => {
const str = 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
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]';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle', type: 'text' },
[23, 45, 56.6]
);
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
});
it('parse line Data with spaces and +,- symbols', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" [ +23 , -45 , 56.6 ] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle with space', type: 'text' },
[23, -45, 56.6]
);
});
it('parse line Data without title', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line [ +23 , -45 , 56.6 , .33] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: '', type: 'text' },
[23, -45, 56.6, 0.33]
);
});
it('parse line Data throws error unbalanced brackets', () => {
let str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" [ +23 [ -45 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" [ +23 , -45 ] 56.6 ] ';
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" ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse line Data throws error if data is empty', () => {
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 , is not in proper', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" [ +23 , , -45 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse line Data throws error if not number', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" [ +23 , -4aa5 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse bar Data', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar barTitle [23, 45, 56.6, .22]';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle', type: 'text' },
[23, 45, 56.6, 0.22]
);
});
it('parse bar Data spaces and +,- symbol', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" [ +23 , -45 , 56.6 ] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle with space', type: 'text' },
[23, -45, 56.6]
);
});
it('parse bar Data without plot title', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar [ +23 , -45 , 56.6 ] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setBarData).toHaveBeenCalledWith({ text: '', type: 'text' }, [23, -45, 56.6]);
});
it('parse bar should throw for unbalanced brackets', () => {
let str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" [ +23 [ -45 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" [ +23 , -45 ] 56.6 ] ';
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" ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse bar should throw error if data is empty', () => {
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 comma is not proper', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" [ +23 , , -45 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse bar should throw error if number is not passed', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" [ +23 , -4aa5 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse multiple bar and line variant 1', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar barTitle1 [23, 45, 56.6] \n line lineTitle1 [11, 45.5, 67, 23] \n bar barTitle2 [13, 42, 56.89] \n line lineTitle2 [45, 99, 012]';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle1', type: 'text' },
[23, 45, 56.6]
);
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle2', type: 'text' },
[13, 42, 56.89]
);
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle1', type: 'text' },
[11, 45.5, 67, 23]
);
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle2', type: 'text' },
[45, 99, 12]
);
});
it('parse multiple bar and line variant 2', () => {
const str = `
it('parse x-axis with axis name with " with spaces', () => {
const str = 'xychart-beta \n x-axis " xAxisName has space " \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: ' xAxisName has space ',
type: 'text',
});
});
it('parse x-axis with axis name and range data', () => {
const str = 'xychart-beta \nx-axis xAxisName 45.5 --> 33 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName',
type: 'text',
});
expect(mockDB.setXAxisRangeData).toHaveBeenCalledWith(45.5, 33);
});
it('parse x-axis throw error for invalid range data', () => {
const str = 'xychart-beta \nx-axis xAxisName aaa --> 33 \n';
expect(parserFnConstructor(str)).toThrow();
});
it('parse x-axis with axis name and range data with only decimal part', () => {
const str = 'xychart-beta \nx-axis xAxisName 45.5 --> .34 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName',
type: 'text',
});
expect(mockDB.setXAxisRangeData).toHaveBeenCalledWith(45.5, 0.34);
});
it('parse x-axis without axisname and range data', () => {
const str = 'xychart-beta \nx-axis 45.5 --> 1.34 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: '',
type: 'text',
});
expect(mockDB.setXAxisRangeData).toHaveBeenCalledWith(45.5, 1.34);
});
it('parse x-axis with axis name and category data', () => {
const str = 'xychart-beta \nx-axis xAxisName [ "cat1" , cat2a ] \n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: 'xAxisName',
type: 'text',
});
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{
text: 'cat1',
type: 'text',
},
{ text: 'cat2a', type: 'text' },
]);
});
it('parse x-axis without axisname and category data', () => {
const str = 'xychart-beta \nx-axis [ "cat1" , cat2a ] \n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({
text: '',
type: 'text',
});
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{
text: 'cat1',
type: 'text',
},
{ text: 'cat2a', type: 'text' },
]);
});
it('parse x-axis throw error if unbalanced bracket', () => {
let str = 'xychart-beta \nx-axis xAxisName [ "cat1" [ cat2a ] \n ';
expect(parserFnConstructor(str)).toThrow();
str = 'xychart-beta \nx-axis xAxisName [ "cat1" , cat2a ] ] \n ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse x-axis complete variant 1', () => {
const str = `xychart-beta \n x-axis "this is x axis" [category1, "category 2", category3]\n`;
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'this is x axis', type: 'text' });
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{ text: 'category1', type: 'text' },
{ text: 'category 2', type: 'text' },
{ text: 'category3', type: 'text' },
]);
});
it('parse x-axis complete variant 2', () => {
const str =
'xychart-beta \nx-axis xAxisName [ "cat1 with space" , cat2 , cat3] \n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{ text: 'cat1 with space', type: 'text' },
{ text: 'cat2', type: 'text' },
{ text: 'cat3', type: 'text' },
]);
});
it('parse x-axis complete variant 3', () => {
const str =
'xychart-beta \nx-axis xAxisName [ "cat1 with space" , cat2 asdf , cat3] \n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{ text: 'cat1 with space', type: 'text' },
{ text: 'cat2asdf', type: 'text' },
{ text: 'cat3', type: 'text' },
]);
});
it('parse y-axis with axis name', () => {
const str = 'xychart-beta \ny-axis yAxisName\n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
});
it('parse y-axis with axis name with spaces', () => {
const str = 'xychart-beta \ny-axis yAxisName \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
});
it('parse y-axis with axis name with "', () => {
const str = 'xychart-beta \n y-axis "yAxisName has space"\n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({
text: 'yAxisName has space',
type: 'text',
});
});
it('parse y-axis with axis name with " and spaces', () => {
const str = 'xychart-beta \n y-axis " yAxisName has space " \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({
text: ' yAxisName has space ',
type: 'text',
});
});
it('parse y-axis with axis name with range data', () => {
const str = 'xychart-beta \ny-axis yAxisName 45.5 --> 33 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setYAxisRangeData).toHaveBeenCalledWith(45.5, 33);
});
it('parse y-axis without axisname with range data', () => {
const str = 'xychart-beta \ny-axis 45.5 --> 33 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: '', type: 'text' });
expect(mockDB.setYAxisRangeData).toHaveBeenCalledWith(45.5, 33);
});
it('parse y-axis with axis name with range data with only decimal part', () => {
const str = 'xychart-beta \ny-axis yAxisName 45.5 --> .33 \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setYAxisRangeData).toHaveBeenCalledWith(45.5, 0.33);
});
it('parse y-axis throw error for invalid number in range data', () => {
const str = 'xychart-beta \ny-axis yAxisName 45.5 --> abc \n';
expect(parserFnConstructor(str)).toThrow();
});
it('parse y-axis throws error if range data is passed', () => {
const str = 'xychart-beta \ny-axis yAxisName [ 45.3, 33 ] \n';
expect(parserFnConstructor(str)).toThrow();
});
it('parse both axis at once', () => {
const str = 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
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]';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle', type: 'text' },
[23, 45, 56.6]
);
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
});
it('parse line Data with spaces and +,- symbols', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" [ +23 , -45 , 56.6 ] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle with space', type: 'text' },
[23, -45, 56.6]
);
});
it('parse line Data without title', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line [ +23 , -45 , 56.6 , .33] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: '', type: 'text' },
[23, -45, 56.6, 0.33]
);
});
it('parse line Data throws error unbalanced brackets', () => {
let str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" [ +23 [ -45 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" [ +23 , -45 ] 56.6 ] ';
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" ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse line Data throws error if data is empty', () => {
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 , is not in proper', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" [ +23 , , -45 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse line Data throws error if not number', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n line "lineTitle with space" [ +23 , -4aa5 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse bar Data', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar barTitle [23, 45, 56.6, .22]';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle', type: 'text' },
[23, 45, 56.6, 0.22]
);
});
it('parse bar Data spaces and +,- symbol', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" [ +23 , -45 , 56.6 ] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle with space', type: 'text' },
[23, -45, 56.6]
);
});
it('parse bar Data without plot title', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar [ +23 , -45 , 56.6 ] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setBarData).toHaveBeenCalledWith({ text: '', type: 'text' }, [23, -45, 56.6]);
});
it('parse bar should throw for unbalanced brackets', () => {
let str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" [ +23 [ -45 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" [ +23 , -45 ] 56.6 ] ';
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" ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse bar should throw error if data is empty', () => {
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 comma is not proper', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" [ +23 , , -45 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse bar should throw error if number is not passed', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar "barTitle with space" [ +23 , -4aa5 , 56.6 ] ';
expect(parserFnConstructor(str)).toThrow();
});
it('parse multiple bar and line variant 1', () => {
const str =
'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\n bar barTitle1 [23, 45, 56.6] \n line lineTitle1 [11, 45.5, 67, 23] \n bar barTitle2 [13, 42, 56.89] \n line lineTitle2 [45, 99, 012]';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yAxisName', type: 'text' });
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'xAxisName', type: 'text' });
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle1', type: 'text' },
[23, 45, 56.6]
);
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle2', type: 'text' },
[13, 42, 56.89]
);
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle1', type: 'text' },
[11, 45.5, 67, 23]
);
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle2', type: 'text' },
[45, 99, 12]
);
});
it('parse multiple bar and line variant 2', () => {
const str = `
xychart-beta horizontal
title Basic xychart
x-axis "this is x axis" [category1, "category 2", category3]
@@ -419,30 +423,81 @@ describe('Testing xychart jison file', () => {
line lineTitle1 [11, 45.5, 67, 23]
bar barTitle2 [13, 42, 56.89]
line lineTitle2 [45, 99, 012]`;
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yaxisText', type: 'text' });
expect(mockDB.setYAxisRangeData).toHaveBeenCalledWith(10, 150);
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'this is x axis', type: 'text' });
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{ text: 'category1', type: 'text' },
{ text: 'category 2', type: 'text' },
{ text: 'category3', type: 'text' },
]);
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle1', type: 'text' },
[23, 45, 56.6]
);
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle2', type: 'text' },
[13, 42, 56.89]
);
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle1', type: 'text' },
[11, 45.5, 67, 23]
);
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle2', type: 'text' },
[45, 99, 12]
);
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith({ text: 'yaxisText', type: 'text' });
expect(mockDB.setYAxisRangeData).toHaveBeenCalledWith(10, 150);
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith({ text: 'this is x axis', type: 'text' });
expect(mockDB.setXAxisBand).toHaveBeenCalledWith([
{ text: 'category1', type: 'text' },
{ text: 'category 2', type: 'text' },
{ text: 'category3', type: 'text' },
]);
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle1', type: 'text' },
[23, 45, 56.6]
);
expect(mockDB.setBarData).toHaveBeenCalledWith(
{ text: 'barTitle2', type: 'text' },
[13, 42, 56.89]
);
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle1', type: 'text' },
[11, 45.5, 67, 23]
);
expect(mockDB.setLineData).toHaveBeenCalledWith(
{ text: 'lineTitle2', type: 'text' },
[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]
);
});
});
});

View File

@@ -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 {

View File

@@ -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"