Able to draw till axis from chart configuration

- there is some issue with drawing the plot from chart data
This commit is contained in:
Subhash Halder
2023-07-02 17:14:12 +05:30
parent 1d98ead5c2
commit ebd329149b
14 changed files with 282 additions and 224 deletions

View File

@@ -16,11 +16,23 @@
<body> <body>
<h1>XY Charts demos</h1> <h1>XY Charts demos</h1>
<pre class="mermaid"> <pre class="mermaid">
xychart xychart-beta horizontal
title Basic xychart
x-axis "this is x axis" [category1, "category 2", category3]
y-axis yaxisText 10 --> 150
</pre> </pre>
<hr /> <hr />
<h1>XY Charts demos</h1>
<pre class="mermaid">
xychart-beta
title Basic xychart
x-axis "this is x axis" [category1, "category 2", category3]
y-axis yaxisText 10 --> 150
</pre>
<hr />
<script type="module"> <script type="module">
import mermaid from './mermaid.esm.mjs'; import mermaid from './mermaid.esm.mjs';
mermaid.initialize({ mermaid.initialize({

View File

@@ -150,6 +150,7 @@ export interface MermaidConfig {
er?: ErDiagramConfig; er?: ErDiagramConfig;
pie?: PieDiagramConfig; pie?: PieDiagramConfig;
quadrantChart?: QuadrantChartConfig; quadrantChart?: QuadrantChartConfig;
xyChart?: XYChartConfig;
requirement?: RequirementDiagramConfig; requirement?: RequirementDiagramConfig;
mindmap?: MindmapDiagramConfig; mindmap?: MindmapDiagramConfig;
gitGraph?: GitGraphDiagramConfig; gitGraph?: GitGraphDiagramConfig;
@@ -703,6 +704,37 @@ export interface QuadrantChartConfig extends BaseDiagramConfig {
*/ */
quadrantExternalBorderStrokeWidth?: number; quadrantExternalBorderStrokeWidth?: number;
} }
export interface XYChartAxisConfig {
showLabel: boolean;
labelFontSize: number;
lablePadding: number;
labelFill: string;
showTitle: boolean;
titleFontSize: number;
titlePadding: number;
titleFill: string;
showTick: boolean;
tickLength: number;
tickWidth: number;
tickFill: string;
}
export interface XYChartConfig extends BaseDiagramConfig {
width: number;
height: number;
fontFamily: string;
titleFontSize: number;
titleFill: string;
titlePadding: number;
showtitle: boolean;
xAxis: XYChartAxisConfig;
yAxis: XYChartAxisConfig;
plotBorderWidth: number;
chartOrientation: 'vertical' | 'horizontal';
plotReservedSpacePercent: number;
}
/** /**
* The object containing configurations specific for entity relationship diagrams * The object containing configurations specific for entity relationship diagrams
* *

View File

@@ -9,41 +9,6 @@ export interface ChartComponent {
getDrawableElements(): DrawableElem[]; getDrawableElements(): DrawableElem[];
} }
export enum OrientationEnum {
VERTICAL = 'vertical',
HORIZONTAL = 'horizontal',
}
export interface AxisConfig {
showLabel: boolean;
labelFontSize: number;
lablePadding: number;
labelFill: string;
showTitle: boolean;
titleFontSize: number;
titlePadding: number;
titleFill: string;
showTick: boolean;
tickLength: number;
tickWidth: number;
tickFill: string;
}
export interface XYChartConfig {
width: number;
height: number;
fontFamily: string;
titleFontSize: number;
titleFill: string;
titlePadding: number;
showtitle: boolean;
xAxis: AxisConfig;
yAxis: AxisConfig;
plotBorderWidth: number;
chartOrientation: OrientationEnum;
plotReservedSpacePercent: number;
}
export type SimplePlotDataType = [string | number, number][]; export type SimplePlotDataType = [string | number, number][];
export interface LinePlotData { export interface LinePlotData {
@@ -74,6 +39,11 @@ export interface LinearAxisDataType{
export type AxisDataType = LinearAxisDataType | BandAxisDataType; export type AxisDataType = LinearAxisDataType | BandAxisDataType;
export function isBandAxisData(data: any): data is BandAxisDataType {
return data.categories && Array.isArray(data.categories);
}
export interface XYChartData { export interface XYChartData {
xAxis: AxisDataType; xAxis: AxisDataType;
yAxis: AxisDataType; yAxis: AxisDataType;
@@ -88,19 +58,6 @@ export interface Dimension {
export interface BoundingRect extends Point, Dimension {} export interface BoundingRect extends Point, Dimension {}
export interface XYChartSpaceProperty extends BoundingRect {
orientation: OrientationEnum;
}
export interface XYChartSpace {
chart: XYChartSpaceProperty;
title: XYChartSpaceProperty;
xAxisLabels: XYChartSpaceProperty;
xAxisTitle: XYChartSpaceProperty;
yAxisLabel: XYChartSpaceProperty;
yAxisTitle: XYChartSpaceProperty;
}
export interface Point { export interface Point {
x: number; x: number;
y: number; y: number;

View File

@@ -1,9 +1,10 @@
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { DrawableElem, OrientationEnum, XYChartConfig, XYChartData } from './Interfaces.js'; import { DrawableElem, XYChartData } from './Interfaces.js';
import { getChartTitleComponent } from './components/ChartTitle.js'; import { getChartTitleComponent } from './components/ChartTitle.js';
import { ChartComponent } from './Interfaces.js'; import { ChartComponent } from './Interfaces.js';
import { IAxis, getAxis } from './components/axis/index.js'; import { IAxis, getAxis } from './components/axis/index.js';
import { IPlot, getPlotComponent } from './components/plot/index.js'; import { IPlot, getPlotComponent } from './components/plot/index.js';
import { XYChartConfig } from '../../../config.type.js';
export class Orchestrator { export class Orchestrator {
private componentStore: { private componentStore: {
@@ -158,7 +159,7 @@ export class Orchestrator {
} }
private calculateSpace() { private calculateSpace() {
if (this.chartConfig.chartOrientation === OrientationEnum.HORIZONTAL) { if (this.chartConfig.chartOrientation === 'horizontal') {
this.calculateHorizonatalSpace(); this.calculateHorizonatalSpace();
} else { } else {
this.calculateVerticalSpace(); this.calculateVerticalSpace();

View File

@@ -1,14 +1,13 @@
import { ITextDimensionCalculator, TextDimensionCalculator } from '../TextDimensionCalculator.js'; import { ITextDimensionCalculator, TextDimensionCalculator } from '../TextDimensionCalculator.js';
import { import {
XYChartConfig,
XYChartData, XYChartData,
Dimension, Dimension,
BoundingRect, BoundingRect,
DrawableElem, DrawableElem,
Point, Point,
OrientationEnum,
} from '../Interfaces.js'; } from '../Interfaces.js';
import { ChartComponent } from '../Interfaces.js'; import { ChartComponent } from '../Interfaces.js';
import { XYChartConfig } from '../../../../config.type.js';
export class ChartTitle implements ChartComponent { export class ChartTitle implements ChartComponent {
private boundingRect: BoundingRect; private boundingRect: BoundingRect;

View File

@@ -1,13 +1,12 @@
import { import {
AxisConfig,
AxisDataType, AxisDataType,
BandAxisDataType, isBandAxisData,
LinearAxisDataType
} from '../../Interfaces.js'; } from '../../Interfaces.js';
import { TextDimensionCalculator } from '../../TextDimensionCalculator.js'; import { TextDimensionCalculator } from '../../TextDimensionCalculator.js';
import { ChartComponent } from '../../Interfaces.js'; import { ChartComponent } from '../../Interfaces.js';
import { BandAxis } from './BandAxis.js'; import { BandAxis } from './BandAxis.js';
import { LinearAxis } from './LinearAxis.js'; import { LinearAxis } from './LinearAxis.js';
import { XYChartAxisConfig } from '../../../../../config.type.js';
export type AxisPosition = 'left' | 'right' | 'top' | 'bottom'; export type AxisPosition = 'left' | 'right' | 'top' | 'bottom';
@@ -19,15 +18,7 @@ export interface IAxis extends ChartComponent {
setRange(range: [number, number]): void; setRange(range: [number, number]): void;
} }
function isLinearAxisData(data: any): data is LinearAxisDataType { export function getAxis(data: AxisDataType, axisConfig: XYChartAxisConfig): IAxis {
return !(Number.isNaN(data.min) || Number.isNaN(data.max));
}
function isBandAxisData(data: any): data is BandAxisDataType {
return data.categories && Array.isArray(data.categories);
}
export function getAxis(data: AxisDataType, axisConfig: AxisConfig): IAxis {
const textDimansionCalculator = new TextDimensionCalculator(); const textDimansionCalculator = new TextDimensionCalculator();
if (isBandAxisData(data)) { if (isBandAxisData(data)) {
return new BandAxis(axisConfig, data.categories, data.title, textDimansionCalculator); return new BandAxis(axisConfig, data.categories, data.title, textDimansionCalculator);

View File

@@ -1,9 +1,8 @@
import { XYChartConfig } from '../../../../../config.type.js';
import { import {
BarPlotData, BarPlotData,
BoundingRect, BoundingRect,
DrawableElem, DrawableElem,
OrientationEnum,
SimplePlotDataType,
} from '../../Interfaces.js'; } from '../../Interfaces.js';
import { IAxis } from '../axis/index.js'; import { IAxis } from '../axis/index.js';
@@ -13,7 +12,7 @@ export class BarPlot {
private boundingRect: BoundingRect, private boundingRect: BoundingRect,
private xAxis: IAxis, private xAxis: IAxis,
private yAxis: IAxis, private yAxis: IAxis,
private orientation: OrientationEnum private orientation: XYChartConfig['chartOrientation']
) {} ) {}
getDrawableElement(): DrawableElem[] { getDrawableElement(): DrawableElem[] {
@@ -29,7 +28,7 @@ export class BarPlot {
(1 - barPaddingPercent / 100); (1 - barPaddingPercent / 100);
const barWidthHalf = barWidth / 2; const barWidthHalf = barWidth / 2;
if (this.orientation === OrientationEnum.HORIZONTAL) { if (this.orientation === 'horizontal') {
return [ return [
{ {
groupTexts: ['plot', 'bar-plot'], groupTexts: ['plot', 'bar-plot'],

View File

@@ -1,13 +1,14 @@
import { line } from 'd3'; import { line } from 'd3';
import { DrawableElem, LinePlotData, OrientationEnum } from '../../Interfaces.js'; import { DrawableElem, LinePlotData } from '../../Interfaces.js';
import { IAxis } from '../axis/index.js'; import { IAxis } from '../axis/index.js';
import { XYChartConfig } from '../../../../../config.type.js';
export class LinePlot { export class LinePlot {
constructor( constructor(
private plotData: LinePlotData, private plotData: LinePlotData,
private xAxis: IAxis, private xAxis: IAxis,
private yAxis: IAxis, private yAxis: IAxis,
private orientation: OrientationEnum private orientation: XYChartConfig['chartOrientation']
) {} ) {}
getDrawableElement(): DrawableElem[] { getDrawableElement(): DrawableElem[] {
@@ -17,7 +18,7 @@ export class LinePlot {
]); ]);
let path: string | null; let path: string | null;
if (this.orientation === OrientationEnum.HORIZONTAL) { if (this.orientation === 'horizontal') {
path = line() path = line()
.y((d) => d[0]) .y((d) => d[0])
.x((d) => d[1])(finalData); .x((d) => d[1])(finalData);

View File

@@ -1,17 +1,20 @@
import { BoundingRect, DrawableElem, OrientationEnum } from '../../Interfaces.js'; import { XYChartConfig } from '../../../../../config.type.js';
import { BoundingRect, DrawableElem } from '../../Interfaces.js';
export class PlotBorder { export class PlotBorder {
constructor(private boundingRect: BoundingRect, private orientation: OrientationEnum) {} constructor(private boundingRect: BoundingRect, private orientation: XYChartConfig['chartOrientation']) {}
getDrawableElement(): DrawableElem[] { getDrawableElement(): DrawableElem[] {
const {x, y, width, height} = this.boundingRect; const { x, y, width, height } = this.boundingRect;
if(this.orientation === OrientationEnum.HORIZONTAL) { if (this.orientation === 'horizontal') {
return [ return [
{ {
groupTexts: ['plot', 'chart-border'], groupTexts: ['plot', 'chart-border'],
type: 'path', type: 'path',
data: [ data: [
{ {
path: `M ${x},${y} L ${x + width},${y} M ${x + width},${y + height} M ${x},${y + height} L ${x},${y}`, path: `M ${x},${y} L ${x + width},${y} M ${x + width},${y + height} M ${x},${
y + height
} L ${x},${y}`,
strokeFill: '#000000', strokeFill: '#000000',
strokeWidth: 1, strokeWidth: 1,
}, },
@@ -25,7 +28,9 @@ export class PlotBorder {
type: 'path', type: 'path',
data: [ data: [
{ {
path: `M ${x},${y} M ${x + width},${y} M ${x + width},${y + height} L ${x},${y + height} L ${x},${y}`, path: `M ${x},${y} M ${x + width},${y} M ${x + width},${y + height} L ${x},${
y + height
} L ${x},${y}`,
strokeFill: '#000000', strokeFill: '#000000',
strokeWidth: 1, strokeWidth: 1,
}, },

View File

@@ -1,11 +1,9 @@
import { import {
XYChartConfig,
XYChartData, XYChartData,
Dimension, Dimension,
BoundingRect, BoundingRect,
DrawableElem, DrawableElem,
Point, Point,
OrientationEnum,
ChartPlotEnum, ChartPlotEnum,
} from '../../Interfaces.js'; } from '../../Interfaces.js';
import { IAxis } from '../axis/index.js'; import { IAxis } from '../axis/index.js';
@@ -13,6 +11,7 @@ import { ChartComponent } from '../../Interfaces.js';
import { LinePlot } from './LinePlot.js'; import { LinePlot } from './LinePlot.js';
import { PlotBorder } from './PlotBorder.js'; import { PlotBorder } from './PlotBorder.js';
import { BarPlot } from './BarPlot.js'; import { BarPlot } from './BarPlot.js';
import { XYChartConfig } from '../../../../../config.type.js';
export interface IPlot extends ChartComponent { export interface IPlot extends ChartComponent {

View File

@@ -1,107 +1,18 @@
// @ts-ignore: TODO Fix ts errors // @ts-ignore: TODO Fix ts errors
import { defaultConfig } from '../../../config.js'; import { XYChartConfig } from '../../../config.type.js';
import { log } from '../../../logger.js'; import { log } from '../../../logger.js';
import { import {
ChartPlotEnum,
DrawableElem, DrawableElem,
OrientationEnum, XYChartData,
XYChartConfig,
XYChartData
} from './Interfaces.js'; } from './Interfaces.js';
import { Orchestrator } from './Orchestrator.js'; import { Orchestrator } from './Orchestrator.js';
export class XYChartBuilder { export class XYChartBuilder {
private config: XYChartConfig;
private chartData: XYChartData;
constructor() { static build(config: XYChartConfig, chartData: XYChartData): DrawableElem[] {
this.config = { log.trace(`Build start with Config: ${JSON.stringify(config, null, 2)}`);
width: 700, log.trace(`Build start with ChartData: ${JSON.stringify(chartData, null, 2)}`);
height: 500, const orchestrator = new Orchestrator(config, chartData);
fontFamily: defaultConfig.fontFamily || 'Sans',
titleFontSize: 16,
titleFill: '#000000',
titlePadding: 5,
showtitle: true,
plotBorderWidth: 2,
yAxis: {
showLabel: true,
labelFontSize: 14,
lablePadding: 5,
labelFill: '#000000',
showTitle: true,
titleFontSize: 16,
titlePadding: 5,
titleFill: '#000000',
showTick: true,
tickLength: 5,
tickWidth: 2,
tickFill: '#000000',
},
xAxis: {
showLabel: true,
labelFontSize: 14,
lablePadding: 5,
labelFill: '#000000',
showTitle: true,
titleFontSize: 16,
titlePadding: 5,
titleFill: '#000000',
showTick: true,
tickLength: 5,
tickWidth: 2,
tickFill: '#000000',
},
chartOrientation: OrientationEnum.HORIZONTAL,
plotReservedSpacePercent: 50,
};
this.chartData = {
yAxis: {
title: 'yAxis1',
min: 0,
max: 100,
},
xAxis: {
title: 'xAxis',
categories: ['category1', 'category2', 'category3'],
},
title: 'this is a sample task',
plots: [
{
type: ChartPlotEnum.BAR,
fill: '#0000bb',
data: [
['category1', 23],
['category2', 56],
['category3', 34],
],
},
{
type: ChartPlotEnum.LINE,
strokeFill: '#bb0000',
strokeWidth: 2,
data: [
['category1', 33],
['category2', 45],
['category3', 65],
],
},
],
};
}
setWidth(width: number) {
this.config.width = width;
}
setHeight(height: number) {
this.config.height = height;
}
build(): DrawableElem[] {
log.trace(`Build start with Config: ${JSON.stringify(this.config, null, 2)}`);
log.trace(`Build start with ChartData: ${JSON.stringify(this.chartData, null, 2)}`);
const orchestrator = new Orchestrator(this.config, this.chartData);
return orchestrator.getDrawableElement(); return orchestrator.getDrawableElement();
} }
} }

View File

@@ -20,7 +20,6 @@
%x axis_data %x axis_data
%x axis_data_band %x axis_data_band
%x axis_data_band_capture %x axis_data_band_capture
%x axis_data_band_str
%x line %x line
%x line_title %x line_title
%x line_data %x line_data
@@ -67,14 +66,10 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
<axis_data>[+-]?\d+(?:\.\d+)?" "*"-->"" "*[+-]?\d+(?:\.\d+)?" "* { return 'AXIS_RANGE_DATA';} <axis_data>[+-]?\d+(?:\.\d+)?" "*"-->"" "*[+-]?\d+(?:\.\d+)?" "* { return 'AXIS_RANGE_DATA';}
<axis_data>[\[]" "* {this.begin("axis_data_band"); this.begin("axis_data_band_capture")} <axis_data>[\[]" "* {this.begin("axis_data_band"), this.begin("axis_data_band_capture")}
<axis_data_band>[,]" "* {this.begin("axis_data_band_capture")} <axis_data_band_capture>(["][^",]+["]|[^\s\"\,]]+)" "*([,]" "*(["][^",]+["]|[^\s\]",]+)" "*)* { this.popState(); return "AXIS_BAND_DATA"; }
<axis_data_band_capture>["] {this.begin("axis_data_band_str");}
<axis_data_band_str>[^"]+ {return "AXIS_BAND_DATA";}
<axis_data_band_str>["]" "* {this.popState(); this.popState();}
<axis_data_band_capture>[^\s]+" "* {this.popState(); return "AXIS_BAND_DATA"}
<axis_data_band>[\]]" "* {this.popState(); return "AXIS_BAND_DATA_END"} <axis_data_band>[\]]" "* {this.popState(); return "AXIS_BAND_DATA_END"}
<axis_data>[\r\n]+ {this.popState(); this.popState();} <axis_data>[\s]+ {this.popState(); this.popState();}
"line"" "* {this.begin("line"); return 'LINE';} "line"" "* {this.begin("line"); return 'LINE';}
@@ -162,27 +157,27 @@ statement
; ;
parseLine parseLine
: LINE LINE_TITLE LINE_DATA {yy.addLineData($2.trim(), $3.split(',').map(d => Number(d.trim())));} : LINE LINE_TITLE LINE_DATA {yy.setLineData($2.trim(), $3.split(',').map(d => Number(d.trim())));}
; ;
parseBar parseBar
: BAR BAR_TITLE BAR_DATA {yy.addBarData($2.trim(), $3.split(',').map(d => Number(d.trim())));} : BAR BAR_TITLE BAR_DATA {yy.setBarData($2.trim(), $3.split(',').map(d => Number(d.trim())));}
; ;
parseXAxis parseXAxis
: AXIS_TITLE statement {yy.setXAxisTitle($1.trim());} : AXIS_TITLE statement {yy.setXAxisTitle($1.trim());}
| AXIS_TITLE xAxisBandData {yy.setXAxisTitle($1.trim());} | AXIS_TITLE xAxisBandData statement {yy.setXAxisTitle($1.trim());}
| AXIS_TITLE AXIS_RANGE_DATA {yy.setXAxisTitle($1.trim()); $$ = $2.split("-->"); yy.setXAxisRangeData(Number($$[0]), Number($$[1]));} | AXIS_TITLE AXIS_RANGE_DATA statement {yy.setXAxisTitle($1.trim()); $$ = $2.split("-->"); yy.setXAxisRangeData(Number($$[0]), Number($$[1]));}
; ;
xAxisBandData xAxisBandData
: AXIS_BAND_DATA xAxisBandData {yy.addXAxisBand($1.trim());} : AXIS_BAND_DATA xAxisBandData {yy.setXAxisBand($1.split(',').map(d => { let m = d.trim().match(/^(?:["]([^"]+)["]|([^\s"]+))$/); return m ? m[1] || m[2] : "";}));}
| AXIS_BAND_DATA_END | AXIS_BAND_DATA_END
; ;
parseYAxis parseYAxis
: AXIS_TITLE statement {yy.setYAxisTitle($1.trim());} : AXIS_TITLE statement {yy.setYAxisTitle($1.trim());}
| AXIS_TITLE AXIS_RANGE_DATA {yy.setYAxisTitle($1.trim()); $$ = $2.split("-->"); yy.setYAxisRangeData(Number($$[0]), Number($$[1]));} | AXIS_TITLE AXIS_RANGE_DATA statement {yy.setYAxisTitle($1.trim()); $$ = $2.split("-->"); yy.setYAxisRangeData(Number($$[0]), Number($$[1]));}
; ;
directive directive

View File

@@ -14,12 +14,11 @@ const mockDB: Record<string, Mock<any, any>> = {
setDiagramTitle: vi.fn(), setDiagramTitle: vi.fn(),
setXAxisTitle: vi.fn(), setXAxisTitle: vi.fn(),
setXAxisRangeData: vi.fn(), setXAxisRangeData: vi.fn(),
addXAxisBand: vi.fn(), setXAxisBand: vi.fn(),
setYAxisTitle: vi.fn(), setYAxisTitle: vi.fn(),
setYAxisRangeData: vi.fn(), setYAxisRangeData: vi.fn(),
addYAxisBand: vi.fn(), setLineData: vi.fn(),
addLineData: vi.fn(), setBarData: vi.fn(),
addBarData: vi.fn(),
}; };
function clearMocks() { function clearMocks() {
@@ -109,12 +108,27 @@ describe('Testing xychart jison file', () => {
clearMocks(); clearMocks();
str = 'xychart-beta \nx-axis xAxisName [ "cat1" , cat2 ] \n'; str = 'xychart-beta \nx-axis xAxisName [ "cat1" , cat2 ] \n ';
expect(parserFnConstructor(str)).not.toThrow(); expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName'); expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName');
expect(mockDB.addXAxisBand).toHaveBeenCalledTimes(2); expect(mockDB.setXAxisBand).toHaveBeenCalledWith(["cat1", "cat2"]);
expect(mockDB.addXAxisBand).toHaveBeenNthCalledWith(1, 'cat2'); clearMocks();
expect(mockDB.addXAxisBand).toHaveBeenNthCalledWith(2, 'cat1');
str = `xychart-beta \n x-axis "this is x axis" [category1, "category 2", category3]\n`
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('this is x axis');
expect(mockDB.setXAxisBand).toHaveBeenCalledWith(["category1", "category 2", "category3"]);
clearMocks();
str = 'xychart-beta \nx-axis xAxisName [ "cat1 with space" , cat2 , cat3] \n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName');
expect(mockDB.setXAxisBand).toHaveBeenCalledWith(["cat1 with space", "cat2", "cat3"]);
clearMocks();
str = 'xychart-beta \nx-axis xAxisName [ "cat1 with space" , cat2 asdf , cat3] \n ';
expect(parserFnConstructor(str)).toThrow();
}); });
it('parse y-axis', () => { it('parse y-axis', () => {
let str = 'xychart-beta \ny-axis yAxisName\n'; let str = 'xychart-beta \ny-axis yAxisName\n';
@@ -150,7 +164,7 @@ describe('Testing xychart jison file', () => {
expect(parserFnConstructor(str)).not.toThrow(); expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName'); expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName');
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName'); expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName');
expect(mockDB.addLineData).toHaveBeenCalledWith('lineTitle', [23, 45, 56.6]); expect(mockDB.setLineData).toHaveBeenCalledWith('lineTitle', [23, 45, 56.6]);
clearMocks(); clearMocks();
@@ -159,7 +173,7 @@ describe('Testing xychart jison file', () => {
expect(parserFnConstructor(str)).not.toThrow(); expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName'); expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName');
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName'); expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName');
expect(mockDB.addLineData).toHaveBeenCalledWith('lineTitle with space', [23, -45, 56.6]); expect(mockDB.setLineData).toHaveBeenCalledWith('lineTitle with space', [23, -45, 56.6]);
clearMocks(); clearMocks();
str = str =
@@ -171,7 +185,7 @@ describe('Testing xychart jison file', () => {
expect(parserFnConstructor(str)).not.toThrow(); expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName'); expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName');
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName'); expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName');
expect(mockDB.addBarData).toHaveBeenCalledWith('barTitle', [23, 45, 56.6]); expect(mockDB.setBarData).toHaveBeenCalledWith('barTitle', [23, 45, 56.6]);
clearMocks(); clearMocks();
@@ -180,7 +194,7 @@ describe('Testing xychart jison file', () => {
expect(parserFnConstructor(str)).not.toThrow(); expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName'); expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName');
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName'); expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName');
expect(mockDB.addBarData).toHaveBeenCalledWith('barTitle with space', [23, -45, 56.6]); expect(mockDB.setBarData).toHaveBeenCalledWith('barTitle with space', [23, -45, 56.6]);
clearMocks(); clearMocks();
str = str =
@@ -193,9 +207,29 @@ describe('Testing xychart jison file', () => {
expect(parserFnConstructor(str)).not.toThrow(); expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName'); expect(mockDB.setYAxisTitle).toHaveBeenCalledWith('yAxisName');
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName'); expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('xAxisName');
expect(mockDB.addBarData).toHaveBeenCalledWith('barTitle1', [23, 45, 56.6]); expect(mockDB.setBarData).toHaveBeenCalledWith('barTitle1', [23, 45, 56.6]);
expect(mockDB.addBarData).toHaveBeenCalledWith('barTitle2', [13, 42, 56.89]); expect(mockDB.setBarData).toHaveBeenCalledWith('barTitle2', [13, 42, 56.89]);
expect(mockDB.addLineData).toHaveBeenCalledWith('lineTitle1', [11, 45.5, 67, 23]); expect(mockDB.setLineData).toHaveBeenCalledWith('lineTitle1', [11, 45.5, 67, 23]);
expect(mockDB.addLineData).toHaveBeenCalledWith('lineTitle2', [45, 99, 12]); expect(mockDB.setLineData).toHaveBeenCalledWith('lineTitle2', [45, 99, 12]);
clearMocks();
str = `
xychart-beta horizontal
title Basic xychart
x-axis "this is x axis" [category1, "category 2", category3]
y-axis yaxisText 10 --> 150
bar barTitle1 [23, 45, 56.6]
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('yaxisText');
expect(mockDB.setYAxisRangeData).toHaveBeenCalledWith(10, 150);
expect(mockDB.setXAxisTitle).toHaveBeenCalledWith('this is x axis');
expect(mockDB.setXAxisBand).toHaveBeenCalledWith(["category1", "category 2", "category3"]);
expect(mockDB.setBarData).toHaveBeenCalledWith('barTitle1', [23, 45, 56.6]);
expect(mockDB.setBarData).toHaveBeenCalledWith('barTitle2', [13, 42, 56.89]);
expect(mockDB.setLineData).toHaveBeenCalledWith('lineTitle1', [11, 45.5, 67, 23]);
expect(mockDB.setLineData).toHaveBeenCalledWith('lineTitle2', [45, 99, 12]);
}); });
}); });

View File

@@ -12,9 +12,96 @@ import {
clear as commonClear, clear as commonClear,
} from '../../commonDb.js'; } from '../../commonDb.js';
import { XYChartBuilder } from './chartBuilder/index.js'; import { XYChartBuilder } from './chartBuilder/index.js';
import { DrawableElem } from './chartBuilder/Interfaces.js'; import { ChartPlotEnum, DrawableElem, XYChartData, isBandAxisData } from './chartBuilder/Interfaces.js';
import { XYChartConfig } from '../../config.type.js';
const config = configApi.getConfig(); const config = configApi.getConfig();
let chartWidth = 600;
let chartHeight = 500;
function getChartDefaultConfig(): XYChartConfig {
return config.xyChart
? { ...config.xyChart, yAxis: { ...config.xyChart.yAxis }, xAxis: { ...config.xyChart.xAxis } }
: {
width: 700,
height: 500,
fontFamily: config.fontFamily || 'Sans',
titleFontSize: 16,
titleFill: '#000000',
titlePadding: 5,
showtitle: true,
plotBorderWidth: 2,
yAxis: {
showLabel: true,
labelFontSize: 14,
lablePadding: 5,
labelFill: '#000000',
showTitle: true,
titleFontSize: 16,
titlePadding: 5,
titleFill: '#000000',
showTick: true,
tickLength: 5,
tickWidth: 2,
tickFill: '#000000',
},
xAxis: {
showLabel: true,
labelFontSize: 14,
lablePadding: 5,
labelFill: '#000000',
showTitle: true,
titleFontSize: 16,
titlePadding: 5,
titleFill: '#000000',
showTick: true,
tickLength: 5,
tickWidth: 2,
tickFill: '#000000',
},
chartOrientation: 'vertical',
plotReservedSpacePercent: 50,
};
}
function getChartDefalutData(): XYChartData {
return {
yAxis: {
title: 'yAxis1',
min: 0,
max: 100,
},
xAxis: {
title: 'xAxis',
categories: [],
},
title: '',
plots: [
{
type: ChartPlotEnum.BAR,
fill: '#0000bb',
data: [
['category1', 23],
['category 2', 56],
['category3', 34],
],
},
{
type: ChartPlotEnum.LINE,
strokeFill: '#bb0000',
strokeWidth: 2,
data: [
['category1', 33],
['category 2', 45],
['category3', 65],
],
},
],
};
}
let xyChartConfig: XYChartConfig = getChartDefaultConfig();
let xyChartData: XYChartData = getChartDefalutData();
function textSanitizer(text: string) { function textSanitizer(text: string) {
return sanitizeText(text.trim(), config); return sanitizeText(text.trim(), config);
@@ -23,24 +110,51 @@ function textSanitizer(text: string) {
function parseDirective(statement: string, context: string, type: string) { function parseDirective(statement: string, context: string, type: string) {
// @ts-ignore: TODO Fix ts errors // @ts-ignore: TODO Fix ts errors
mermaidAPI.parseDirective(this, statement, context, type); mermaidAPI.parseDirective(this, statement, context, type);
}; }
const xyChartBuilder = new XYChartBuilder();
function setOrientation(oriantation: string) {
if (oriantation === 'horizontal') {
xyChartConfig.chartOrientation = 'horizontal';
} else {
xyChartConfig.chartOrientation = 'vertical';
}
}
function setXAxisTitle(title: string) {
xyChartData.xAxis.title = textSanitizer(title);
}
function setXAxisRangeData(min: number, max: number) {
xyChartData.xAxis = {title: xyChartData.xAxis.title, min, max};
}
function setXAxisBand(categories: string[]) {
xyChartData.xAxis = {title: xyChartData.xAxis.title, categories: categories.map(c => textSanitizer(c))};
}
function setYAxisTitle(title: string) {
xyChartData.yAxis.title = textSanitizer(title);
}
function setYAxisRangeData(min: number, max: number) {
xyChartData.yAxis = {title: xyChartData.yAxis.title, min, max};
}
function setLineData(title: string, data: number[]) {}
function setBarData(title: string, data: number[]) {}
function getDrawableElem(): DrawableElem[] { function getDrawableElem(): DrawableElem[] {
return xyChartBuilder.build(); xyChartData.title = getDiagramTitle();
return XYChartBuilder.build(xyChartConfig, xyChartData);
} }
function setHeight(height: number) { function setHeight(height: number) {
xyChartBuilder.setHeight(height); xyChartConfig.height = height;
} }
function setWidth(width: number) { function setWidth(width: number) {
xyChartBuilder.setWidth(width); xyChartConfig.width = width;
} }
const clear = function () { const clear = function () {
commonClear(); commonClear();
xyChartConfig = getChartDefaultConfig();
xyChartData = getChartDefalutData();
}; };
export default { export default {
@@ -55,4 +169,12 @@ export default {
getDiagramTitle, getDiagramTitle,
getAccDescription, getAccDescription,
setAccDescription, setAccDescription,
setOrientation,
setXAxisTitle,
setXAxisRangeData,
setXAxisBand,
setYAxisTitle,
setYAxisRangeData,
setLineData,
setBarData,
}; };