2023-07-21 19:12:46 +02:00
|
|
|
// @ts-ignore: TODO Fix ts errors
|
2023-08-06 12:15:01 +02:00
|
|
|
import { adjust, channel } from 'khroma';
|
2023-08-20 14:21:53 +02:00
|
|
|
import { Selection } from 'd3-selection';
|
2023-05-20 16:02:20 +02:00
|
|
|
import mermaidAPI from '../../mermaidAPI.js';
|
|
|
|
import * as configApi from '../../config.js';
|
2023-08-13 19:26:50 +02:00
|
|
|
import defaultConfig from '../../defaultConfig.js';
|
2023-05-20 16:02:20 +02:00
|
|
|
import { sanitizeText } from '../common/common.js';
|
|
|
|
import {
|
|
|
|
setAccTitle,
|
|
|
|
getAccTitle,
|
|
|
|
setDiagramTitle,
|
|
|
|
getDiagramTitle,
|
|
|
|
getAccDescription,
|
|
|
|
setAccDescription,
|
|
|
|
clear as commonClear,
|
|
|
|
} from '../../commonDb.js';
|
2023-06-08 18:30:02 +02:00
|
|
|
import { XYChartBuilder } from './chartBuilder/index.js';
|
2023-07-05 08:05:57 +02:00
|
|
|
import {
|
|
|
|
DrawableElem,
|
|
|
|
SimplePlotDataType,
|
|
|
|
XYChartData,
|
2023-07-19 19:11:41 +02:00
|
|
|
XYChartThemeConfig,
|
2023-07-05 08:05:57 +02:00
|
|
|
isBandAxisData,
|
|
|
|
isLinearAxisData,
|
2023-08-13 19:26:50 +02:00
|
|
|
XYChartConfig,
|
2023-07-05 08:05:57 +02:00
|
|
|
} from './chartBuilder/Interfaces.js';
|
2023-07-19 19:11:41 +02:00
|
|
|
import { getThemeVariables } from '../../themes/theme-default.js';
|
|
|
|
|
2023-08-20 14:52:34 +02:00
|
|
|
export type SVGGType = Selection<SVGGElement, unknown, Element | null, unknown>;
|
2023-08-20 14:21:53 +02:00
|
|
|
|
2023-07-19 19:11:41 +02:00
|
|
|
const defaultThemeVariables = getThemeVariables();
|
2023-05-20 16:02:20 +02:00
|
|
|
|
|
|
|
const config = configApi.getConfig();
|
2023-07-19 19:11:41 +02:00
|
|
|
|
2023-08-13 19:26:50 +02:00
|
|
|
let plotIndex = 0;
|
|
|
|
|
2023-08-20 14:21:53 +02:00
|
|
|
let tmpSVGGElem: SVGGType;
|
|
|
|
|
|
|
|
let xyChartConfig: XYChartConfig = getChartDefaultConfig();
|
|
|
|
let xyChartThemeConfig: XYChartThemeConfig = getChartDefaultThemeConfig();
|
|
|
|
let xyChartData: XYChartData = getChartDefalutData();
|
|
|
|
let plotColorPalette = Array.isArray(xyChartThemeConfig.plotBaseColor)
|
|
|
|
? xyChartThemeConfig.plotBaseColor
|
|
|
|
: plotColorPaletteGenerator(xyChartThemeConfig.plotBaseColor);
|
|
|
|
let hasSetXAxis = false;
|
|
|
|
let hasSetYAxis = false;
|
|
|
|
|
|
|
|
interface NormalTextType {
|
|
|
|
type: 'text';
|
|
|
|
text: string;
|
|
|
|
}
|
|
|
|
|
2023-07-21 19:12:46 +02:00
|
|
|
function plotColorPaletteGenerator(baseColor: string, noOfColorNeeded = 15): string[] {
|
|
|
|
const colors = [];
|
|
|
|
const MAX_HUE_VALUE = 360;
|
|
|
|
const baseHue = channel(baseColor, 'h');
|
|
|
|
if (baseHue > MAX_HUE_VALUE / 2) {
|
|
|
|
const decr = Math.floor(baseHue / noOfColorNeeded);
|
|
|
|
for (let i = 0; i <= baseHue; i += decr) {
|
|
|
|
colors.push(adjust(baseColor, { h: -i }));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const incr = Math.floor((MAX_HUE_VALUE - baseHue) / noOfColorNeeded);
|
|
|
|
for (let i = 0; i <= baseHue; i += incr) {
|
|
|
|
colors.push(adjust(baseColor, { h: i }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return colors;
|
|
|
|
}
|
|
|
|
|
2023-07-19 19:11:41 +02:00
|
|
|
function getChartDefaultThemeConfig(): XYChartThemeConfig {
|
|
|
|
return {
|
2023-08-13 19:26:50 +02:00
|
|
|
titleColor:
|
|
|
|
config.themeVariables?.xyChart?.titleColor || defaultThemeVariables.xyChart.titleColor,
|
|
|
|
axisLineColor:
|
|
|
|
config.themeVariables?.xyChart?.axisLineColor || defaultThemeVariables.xyChart.axisLineColor,
|
|
|
|
xAxisLableColor:
|
|
|
|
config.themeVariables?.xyChart?.xAxisLableColor ||
|
|
|
|
defaultThemeVariables.xyChart.xAxisLableColor,
|
|
|
|
xAxisTitleColor:
|
|
|
|
config.themeVariables?.xyChart?.xAxisTitleColor ||
|
|
|
|
defaultThemeVariables.xyChart.xAxisTitleColor,
|
|
|
|
xAxisTickColor:
|
|
|
|
config.themeVariables?.xyChart?.xAxisTickColor ||
|
|
|
|
defaultThemeVariables.xyChart.xAxisTickColor,
|
|
|
|
yAxisLableColor:
|
|
|
|
config.themeVariables?.xyChart?.yAxisLableColor ||
|
|
|
|
defaultThemeVariables.xyChart.yAxisLableColor,
|
|
|
|
yAxisTitleColor:
|
|
|
|
config.themeVariables?.xyChart?.yAxisTitleColor ||
|
|
|
|
defaultThemeVariables.xyChart.yAxisTitleColor,
|
|
|
|
yAxisTickColor:
|
|
|
|
config.themeVariables?.xyChart?.yAxisTickColor ||
|
|
|
|
defaultThemeVariables.xyChart.yAxisTickColor,
|
|
|
|
plotBaseColor:
|
|
|
|
config.themeVariables?.xyChart?.plotBaseColor || defaultThemeVariables.xyChart.plotBaseColor,
|
2023-07-19 19:11:41 +02:00
|
|
|
};
|
|
|
|
}
|
2023-07-02 13:44:12 +02:00
|
|
|
function getChartDefaultConfig(): XYChartConfig {
|
2023-08-13 19:26:50 +02:00
|
|
|
return {
|
|
|
|
...(defaultConfig.xyChart as XYChartConfig),
|
|
|
|
...(config.xyChart ? config.xyChart : {}),
|
|
|
|
yAxis: {
|
|
|
|
...(defaultConfig.xyChart as XYChartConfig).yAxis,
|
|
|
|
...(config.xyChart?.yAxis ? config.xyChart.yAxis : {}),
|
|
|
|
},
|
|
|
|
xAxis: {
|
|
|
|
...(defaultConfig.xyChart as XYChartConfig).xAxis,
|
|
|
|
...(config.xyChart?.xAxis ? config.xyChart.xAxis : {}),
|
|
|
|
},
|
|
|
|
};
|
2023-07-02 13:44:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function getChartDefalutData(): XYChartData {
|
|
|
|
return {
|
|
|
|
yAxis: {
|
2023-07-05 08:05:57 +02:00
|
|
|
type: 'linear',
|
|
|
|
title: '',
|
|
|
|
min: Infinity,
|
|
|
|
max: -Infinity,
|
2023-07-02 13:44:12 +02:00
|
|
|
},
|
|
|
|
xAxis: {
|
2023-07-05 08:05:57 +02:00
|
|
|
type: 'band',
|
|
|
|
title: '',
|
2023-07-02 13:44:12 +02:00
|
|
|
categories: [],
|
|
|
|
},
|
|
|
|
title: '',
|
2023-07-03 18:14:56 +02:00
|
|
|
plots: [],
|
2023-07-02 13:44:12 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-05-20 16:02:20 +02:00
|
|
|
function textSanitizer(text: string) {
|
|
|
|
return sanitizeText(text.trim(), config);
|
|
|
|
}
|
|
|
|
|
2023-06-08 18:30:02 +02:00
|
|
|
function parseDirective(statement: string, context: string, type: string) {
|
2023-05-20 16:02:20 +02:00
|
|
|
// @ts-ignore: TODO Fix ts errors
|
|
|
|
mermaidAPI.parseDirective(this, statement, context, type);
|
2023-07-02 13:44:12 +02:00
|
|
|
}
|
2023-05-20 16:02:20 +02:00
|
|
|
|
2023-08-20 14:21:53 +02:00
|
|
|
function setTmpSVGG(SVGG: SVGGType) {
|
|
|
|
tmpSVGGElem = SVGG;
|
|
|
|
}
|
2023-07-02 13:44:12 +02:00
|
|
|
function setOrientation(oriantation: string) {
|
|
|
|
if (oriantation === 'horizontal') {
|
|
|
|
xyChartConfig.chartOrientation = 'horizontal';
|
|
|
|
} else {
|
|
|
|
xyChartConfig.chartOrientation = 'vertical';
|
|
|
|
}
|
|
|
|
}
|
2023-08-06 12:15:01 +02:00
|
|
|
function setXAxisTitle(title: NormalTextType) {
|
|
|
|
xyChartData.xAxis.title = textSanitizer(title.text);
|
2023-07-02 13:44:12 +02:00
|
|
|
}
|
|
|
|
function setXAxisRangeData(min: number, max: number) {
|
2023-07-05 08:05:57 +02:00
|
|
|
xyChartData.xAxis = { type: 'linear', title: xyChartData.xAxis.title, min, max };
|
|
|
|
hasSetXAxis = true;
|
2023-07-02 13:44:12 +02:00
|
|
|
}
|
2023-08-06 12:15:01 +02:00
|
|
|
function setXAxisBand(categories: NormalTextType[]) {
|
2023-07-03 18:14:56 +02:00
|
|
|
xyChartData.xAxis = {
|
2023-07-05 08:05:57 +02:00
|
|
|
type: 'band',
|
2023-07-03 18:14:56 +02:00
|
|
|
title: xyChartData.xAxis.title,
|
2023-08-06 12:15:01 +02:00
|
|
|
categories: categories.map((c) => textSanitizer(c.text)),
|
2023-07-03 18:14:56 +02:00
|
|
|
};
|
2023-07-05 08:05:57 +02:00
|
|
|
hasSetXAxis = true;
|
2023-07-02 13:44:12 +02:00
|
|
|
}
|
2023-08-06 12:15:01 +02:00
|
|
|
function setYAxisTitle(title: NormalTextType) {
|
|
|
|
xyChartData.yAxis.title = textSanitizer(title.text);
|
2023-07-02 13:44:12 +02:00
|
|
|
}
|
|
|
|
function setYAxisRangeData(min: number, max: number) {
|
2023-07-05 08:05:57 +02:00
|
|
|
xyChartData.yAxis = { type: 'linear', title: xyChartData.yAxis.title, min, max };
|
|
|
|
hasSetYAxis = true;
|
2023-07-03 18:14:56 +02:00
|
|
|
}
|
2023-07-05 08:05:57 +02:00
|
|
|
|
|
|
|
// this function does not set `hasSetYAxis` as there can be multiple data so we should calculate the range accordingly
|
|
|
|
function setYAxisRangeFromPlotData(data: number[]) {
|
|
|
|
const minValue = Math.min(...data);
|
|
|
|
const maxValue = Math.max(...data);
|
|
|
|
const prevMinValue = isLinearAxisData(xyChartData.yAxis) ? xyChartData.yAxis.min : Infinity;
|
|
|
|
const prevMaxValue = isLinearAxisData(xyChartData.yAxis) ? xyChartData.yAxis.max : -Infinity;
|
|
|
|
xyChartData.yAxis = {
|
|
|
|
type: 'linear',
|
|
|
|
title: xyChartData.yAxis.title,
|
|
|
|
min: Math.min(prevMinValue, minValue),
|
|
|
|
max: Math.max(prevMaxValue, maxValue),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-20 14:21:53 +02:00
|
|
|
function transformDataWithoutCategory(data: number[]): SimplePlotDataType {
|
2023-07-05 08:05:57 +02:00
|
|
|
let retData: SimplePlotDataType = [];
|
|
|
|
if (data.length === 0) {
|
|
|
|
return retData;
|
|
|
|
}
|
|
|
|
if (!hasSetXAxis) {
|
|
|
|
const prevMinValue = isLinearAxisData(xyChartData.xAxis) ? xyChartData.xAxis.min : Infinity;
|
|
|
|
const prevMaxValue = isLinearAxisData(xyChartData.xAxis) ? xyChartData.xAxis.max : -Infinity;
|
|
|
|
setXAxisRangeData(Math.min(prevMinValue, 1), Math.max(prevMaxValue, data.length));
|
|
|
|
}
|
|
|
|
if (!hasSetYAxis) {
|
|
|
|
setYAxisRangeFromPlotData(data);
|
|
|
|
}
|
|
|
|
|
2023-07-03 18:14:56 +02:00
|
|
|
if (isBandAxisData(xyChartData.xAxis)) {
|
2023-07-05 08:05:57 +02:00
|
|
|
retData = xyChartData.xAxis.categories.map((c, i) => [c, data[i]]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isLinearAxisData(xyChartData.xAxis)) {
|
|
|
|
const min = xyChartData.xAxis.min;
|
|
|
|
const max = xyChartData.xAxis.max;
|
|
|
|
const step = (max - min + 1) / data.length;
|
|
|
|
const categories: string[] = [];
|
|
|
|
for (let i = min; i <= max; i += step) {
|
|
|
|
categories.push(`${i}`);
|
|
|
|
}
|
|
|
|
retData = categories.map((c, i) => [c, data[i]]);
|
2023-07-03 18:14:56 +02:00
|
|
|
}
|
2023-07-05 08:05:57 +02:00
|
|
|
|
|
|
|
return retData;
|
|
|
|
}
|
2023-07-21 19:12:46 +02:00
|
|
|
|
|
|
|
function getPlotColorFromPalette(plotIndex: number): string {
|
|
|
|
return plotColorPalette[plotIndex === 0 ? 0 : plotIndex % (plotColorPalette.length - 1)];
|
|
|
|
}
|
|
|
|
|
2023-08-06 12:15:01 +02:00
|
|
|
function setLineData(title: NormalTextType, data: number[]) {
|
2023-08-20 14:21:53 +02:00
|
|
|
const plotData = transformDataWithoutCategory(data);
|
2023-07-05 08:05:57 +02:00
|
|
|
xyChartData.plots.push({
|
|
|
|
type: 'line',
|
2023-07-21 19:12:46 +02:00
|
|
|
strokeFill: getPlotColorFromPalette(plotIndex),
|
2023-07-05 08:05:57 +02:00
|
|
|
strokeWidth: 2,
|
|
|
|
data: plotData,
|
|
|
|
});
|
2023-07-21 19:12:46 +02:00
|
|
|
plotIndex++;
|
2023-07-03 18:14:56 +02:00
|
|
|
}
|
2023-07-21 19:12:46 +02:00
|
|
|
|
2023-08-06 12:15:01 +02:00
|
|
|
function setBarData(title: NormalTextType, data: number[]) {
|
2023-08-20 14:21:53 +02:00
|
|
|
const plotData = transformDataWithoutCategory(data);
|
2023-07-05 08:05:57 +02:00
|
|
|
xyChartData.plots.push({
|
|
|
|
type: 'bar',
|
2023-07-21 19:12:46 +02:00
|
|
|
fill: getPlotColorFromPalette(plotIndex),
|
2023-07-05 08:05:57 +02:00
|
|
|
data: plotData,
|
|
|
|
});
|
2023-07-21 19:12:46 +02:00
|
|
|
plotIndex++;
|
2023-07-02 13:44:12 +02:00
|
|
|
}
|
2023-06-08 18:30:02 +02:00
|
|
|
|
|
|
|
function getDrawableElem(): DrawableElem[] {
|
2023-07-05 08:05:57 +02:00
|
|
|
if (xyChartData.plots.length === 0) {
|
|
|
|
throw Error('No Plot to render, please provide a plot with some data');
|
|
|
|
}
|
2023-07-02 13:44:12 +02:00
|
|
|
xyChartData.title = getDiagramTitle();
|
2023-08-20 14:21:53 +02:00
|
|
|
return XYChartBuilder.build(xyChartConfig, xyChartData, xyChartThemeConfig, tmpSVGGElem);
|
2023-06-08 18:30:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function setHeight(height: number) {
|
2023-07-02 13:44:12 +02:00
|
|
|
xyChartConfig.height = height;
|
2023-06-08 18:30:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function setWidth(width: number) {
|
2023-07-02 13:44:12 +02:00
|
|
|
xyChartConfig.width = width;
|
2023-06-08 18:30:02 +02:00
|
|
|
}
|
|
|
|
|
2023-05-20 16:02:20 +02:00
|
|
|
const clear = function () {
|
|
|
|
commonClear();
|
2023-07-21 19:12:46 +02:00
|
|
|
plotIndex = 0;
|
2023-07-02 13:44:12 +02:00
|
|
|
xyChartConfig = getChartDefaultConfig();
|
|
|
|
xyChartData = getChartDefalutData();
|
2023-07-19 19:11:41 +02:00
|
|
|
xyChartThemeConfig = getChartDefaultThemeConfig();
|
2023-08-13 19:26:50 +02:00
|
|
|
plotColorPalette = Array.isArray(xyChartThemeConfig.plotBaseColor)
|
|
|
|
? xyChartThemeConfig.plotBaseColor
|
|
|
|
: plotColorPaletteGenerator(xyChartThemeConfig.plotBaseColor);
|
2023-07-05 08:05:57 +02:00
|
|
|
hasSetXAxis = false;
|
|
|
|
hasSetYAxis = false;
|
2023-05-20 16:02:20 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
export default {
|
2023-06-08 18:30:02 +02:00
|
|
|
setWidth,
|
|
|
|
setHeight,
|
|
|
|
getDrawableElem,
|
2023-05-20 16:02:20 +02:00
|
|
|
parseDirective,
|
|
|
|
clear,
|
|
|
|
setAccTitle,
|
|
|
|
getAccTitle,
|
|
|
|
setDiagramTitle,
|
|
|
|
getDiagramTitle,
|
|
|
|
getAccDescription,
|
|
|
|
setAccDescription,
|
2023-07-02 13:44:12 +02:00
|
|
|
setOrientation,
|
|
|
|
setXAxisTitle,
|
|
|
|
setXAxisRangeData,
|
|
|
|
setXAxisBand,
|
|
|
|
setYAxisTitle,
|
|
|
|
setYAxisRangeData,
|
|
|
|
setLineData,
|
|
|
|
setBarData,
|
2023-08-20 14:21:53 +02:00
|
|
|
setTmpSVGG,
|
2023-05-20 16:02:20 +02:00
|
|
|
};
|