Skip to content

Commit

Permalink
Merge pull request #5167 from camueller/feature/xychart_multiple_data…
Browse files Browse the repository at this point in the history
…sets

xychart: support for multiple datasets added
  • Loading branch information
sidharthv96 authored Feb 5, 2024
2 parents b043d79 + f1490ff commit 90be8de
Show file tree
Hide file tree
Showing 11 changed files with 917 additions and 625 deletions.
339 changes: 214 additions & 125 deletions cypress/integration/rendering/xyChart.spec.js

Large diffs are not rendered by default.

27 changes: 24 additions & 3 deletions demos/xychart.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ <h1>XY Charts with +ve and -ve numbers</h1>
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"
Expand All @@ -61,7 +61,28 @@ <h1>XY Charts Bar with multiple category</h1>
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"
Expand All @@ -70,7 +91,7 @@ <h1>XY Charts line with multiple category</h1>
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"
Expand Down
44 changes: 43 additions & 1 deletion docs/syntax/xyChart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*eslint-disable no-restricted-syntax */
export enum PlotType {
BAR = 'bar',
LINE = 'line',
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
};
}),
};
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Loading

0 comments on commit 90be8de

Please sign in to comment.