mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-17 11:14:12 +01:00
Generated the base architecture
This commit is contained in:
@@ -25,7 +25,7 @@
|
|||||||
import mermaid from './mermaid.esm.mjs';
|
import mermaid from './mermaid.esm.mjs';
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
logLevel: 3,
|
logLevel: 0,
|
||||||
securityLevel: 'loose',
|
securityLevel: 'loose',
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -82,6 +82,8 @@
|
|||||||
"@types/d3": "^7.4.0",
|
"@types/d3": "^7.4.0",
|
||||||
"@types/d3-sankey": "^0.12.1",
|
"@types/d3-sankey": "^0.12.1",
|
||||||
"@types/d3-selection": "^3.0.5",
|
"@types/d3-selection": "^3.0.5",
|
||||||
|
"@types/d3-scale": "^4.0.2",
|
||||||
|
"@types/d3-shape": "^3.1.0",
|
||||||
"@types/dompurify": "^3.0.2",
|
"@types/dompurify": "^3.0.2",
|
||||||
"@types/jsdom": "^21.1.1",
|
"@types/jsdom": "^21.1.1",
|
||||||
"@types/lodash-es": "^4.17.7",
|
"@types/lodash-es": "^4.17.7",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// @ts-ignore: TODO Fix ts errors
|
|
||||||
import { scaleLinear } from 'd3';
|
import { scaleLinear } from 'd3';
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import type { BaseDiagramConfig, QuadrantChartConfig } from '../../config.type.js';
|
import type { BaseDiagramConfig, QuadrantChartConfig } from '../../config.type.js';
|
||||||
|
|||||||
151
packages/mermaid/src/diagrams/xychart/chartBuilder/Interfaces.ts
Normal file
151
packages/mermaid/src/diagrams/xychart/chartBuilder/Interfaces.ts
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
export enum ChartPlotEnum {
|
||||||
|
LINE = 'line',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ChartLayoutElem {
|
||||||
|
NULL = 'null',
|
||||||
|
CHART = 'chart',
|
||||||
|
TITLE = 'title',
|
||||||
|
XAXISLABEL = 'xaxislabel',
|
||||||
|
XAXISTITLE = 'xaxistitle',
|
||||||
|
YAXISLABEL = 'yaxislabel',
|
||||||
|
YAXISTITLE = 'yaxistitle',
|
||||||
|
}
|
||||||
|
export enum XYChartYAxisPosition {
|
||||||
|
LEFT = 'left',
|
||||||
|
RIGHT = 'right',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum OrientationEnum {
|
||||||
|
VERTICAL = 'vertical',
|
||||||
|
HORIZONTAL = 'horizontal',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChartLayout = ChartLayoutElem[][];
|
||||||
|
|
||||||
|
export type VisibilityOption = {
|
||||||
|
chartTitle: boolean;
|
||||||
|
xAxisTitle: boolean;
|
||||||
|
xAxisLabel: boolean;
|
||||||
|
yAxisTitle: boolean;
|
||||||
|
yAxisLabel: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface XYChartConfig {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
fontFamily: string;
|
||||||
|
titleFontSize: number;
|
||||||
|
titleFill: string;
|
||||||
|
titlePadding: number;
|
||||||
|
xAxisFontSize: number;
|
||||||
|
xAxisTitleFontSize: number;
|
||||||
|
yAxisFontSize: number;
|
||||||
|
yAxisTitleFontSize: number;
|
||||||
|
yAxisPosition: XYChartYAxisPosition;
|
||||||
|
showChartTitle: boolean;
|
||||||
|
showXAxisLable: boolean;
|
||||||
|
showXAxisTitle: boolean;
|
||||||
|
showYAxisLabel: boolean;
|
||||||
|
showYAxisTitle: boolean;
|
||||||
|
chartOrientation: OrientationEnum;
|
||||||
|
plotReservedSpacePercent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SimplePlotDataType = [string | number, number][];
|
||||||
|
|
||||||
|
export interface LinePlotData {
|
||||||
|
type: ChartPlotEnum.LINE;
|
||||||
|
data: SimplePlotDataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlotData = LinePlotData;
|
||||||
|
|
||||||
|
export interface BandAxisDataType {
|
||||||
|
title: string;
|
||||||
|
categories: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LinearAxisDataType{
|
||||||
|
title: string;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AxisDataType = LinearAxisDataType | BandAxisDataType;
|
||||||
|
|
||||||
|
export interface XYChartData {
|
||||||
|
xAxis: AxisDataType;
|
||||||
|
yAxis: AxisDataType;
|
||||||
|
title: string;
|
||||||
|
plots: PlotData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Dimension {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TextVerticalPos = 'left' | 'center' | 'right';
|
||||||
|
export type TextHorizontalPos = 'top' | 'middle' | 'bottom';
|
||||||
|
|
||||||
|
export interface RectElem extends Point {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
fill: string;
|
||||||
|
strokeWidth: number;
|
||||||
|
strokeFill: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextElem extends Point {
|
||||||
|
text: string;
|
||||||
|
fill: string;
|
||||||
|
verticalPos: TextVerticalPos;
|
||||||
|
horizontalPos: TextHorizontalPos;
|
||||||
|
fontSize: number;
|
||||||
|
rotation: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PathElem {
|
||||||
|
path: string;
|
||||||
|
fill?: string;
|
||||||
|
strokeWidth: number;
|
||||||
|
strokeFill: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DrawableElem =
|
||||||
|
| {
|
||||||
|
groupText: string;
|
||||||
|
type: 'rect';
|
||||||
|
data: RectElem[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
groupText: string;
|
||||||
|
type: 'text';
|
||||||
|
data: TextElem[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
groupText: string;
|
||||||
|
type: 'path';
|
||||||
|
data: PathElem[];
|
||||||
|
};
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { DrawableElem, XYChartConfig, XYChartData } from './Interfaces.js';
|
||||||
|
import { getChartTitleComponent } from './components/ChartTitle.js';
|
||||||
|
import { ChartComponent } from './components/Interfaces.js';
|
||||||
|
import { IAxis, getAxis } from './components/axis/index.js';
|
||||||
|
import { IPlot, getPlotComponent, isTypeIPlot } from './components/plot/index.js';
|
||||||
|
|
||||||
|
export class Orchestrator {
|
||||||
|
private componentStore: {
|
||||||
|
title: ChartComponent,
|
||||||
|
plot: IPlot,
|
||||||
|
xAxis: IAxis,
|
||||||
|
yAxis: IAxis,
|
||||||
|
};
|
||||||
|
constructor(private chartConfig: XYChartConfig, chartData: XYChartData) {
|
||||||
|
this.componentStore = {
|
||||||
|
title: getChartTitleComponent(chartConfig, chartData),
|
||||||
|
plot: getPlotComponent(chartConfig, chartData),
|
||||||
|
xAxis: getAxis(chartData.xAxis, chartConfig),
|
||||||
|
yAxis: getAxis(chartData.yAxis, chartConfig),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateSpace() {
|
||||||
|
let availableWidth = this.chartConfig.width;
|
||||||
|
let availableHeight = this.chartConfig.height;
|
||||||
|
let chartX = 0;
|
||||||
|
let chartY = 0;
|
||||||
|
const chartWidth = Math.floor((availableWidth * this.chartConfig.plotReservedSpacePercent) / 100);
|
||||||
|
const chartHeight = Math.floor((availableHeight * this.chartConfig.plotReservedSpacePercent) / 100);
|
||||||
|
|
||||||
|
let spaceUsed = this.componentStore.plot.calculateSpace({
|
||||||
|
width: chartWidth,
|
||||||
|
height: chartHeight,
|
||||||
|
});
|
||||||
|
availableWidth -= spaceUsed.width;
|
||||||
|
availableHeight -= spaceUsed.height;
|
||||||
|
|
||||||
|
spaceUsed = this.componentStore.title.calculateSpace({
|
||||||
|
width: this.chartConfig.width,
|
||||||
|
height: availableHeight,
|
||||||
|
});
|
||||||
|
chartY = spaceUsed.height;
|
||||||
|
availableWidth -= spaceUsed.width;
|
||||||
|
availableHeight -= spaceUsed.height;
|
||||||
|
//
|
||||||
|
// spaceUsed = this.componentStore.xAxis.calculateSpace({
|
||||||
|
// width: availableWidth,
|
||||||
|
// height: availableHeight,
|
||||||
|
// });
|
||||||
|
// availableWidth -= spaceUsed.width;
|
||||||
|
// availableHeight -= spaceUsed.height;
|
||||||
|
this.componentStore.plot.setBoundingBoxXY({x: chartX, y: chartY});
|
||||||
|
this.componentStore.xAxis.setRange([chartX, chartX + chartWidth]);
|
||||||
|
this.componentStore.yAxis.setRange([chartY, chartY + chartHeight]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDrawableElement() {
|
||||||
|
this.calculateSpace();
|
||||||
|
const drawableElem: DrawableElem[] = [];
|
||||||
|
this.componentStore.plot.setAxes(this.componentStore.xAxis, this.componentStore.yAxis);
|
||||||
|
for (const component of Object.values(this.componentStore)) {
|
||||||
|
drawableElem.push(...component.getDrawableElements());
|
||||||
|
}
|
||||||
|
return drawableElem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Dimension } from './Interfaces.js';
|
||||||
|
|
||||||
|
export interface ITextDimensionCalculator {
|
||||||
|
getDimension(text: string): Dimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TextDimensionCalculator implements ITextDimensionCalculator {
|
||||||
|
constructor(private fontSize: number, private fontFamily: string) {}
|
||||||
|
getDimension(text: string): Dimension {
|
||||||
|
return {
|
||||||
|
width: text.length * this.fontSize,
|
||||||
|
height: this.fontSize,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { ITextDimensionCalculator, TextDimensionCalculator } from '../TextDimensionCalculator.js';
|
||||||
|
import {
|
||||||
|
XYChartConfig,
|
||||||
|
XYChartData,
|
||||||
|
Dimension,
|
||||||
|
BoundingRect,
|
||||||
|
DrawableElem,
|
||||||
|
Point,
|
||||||
|
OrientationEnum,
|
||||||
|
} from '../Interfaces.js';
|
||||||
|
import { ChartComponent } from './Interfaces.js';
|
||||||
|
|
||||||
|
export class ChartTitle implements ChartComponent {
|
||||||
|
private boundingRect: BoundingRect;
|
||||||
|
private showChartTitle: boolean;
|
||||||
|
private orientation: OrientationEnum;
|
||||||
|
constructor(
|
||||||
|
private textDimensionCalculator: ITextDimensionCalculator,
|
||||||
|
private chartConfig: XYChartConfig,
|
||||||
|
private chartData: XYChartData
|
||||||
|
) {
|
||||||
|
this.boundingRect = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
};
|
||||||
|
this.showChartTitle = !!(this.chartData.title && this.chartConfig.showChartTitle);
|
||||||
|
this.orientation = OrientationEnum.VERTICAL;
|
||||||
|
}
|
||||||
|
setOrientation(orientation: OrientationEnum): void {
|
||||||
|
this.orientation = orientation;
|
||||||
|
}
|
||||||
|
setBoundingBoxXY(point: Point): void {
|
||||||
|
this.boundingRect.x = point.x;
|
||||||
|
this.boundingRect.y = point.y;
|
||||||
|
}
|
||||||
|
calculateSpace(availableSpace: Dimension): Dimension {
|
||||||
|
const titleDimension = this.textDimensionCalculator.getDimension(this.chartData.title);
|
||||||
|
const widthRequired = Math.max(titleDimension.width, availableSpace.width);
|
||||||
|
const heightRequired = titleDimension.height + 2 * this.chartConfig.titlePadding;
|
||||||
|
if (
|
||||||
|
titleDimension.width <= widthRequired &&
|
||||||
|
titleDimension.height <= heightRequired &&
|
||||||
|
this.showChartTitle
|
||||||
|
) {
|
||||||
|
this.boundingRect.width = widthRequired;
|
||||||
|
this.boundingRect.height = heightRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: this.boundingRect.width,
|
||||||
|
height: this.boundingRect.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
getDrawableElements(): DrawableElem[] {
|
||||||
|
const drawableElem: DrawableElem[] = [];
|
||||||
|
if (this.boundingRect.height > 0 && this.boundingRect.width > 0) {
|
||||||
|
drawableElem.push({
|
||||||
|
groupText: 'chart-title',
|
||||||
|
type: 'text',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
fontSize: this.chartConfig.titleFontSize,
|
||||||
|
text: this.chartData.title,
|
||||||
|
verticalPos: 'center',
|
||||||
|
horizontalPos: 'middle',
|
||||||
|
x: this.boundingRect.x + this.boundingRect.width / 2,
|
||||||
|
y: this.boundingRect.y + this.boundingRect.height / 2,
|
||||||
|
fill: this.chartConfig.titleFill,
|
||||||
|
rotation: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return drawableElem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChartTitleComponent(
|
||||||
|
chartConfig: XYChartConfig,
|
||||||
|
chartData: XYChartData
|
||||||
|
): ChartComponent {
|
||||||
|
const textDimensionCalculator = new TextDimensionCalculator(
|
||||||
|
chartConfig.titleFontSize,
|
||||||
|
chartConfig.fontFamily
|
||||||
|
);
|
||||||
|
return new ChartTitle(textDimensionCalculator, chartConfig, chartData);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Dimension, DrawableElem, OrientationEnum, Point } from '../Interfaces.js';
|
||||||
|
|
||||||
|
export interface ChartComponent {
|
||||||
|
setOrientation(orientation: OrientationEnum): void;
|
||||||
|
calculateSpace(availableSpace: Dimension): Dimension;
|
||||||
|
setBoundingBoxXY(point: Point): void;
|
||||||
|
getDrawableElements(): DrawableElem[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { ScaleBand, scaleBand } from 'd3';
|
||||||
|
import {
|
||||||
|
Dimension,
|
||||||
|
Point,
|
||||||
|
DrawableElem,
|
||||||
|
BoundingRect,
|
||||||
|
OrientationEnum,
|
||||||
|
XYChartConfig,
|
||||||
|
} from '../../Interfaces.js';
|
||||||
|
import { IAxis } from './index.js';
|
||||||
|
|
||||||
|
export class BandAxis implements IAxis {
|
||||||
|
private scale: ScaleBand<string>;
|
||||||
|
private range: [number, number];
|
||||||
|
private boundingRect: BoundingRect;
|
||||||
|
private orientation: OrientationEnum;
|
||||||
|
private categories: string[];
|
||||||
|
|
||||||
|
constructor(private chartConfig: XYChartConfig, categories: string[]) {
|
||||||
|
this.range = [0, 10];
|
||||||
|
this.categories = categories;
|
||||||
|
this.scale = scaleBand().domain(this.categories).range(this.range);
|
||||||
|
this.boundingRect = { x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
this.orientation = OrientationEnum.VERTICAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRange(range: [number, number]): void {
|
||||||
|
this.range = range;
|
||||||
|
this.scale = scaleBand().domain(this.categories).range(this.range);
|
||||||
|
}
|
||||||
|
setOrientation(orientation: OrientationEnum): void {
|
||||||
|
this.orientation = orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
getScaleValue(value: string): number {
|
||||||
|
return this.scale(value) || this.range[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateSpace(availableSpace: Dimension): Dimension {
|
||||||
|
return {
|
||||||
|
width: availableSpace.width,
|
||||||
|
height: availableSpace.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setBoundingBoxXY(point: Point): void {
|
||||||
|
this.boundingRect.x = point.x;
|
||||||
|
this.boundingRect.y = point.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDrawableElements(): DrawableElem[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { scaleLinear, ScaleLinear } from 'd3';
|
||||||
|
import {
|
||||||
|
Dimension,
|
||||||
|
Point,
|
||||||
|
DrawableElem,
|
||||||
|
BoundingRect,
|
||||||
|
OrientationEnum,
|
||||||
|
XYChartConfig,
|
||||||
|
} from '../../Interfaces.js';
|
||||||
|
import { IAxis } from './index.js';
|
||||||
|
|
||||||
|
export class LinearAxis implements IAxis {
|
||||||
|
private scale: ScaleLinear<number, number>;
|
||||||
|
private boundingRect: BoundingRect;
|
||||||
|
private orientation: OrientationEnum;
|
||||||
|
private domain: [number, number];
|
||||||
|
private range: [number, number];
|
||||||
|
|
||||||
|
constructor(private chartConfig: XYChartConfig, domain: [number, number]) {
|
||||||
|
this.domain = domain;
|
||||||
|
this.range = [0, 10];
|
||||||
|
this.scale = scaleLinear().domain(this.domain).range(this.range);
|
||||||
|
this.boundingRect = { x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
this.orientation = OrientationEnum.VERTICAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRange(range: [number, number]): void {
|
||||||
|
this.range = range;
|
||||||
|
this.scale = scaleLinear().domain(this.domain).range(this.range);
|
||||||
|
}
|
||||||
|
|
||||||
|
setOrientation(orientation: OrientationEnum): void {
|
||||||
|
this.orientation = orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
getScaleValue(value: number): number {
|
||||||
|
return this.scale(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateSpace(availableSpace: Dimension): Dimension {
|
||||||
|
return {
|
||||||
|
width: availableSpace.width,
|
||||||
|
height: availableSpace.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setBoundingBoxXY(point: Point): void {
|
||||||
|
this.boundingRect.x = point.x;
|
||||||
|
this.boundingRect.y = point.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDrawableElements(): DrawableElem[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import {
|
||||||
|
AxisDataType,
|
||||||
|
BandAxisDataType,
|
||||||
|
BoundingRect,
|
||||||
|
LinearAxisDataType,
|
||||||
|
XYChartConfig,
|
||||||
|
XYChartData,
|
||||||
|
} from '../../Interfaces.js';
|
||||||
|
import { ChartComponent } from '../Interfaces.js';
|
||||||
|
import { BandAxis } from './BandAxis.js';
|
||||||
|
import { LinearAxis } from './LinearAxis.js';
|
||||||
|
|
||||||
|
export interface IAxis extends ChartComponent {
|
||||||
|
getScaleValue(value: string | number): number;
|
||||||
|
setRange(range: [number, number]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLinearAxisData(data: any): data is LinearAxisDataType {
|
||||||
|
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, chartConfig: XYChartConfig): IAxis {
|
||||||
|
if (isBandAxisData(data)) {
|
||||||
|
return new BandAxis(chartConfig, data.categories);
|
||||||
|
}
|
||||||
|
return new LinearAxis(chartConfig, [data.min, data.max]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { line } from 'd3';
|
||||||
|
import { DrawableElem, SimplePlotDataType } from '../../Interfaces.js';
|
||||||
|
import { IAxis } from '../axis/index.js';
|
||||||
|
|
||||||
|
export class LinePlot {
|
||||||
|
constructor(private data: SimplePlotDataType, private xAxis: IAxis, private yAxis: IAxis) {}
|
||||||
|
|
||||||
|
getDrawableElement(): DrawableElem[] {
|
||||||
|
const finalData: [number, number][] = this.data.map((d) => [
|
||||||
|
this.xAxis.getScaleValue(d[0]),
|
||||||
|
this.yAxis.getScaleValue(d[1]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const path = line()
|
||||||
|
.x((d) => d[0])
|
||||||
|
.y((d) => d[1])(finalData);
|
||||||
|
if (!path) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
groupText: 'line-plot',
|
||||||
|
type: 'path',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
path,
|
||||||
|
strokeFill: '#0000ff',
|
||||||
|
strokeWidth: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
XYChartConfig,
|
||||||
|
XYChartData,
|
||||||
|
Dimension,
|
||||||
|
BoundingRect,
|
||||||
|
DrawableElem,
|
||||||
|
Point,
|
||||||
|
OrientationEnum,
|
||||||
|
ChartPlotEnum,
|
||||||
|
} from '../../Interfaces.js';
|
||||||
|
import { IAxis } from '../axis/index.js';
|
||||||
|
import { ChartComponent } from './../Interfaces.js';
|
||||||
|
import { LinePlot } from './LinePlot.js';
|
||||||
|
|
||||||
|
|
||||||
|
export interface IPlot extends ChartComponent {
|
||||||
|
setAxes(xAxis: IAxis, yAxis: IAxis): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Plot implements IPlot {
|
||||||
|
private boundingRect: BoundingRect;
|
||||||
|
private orientation: OrientationEnum;
|
||||||
|
private xAxis?: IAxis;
|
||||||
|
private yAxis?: IAxis;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private chartConfig: XYChartConfig,
|
||||||
|
private chartData: XYChartData,
|
||||||
|
) {
|
||||||
|
this.boundingRect = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
};
|
||||||
|
this.orientation = OrientationEnum.VERTICAL;
|
||||||
|
}
|
||||||
|
setAxes(xAxis: IAxis, yAxis: IAxis) {
|
||||||
|
this.xAxis = xAxis;
|
||||||
|
this.yAxis = yAxis;
|
||||||
|
}
|
||||||
|
setOrientation(orientation: OrientationEnum): void {
|
||||||
|
this.orientation = orientation;
|
||||||
|
}
|
||||||
|
setBoundingBoxXY(point: Point): void {
|
||||||
|
this.boundingRect.x = point.x;
|
||||||
|
this.boundingRect.y = point.y;
|
||||||
|
}
|
||||||
|
calculateSpace(availableSpace: Dimension): Dimension {
|
||||||
|
this.boundingRect.width = availableSpace.width;
|
||||||
|
this.boundingRect.height = availableSpace.height;
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: this.boundingRect.width,
|
||||||
|
height: this.boundingRect.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
getDrawableElements(): DrawableElem[] {
|
||||||
|
if(!(this.xAxis && this.yAxis)) {
|
||||||
|
throw Error("Axes must be passed to render Plots");
|
||||||
|
}
|
||||||
|
const drawableElem: DrawableElem[] = [];
|
||||||
|
for(const plot of this.chartData.plots) {
|
||||||
|
switch(plot.type) {
|
||||||
|
case ChartPlotEnum.LINE: {
|
||||||
|
const linePlot = new LinePlot(plot.data, this.xAxis, this.yAxis);
|
||||||
|
drawableElem.push(...linePlot.getDrawableElement())
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return drawableElem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPlotComponent(
|
||||||
|
chartConfig: XYChartConfig,
|
||||||
|
chartData: XYChartData,
|
||||||
|
): IPlot {
|
||||||
|
return new Plot(chartConfig, chartData);
|
||||||
|
}
|
||||||
77
packages/mermaid/src/diagrams/xychart/chartBuilder/index.ts
Normal file
77
packages/mermaid/src/diagrams/xychart/chartBuilder/index.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// @ts-ignore: TODO Fix ts errors
|
||||||
|
import { defaultConfig } from '../../../config.js';
|
||||||
|
import { log } from '../../../logger.js';
|
||||||
|
import {
|
||||||
|
ChartPlotEnum,
|
||||||
|
DrawableElem,
|
||||||
|
XYChartConfig,
|
||||||
|
XYChartData,
|
||||||
|
OrientationEnum,
|
||||||
|
XYChartYAxisPosition,
|
||||||
|
} from './Interfaces.js';
|
||||||
|
import { Orchestrator } from './Orchestrator.js';
|
||||||
|
|
||||||
|
export class XYChartBuilder {
|
||||||
|
private config: XYChartConfig;
|
||||||
|
private chartData: XYChartData;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.config = {
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
fontFamily: defaultConfig.fontFamily || 'Sans',
|
||||||
|
titleFontSize: 16,
|
||||||
|
titleFill: '#000000',
|
||||||
|
titlePadding: 5,
|
||||||
|
xAxisFontSize: 14,
|
||||||
|
xAxisTitleFontSize: 16,
|
||||||
|
yAxisFontSize: 14,
|
||||||
|
yAxisTitleFontSize: 16,
|
||||||
|
yAxisPosition: XYChartYAxisPosition.LEFT,
|
||||||
|
showChartTitle: true,
|
||||||
|
showXAxisLable: true,
|
||||||
|
showXAxisTitle: true,
|
||||||
|
showYAxisLabel: true,
|
||||||
|
showYAxisTitle: true,
|
||||||
|
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.LINE,
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// @ts-ignore: TODO Fix ts errors
|
|
||||||
import { scaleLinear } from 'd3';
|
|
||||||
import { log } from '../../logger.js';
|
|
||||||
|
|
||||||
export class XYChartBuilder {}
|
|
||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
setAccDescription,
|
setAccDescription,
|
||||||
clear as commonClear,
|
clear as commonClear,
|
||||||
} from '../../commonDb.js';
|
} from '../../commonDb.js';
|
||||||
|
import { XYChartBuilder } from './chartBuilder/index.js';
|
||||||
|
import { DrawableElem } from './chartBuilder/Interfaces.js';
|
||||||
|
|
||||||
const config = configApi.getConfig();
|
const config = configApi.getConfig();
|
||||||
|
|
||||||
@@ -18,16 +20,33 @@ function textSanitizer(text: string) {
|
|||||||
return sanitizeText(text.trim(), config);
|
return sanitizeText(text.trim(), config);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseDirective = function (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 getDrawableElem(): DrawableElem[] {
|
||||||
|
return xyChartBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHeight(height: number) {
|
||||||
|
xyChartBuilder.setHeight(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWidth(width: number) {
|
||||||
|
xyChartBuilder.setWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
const clear = function () {
|
const clear = function () {
|
||||||
commonClear();
|
commonClear();
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
setWidth,
|
||||||
|
setHeight,
|
||||||
|
getDrawableElem,
|
||||||
parseDirective,
|
parseDirective,
|
||||||
clear,
|
clear,
|
||||||
setAccTitle,
|
setAccTitle,
|
||||||
|
|||||||
@@ -1,11 +1,27 @@
|
|||||||
// @ts-ignore: TODO Fix ts errors
|
import { select, scaleOrdinal, scaleLinear, axisBottom, line } from 'd3';
|
||||||
import { select } from 'd3';
|
|
||||||
import * as configApi from '../../config.js';
|
import * as configApi from '../../config.js';
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||||
import { Diagram } from '../../Diagram.js';
|
import { Diagram } from '../../Diagram.js';
|
||||||
|
import {
|
||||||
|
DrawableElem,
|
||||||
|
TextElem,
|
||||||
|
TextHorizontalPos,
|
||||||
|
TextVerticalPos,
|
||||||
|
} from './chartBuilder/Interfaces.js';
|
||||||
|
|
||||||
export const draw = (txt: string, id: string, _version: string, diagObj: Diagram) => {
|
export const draw = (txt: string, id: string, _version: string, diagObj: Diagram) => {
|
||||||
|
function getDominantBaseLine(horizontalPos: TextHorizontalPos) {
|
||||||
|
return horizontalPos === 'top' ? 'hanging' : 'middle';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTextAnchor(verticalPos: TextVerticalPos) {
|
||||||
|
return verticalPos === 'left' ? 'start' : 'middle';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTextTransformation(data: TextElem) {
|
||||||
|
return `translate(${data.x}, ${data.y}) rotate(${data.rotation || 0})`;
|
||||||
|
}
|
||||||
const conf = configApi.getConfig();
|
const conf = configApi.getConfig();
|
||||||
|
|
||||||
log.debug('Rendering xychart chart\n' + txt);
|
log.debug('Rendering xychart chart\n' + txt);
|
||||||
@@ -17,8 +33,8 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
|||||||
sandboxElement = select('#i' + id);
|
sandboxElement = select('#i' + id);
|
||||||
}
|
}
|
||||||
const root =
|
const root =
|
||||||
securityLevel === 'sandbox'
|
sandboxElement
|
||||||
? select(sandboxElement.nodes()[0].contentDocument.body)
|
? sandboxElement
|
||||||
: select('body');
|
: select('body');
|
||||||
|
|
||||||
const svg = root.select(`[id="${id}"]`);
|
const svg = root.select(`[id="${id}"]`);
|
||||||
@@ -28,9 +44,80 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
|||||||
const width = 500;
|
const width = 500;
|
||||||
const height = 500;
|
const height = 500;
|
||||||
|
|
||||||
|
// @ts-ignore: TODO Fix ts errors
|
||||||
configureSvgSize(svg, height, width, true);
|
configureSvgSize(svg, height, width, true);
|
||||||
|
|
||||||
svg.attr('viewBox', '0 0 ' + width + ' ' + height);
|
svg.attr('viewBox', '0 0 ' + width + ' ' + height);
|
||||||
|
|
||||||
|
// @ts-ignore: TODO Fix ts errors
|
||||||
|
diagObj.db.setHeight(height);
|
||||||
|
// @ts-ignore: TODO Fix ts errors
|
||||||
|
diagObj.db.setWidth(width);
|
||||||
|
|
||||||
|
// @ts-ignore: TODO Fix ts errors
|
||||||
|
const shapes: DrawableElem[] = diagObj.db.getDrawableElem();
|
||||||
|
|
||||||
|
for (const shape of shapes) {
|
||||||
|
if (shape.data.length === 0) {
|
||||||
|
log.trace(
|
||||||
|
`Skipped drawing of shape of type: ${shape.type} with data: ${JSON.stringify(
|
||||||
|
shape.data,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log.trace(
|
||||||
|
`Drawing shape of type: ${shape.type} with data: ${JSON.stringify(shape.data, null, 2)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const shapeGroup = group.append('g').attr('class', shape.groupText);
|
||||||
|
|
||||||
|
|
||||||
|
switch (shape.type) {
|
||||||
|
case 'rect':
|
||||||
|
shapeGroup
|
||||||
|
.selectAll('rect')
|
||||||
|
.data(shape.data)
|
||||||
|
.enter()
|
||||||
|
.append('rect')
|
||||||
|
.attr('x', data => data.x)
|
||||||
|
.attr('y', data => data.y)
|
||||||
|
.attr('width', data => data.width)
|
||||||
|
.attr('height', data => data.height)
|
||||||
|
.attr('fill', data => data.fill)
|
||||||
|
.attr('stroke', data => data.strokeFill)
|
||||||
|
.attr('stroke-width', data => data.strokeWidth);
|
||||||
|
break;
|
||||||
|
case 'text':
|
||||||
|
shapeGroup
|
||||||
|
.selectAll('text')
|
||||||
|
.data(shape.data)
|
||||||
|
.enter()
|
||||||
|
.append('text')
|
||||||
|
.attr('x', 0)
|
||||||
|
.attr('y', 0)
|
||||||
|
.attr('fill', data => data.fill)
|
||||||
|
.attr('font-size', data => data.fontSize)
|
||||||
|
.attr('dominant-baseline', data => getDominantBaseLine(data.horizontalPos))
|
||||||
|
.attr('text-anchor', data => getTextAnchor(data.verticalPos))
|
||||||
|
.attr('transform', data => getTextTransformation(data))
|
||||||
|
.text(data => data.text);
|
||||||
|
break;
|
||||||
|
case 'path':
|
||||||
|
shapeGroup
|
||||||
|
.selectAll('path')
|
||||||
|
.data(shape.data)
|
||||||
|
.enter()
|
||||||
|
.append('path')
|
||||||
|
.attr('d', data => data.path)
|
||||||
|
.attr('fill', data => data.fill? data.fill: "none")
|
||||||
|
.attr('stroke', data => data.strokeFill)
|
||||||
|
.attr('stroke-width', data => data.strokeWidth)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
62
pnpm-lock.yaml
generated
62
pnpm-lock.yaml
generated
@@ -264,9 +264,15 @@ importers:
|
|||||||
'@types/d3-sankey':
|
'@types/d3-sankey':
|
||||||
specifier: ^0.12.1
|
specifier: ^0.12.1
|
||||||
version: 0.12.1
|
version: 0.12.1
|
||||||
|
'@types/d3-scale':
|
||||||
|
specifier: ^4.0.2
|
||||||
|
version: 4.0.2
|
||||||
'@types/d3-selection':
|
'@types/d3-selection':
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.3
|
||||||
version: 3.0.5
|
version: 3.0.3
|
||||||
|
'@types/d3-shape':
|
||||||
|
specifier: ^3.1.0
|
||||||
|
version: 3.1.0
|
||||||
'@types/dompurify':
|
'@types/dompurify':
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
@@ -472,6 +478,58 @@ importers:
|
|||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.0.0
|
version: 7.0.0
|
||||||
|
|
||||||
|
packages/mermaid/src/vitepress:
|
||||||
|
dependencies:
|
||||||
|
'@vueuse/core':
|
||||||
|
specifier: ^10.1.0
|
||||||
|
version: 10.1.0(vue@3.2.47)
|
||||||
|
jiti:
|
||||||
|
specifier: ^1.18.2
|
||||||
|
version: 1.18.2
|
||||||
|
vue:
|
||||||
|
specifier: ^3.2.47
|
||||||
|
version: 3.2.47
|
||||||
|
devDependencies:
|
||||||
|
'@iconify-json/carbon':
|
||||||
|
specifier: ^1.1.16
|
||||||
|
version: 1.1.16
|
||||||
|
'@unocss/reset':
|
||||||
|
specifier: ^0.51.8
|
||||||
|
version: 0.51.8
|
||||||
|
'@vite-pwa/vitepress':
|
||||||
|
specifier: ^0.0.5
|
||||||
|
version: 0.0.5(vite-plugin-pwa@0.14.7)
|
||||||
|
'@vitejs/plugin-vue':
|
||||||
|
specifier: ^4.2.1
|
||||||
|
version: 4.2.1(vite@4.3.3)(vue@3.2.47)
|
||||||
|
fast-glob:
|
||||||
|
specifier: ^3.2.12
|
||||||
|
version: 3.2.12
|
||||||
|
https-localhost:
|
||||||
|
specifier: ^4.7.1
|
||||||
|
version: 4.7.1
|
||||||
|
pathe:
|
||||||
|
specifier: ^1.1.0
|
||||||
|
version: 1.1.0
|
||||||
|
unocss:
|
||||||
|
specifier: ^0.51.8
|
||||||
|
version: 0.51.8(postcss@8.4.23)(rollup@2.79.1)(vite@4.3.3)
|
||||||
|
unplugin-vue-components:
|
||||||
|
specifier: ^0.24.1
|
||||||
|
version: 0.24.1(rollup@2.79.1)(vue@3.2.47)
|
||||||
|
vite:
|
||||||
|
specifier: ^4.3.3
|
||||||
|
version: 4.3.3(@types/node@18.16.0)
|
||||||
|
vite-plugin-pwa:
|
||||||
|
specifier: ^0.14.7
|
||||||
|
version: 0.14.7(vite@4.3.3)(workbox-build@6.5.4)(workbox-window@6.5.4)
|
||||||
|
vitepress:
|
||||||
|
specifier: 1.0.0-alpha.75
|
||||||
|
version: 1.0.0-alpha.75(@algolia/client-search@4.14.2)(@types/node@18.16.0)
|
||||||
|
workbox-window:
|
||||||
|
specifier: ^6.5.4
|
||||||
|
version: 6.5.4
|
||||||
|
|
||||||
tests/webpack:
|
tests/webpack:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mermaid-js/mermaid-example-diagram':
|
'@mermaid-js/mermaid-example-diagram':
|
||||||
|
|||||||
Reference in New Issue
Block a user