mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-28 03:39:38 +02:00
Generated the base architecture
This commit is contained in:
@@ -82,6 +82,8 @@
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-sankey": "^0.12.1",
|
||||
"@types/d3-selection": "^3.0.5",
|
||||
"@types/d3-scale": "^4.0.2",
|
||||
"@types/d3-shape": "^3.1.0",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
|
@@ -1,4 +1,3 @@
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import { scaleLinear } from 'd3';
|
||||
import { log } from '../../logger.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,
|
||||
clear as commonClear,
|
||||
} from '../../commonDb.js';
|
||||
import { XYChartBuilder } from './chartBuilder/index.js';
|
||||
import { DrawableElem } from './chartBuilder/Interfaces.js';
|
||||
|
||||
const config = configApi.getConfig();
|
||||
|
||||
@@ -18,16 +20,33 @@ function textSanitizer(text: string) {
|
||||
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
|
||||
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 () {
|
||||
commonClear();
|
||||
};
|
||||
|
||||
export default {
|
||||
setWidth,
|
||||
setHeight,
|
||||
getDrawableElem,
|
||||
parseDirective,
|
||||
clear,
|
||||
setAccTitle,
|
||||
|
@@ -1,11 +1,27 @@
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import { select } from 'd3';
|
||||
import { select, scaleOrdinal, scaleLinear, axisBottom, line } from 'd3';
|
||||
import * as configApi from '../../config.js';
|
||||
import { log } from '../../logger.js';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox.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) => {
|
||||
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();
|
||||
|
||||
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);
|
||||
}
|
||||
const root =
|
||||
securityLevel === 'sandbox'
|
||||
? select(sandboxElement.nodes()[0].contentDocument.body)
|
||||
sandboxElement
|
||||
? sandboxElement
|
||||
: select('body');
|
||||
|
||||
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 height = 500;
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
configureSvgSize(svg, height, width, true);
|
||||
|
||||
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 {
|
||||
|
Reference in New Issue
Block a user