This is an automated email from the ASF dual-hosted git repository.

ovilia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/echarts-from-mermaid.git

commit 68f6c12150959e900ab2f5a1259932509914db4b
Author: Ovilia <[email protected]>
AuthorDate: Thu Mar 20 17:14:27 2025 +0800

    feat(xy-chart); basic xy-chart done
---
 src/__tests__/config.test.ts                       | 170 ---------------------
 src/__tests__/xychart.test.ts                      | 135 ----------------
 src/base/parseAxis.test.ts                         |   6 +-
 src/base/parseAxis.ts                              |   7 +-
 src/base/util.test.ts                              |  34 ++++-
 src/base/util.ts                                   |  33 ++++
 .../pie.test.ts => mermaid/pie/PieChart.test.ts}   |   2 +-
 src/mermaid/xy/XYChart.test.ts                     | 132 ++++++++++++++++
 src/mermaid/xy/XYChart.ts                          |  41 +++--
 9 files changed, 227 insertions(+), 333 deletions(-)

diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts
deleted file mode 100644
index 4342cfb..0000000
--- a/src/__tests__/config.test.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-// import { XYChart } from '../mermaid/xychart';
-
-// describe('Config', () => {
-//   it('should work with width and height', () => {
-//     const definition = `
-// ---
-// config:
-//     xyChart:
-//         width: 900
-//         height: 600
-//     themeVariables:
-//         xyChart:
-//             titleColor: "#ff0000"
-// ---
-// xychart-beta
-//   bar [1, 4, 2]
-// `;
-//     const chart = new XYChart(definition);
-//     expect(chart.getOption()).toEqual({
-//       grid: {
-//         width: 900,
-//         height: 600,
-//       },
-//       title: {
-//         text: 'This is a simple example',
-//         textStyle: {
-//           color: '#ff0000',
-//         },
-//       },
-//       xAxis: {
-//         type: 'value',
-//       },
-//       yAxis: {
-//         type: 'value',
-//       },
-//       series: [
-//         {
-//           type: 'bar',
-//           data: [
-//             [1, 1],
-//             [2, 4],
-//             [3, 2],
-//           ],
-//         },
-//       ],
-//     });
-//   });
-
-//   it('should work with axis configurations', () => {
-//     const definition = `
-// ---
-// config:
-//     xyChart:
-//         xAxis:
-//             labelFontSize: 16
-//             labelPadding: 8
-//             showTitle: false
-//             tickLength: 8
-//             showAxisLine: false
-//         yAxis:
-//             showLabel: false
-//             titleFontSize: 18
-//             showTick: false
-//             axisLineWidth: 3
-// ---
-// xychart-beta
-//   bar [1, 4, 2]
-// `;
-//     const chart = new XYChart(definition);
-//     expect(chart.getOption()).toEqual({
-//       xAxis: {
-//         type: 'value',
-//         show: true,
-//         axisLabel: {
-//           show: true,
-//           fontSize: 16,
-//           margin: 8,
-//         },
-//         axisTick: {
-//           show: true,
-//           length: 8,
-//         },
-//         axisLine: {
-//           show: false,
-//         },
-//         name: '', // due to showTitle: false
-//         nameShow: false,
-//       },
-//       yAxis: {
-//         type: 'value',
-//         show: true,
-//         axisLabel: {
-//           show: false,
-//         },
-//         name: '',
-//         nameTextStyle: {
-//           fontSize: 18,
-//         },
-//         axisTick: {
-//           show: false,
-//         },
-//         axisLine: {
-//           show: true,
-//           lineStyle: {
-//             width: 3,
-//           },
-//         },
-//       },
-//       series: [
-//         {
-//           type: 'bar',
-//           data: [
-//             [1, 1],
-//             [2, 4],
-//             [3, 2],
-//           ],
-//         },
-//       ],
-//     });
-//   });
-
-//   it('should work with theme variables', () => {
-//     const definition = `
-// ---
-// config:
-//     themeVariables:
-//         xyChart:
-//             backgroundColor: "#f0f0f0"
-//             xAxisLabelColor: "#333"
-//             yAxisLineColor: "#666"
-//             plotColorPalette: "#f3456,#43445"
-// ---
-// xychart-beta
-//   bar [1, 4, 2]
-// `;
-//     const chart = new XYChart(definition);
-//     expect(chart.getOption()).toEqual({
-//       backgroundColor: '#f0f0f0',
-//       xAxis: {
-//         type: 'value',
-//         axisLabel: {
-//           show: true,
-//           textStyle: {
-//             color: '#333',
-//           },
-//         },
-//       },
-//       yAxis: {
-//         type: 'value',
-//         axisLine: {
-//           show: true,
-//           lineStyle: {
-//             color: '#666',
-//           },
-//         },
-//       },
-//       color: ['#f3456', '#43445'],
-//       series: [
-//         {
-//           type: 'bar',
-//           data: [
-//             [1, 1],
-//             [2, 4],
-//             [3, 2],
-//           ],
-//         },
-//       ],
-//     });
-//   });
-// });
diff --git a/src/__tests__/xychart.test.ts b/src/__tests__/xychart.test.ts
deleted file mode 100644
index 439244d..0000000
--- a/src/__tests__/xychart.test.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-import { EChartsFromMermaid } from '../index';
-
-const chartTypes = [
-  'line',
-  // 'bar'
-];
-
-describe('XYChart', () => {
-  for (const chartType of chartTypes) {
-    describe(chartType, () => {
-      describe('orientation', () => {
-        //         it('should render vertical chart', () => {
-        //           const definition = `
-        // xychart-beta
-        //   ${chartType} [1, 4, 3]
-        // `;
-        //           const option = EChartsFromMermaid.getOption(definition);
-        //           expect(option).toEqual({
-        //             xAxis: {
-        //               type: 'value',
-        //             },
-        //             yAxis: {
-        //               type: 'value',
-        //             },
-        //             series: [
-        //               {
-        //                 type: chartType,
-        //                 data: [
-        //                   [1, 1],
-        //                   [2, 4],
-        //                   [3, 3],
-        //                 ],
-        //               },
-        //             ],
-        //           });
-        //         });
-        // it('should render horizontal chart', () => {
-        //   const definition = `
-        //     xychart-beta horizontal
-        //       ${chartType} [1, 4, 3]
-        //   `;
-        //   const option = EChartsFromMermaid.getOption(definition);
-        //   expect(option).toEqual({
-        //     xAxis: { type: 'value' },
-        //     yAxis: { type: 'value' },
-        //     series: [
-        //       {
-        //         type: chartType,
-        //         data: [
-        //           [1, 1],
-        //           [4, 2],
-        //           [3, 3],
-        //         ],
-        //       },
-        //     ],
-        //   });
-        // });
-      });
-
-      const axisTypes = ['x' /*, 'y'*/];
-      for (const axisType of axisTypes) {
-        describe(axisType, () => {
-          const otherAxis = axisType === 'x' ? 'y' : 'x';
-          // it(`should render category ${axisType}Axis`, () => {
-          //   const definition = `
-          // xychart-beta
-          //   ${axisType}-axis [cat1, "cat2 with space", cat3]
-          //   ${chartType} [1, 4, 2]
-          //       `;
-          //   const option = EChartsFromMermaid.getOption(definition);
-          //   expect(option).toEqual({
-          //     [axisType + 'Axis']: {
-          //       type: 'category',
-          //       data: ['cat1', 'cat2 with space', 'cat3'],
-          //     },
-          //     [otherAxis + 'Axis']: { type: 'value' },
-          //     series: [
-          //       {
-          //         type: chartType,
-          //         data: [1, 4, 2],
-          //       },
-          //     ],
-          //   });
-          // });
-
-          it(`should render value ${axisType}Axis with range`, () => {
-            const definition = `
-              xychart-beta
-                ${axisType}-axis 100 --> 200
-                ${chartType} [1, 4, 2]
-              `;
-            const option = EChartsFromMermaid.getOption(definition);
-            expect(option).toEqual({
-              [axisType + 'Axis']: { type: 'value', min: 100, max: 200 },
-              [otherAxis + 'Axis']: { type: 'value' },
-              series: [
-                {
-                  type: chartType,
-                  data: [
-                    [100, 1],
-                    [150, 4],
-                    [200, 2],
-                  ],
-                },
-              ],
-            });
-          });
-
-          //   it(`should work with ${axisType}Axis title`, () => {
-          //     const definition = `
-          //     xychart-beta
-          //       ${axisType}-axis "Cats" [cat1, "cat2 with space", cat3]
-          //       ${chartType} [1, 4, 2]
-          //     `;
-          //     const option = EChartsFromMermaid.getOption(definition);
-          //     expect(option).toEqual({
-          //       [axisType + 'Axis']: {
-          //         type: 'category',
-          //         name: 'Cats',
-          //         value: ['cat1', 'cat2 with space', 'cat3'],
-          //       },
-          //       [otherAxis + 'Axis']: { type: 'value' },
-          //       series: [
-          //         {
-          //           type: chartType,
-          //           data: [1, 4, 2],
-          //         },
-          //       ],
-          //     });
-          //   });
-        });
-      }
-    });
-  }
-});
diff --git a/src/base/parseAxis.test.ts b/src/base/parseAxis.test.ts
index dd3c24d..44c5acc 100644
--- a/src/base/parseAxis.test.ts
+++ b/src/base/parseAxis.test.ts
@@ -6,7 +6,7 @@ describe('parseAxis', () => {
     expect(axis).toEqual({
       key: 'x',
       type: 'category',
-      categories: ['cat1', 'cat2', 'cat3'],
+      data: ['cat1', 'cat2', 'cat3'],
     });
   });
 
