From a36fa7cd2fa94d20461bc9d03fbc7ebdcb570d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Wed, 27 Dec 2023 12:13:30 +0100 Subject: [PATCH 01/17] support for multiple datasets added --- demos/xychart.html | 24 ++++- .../chartBuilder/components/plot/barPlot.ts | 95 ++++++++++--------- .../chartBuilder/components/plot/index.ts | 55 ++++++----- .../src/diagrams/xychart/parser/xychart.jison | 19 +++- .../mermaid/src/diagrams/xychart/xychartDb.ts | 18 ++-- 5 files changed, 128 insertions(+), 83 deletions(-) diff --git a/demos/xychart.html b/demos/xychart.html index 1d8bad78b..4f2711016 100644 --- a/demos/xychart.html +++ b/demos/xychart.html @@ -52,7 +52,7 @@ line [+1.3, .6, 2.4, -.34] -

XY Charts Bar with multiple category

+

XY Charts bar with single dataset

     xychart-beta
     title "Basic xychart with many categories"
@@ -61,7 +61,25 @@
     bar "sample bar" [52, 96, 35, 10, 87, 34, 67, 99]
     
-

XY Charts line with multiple category

+

XY Charts bar with multiple datasets

+
+      xychart-beta
+      title "Basic xychart with multiple datasets"
+      x-axis "Relevant categories" [category1, "category 2", category3, category4, category5, category6, category7]
+      y-axis Animals 10 --> 200
+      bar [["dogs" [52, 96, 35, 10, 87, 34, 67, 99]],["cats" [15, 7, 23, 55, 11, 41, 26, 3]]]
+    
+ +

XY Charts bar horizontal with multiple datasets

+
+      xychart-beta horizontal
+      title "Basic xychart with multiple datasets"
+      x-axis "Relevant categories" [category1, "category 2", category3, category4, category5, category6, category7]
+      y-axis Animals 10 --> 200
+      bar [["dogs" [52, 96, 35, 10, 87, 34, 67, 99]],["cats" [15, 7, 23, 55, 11, 41, 26, 3]]]
+    
+ +

XY Charts line single dataset

     xychart-beta
     title "Line chart with many category"
@@ -70,7 +88,7 @@
     line "sample line" [52, 96, 35, 10, 87, 34, 67, 99]
     
-

XY Charts category with large text

+

XY Charts bar with large text

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

From 17426f0a97be97421af833cbc507bbc9eab5ec02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Axel=20M=C3=BCller?= 
Date: Sun, 7 Jan 2024 07:21:04 +0100
Subject: [PATCH 02/17] unit tests fixed

---
 .../xychart/parser/xychart.jison.spec.ts      | 30 +++++++++++--------
 1 file changed, 17 insertions(+), 13 deletions(-)

diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts
index d113250aa..e4da42b65 100644
--- a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts
+++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts
@@ -335,10 +335,10 @@ describe('Testing xychart jison file', () => {
     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(
+    expect(mockDB.setBarData).toHaveBeenCalledWith([[
       { text: 'barTitle', type: 'text' },
       [23, 45, 56.6, 0.22]
-    );
+    ]]);
   });
   it('parse bar Data spaces and +,- symbol', () => {
     const str =
@@ -346,10 +346,10 @@ describe('Testing xychart jison file', () => {
     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(
+    expect(mockDB.setBarData).toHaveBeenCalledWith([[
       { text: 'barTitle with space', type: 'text' },
       [23, -45, 56.6]
-    );
+    ]]);
   });
   it('parse bar Data without plot title', () => {
     const str =
@@ -357,7 +357,7 @@ describe('Testing xychart jison file', () => {
     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]);
+    expect(mockDB.setBarData).toHaveBeenCalledWith([['', [23, -45, 56.6]]]);
   });
   it('parse bar should throw for unbalanced brackets', () => {
     let str =
@@ -392,13 +392,17 @@ describe('Testing xychart jison file', () => {
     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(
+    expect(mockDB.setBarData).toHaveBeenCalledWith([[
       { text: 'barTitle1', type: 'text' },
       [23, 45, 56.6]
-    );
+    ]]);
     expect(mockDB.setBarData).toHaveBeenCalledWith(
-      { text: 'barTitle2', type: 'text' },
-      [13, 42, 56.89]
+      [
+        [
+          { text: 'barTitle2', type: 'text' },
+          [13, 42, 56.89]
+        ],
+      ],
     );
     expect(mockDB.setLineData).toHaveBeenCalledWith(
       { text: 'lineTitle1', type: 'text' },
@@ -428,14 +432,14 @@ describe('Testing xychart jison file', () => {
       { text: 'category 2', type: 'text' },
       { text: 'category3', type: 'text' },
     ]);
-    expect(mockDB.setBarData).toHaveBeenCalledWith(
+    expect(mockDB.setBarData).toHaveBeenCalledWith([[
       { text: 'barTitle1', type: 'text' },
       [23, 45, 56.6]
-    );
-    expect(mockDB.setBarData).toHaveBeenCalledWith(
+    ]]);
+    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]

From 39d175314cea234451e6f74a02844ac2fb498530 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Axel=20M=C3=BCller?= 
Date: Sun, 7 Jan 2024 07:33:32 +0100
Subject: [PATCH 03/17] formatting fixed

---
 demos/xychart.html                            |  2 +-
 .../chartBuilder/components/plot/barPlot.ts   | 58 +++++++++----------
 .../chartBuilder/components/plot/index.ts     |  8 +--
 .../xychart/parser/xychart.jison.spec.ts      | 46 ++++++---------
 .../mermaid/src/diagrams/xychart/xychartDb.ts |  2 +-
 5 files changed, 53 insertions(+), 63 deletions(-)

diff --git a/demos/xychart.html b/demos/xychart.html
index 4f2711016..4017cc86f 100644
--- a/demos/xychart.html
+++ b/demos/xychart.html
@@ -69,7 +69,7 @@
       y-axis Animals 10 --> 200
       bar [["dogs" [52, 96, 35, 10, 87, 34, 67, 99]],["cats" [15, 7, 23, 55, 11, 41, 26, 3]]]
     
- +

XY Charts bar horizontal with multiple datasets

       xychart-beta horizontal
diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts
index 8d4c3aa1e..753d1d345 100644
--- a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts
+++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts
@@ -18,52 +18,52 @@ export class BarPlot {
         this.xAxis.getScaleValue(d[0]),
         this.yAxis.getScaleValue(d[1]),
       ]);
-  
+
       const barPaddingPercent = 0.05;
 
       const barWidth =
         Math.min(this.xAxis.getAxisOuterPadding() * 2, this.xAxis.getTickDistance()) *
         (1 - barPaddingPercent);
       const barWidthHalf = barWidth / 2;
-  
+
       if (this.orientation === 'horizontal') {
         return {
-            groupTexts: ['plot', `bar-plot-${this.plotIndex}-${dataIndex}`],
-            type: 'rect',
-            data: finalData.map((data, index) => {
-              const x = offset[index] + this.boundingRect.x;
-              const width = data[1] - this.boundingRect.x;
-              offset[index] += width;
-              return {
-                x,
-                y: data[0] - barWidthHalf,
-                height: barWidth,
-                width,
-                fill: barData.fill,
-                strokeWidth: 0,
-                strokeFill: barData.fill,
-              }              
-            }),
-          };
-      }
-      return {
           groupTexts: ['plot', `bar-plot-${this.plotIndex}-${dataIndex}`],
           type: 'rect',
           data: finalData.map((data, index) => {
-            const y = data[1] - offset[index];
-            const height = this.boundingRect.y + this.boundingRect.height - data[1];
-            offset[index] += height;
+            const x = offset[index] + this.boundingRect.x;
+            const width = data[1] - this.boundingRect.x;
+            offset[index] += width;
             return {
-              x: data[0] - barWidthHalf,
-              y,
-              width: barWidth,
-              height,
+              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, index) => {
+          const y = data[1] - offset[index];
+          const height = this.boundingRect.y + this.boundingRect.height - data[1];
+          offset[index] += height;
+          return {
+            x: data[0] - barWidthHalf,
+            y,
+            width: barWidth,
+            height,
+            fill: barData.fill,
+            strokeWidth: 0,
+            strokeFill: barData.fill,
+          };
+        }),
+      };
+    });
   }
 }
diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts
index 1eea56436..d21b70ca6 100644
--- a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts
+++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts
@@ -57,11 +57,11 @@ export class BasePlot implements Plot {
       throw Error('Axes must be passed to render Plots');
     }
     const drawableElem: DrawableElem[] = [];
-    const linePlots = this.chartData.plots.filter(plot => plot.type === 'line') as LinePlotData[];
-    const barPlots = this.chartData.plots.filter(plot => plot.type === 'bar') as BarPlotData[];
+    const linePlots = this.chartData.plots.filter((plot) => plot.type === 'line') as LinePlotData[];
+    const barPlots = this.chartData.plots.filter((plot) => plot.type === 'bar') as BarPlotData[];
 
     let plotIndex = 0;
-    if(linePlots.length) {
+    if (linePlots.length) {
       const linePlot = new LinePlot(
         linePlots[0],
         this.xAxis,
@@ -71,7 +71,7 @@ export class BasePlot implements Plot {
       );
       drawableElem.push(...linePlot.getDrawableElement());
     }
-    if(barPlots.length) {
+    if (barPlots.length) {
       const barPlot = new BarPlot(
         barPlots,
         this.boundingRect,
diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts
index e4da42b65..d79e20f87 100644
--- a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts
+++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts
@@ -335,10 +335,9 @@ describe('Testing xychart jison file', () => {
     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]
-    ]]);
+    expect(mockDB.setBarData).toHaveBeenCalledWith([
+      [{ text: 'barTitle', type: 'text' }, [23, 45, 56.6, 0.22]],
+    ]);
   });
   it('parse bar Data spaces and +,- symbol', () => {
     const str =
@@ -346,10 +345,9 @@ describe('Testing xychart jison file', () => {
     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]
-    ]]);
+    expect(mockDB.setBarData).toHaveBeenCalledWith([
+      [{ text: 'barTitle with space', type: 'text' }, [23, -45, 56.6]],
+    ]);
   });
   it('parse bar Data without plot title', () => {
     const str =
@@ -392,18 +390,12 @@ describe('Testing xychart jison file', () => {
     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.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]
@@ -432,14 +424,12 @@ describe('Testing xychart jison file', () => {
       { 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.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]
diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts
index 65b7d3b66..218085769 100644
--- a/packages/mermaid/src/diagrams/xychart/xychartDb.ts
+++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts
@@ -172,7 +172,7 @@ function setLineData(title: NormalTextType, data: number[]) {
 type NamedDataset = [title: NormalTextType, data: number[]];
 
 function setBarData(datasets: NamedDataset[]) {
-  datasets.forEach(dataset => {
+  datasets.forEach((dataset) => {
     const plotData = transformDataWithoutCategory(dataset[1]);
     xyChartData.plots.push({
       type: 'bar',

From e1d085925e3e63ac769bbff509dce9127cd88a78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Axel=20M=C3=BCller?= 
Date: Sun, 7 Jan 2024 15:56:46 +0100
Subject: [PATCH 04/17] parser fixed for > 2 datasets some unit tests added

---
 demos/xychart.html                            |  12 +-
 .../chartBuilder/components/plot/barPlot.ts   |  11 +-
 .../src/diagrams/xychart/parser/xychart.jison |   6 +-
 .../xychart/parser/xychart.jison.spec.ts      | 798 +++++++++---------
 .../mermaid/src/diagrams/xychart/xychartDb.ts |  21 +-
 5 files changed, 447 insertions(+), 401 deletions(-)

diff --git a/demos/xychart.html b/demos/xychart.html
index 4017cc86f..bfa7f12b1 100644
--- a/demos/xychart.html
+++ b/demos/xychart.html
@@ -65,18 +65,18 @@
     
       xychart-beta
       title "Basic xychart with multiple datasets"
-      x-axis "Relevant categories" [category1, "category 2", category3, category4, category5, category6, category7]
-      y-axis Animals 10 --> 200
-      bar [["dogs" [52, 96, 35, 10, 87, 34, 67, 99]],["cats" [15, 7, 23, 55, 11, 41, 26, 3]]]
+      x-axis "Relevant categories" [category1, "category 2", category3, category4]
+      y-axis Animals 0 --> 160
+      bar [["dogs" [40, 20, 40, 30]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]]
     

XY Charts bar horizontal with multiple datasets

       xychart-beta horizontal
       title "Basic xychart with multiple datasets"
-      x-axis "Relevant categories" [category1, "category 2", category3, category4, category5, category6, category7]
-      y-axis Animals 10 --> 200
-      bar [["dogs" [52, 96, 35, 10, 87, 34, 67, 99]],["cats" [15, 7, 23, 55, 11, 41, 26, 3]]]
+      x-axis "Relevant categories" [category1, "category 2", category3, category4]
+      y-axis Animals 0 --> 160
+      bar [["dogs" [40, 20, 40, 30]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]]
     

XY Charts line single dataset

diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts index 753d1d345..93f5eb585 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts @@ -32,7 +32,10 @@ export class BarPlot { type: 'rect', data: finalData.map((data, index) => { const x = offset[index] + this.boundingRect.x; - const width = data[1] - this.boundingRect.x; + const width = + data[1] - + this.boundingRect.x - + (dataIndex > 0 ? this.yAxis.getAxisOuterPadding() : 0); offset[index] += width; return { x, @@ -50,8 +53,10 @@ export class BarPlot { groupTexts: ['plot', `bar-plot-${this.plotIndex}-${dataIndex}`], type: 'rect', data: finalData.map((data, index) => { - const y = data[1] - offset[index]; - const height = this.boundingRect.y + this.boundingRect.height - data[1]; + const adjustForAxisOuterPadding = dataIndex > 0 ? this.yAxis.getAxisOuterPadding() : 0; + const y = data[1] - offset[index] + adjustForAxisOuterPadding; + const height = + this.boundingRect.y + this.boundingRect.height - data[1] - adjustForAxisOuterPadding; offset[index] += height; return { x: data[0] - barWidthHalf, diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison index d44dbd170..a52c2ebe0 100644 --- a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison +++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison @@ -109,14 +109,14 @@ statement ; datasets - : SQUARE_BRACES_START datasetBraced COMMA datasets SQUARE_BRACES_END { $$ = [$datasetBraced, ...$datasets] } - | SQUARE_BRACES_START datasetBraced SQUARE_BRACES_END { $$ = [$datasetBraced] } + : SQUARE_BRACES_START datasetBraced SQUARE_BRACES_END { $$ = [$datasetBraced] } | datasetBraced { $$ = [$datasetBraced] } | dataset { $$ = [$dataset] } ; datasetBraced - : SQUARE_BRACES_START dataset SQUARE_BRACES_END { $$ = $dataset } + : SQUARE_BRACES_START dataset SQUARE_BRACES_END COMMA datasetBraced { $$ = [$dataset, ...$datasetBraced] } + | SQUARE_BRACES_START dataset SQUARE_BRACES_END { $$ = [$dataset] } ; dataset diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts index d79e20f87..cca21720e 100644 --- a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts +++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts @@ -33,380 +33,384 @@ 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([['', [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([['', [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] @@ -415,28 +419,60 @@ 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\nx-axis xAxisName\ny-axis yAxisName\nbar [["barTitle1" [23, 45, 56.6]],["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).toHaveBeenCalledWith([ + [ + [{ text: 'barTitle1', type: 'text' }, [23, 45, 56.6]], + [{ text: 'barTitle2', type: 'text' }, [13, 42, 56.89]], + ], + ]); + }); + + it('parse 3 datasets', () => { + const str = + 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\nbar [["barTitle1" [23, 45, 56.6]],["barTitle2" [13, 42, 56.89]],["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).toHaveBeenCalledWith([ + [ + [{ text: 'barTitle1', type: 'text' }, [23, 45, 56.6]], + [{ text: 'barTitle2', type: 'text' }, [13, 42, 56.89]], + [{ text: 'barTitle3', type: 'text' }, [18, 37, 56.1]], + ], + ]); + }); }); }); diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts index 218085769..595c9c6a6 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartDb.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts @@ -172,15 +172,20 @@ function setLineData(title: NormalTextType, data: number[]) { type NamedDataset = [title: NormalTextType, data: number[]]; function setBarData(datasets: NamedDataset[]) { - datasets.forEach((dataset) => { - const plotData = transformDataWithoutCategory(dataset[1]); - xyChartData.plots.push({ - type: 'bar', - fill: getPlotColorFromPalette(plotIndex), - data: plotData, + datasets[0] + .filter((dataset) => Array.isArray(dataset)) + .forEach((dataset) => { + const data = dataset as any as NamedDataset; + const plotData = transformDataWithoutCategory( + Array.isArray(data[1]) ? data[1] : (dataset as any as number[]) + ); + xyChartData.plots.push({ + type: 'bar', + fill: getPlotColorFromPalette(plotIndex), + data: plotData, + }); + plotIndex++; }); - plotIndex++; - }); } function getDrawableElem(): DrawableElem[] { From 1273f440a8714c3c591bf9e89fb3e8df00d46728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Mon, 8 Jan 2024 16:41:53 +0100 Subject: [PATCH 05/17] e2e tests added --- cypress/integration/rendering/xyChart.spec.js | 266 ++++++++++-------- 1 file changed, 149 insertions(+), 117 deletions(-) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index 85d998c50..bdb0b65ff 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -1,19 +1,20 @@ 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] `, - {} - ); - cy.get('svg'); - }); - it('Should render a complete chart', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + 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] @@ -21,62 +22,62 @@ 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] `, - {} - ); - cy.get('svg'); - }); - it('y-axis title not required', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + 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] `, - {} - ); - cy.get('svg'); - }); - it('Should render a chart without y-axis with different range', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + 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] `, - {} - ); - cy.get('svg'); - }); - it('x axis title not required', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + 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] `, - {} - ); - cy.get('svg'); - }); - it('Multiple plots can be rendered', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + it('Multiple plots can be rendered', () => { + imgSnapshotTest( + ` xychart-beta line [23, 46, 77, 34] line [45, 32, 33, 12] @@ -85,24 +86,24 @@ describe('XY Chart', () => { line [22, 29, 75, 33] bar [52, 96, 35, 10] `, - {} - ); - cy.get('svg'); - }); - it('Decimals and negative numbers are supported', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + it('Decimals and negative numbers are supported', () => { + imgSnapshotTest( + ` xychart-beta y-axis -2.4 --> 3.5 line [+1.3, .6, 2.4, -.34] `, - {} - ); - cy.get('svg'); - }); - it('Render spark line with "plotReservedSpacePercent"', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + it('Render spark line with "plotReservedSpacePercent"', () => { + imgSnapshotTest( + ` --- config: theme: dark @@ -114,13 +115,13 @@ describe('XY Chart', () => { xychart-beta line [5000, 9000, 7500, 6200, 9500, 5500, 11000, 8200, 9200, 9500, 7000, 8800] `, - {} - ); - cy.get('svg'); - }); - it('Render spark bar without displaying other property', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + it('Render spark bar without displaying other property', () => { + imgSnapshotTest( + ` --- config: theme: dark @@ -141,13 +142,13 @@ describe('XY Chart', () => { xychart-beta bar [5000, 9000, 7500, 6200, 9500, 5500, 11000, 8200, 9200, 9500, 7000, 8800] `, - {} - ); - cy.get('svg'); - }); - it('Should use all the config from directive', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + 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" @@ -156,13 +157,13 @@ 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] `, - {} - ); - cy.get('svg'); - }); - it('Should use all the config from yaml', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + it('Should use all the config from yaml', () => { + imgSnapshotTest( + ` --- config: theme: forest @@ -197,13 +198,13 @@ 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] `, - {} - ); - cy.get('svg'); - }); - it('Render with show axis title false', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + it('Render with show axis title false', () => { + imgSnapshotTest( + ` --- config: xyChart: @@ -219,13 +220,13 @@ 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] `, - {} - ); - cy.get('svg'); - }); - it('Render with show axis label false', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + it('Render with show axis label false', () => { + imgSnapshotTest( + ` --- config: xyChart: @@ -241,13 +242,13 @@ 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] `, - {} - ); - cy.get('svg'); - }); - it('Render with show axis tick false', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + it('Render with show axis tick false', () => { + imgSnapshotTest( + ` --- config: xyChart: @@ -263,13 +264,13 @@ 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] `, - {} - ); - cy.get('svg'); - }); - it('Render with show axis line false', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + it('Render with show axis line false', () => { + imgSnapshotTest( + ` --- config: xyChart: @@ -285,13 +286,13 @@ 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] `, - {} - ); - cy.get('svg'); - }); - it('Render all the theme color', () => { - imgSnapshotTest( - ` + {} + ); + cy.get('svg'); + }); + it('Render all the theme color', () => { + imgSnapshotTest( + ` --- config: themeVariables: @@ -315,8 +316,39 @@ 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] `, - {} - ); - cy.get('svg'); + {} + ); + cy.get('svg'); + }); + }); + + describe('multiple datasets', () => { + it('should render 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 + bar [["dogs" [40, 20, 40, 30]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] + `, + {} + ); + cy.get('svg'); + }); + + it('should render 3 datasets in horizontal orientation', () => { + 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" [40, 20, 40, 30]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] + `, + {} + ); + cy.get('svg'); + }); }); }); From 25a9479acf07a068d769f9dccf1e764e355b270a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Mon, 8 Jan 2024 16:55:44 +0100 Subject: [PATCH 06/17] documentation updated --- docs/syntax/xyChart.md | 40 ++++++++++++++++++++- packages/mermaid/src/docs/syntax/xyChart.md | 24 ++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/docs/syntax/xyChart.md b/docs/syntax/xyChart.md index 7e91863f9..a1494b396 100644 --- a/docs/syntax/xyChart.md +++ b/docs/syntax/xyChart.md @@ -6,12 +6,50 @@ # 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]],["cats" [20, 40, 50, 30]],["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]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] +``` + +### combined bar/line chart displaying 2 datasets + ```mermaid-example xychart-beta title "Sales Revenue" diff --git a/packages/mermaid/src/docs/syntax/xyChart.md b/packages/mermaid/src/docs/syntax/xyChart.md index 8edfecbea..c6653821d 100644 --- a/packages/mermaid/src/docs/syntax/xyChart.md +++ b/packages/mermaid/src/docs/syntax/xyChart.md @@ -1,11 +1,33 @@ # 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]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] +``` + +### combined bar/line chart displaying 2 datasets + ```mermaid-example xychart-beta title "Sales Revenue" From 506314598e93f1df3aa42f5aba3a56ff467dd794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Sat, 13 Jan 2024 07:39:58 +0100 Subject: [PATCH 07/17] review findings --- cypress/integration/rendering/xyChart.spec.js | 8 +- demos/xychart.html | 8 +- docs/syntax/xyChart.md | 8 +- .../src/diagrams/xychart/parser/xychart.jison | 19 +--- .../xychart/parser/xychart.jison.spec.ts | 86 +++++++++++-------- .../mermaid/src/diagrams/xychart/xychartDb.ts | 25 ++---- packages/mermaid/src/docs/syntax/xyChart.md | 4 +- 7 files changed, 83 insertions(+), 75 deletions(-) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index bdb0b65ff..a47c87987 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -330,7 +330,9 @@ describe('XY Chart', () => { 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]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] + bar "dogs" [40, 20, 40, 30] + bar "cats" [20, 40, 50, 30] + bar "birds" [30, 60, 50, 30] `, {} ); @@ -344,7 +346,9 @@ describe('XY Chart', () => { 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]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] + bar "dogs" [40, 20, 40, 30] + bar "cats" [20, 40, 50, 30] + bar "birds" [30, 60, 50, 30] `, {} ); diff --git a/demos/xychart.html b/demos/xychart.html index bfa7f12b1..8cd6a64af 100644 --- a/demos/xychart.html +++ b/demos/xychart.html @@ -67,7 +67,9 @@ 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]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] + bar "dogs" [40, 20, 40, 30] + bar "cats" [20, 40, 50, 30] + bar "birds" [30, 60, 50, 30]

XY Charts bar horizontal with multiple datasets

@@ -76,7 +78,9 @@ 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]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] + bar "dogs" [40, 20, 40, 30] + bar "cats" [20, 40, 50, 30] + bar "birds" [30, 60, 50, 30]

XY Charts line single dataset

diff --git a/docs/syntax/xyChart.md b/docs/syntax/xyChart.md index a1494b396..b6e904a27 100644 --- a/docs/syntax/xyChart.md +++ b/docs/syntax/xyChart.md @@ -37,7 +37,9 @@ 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]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] + bar "dogs" [40, 20, 40, 30] + bar "cats" [20, 40, 50, 30] + bar "birds" [30, 60, 50, 30] ``` ```mermaid @@ -45,7 +47,9 @@ 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]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] + 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 diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison index a52c2ebe0..987132d17 100644 --- a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison +++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison @@ -102,28 +102,13 @@ statement | Y_AXIS parseYAxis | LINE plotData { yy.setLineData({text: '', type: 'text'}, $plotData); } | LINE text plotData { yy.setLineData($text, $plotData); } - | BAR datasets { yy.setBarData($datasets); } + | BAR plotData { yy.setBarData({text: '', type: 'text'}, $plotData); } + | BAR text plotData { yy.setBarData($text, $plotData); } | acc_title acc_title_value { $$=$acc_title_value.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$acc_descr_value.trim();yy.setAccDescription($$); } | acc_descr_multiline_value { $$=$acc_descr_multiline_value.trim();yy.setAccDescription($$); } ; -datasets - : SQUARE_BRACES_START datasetBraced SQUARE_BRACES_END { $$ = [$datasetBraced] } - | datasetBraced { $$ = [$datasetBraced] } - | dataset { $$ = [$dataset] } - ; - -datasetBraced - : SQUARE_BRACES_START dataset SQUARE_BRACES_END COMMA datasetBraced { $$ = [$dataset, ...$datasetBraced] } - | SQUARE_BRACES_START dataset SQUARE_BRACES_END { $$ = [$dataset] } - ; - -dataset - : plotData { $$ = ['', $plotData] } - | text plotData { $$ = [$text, $plotData] } - ; - plotData : SQUARE_BRACES_START commaSeparatedNumbers SQUARE_BRACES_END { $$ = $commaSeparatedNumbers } ; diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts index cca21720e..e44b10403 100644 --- a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts +++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts @@ -338,9 +338,10 @@ describe('Testing xychart jison file', () => { 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]], - ]); + expect(mockDB.setBarData).toHaveBeenCalledWith( + { text: 'barTitle', type: 'text' }, + [23, 45, 56.6, 0.22] + ); }); it('parse bar Data spaces and +,- symbol', () => { const str = @@ -348,9 +349,10 @@ describe('Testing xychart jison file', () => { 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]], - ]); + expect(mockDB.setBarData).toHaveBeenCalledWith( + { text: 'barTitle with space', type: 'text' }, + [23, -45, 56.6] + ); }); it('parse bar Data without plot title', () => { const str = @@ -358,7 +360,7 @@ describe('Testing xychart jison file', () => { 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([['', [23, -45, 56.6]]]); + expect(mockDB.setBarData).toHaveBeenCalledWith({ text: '', type: 'text' }, [23, -45, 56.6]); }); it('parse bar should throw for unbalanced brackets', () => { let str = @@ -394,12 +396,14 @@ describe('Testing xychart jison file', () => { 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.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] @@ -428,12 +432,14 @@ describe('Testing xychart jison file', () => { { 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.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] @@ -448,31 +454,43 @@ describe('Testing xychart jison file', () => { describe('multiple datasets', () => { it('parse 2 datasets', () => { const str = - 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\nbar [["barTitle1" [23, 45, 56.6]],["barTitle2" [13, 42, 56.89]]]'; + 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\nbar "barTitle1" [23, 45, 56.6]\nbar "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).toHaveBeenCalledWith([ - [ - [{ text: 'barTitle1', type: 'text' }, [23, 45, 56.6]], - [{ text: 'barTitle2', type: 'text' }, [13, 42, 56.89]], - ], - ]); + 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\nx-axis xAxisName\ny-axis yAxisName\nbar [["barTitle1" [23, 45, 56.6]],["barTitle2" [13, 42, 56.89]],["barTitle3" [18, 37, 56.1]]]'; + 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\nbar "barTitle1" [23, 45, 56.6]\nbar "barTitle2" [13, 42, 56.89]\nbar "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).toHaveBeenCalledWith([ - [ - [{ text: 'barTitle1', type: 'text' }, [23, 45, 56.6]], - [{ text: 'barTitle2', type: 'text' }, [13, 42, 56.89]], - [{ text: 'barTitle3', type: 'text' }, [18, 37, 56.1]], - ], - ]); + 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] + ); }); }); }); diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts index 595c9c6a6..637477f28 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartDb.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts @@ -169,23 +169,14 @@ function setLineData(title: NormalTextType, data: number[]) { plotIndex++; } -type NamedDataset = [title: NormalTextType, data: number[]]; - -function setBarData(datasets: NamedDataset[]) { - datasets[0] - .filter((dataset) => Array.isArray(dataset)) - .forEach((dataset) => { - const data = dataset as any as NamedDataset; - const plotData = transformDataWithoutCategory( - Array.isArray(data[1]) ? data[1] : (dataset as any as number[]) - ); - xyChartData.plots.push({ - type: 'bar', - fill: getPlotColorFromPalette(plotIndex), - data: plotData, - }); - plotIndex++; - }); +function setBarData(title: NormalTextType, data: number[]) { + const plotData = transformDataWithoutCategory(data); + xyChartData.plots.push({ + type: 'bar', + fill: getPlotColorFromPalette(plotIndex), + data: plotData, + }); + plotIndex++; } function getDrawableElem(): DrawableElem[] { diff --git a/packages/mermaid/src/docs/syntax/xyChart.md b/packages/mermaid/src/docs/syntax/xyChart.md index c6653821d..91de2ba97 100644 --- a/packages/mermaid/src/docs/syntax/xyChart.md +++ b/packages/mermaid/src/docs/syntax/xyChart.md @@ -23,7 +23,9 @@ 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]],["cats" [20, 40, 50, 30]],["birds" [30, 60, 50, 30]]] + 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 From 3262a06a8e70cb4c0261405c32b90314c1f50008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Sat, 13 Jan 2024 11:54:14 +0100 Subject: [PATCH 08/17] fix 0 values --- .../chartBuilder/components/plot/barPlot.ts | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts index 93f5eb585..718909fab 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts @@ -13,6 +13,7 @@ export class BarPlot { getDrawableElement(): DrawableElem[] { 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]), @@ -31,12 +32,23 @@ export class BarPlot { groupTexts: ['plot', `bar-plot-${this.plotIndex}-${dataIndex}`], type: 'rect', data: finalData.map((data, index) => { - const x = offset[index] + this.boundingRect.x; - const width = + let x = offset[index] + this.boundingRect.x; + let width = data[1] - this.boundingRect.x - (dataIndex > 0 ? this.yAxis.getAxisOuterPadding() : 0); + if (enlarge[index] > 0) { + x -= enlarge[index]; + width += enlarge[index]; + enlarge[index] = 0; + } 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, @@ -55,9 +67,19 @@ export class BarPlot { data: finalData.map((data, index) => { const adjustForAxisOuterPadding = dataIndex > 0 ? this.yAxis.getAxisOuterPadding() : 0; const y = data[1] - offset[index] + adjustForAxisOuterPadding; - const height = + let height = this.boundingRect.y + this.boundingRect.height - data[1] - adjustForAxisOuterPadding; + if (enlarge[index] > 0) { + height += enlarge[index]; + enlarge[index] = 0; + } 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, From 60d34bdc72642c1496dece091822b67ad633df1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Sat, 13 Jan 2024 13:30:18 +0100 Subject: [PATCH 09/17] fix missing values --- demos/xychart.html | 4 ++-- packages/mermaid/src/diagrams/xychart/xychartDb.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/demos/xychart.html b/demos/xychart.html index 8cd6a64af..31fff36e8 100644 --- a/demos/xychart.html +++ b/demos/xychart.html @@ -67,7 +67,7 @@ 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 "dogs" [0, 60, 40, 30] bar "cats" [20, 40, 50, 30] bar "birds" [30, 60, 50, 30] @@ -78,7 +78,7 @@ 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 "dogs" [0, 60, 40, 30] bar "cats" [20, 40, 50, 30] bar "birds" [30, 60, 50, 30] diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts index 637477f28..b8d27e88c 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartDb.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts @@ -137,7 +137,7 @@ function transformDataWithoutCategory(data: number[]): SimplePlotDataType { } 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 +148,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; From 7830d0c4bf1765ad6d6f02622e5da7cda1563008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Sat, 13 Jan 2024 13:39:55 +0100 Subject: [PATCH 10/17] fix 0 values --- .../xychart/chartBuilder/components/plot/barPlot.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts index 718909fab..95ffcf195 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/barPlot.ts @@ -32,15 +32,14 @@ export class BarPlot { groupTexts: ['plot', `bar-plot-${this.plotIndex}-${dataIndex}`], type: 'rect', 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 - - (dataIndex > 0 ? this.yAxis.getAxisOuterPadding() : 0); + 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) { @@ -72,6 +71,7 @@ export class BarPlot { 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) { From 2972012059c3002fc28cd5ebf83207c1460c4d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Tue, 16 Jan 2024 18:41:30 +0100 Subject: [PATCH 11/17] use edge case for better coverage --- cypress/integration/rendering/xyChart.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index a47c87987..4a29bf67f 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -330,8 +330,8 @@ describe('XY Chart', () => { 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 "dogs" [0, 20, 40, 30] + bar "cats" [20, 40, 0, 30] bar "birds" [30, 60, 50, 30] `, {} @@ -346,8 +346,8 @@ describe('XY Chart', () => { 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 "dogs" [0, 20, 40, 30] + bar "cats" [20, 40, 0, 30] bar "birds" [30, 60, 50, 30] `, {} From da0a4ae37dfdb946b7e81158ecc7283fa3e17731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Sat, 20 Jan 2024 08:46:39 +0100 Subject: [PATCH 12/17] multiple line charts fixed --- .../chartBuilder/components/plot/index.ts | 2 +- .../chartBuilder/components/plot/linePlot.ts | 66 ++++++++++--------- .../xychart/parser/xychart.jison.spec.ts | 15 +++-- 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts index d21b70ca6..0c727ba9e 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts @@ -63,7 +63,7 @@ export class BasePlot implements Plot { let plotIndex = 0; if (linePlots.length) { const linePlot = new LinePlot( - linePlots[0], + linePlots, this.xAxis, this.yAxis, this.chartConfig.chartOrientation, diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/linePlot.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/linePlot.ts index d8d0666de..066581f76 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/linePlot.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/linePlot.ts @@ -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; } } diff --git a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts index e44b10403..ff89c0fb1 100644 --- a/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts +++ b/packages/mermaid/src/diagrams/xychart/parser/xychart.jison.spec.ts @@ -453,8 +453,11 @@ describe('Testing xychart jison file', () => { describe('multiple datasets', () => { it('parse 2 datasets', () => { - const str = - 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\nbar "barTitle1" [23, 45, 56.6]\nbar "barTitle2" [13, 42, 56.89]'; + 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' }); @@ -471,8 +474,12 @@ describe('Testing xychart jison file', () => { }); it('parse 3 datasets', () => { - const str = - 'xychart-beta\nx-axis xAxisName\ny-axis yAxisName\nbar "barTitle1" [23, 45, 56.6]\nbar "barTitle2" [13, 42, 56.89]\nbar "barTitle3" [18, 37, 56.1]'; + 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' }); From 48a20c5cb89729f7c7e0e9ab201bd7b82aaafc5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Mon, 22 Jan 2024 15:59:33 +0100 Subject: [PATCH 13/17] more tests added --- cypress/integration/rendering/xyChart.spec.js | 107 +++++++++++++----- 1 file changed, 80 insertions(+), 27 deletions(-) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index 4a29bf67f..0b35b5ce6 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -75,21 +75,6 @@ describe('XY Chart', () => { ); cy.get('svg'); }); - 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] - `, - {} - ); - cy.get('svg'); - }); it('Decimals and negative numbers are supported', () => { imgSnapshotTest( ` @@ -323,9 +308,10 @@ describe('XY Chart', () => { }); describe('multiple datasets', () => { - it('should render 3 datasets', () => { - imgSnapshotTest( - ` + describe('vertical', () => { + it('should render bar diagram for 3 datasets', () => { + imgSnapshotTest( + ` xychart-beta title "Basic xychart with multiple datasets" x-axis "Relevant categories" [category1, "category 2", category3, category4] @@ -334,14 +320,48 @@ describe('XY Chart', () => { 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'); + }); }); - it('should render 3 datasets in horizontal orientation', () => { - imgSnapshotTest( - ` + 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] @@ -350,9 +370,42 @@ describe('XY Chart', () => { 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 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'); + }); }); }); }); From 54f143583967061dbac27363a753b74e529cd938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Wed, 24 Jan 2024 17:24:48 +0100 Subject: [PATCH 14/17] axis calculation fixed --- .../chartBuilder/components/plot/PlotType.ts | 5 +++++ .../chartBuilder/components/plot/index.ts | 9 ++++++-- .../xychart/chartBuilder/interfaces.ts | 8 ++++--- .../mermaid/src/diagrams/xychart/xychartDb.ts | 22 ++++++++++++------- 4 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/PlotType.ts diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/PlotType.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/PlotType.ts new file mode 100644 index 000000000..e4be8e58d --- /dev/null +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/PlotType.ts @@ -0,0 +1,5 @@ +/*eslint-disable no-restricted-syntax */ +export enum PlotType { + BAR = 'bar', + LINE = 'line', +} diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts index 0c727ba9e..4f0e3d002 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/components/plot/index.ts @@ -13,6 +13,7 @@ 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; @@ -57,8 +58,12 @@ export class BasePlot implements Plot { throw Error('Axes must be passed to render Plots'); } const drawableElem: DrawableElem[] = []; - const linePlots = this.chartData.plots.filter((plot) => plot.type === 'line') as LinePlotData[]; - const barPlots = this.chartData.plots.filter((plot) => plot.type === 'bar') as BarPlotData[]; + 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) { diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts index 3d188895f..357d2fcc1 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts @@ -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 { diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts index b8d27e88c..5dce5ee2e 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartDb.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts @@ -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; @@ -109,7 +110,7 @@ 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[]) { +function setYAxisRangeFromPlotData(data: number[], plotType: PlotType) { const minValue = Math.min(...data); const maxValue = Math.max(...data); const prevMinValue = isLinearAxisData(xyChartData.yAxis) ? xyChartData.yAxis.min : Infinity; @@ -118,11 +119,16 @@ function setYAxisRangeFromPlotData(data: number[]) { type: 'linear', title: xyChartData.yAxis.title, min: Math.min(prevMinValue, minValue), - max: Math.max(prevMaxValue, maxValue), + max: + plotType === PlotType.BAR + ? prevMaxValue > -Infinity + ? prevMaxValue + maxValue + : maxValue + : Math.max(prevMaxValue, maxValue), }; } -function transformDataWithoutCategory(data: number[]): SimplePlotDataType { +function transformDataWithoutCategory(data: number[], plotType: PlotType): SimplePlotDataType { let retData: SimplePlotDataType = []; if (data.length === 0) { return retData; @@ -133,7 +139,7 @@ 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)) { @@ -159,9 +165,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 +176,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, }); From 737f4f0cf3d289a76557a4db72d5078a77240bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Sat, 27 Jan 2024 14:06:30 +0100 Subject: [PATCH 15/17] axis calculation fixed --- demos/xychart.html | 1 - .../mermaid/src/diagrams/xychart/xychartDb.ts | 48 ++++++++++++------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/demos/xychart.html b/demos/xychart.html index 31fff36e8..32298a52b 100644 --- a/demos/xychart.html +++ b/demos/xychart.html @@ -66,7 +66,6 @@ xychart-beta 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] diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts index 5dce5ee2e..e3966abe1 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartDb.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts @@ -35,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; @@ -58,7 +60,7 @@ function getChartDefaultData(): XYChartData { yAxis: { type: 'linear', title: '', - min: Infinity, + min: 0, max: -Infinity, }, xAxis: { @@ -111,21 +113,34 @@ 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[], plotType: PlotType) { - 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; - xyChartData.yAxis = { - type: 'linear', - title: xyChartData.yAxis.title, - min: Math.min(prevMinValue, minValue), - max: - plotType === PlotType.BAR - ? prevMaxValue > -Infinity - ? prevMaxValue + maxValue - : maxValue - : Math.max(prevMaxValue, maxValue), - }; + if (plotType === PlotType.BAR) { + dataSets.push(data); + + let sum = new Array(data.length).fill(0); + for (let i = 0; i < data.length; i++) { + for (let dataSetIndex = 0; dataSetIndex < dataSets.length; dataSetIndex++) { + sum[i] += dataSets[dataSetIndex][i]; + } + } + + xyChartData.yAxis = { + type: 'linear', + title: xyChartData.yAxis.title, + min: isLinearAxisData(xyChartData.yAxis) ? xyChartData.yAxis.min : Math.min(...sum), + max: Math.max(...sum), + }; + } else if (plotType === PlotType.LINE) { + 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; + xyChartData.yAxis = { + type: 'linear', + title: xyChartData.yAxis.title, + min: Math.min(prevMinValue, minValue), + max: Math.max(prevMaxValue, maxValue), + }; + } } function transformDataWithoutCategory(data: number[], plotType: PlotType): SimplePlotDataType { @@ -210,6 +225,7 @@ const clear = function () { plotColorPalette = xyChartThemeConfig.plotColorPalette.split(',').map((color) => color.trim()); hasSetXAxis = false; hasSetYAxis = false; + dataSets = []; }; export default { From 533a921ef5d56985233c3f9c1e6b3731575991ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Sat, 27 Jan 2024 15:07:35 +0100 Subject: [PATCH 16/17] axis calculation fixed --- .../mermaid/src/diagrams/xychart/xychartDb.ts | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts index e3966abe1..1af9f777d 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartDb.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts @@ -113,34 +113,21 @@ 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[], plotType: PlotType) { - if (plotType === PlotType.BAR) { - dataSets.push(data); + dataSets.push(data); - let sum = new Array(data.length).fill(0); - for (let i = 0; i < data.length; i++) { - for (let dataSetIndex = 0; dataSetIndex < dataSets.length; dataSetIndex++) { - sum[i] += dataSets[dataSetIndex][i]; - } + const sum = new Array(data.length).fill(0); + 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: isLinearAxisData(xyChartData.yAxis) ? xyChartData.yAxis.min : Math.min(...sum), - max: Math.max(...sum), - }; - } else if (plotType === PlotType.LINE) { - 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; - xyChartData.yAxis = { - type: 'linear', - title: xyChartData.yAxis.title, - min: Math.min(prevMinValue, minValue), - max: Math.max(prevMaxValue, maxValue), - }; } + + xyChartData.yAxis = { + type: 'linear', + title: xyChartData.yAxis.title, + min: isLinearAxisData(xyChartData.yAxis) ? xyChartData.yAxis.min : Math.min(...sum), + max: Math.max(...sum), + }; } function transformDataWithoutCategory(data: number[], plotType: PlotType): SimplePlotDataType { From 25160d9688ba1c248b7fcf8f13cbd1d98a419615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20M=C3=BCller?= Date: Sat, 27 Jan 2024 17:11:40 +0100 Subject: [PATCH 17/17] axis calculation fixed for mix of line and bar charts --- packages/mermaid/src/diagrams/xychart/xychartDb.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts index 1af9f777d..bb829eebd 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartDb.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts @@ -113,12 +113,13 @@ 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[], plotType: PlotType) { - dataSets.push(data); - const sum = new Array(data.length).fill(0); - for (let i = 0; i < data.length; i++) { - for (const entry of dataSets) { - sum[i] += entry[i]; + if (plotType === PlotType.BAR) { + dataSets.push(data); + for (let i = 0; i < data.length; i++) { + for (const entry of dataSets) { + sum[i] += entry[i]; + } } }