@@ -15,7 +15,7 @@ describe('parseAxis', () => {
     expect(axis).toEqual({
       key: 'x',
       type: 'category',
-      categories: ['cat1', 'cat2', 'cat3'],
+      data: ['cat1', 'cat2', 'cat3'],
     });
   });
 
@@ -35,7 +35,7 @@ describe('parseAxis', () => {
       key: 'x',
       type: 'category',
       name: 'Title',
-      categories: ['cat1', 'cat2', 'cat3'],
+      data: ['cat1', 'cat2', 'cat3'],
     });
   });
 
diff --git a/src/base/parseAxis.ts b/src/base/parseAxis.ts
index d8ad583..d44742b 100644
--- a/src/base/parseAxis.ts
+++ b/src/base/parseAxis.ts
@@ -12,7 +12,6 @@ export interface AxisDefinition {
 const axisKeys = ['x', 'y'] as const;
 
 export function parseAxis(line: string): AxisDefinition | null {
-  console.log('parseAxis', line);
   let key: 'x' | 'y' | undefined;
   for (const axisKey of axisKeys) {
     if (line.startsWith(axisKey + '-axis')) {
@@ -31,9 +30,9 @@ export function parseAxis(line: string): AxisDefinition | 
null {
    * y-axis "Time trained (minutes)" 0 --> 300
    */
   const rest = line.substring((key + '-axis').length).trim();
-  const titleRe = /^\s*"[^"]*"\s+/.exec(rest);
-  const title = titleRe ? titleRe[0].trim() : undefined;
-  const data = title ? rest.substring(title.length) : rest;
+  const titleRe = /^\s*"([^"]*)"\s+/.exec(rest);
+  const title = titleRe ? titleRe[1] : undefined;
+  const data = titleRe ? rest.substring(titleRe[0].length) : rest;
 
   const arrayResult = parseArray(data);
   const result: AxisDefinition = {
diff --git a/src/base/util.test.ts b/src/base/util.test.ts
index 64f5c54..bf084ba 100644
--- a/src/base/util.test.ts
+++ b/src/base/util.test.ts
@@ -1,24 +1,24 @@
-import { parseArray } from './util';
+import { convert2dData, parseArray } from './util';
 
 describe('parseArray', () => {
   it('should parse array', () => {
     expect(parseArray('[abc, de, fg, 34]')).toEqual({
       type: 'category',
-      categories: ['abc', 'de', 'fg', '34'],
+      data: ['abc', 'de', 'fg', '34'],
     });
   });
 
   it('should parse array awalys as string', () => {
     expect(parseArray('[1, 2, 3]')).toEqual({
       type: 'category',
-      categories: ['1', '2', '3'],
+      data: ['1', '2', '3'],
     });
   });
 
   it('should parse array with quotes', () => {
     expect(parseArray('["abc", "de", "fg", "34"]')).toEqual({
       type: 'category',
-      categories: ['abc', 'de', 'fg', '34'],
+      data: ['abc', 'de', 'fg', '34'],
     });
   });
 
@@ -30,3 +30,29 @@ describe('parseArray', () => {
     });
   });
 });
+
+describe('convert2dData', () => {
+  it('should convert 1d data into 2d data, horizontal false', () => {
+    expect(convert2dData(100, 300, [2, 4, 6], false)).toEqual([
+      [100, 2],
+      [200, 4],
+      [300, 6],
+    ]);
+  });
+
+  it('should convert 1d data into 2d data, horizontal true', () => {
+    expect(convert2dData(100, 300, [2, 4, 6], true)).toEqual([
+      [2, 100],
+      [4, 200],
+      [6, 300],
+    ]);
+  });
+
+  it('should convert 1d data into 2d data, horizontal false, single data', () 
=> {
+    expect(convert2dData(100, 300, [2], false)).toEqual([[100, 2]]);
+  });
+
+  it('should convert 1d data into 2d data, horizontal true, single data', () 
=> {
+    expect(convert2dData(100, 300, [2], true)).toEqual([[2, 100]]);
+  });
+});
diff --git a/src/base/util.ts b/src/base/util.ts
index 57421ad..0a9e299 100644
--- a/src/base/util.ts
+++ b/src/base/util.ts
@@ -31,3 +31,36 @@ export function parseArray(str: string): ParseArrayResult {
       .map((s) => s.replace(/^"|"$/g, '')),
   };
 }
+
+/**
+ * Convert 1d data into 2d data, linear the first dimension from xMin to xMax,
+ * and the second dimension is the data.
+ * For example, min 100, max 300 data [2, 4, 6],
+ * isHorizontal false: returns [[100, 2], [200, 4], [300, 6]]
+ * isHorizontal true: returns [[2, 100], [4, 200], [6, 300]]
+ *
+ * @param xMin
+ * @param xMax
+ * @param data
+ * @param isHorizontal
+ */
+export function convert2dData(
+  xMin: number,
+  xMax: number,
+  data: number[],
+  isHorizontal: boolean
+) {
+  if (data.length < 1) {
+    return [];
+  } else if (data.length === 1) {
+    return isHorizontal ? [[data[0], xMin]] : [[xMin, data[0]]];
+  }
+  const result = [];
+  const step = (xMax - xMin) / (data.length - 1);
+  for (let i = 0; i < data.length; i++) {
+    result.push(
+      isHorizontal ? [data[i], xMin + i * step] : [xMin + i * step, data[i]]
+    );
+  }
+  return result;
+}
diff --git a/src/__tests__/pie.test.ts b/src/mermaid/pie/PieChart.test.ts
similarity index 95%
rename from src/__tests__/pie.test.ts
rename to src/mermaid/pie/PieChart.test.ts
index 81d6437..25a6e0c 100644
--- a/src/__tests__/pie.test.ts
+++ b/src/mermaid/pie/PieChart.test.ts
@@ -1,4 +1,4 @@
-import { EChartsFromMermaid } from '../index';
+import { EChartsFromMermaid } from '../../index';
 
 describe('Pie Chart', () => {
   it('should parse basic pie chart correctly', () => {
diff --git a/src/mermaid/xy/XYChart.test.ts b/src/mermaid/xy/XYChart.test.ts
new file mode 100644
index 0000000..113e4aa
--- /dev/null
+++ b/src/mermaid/xy/XYChart.test.ts
@@ -0,0 +1,132 @@
+import { EChartsFromMermaid } from '../../index';
+
+const chartTypes = ['line', 'bar'];
+
+describe('XYChart', () => {
+  for (const chartType of chartTypes) {
+    describe(chartType, () => {
+      describe('orientation', () => {
+        it('should render vertical chart', () => {
+          const definition = `
+        xychart-beta
+          ${chartType} [1, 4, 3]
+        `;
+          const option = EChartsFromMermaid.getOption(definition);
+          expect(option).toEqual({
+            xAxis: {
+              type: 'value',
+            },
+            yAxis: {
+              type: 'value',
+            },
+            series: [
+              {
+                type: chartType,
+                data: [
+                  [1, 1],
+                  [2, 4],
+                  [3, 3],
+                ],
+              },
+            ],
+          });
+        });
+        it('should render horizontal chart', () => {
+          const definition = `
+            xychart-beta horizontal
+              ${chartType} [1, 4, 3]
+          `;
+          const option = EChartsFromMermaid.getOption(definition);
+          expect(option).toEqual({
+            xAxis: { type: 'value' },
+            yAxis: { type: 'value' },
+            series: [
+              {
+                type: chartType,
+                data: [
+                  [1, 1],
+                  [4, 2],
+                  [3, 3],
+                ],
+              },
+            ],
+          });
+        });
+      });
+
+      const axisTypes = ['x' /*, 'y'*/];
+      for (const axisType of axisTypes) {
+        describe(axisType, () => {
+          const otherAxis = axisType === 'x' ? 'y' : 'x';
+          it(`should render category ${axisType}Axis`, () => {
+            const definition = `
+          xychart-beta
+            ${axisType}-axis [cat1, "cat2 with space", cat3]
+            ${chartType} [1, 4, 2]
+                `;
+            const option = EChartsFromMermaid.getOption(definition);
+            expect(option).toEqual({
+              [axisType + 'Axis']: {
+                type: 'category',
+                data: ['cat1', 'cat2 with space', 'cat3'],
+              },
+              [otherAxis + 'Axis']: { type: 'value' },
+              series: [
+                {
+                  type: chartType,
+                  data: [1, 4, 2],
+                },
+              ],
+            });
+          });
+
+          it(`should render value ${axisType}Axis with range`, () => {
+            const definition = `
+              xychart-beta
+                ${axisType}-axis 100 --> 200
+                ${chartType} [1, 4, 2]
+              `;
+            const option = EChartsFromMermaid.getOption(definition);
+            expect(option).toEqual({
+              [axisType + 'Axis']: { type: 'value', min: 100, max: 200 },
+              [otherAxis + 'Axis']: { type: 'value' },
+              series: [
+                {
+                  type: chartType,
+                  data: [
+                    [100, 1],
+                    [150, 4],
+                    [200, 2],
+                  ],
+                },
+              ],
+            });
+          });
+
+          it(`should work with ${axisType}Axis title`, () => {
+            const definition = `
+              xychart-beta
+                ${axisType}-axis "Cats" [cat1, "cat2 with space", cat3]
+                ${chartType} [1, 4, 2]
+              `;
+            const option = EChartsFromMermaid.getOption(definition);
+            expect(option).toEqual({
+              [axisType + 'Axis']: {
+                type: 'category',
+                name: 'Cats',
+                data: ['cat1', 'cat2 with space', 'cat3'],
+              },
+              [otherAxis + 'Axis']: { type: 'value' },
+              series: [
+                {
+                  type: chartType,
+                  data: [1, 4, 2],
+                },
+              ],
+            });
+          });
+        });
+      }
+    });
+  }
+});
diff --git a/src/mermaid/xy/XYChart.ts b/src/mermaid/xy/XYChart.ts
index 7580435..367e711 100644
--- a/src/mermaid/xy/XYChart.ts
+++ b/src/mermaid/xy/XYChart.ts
@@ -1,9 +1,9 @@
-import { AxisDefinition, parseAxis } from '../../base/parseAxis';
-import { BaseChart } from '../../base/BaseChart';
 import type { EChartsOption, LineSeriesOption, BarSeriesOption } from 
'echarts';
+import { parseAxis } from '../../base/parseAxis';
+import { convert2dData } from '../../base/util';
+import { BaseChart } from '../../base/BaseChart';
 
 const xySeriesTypes = ['line', 'bar'];
-const axisTypes = ['x-axis', 'y-axis'];
 
 export class XYChart extends BaseChart {
   getOption(): EChartsOption {
@@ -14,22 +14,18 @@ export class XYChart extends BaseChart {
 
     for (; lineId < this._lines.length; lineId++) {
       const line = this._lines[lineId];
-      console.log('line', line);
 
       if (line.indexOf('horizontal') > 0) {
         isHorizontal = true;
       }
 
       const axisDef = parseAxis(line);
-      console.log('-----', isHorizontal, axisDef);
       if (axisDef) {
         const { key, ...rest } = axisDef;
         if ((key === 'x' && !isHorizontal) || (key === 'y' && isHorizontal)) {
           xAxis = rest;
-          console.log('========= xAxis', xAxis);
         } else {
           yAxis = rest;
-          console.log('========= yAxis', yAxis);
         }
       } else if (lineId > 0) {
         break;
@@ -46,23 +42,36 @@ export class XYChart extends BaseChart {
           try {
             let data = JSON.parse(dataStr);
 
-            // If xAxis and yAxis are not defined, convert data into:
-            // [[1, data[0]], [2, data[1]], [3, data[2]]], ...
-            if (!xAxis && !yAxis) {
-              data = data.map((d: number, i: number) =>
-                isHorizontal ? [d, i + 1] : [i + 1, d]
-              );
+            const valueAxis = isHorizontal ? xAxis : yAxis;
+            if (valueAxis && valueAxis.type === 'category') {
+              throw new Error('Category axis is not supported for value 
axis.');
             }
-            console.log('data', data);
 
-            // TODO: double value axes
+            // Case 1: xAxis: null, yAxis: any
+            // -> xAxis: value axis with min(1) and max(data.length),
+            // -> yAxis: value axis without/with min and max
+            // -> data: [[1, data[0]], ... [data.length, data[data.length - 
1]]]
+            if (!xAxis) {
+              data = convert2dData(1, data.length, data, isHorizontal);
+            }
+            // Case 2: xAxis: value axis with min and max, yAxis: any
+            // -> xAxis: value axis with min and max,
+            // -> yAxis: value axis without/with min and max
+            // -> data: [[xAxis.min, data[0]], ... [xAxis.max, 
data[data.length - 1]]]
+            else if (xAxis && xAxis.max != null && xAxis.min != null) {
+              data = convert2dData(xAxis.min, xAxis.max, data, isHorizontal);
+            }
+            // Case 3: xAxis: category, yAxis: null
+            // -> xAxis: category axis
+            // -> yAxis: value axis
+            // -> data: data (not changed)
 
             series.push({
               type: type as 'line' | 'bar',
               data,
             });
           } catch (e) {
-            console.error(`Error parsing data for ${type}: ${dataStr}`);
+            console.error(`Error parsing data for ${type}: ${dataStr}`, e);
           }
         }
       }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to