mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Rendered axis with basic line chart
This commit is contained in:
parent
93697b74f4
commit
183bc0a978
@ -31,6 +31,17 @@ export type VisibilityOption = {
|
|||||||
yAxisLabel: boolean;
|
yAxisLabel: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface AxisConfig {
|
||||||
|
showLabel: boolean;
|
||||||
|
labelFontSize: number;
|
||||||
|
lablePadding: number;
|
||||||
|
labelColor: string;
|
||||||
|
showTitle: boolean;
|
||||||
|
titleFontSize: number;
|
||||||
|
titlePadding: number;
|
||||||
|
titleColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface XYChartConfig {
|
export interface XYChartConfig {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@ -38,16 +49,9 @@ export interface XYChartConfig {
|
|||||||
titleFontSize: number;
|
titleFontSize: number;
|
||||||
titleFill: string;
|
titleFill: string;
|
||||||
titlePadding: number;
|
titlePadding: number;
|
||||||
xAxisFontSize: number;
|
showtitle: boolean;
|
||||||
xAxisTitleFontSize: number;
|
xAxis: AxisConfig;
|
||||||
yAxisFontSize: number;
|
yAxis: AxisConfig;
|
||||||
yAxisTitleFontSize: number;
|
|
||||||
yAxisPosition: XYChartYAxisPosition;
|
|
||||||
showChartTitle: boolean;
|
|
||||||
showXAxisLable: boolean;
|
|
||||||
showXAxisTitle: boolean;
|
|
||||||
showYAxisLabel: boolean;
|
|
||||||
showYAxisTitle: boolean;
|
|
||||||
chartOrientation: OrientationEnum;
|
chartOrientation: OrientationEnum;
|
||||||
plotReservedSpacePercent: number;
|
plotReservedSpacePercent: number;
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
|
import { log } from '../../../logger.js';
|
||||||
import { DrawableElem, XYChartConfig, XYChartData } from './Interfaces.js';
|
import { DrawableElem, XYChartConfig, XYChartData } from './Interfaces.js';
|
||||||
import { getChartTitleComponent } from './components/ChartTitle.js';
|
import { getChartTitleComponent } from './components/ChartTitle.js';
|
||||||
import { ChartComponent } from './components/Interfaces.js';
|
import { ChartComponent } from './components/Interfaces.js';
|
||||||
import { IAxis, getAxis } from './components/axis/index.js';
|
import { IAxis, getAxis } from './components/axis/index.js';
|
||||||
import { IPlot, getPlotComponent, isTypeIPlot } from './components/plot/index.js';
|
import { IPlot, getPlotComponent } from './components/plot/index.js';
|
||||||
|
|
||||||
export class Orchestrator {
|
export class Orchestrator {
|
||||||
private componentStore: {
|
private componentStore: {
|
||||||
title: ChartComponent,
|
title: ChartComponent;
|
||||||
plot: IPlot,
|
plot: IPlot;
|
||||||
xAxis: IAxis,
|
xAxis: IAxis;
|
||||||
yAxis: IAxis,
|
yAxis: IAxis;
|
||||||
};
|
};
|
||||||
constructor(private chartConfig: XYChartConfig, chartData: XYChartData) {
|
constructor(private chartConfig: XYChartConfig, chartData: XYChartData) {
|
||||||
this.componentStore = {
|
this.componentStore = {
|
||||||
title: getChartTitleComponent(chartConfig, chartData),
|
title: getChartTitleComponent(chartConfig, chartData),
|
||||||
plot: getPlotComponent(chartConfig, chartData),
|
plot: getPlotComponent(chartConfig, chartData),
|
||||||
xAxis: getAxis(chartData.xAxis, chartConfig),
|
xAxis: getAxis(chartData.xAxis, chartConfig.xAxis),
|
||||||
yAxis: getAxis(chartData.yAxis, chartConfig),
|
yAxis: getAxis(chartData.yAxis, chartConfig.yAxis),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,9 +26,10 @@ export class Orchestrator {
|
|||||||
let availableHeight = this.chartConfig.height;
|
let availableHeight = this.chartConfig.height;
|
||||||
let chartX = 0;
|
let chartX = 0;
|
||||||
let chartY = 0;
|
let chartY = 0;
|
||||||
const chartWidth = Math.floor((availableWidth * this.chartConfig.plotReservedSpacePercent) / 100);
|
let chartWidth = Math.floor((availableWidth * this.chartConfig.plotReservedSpacePercent) / 100);
|
||||||
const chartHeight = Math.floor((availableHeight * this.chartConfig.plotReservedSpacePercent) / 100);
|
let chartHeight = Math.floor(
|
||||||
|
(availableHeight * this.chartConfig.plotReservedSpacePercent) / 100
|
||||||
|
);
|
||||||
let spaceUsed = this.componentStore.plot.calculateSpace({
|
let spaceUsed = this.componentStore.plot.calculateSpace({
|
||||||
width: chartWidth,
|
width: chartWidth,
|
||||||
height: chartHeight,
|
height: chartHeight,
|
||||||
@ -39,19 +41,42 @@ export class Orchestrator {
|
|||||||
width: this.chartConfig.width,
|
width: this.chartConfig.width,
|
||||||
height: availableHeight,
|
height: availableHeight,
|
||||||
});
|
});
|
||||||
|
log.trace('space used by title: ', spaceUsed);
|
||||||
chartY = spaceUsed.height;
|
chartY = spaceUsed.height;
|
||||||
availableWidth -= spaceUsed.width;
|
|
||||||
availableHeight -= spaceUsed.height;
|
availableHeight -= spaceUsed.height;
|
||||||
//
|
this.componentStore.xAxis.setAxisPosition('bottom');
|
||||||
// spaceUsed = this.componentStore.xAxis.calculateSpace({
|
spaceUsed = this.componentStore.xAxis.calculateSpace({
|
||||||
// width: availableWidth,
|
width: availableWidth,
|
||||||
// height: availableHeight,
|
height: availableHeight,
|
||||||
// });
|
});
|
||||||
// availableWidth -= spaceUsed.width;
|
log.trace('space used by xaxis: ', spaceUsed);
|
||||||
// availableHeight -= spaceUsed.height;
|
availableHeight -= spaceUsed.height;
|
||||||
this.componentStore.plot.setBoundingBoxXY({x: chartX, y: chartY});
|
this.componentStore.yAxis.setAxisPosition('left');
|
||||||
|
spaceUsed = this.componentStore.yAxis.calculateSpace({
|
||||||
|
width: availableWidth,
|
||||||
|
height: availableHeight,
|
||||||
|
});
|
||||||
|
log.trace('space used by yaxis: ', spaceUsed);
|
||||||
|
chartX = spaceUsed.width;
|
||||||
|
availableWidth -= spaceUsed.width;
|
||||||
|
if (availableWidth > 0) {
|
||||||
|
chartWidth += availableWidth;
|
||||||
|
availableWidth = 0;
|
||||||
|
}
|
||||||
|
if (availableHeight > 0) {
|
||||||
|
chartHeight += availableHeight;
|
||||||
|
availableHeight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace(
|
||||||
|
`Final chart dimansion: x = ${chartX}, y = ${chartY}, width = ${chartWidth}, height = ${chartHeight}`
|
||||||
|
);
|
||||||
|
|
||||||
|
this.componentStore.plot.setBoundingBoxXY({ x: chartX, y: chartY });
|
||||||
this.componentStore.xAxis.setRange([chartX, chartX + chartWidth]);
|
this.componentStore.xAxis.setRange([chartX, chartX + chartWidth]);
|
||||||
|
this.componentStore.xAxis.setBoundingBoxXY({ x: chartX, y: chartY + chartHeight });
|
||||||
this.componentStore.yAxis.setRange([chartY, chartY + chartHeight]);
|
this.componentStore.yAxis.setRange([chartY, chartY + chartHeight]);
|
||||||
|
this.componentStore.yAxis.setBoundingBoxXY({ x: 0, y: chartY });
|
||||||
}
|
}
|
||||||
|
|
||||||
getDrawableElement() {
|
getDrawableElement() {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { Dimension } from './Interfaces.js';
|
import { Dimension } from './Interfaces.js';
|
||||||
|
|
||||||
export interface ITextDimensionCalculator {
|
export interface ITextDimensionCalculator {
|
||||||
getDimension(text: string): Dimension;
|
getDimension(texts: string[], fontSize: number, fontFamily?: string ): Dimension;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TextDimensionCalculator implements ITextDimensionCalculator {
|
export class TextDimensionCalculator implements ITextDimensionCalculator {
|
||||||
constructor(private fontSize: number, private fontFamily: string) {}
|
constructor() {}
|
||||||
getDimension(text: string): Dimension {
|
getDimension(texts: string[], fontSize: number): Dimension {
|
||||||
return {
|
return {
|
||||||
width: text.length * this.fontSize,
|
width: texts.reduce((acc, cur) => Math.max(cur.length, acc), 0) * fontSize,
|
||||||
height: this.fontSize,
|
height: fontSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ export class ChartTitle implements ChartComponent {
|
|||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
};
|
};
|
||||||
this.showChartTitle = !!(this.chartData.title && this.chartConfig.showChartTitle);
|
this.showChartTitle = !!(this.chartData.title && this.chartConfig.showtitle);
|
||||||
this.orientation = OrientationEnum.VERTICAL;
|
this.orientation = OrientationEnum.VERTICAL;
|
||||||
}
|
}
|
||||||
setOrientation(orientation: OrientationEnum): void {
|
setOrientation(orientation: OrientationEnum): void {
|
||||||
@ -36,7 +36,7 @@ export class ChartTitle implements ChartComponent {
|
|||||||
this.boundingRect.y = point.y;
|
this.boundingRect.y = point.y;
|
||||||
}
|
}
|
||||||
calculateSpace(availableSpace: Dimension): Dimension {
|
calculateSpace(availableSpace: Dimension): Dimension {
|
||||||
const titleDimension = this.textDimensionCalculator.getDimension(this.chartData.title);
|
const titleDimension = this.textDimensionCalculator.getDimension([this.chartData.title], this.chartConfig.titleFontSize);
|
||||||
const widthRequired = Math.max(titleDimension.width, availableSpace.width);
|
const widthRequired = Math.max(titleDimension.width, availableSpace.width);
|
||||||
const heightRequired = titleDimension.height + 2 * this.chartConfig.titlePadding;
|
const heightRequired = titleDimension.height + 2 * this.chartConfig.titlePadding;
|
||||||
if (
|
if (
|
||||||
@ -81,9 +81,6 @@ export function getChartTitleComponent(
|
|||||||
chartConfig: XYChartConfig,
|
chartConfig: XYChartConfig,
|
||||||
chartData: XYChartData
|
chartData: XYChartData
|
||||||
): ChartComponent {
|
): ChartComponent {
|
||||||
const textDimensionCalculator = new TextDimensionCalculator(
|
const textDimensionCalculator = new TextDimensionCalculator();
|
||||||
chartConfig.titleFontSize,
|
|
||||||
chartConfig.fontFamily
|
|
||||||
);
|
|
||||||
return new ChartTitle(textDimensionCalculator, chartConfig, chartData);
|
return new ChartTitle(textDimensionCalculator, chartConfig, chartData);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Dimension, DrawableElem, OrientationEnum, Point } from '../Interfaces.js';
|
import { Dimension, DrawableElem, OrientationEnum, Point } from '../Interfaces.js';
|
||||||
|
|
||||||
export interface ChartComponent {
|
export interface ChartComponent {
|
||||||
setOrientation(orientation: OrientationEnum): void;
|
|
||||||
calculateSpace(availableSpace: Dimension): Dimension;
|
calculateSpace(availableSpace: Dimension): Dimension;
|
||||||
setBoundingBoxXY(point: Point): void;
|
setBoundingBoxXY(point: Point): void;
|
||||||
getDrawableElements(): DrawableElem[];
|
getDrawableElements(): DrawableElem[];
|
||||||
|
@ -1,54 +1,43 @@
|
|||||||
import { ScaleBand, scaleBand } from 'd3';
|
import { ScaleBand, scaleBand } from 'd3';
|
||||||
import {
|
import { AxisConfig } from '../../Interfaces.js';
|
||||||
Dimension,
|
import { ITextDimensionCalculator } from '../../TextDimensionCalculator.js';
|
||||||
Point,
|
import { BaseAxis } from './BaseAxis.js';
|
||||||
DrawableElem,
|
import { log } from '../../../../../logger.js';
|
||||||
BoundingRect,
|
|
||||||
OrientationEnum,
|
|
||||||
XYChartConfig,
|
|
||||||
} from '../../Interfaces.js';
|
|
||||||
import { IAxis } from './index.js';
|
|
||||||
|
|
||||||
export class BandAxis implements IAxis {
|
export class BandAxis extends BaseAxis {
|
||||||
private scale: ScaleBand<string>;
|
private scale: ScaleBand<string>;
|
||||||
private range: [number, number];
|
|
||||||
private boundingRect: BoundingRect;
|
|
||||||
private orientation: OrientationEnum;
|
|
||||||
private categories: string[];
|
private categories: string[];
|
||||||
|
|
||||||
constructor(private chartConfig: XYChartConfig, categories: string[]) {
|
constructor(
|
||||||
this.range = [0, 10];
|
axisConfig: AxisConfig,
|
||||||
|
categories: string[],
|
||||||
|
title: string,
|
||||||
|
textDimensionCalculator: ITextDimensionCalculator
|
||||||
|
) {
|
||||||
|
super(axisConfig, title, textDimensionCalculator);
|
||||||
this.categories = categories;
|
this.categories = categories;
|
||||||
this.scale = scaleBand().domain(this.categories).range(this.range);
|
this.scale = scaleBand().domain(this.categories).range(this.getRange());
|
||||||
this.boundingRect = { x: 0, y: 0, width: 0, height: 0 };
|
|
||||||
this.orientation = OrientationEnum.VERTICAL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setRange(range: [number, number]): void {
|
setRange(range: [number, number]): void {
|
||||||
this.range = range;
|
super.setRange(range);
|
||||||
this.scale = scaleBand().domain(this.categories).range(this.range);
|
|
||||||
}
|
}
|
||||||
setOrientation(orientation: OrientationEnum): void {
|
|
||||||
this.orientation = orientation;
|
recalculateScale(): void {
|
||||||
|
this.scale = scaleBand()
|
||||||
|
.domain(this.categories)
|
||||||
|
.range(this.getRange())
|
||||||
|
.paddingInner(1)
|
||||||
|
.paddingOuter(0)
|
||||||
|
.align(0.5);
|
||||||
|
log.trace('BandAxis axis final categories, range: ', this.categories, this.getRange());
|
||||||
|
}
|
||||||
|
|
||||||
|
getTickValues(): (string | number)[] {
|
||||||
|
return this.categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
getScaleValue(value: string): number {
|
getScaleValue(value: string): number {
|
||||||
return this.scale(value) || this.range[0];
|
return this.scale(value) || this.getRange()[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,217 @@
|
|||||||
|
import {
|
||||||
|
Dimension,
|
||||||
|
Point,
|
||||||
|
DrawableElem,
|
||||||
|
BoundingRect,
|
||||||
|
AxisConfig,
|
||||||
|
} from '../../Interfaces.js';
|
||||||
|
import { AxisPosition, IAxis } from './index.js';
|
||||||
|
import { ITextDimensionCalculator } from '../../TextDimensionCalculator.js';
|
||||||
|
import { log } from '../../../../../logger.js';
|
||||||
|
|
||||||
|
export abstract class BaseAxis implements IAxis {
|
||||||
|
protected boundingRect: BoundingRect = {x: 0, y: 0, width: 0, height: 0};
|
||||||
|
protected axisPosition: AxisPosition = 'left';
|
||||||
|
private range: [number, number];
|
||||||
|
protected showTitle = false;
|
||||||
|
protected showLabel = false;
|
||||||
|
protected innerPadding = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected axisConfig: AxisConfig,
|
||||||
|
protected title: string,
|
||||||
|
protected textDimensionCalculator: ITextDimensionCalculator
|
||||||
|
) {
|
||||||
|
this.range = [0, 10];
|
||||||
|
this.boundingRect = { x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
this.axisPosition = 'left';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setRange(range: [number, number]): void {
|
||||||
|
this.range = range;
|
||||||
|
this.recalculateScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
getRange(): [number, number] {
|
||||||
|
return [this.range[0] + this.innerPadding, this.range[1] - this.innerPadding];
|
||||||
|
}
|
||||||
|
|
||||||
|
setAxisPosition(axisPosition: AxisPosition): void {
|
||||||
|
this.axisPosition = axisPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getScaleValue(value: number | string): number;
|
||||||
|
|
||||||
|
abstract recalculateScale(): void;
|
||||||
|
|
||||||
|
abstract getTickValues(): Array<string | number>;
|
||||||
|
|
||||||
|
private getLabelDimension(): Dimension {
|
||||||
|
return this.textDimensionCalculator.getDimension(
|
||||||
|
this.getTickValues().map((tick) => tick.toString()),
|
||||||
|
this.axisConfig.labelFontSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateSpaceIfDrawnVertical(availableSpace: Dimension) {
|
||||||
|
let availableHeight = availableSpace.height;
|
||||||
|
if (this.axisConfig.showLabel) {
|
||||||
|
const spaceRequired = this.getLabelDimension();
|
||||||
|
this.innerPadding = spaceRequired.width / 2;
|
||||||
|
const heightRequired = spaceRequired.height + this.axisConfig.lablePadding * 2;
|
||||||
|
log.trace('height required for axis label: ', heightRequired);
|
||||||
|
if (heightRequired <= availableHeight) {
|
||||||
|
availableHeight -= heightRequired;
|
||||||
|
this.showLabel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.axisConfig.showTitle) {
|
||||||
|
const spaceRequired = this.textDimensionCalculator.getDimension(
|
||||||
|
[this.title],
|
||||||
|
this.axisConfig.labelFontSize
|
||||||
|
);
|
||||||
|
const heightRequired = spaceRequired.height + this.axisConfig.titlePadding * 2;
|
||||||
|
log.trace('height required for axis title: ', heightRequired);
|
||||||
|
if (heightRequired <= availableHeight) {
|
||||||
|
availableHeight -= heightRequired;
|
||||||
|
this.showTitle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.boundingRect.width = availableSpace.width;
|
||||||
|
this.boundingRect.height = availableSpace.height - availableHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateSpaceIfDrawnHorizontally(availableSpace: Dimension) {
|
||||||
|
let availableWidth = availableSpace.width;
|
||||||
|
if (this.axisConfig.showLabel) {
|
||||||
|
const spaceRequired = this.getLabelDimension();
|
||||||
|
this.innerPadding = spaceRequired.width / 2;
|
||||||
|
const widthRequired = spaceRequired.width + this.axisConfig.lablePadding * 2;
|
||||||
|
log.trace('width required for axis label: ', widthRequired);
|
||||||
|
if (widthRequired <= availableWidth) {
|
||||||
|
availableWidth -= widthRequired;
|
||||||
|
this.showLabel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.axisConfig.showTitle) {
|
||||||
|
const spaceRequired = this.textDimensionCalculator.getDimension(
|
||||||
|
[this.title],
|
||||||
|
this.axisConfig.labelFontSize
|
||||||
|
);
|
||||||
|
const widthRequired = spaceRequired.height + this.axisConfig.lablePadding * 2;
|
||||||
|
log.trace('width required for axis title: ', widthRequired);
|
||||||
|
if (widthRequired <= availableWidth) {
|
||||||
|
availableWidth -= widthRequired;
|
||||||
|
this.showTitle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.boundingRect.width = availableSpace.width - availableWidth;
|
||||||
|
this.boundingRect.height = availableSpace.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateSpace(availableSpace: Dimension): Dimension {
|
||||||
|
if (!(this.axisConfig.showLabel || this.axisConfig.showTitle)) {
|
||||||
|
this.recalculateScale();
|
||||||
|
return { width: 0, height: 0 };
|
||||||
|
}
|
||||||
|
if (this.axisPosition === 'left') {
|
||||||
|
this.calculateSpaceIfDrawnHorizontally(availableSpace);
|
||||||
|
} else {
|
||||||
|
this.calculateSpaceIfDrawnVertical(availableSpace);
|
||||||
|
}
|
||||||
|
this.recalculateScale();
|
||||||
|
return {
|
||||||
|
width: this.boundingRect.width,
|
||||||
|
height: this.boundingRect.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setBoundingBoxXY(point: Point): void {
|
||||||
|
this.boundingRect.x = point.x;
|
||||||
|
this.boundingRect.y = point.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDrawaableElementsForLeftAxis(): DrawableElem[] {
|
||||||
|
const drawableElement: DrawableElem[] = [];
|
||||||
|
if (this.showLabel) {
|
||||||
|
drawableElement.push({
|
||||||
|
type: 'text',
|
||||||
|
groupText: 'left-axis-label',
|
||||||
|
data: this.getTickValues().map((tick) => ({
|
||||||
|
text: tick.toString(),
|
||||||
|
x: this.boundingRect.x + this.boundingRect.width - this.axisConfig.lablePadding,
|
||||||
|
y: this.getScaleValue(tick),
|
||||||
|
fill: this.axisConfig.labelColor,
|
||||||
|
fontSize: this.axisConfig.labelFontSize,
|
||||||
|
rotation: 0,
|
||||||
|
verticalPos: 'right',
|
||||||
|
horizontalPos: 'middle',
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.showTitle) {
|
||||||
|
drawableElement.push({
|
||||||
|
type: 'text',
|
||||||
|
groupText: 'right-axis-label',
|
||||||
|
data: [{
|
||||||
|
text: this.title,
|
||||||
|
x: this.boundingRect.x + this.axisConfig.titlePadding,
|
||||||
|
y: this.range[0] + (this.range[1] - this.range[0])/2,
|
||||||
|
fill: this.axisConfig.titleColor,
|
||||||
|
fontSize: this.axisConfig.titleFontSize,
|
||||||
|
rotation: 270,
|
||||||
|
verticalPos: 'center',
|
||||||
|
horizontalPos: 'top',
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return drawableElement;
|
||||||
|
}
|
||||||
|
private getDrawaableElementsForBottomAxis(): DrawableElem[] {
|
||||||
|
const drawableElement: DrawableElem[] = [];
|
||||||
|
if (this.showLabel) {
|
||||||
|
drawableElement.push({
|
||||||
|
type: 'text',
|
||||||
|
groupText: 'right-axis-lable',
|
||||||
|
data: this.getTickValues().map((tick) => ({
|
||||||
|
text: tick.toString(),
|
||||||
|
x: this.getScaleValue(tick),
|
||||||
|
y: this.boundingRect.y + this.axisConfig.lablePadding,
|
||||||
|
fill: this.axisConfig.labelColor,
|
||||||
|
fontSize: this.axisConfig.labelFontSize,
|
||||||
|
rotation: 0,
|
||||||
|
verticalPos: 'center',
|
||||||
|
horizontalPos: 'top',
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.showTitle) {
|
||||||
|
drawableElement.push({
|
||||||
|
type: 'text',
|
||||||
|
groupText: 'right-axis-label',
|
||||||
|
data: [{
|
||||||
|
text: this.title,
|
||||||
|
x: this.range[0] + (this.range[1] - this.range[0])/2,
|
||||||
|
y: this.boundingRect.y + this.boundingRect.height - this.axisConfig.titlePadding,
|
||||||
|
fill: this.axisConfig.titleColor,
|
||||||
|
fontSize: this.axisConfig.titleFontSize,
|
||||||
|
rotation: 0,
|
||||||
|
verticalPos: 'center',
|
||||||
|
horizontalPos: 'bottom',
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return drawableElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDrawableElements(): DrawableElem[] {
|
||||||
|
if (this.axisPosition === 'left') {
|
||||||
|
return this.getDrawaableElementsForLeftAxis();
|
||||||
|
}
|
||||||
|
if (this.axisPosition === 'bottom') {
|
||||||
|
return this.getDrawaableElementsForBottomAxis();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@ -1,55 +1,37 @@
|
|||||||
import { scaleLinear, ScaleLinear } from 'd3';
|
import { ScaleLinear, scaleLinear } from 'd3';
|
||||||
import {
|
import { AxisConfig, Dimension } from '../../Interfaces.js';
|
||||||
Dimension,
|
import { ITextDimensionCalculator } from '../../TextDimensionCalculator.js';
|
||||||
Point,
|
import { BaseAxis } from './BaseAxis.js';
|
||||||
DrawableElem,
|
import { log } from '../../../../../logger.js';
|
||||||
BoundingRect,
|
|
||||||
OrientationEnum,
|
|
||||||
XYChartConfig,
|
|
||||||
} from '../../Interfaces.js';
|
|
||||||
import { IAxis } from './index.js';
|
|
||||||
|
|
||||||
export class LinearAxis implements IAxis {
|
export class LinearAxis extends BaseAxis {
|
||||||
private scale: ScaleLinear<number, number>;
|
private scale: ScaleLinear<number, number>;
|
||||||
private boundingRect: BoundingRect;
|
|
||||||
private orientation: OrientationEnum;
|
|
||||||
private domain: [number, number];
|
private domain: [number, number];
|
||||||
private range: [number, number];
|
|
||||||
|
|
||||||
constructor(private chartConfig: XYChartConfig, domain: [number, number]) {
|
constructor(
|
||||||
|
axisConfig: AxisConfig,
|
||||||
|
domain: [number, number],
|
||||||
|
title: string,
|
||||||
|
textDimensionCalculator: ITextDimensionCalculator
|
||||||
|
) {
|
||||||
|
super(axisConfig, title, textDimensionCalculator);
|
||||||
this.domain = domain;
|
this.domain = domain;
|
||||||
this.range = [0, 10];
|
this.scale = scaleLinear().domain(this.domain).range(this.getRange());
|
||||||
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 {
|
getTickValues(): (string | number)[] {
|
||||||
this.range = range;
|
return this.scale.ticks();
|
||||||
this.scale = scaleLinear().domain(this.domain).range(this.range);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setOrientation(orientation: OrientationEnum): void {
|
recalculateScale(): void {
|
||||||
this.orientation = orientation;
|
if (this.axisPosition === 'left') {
|
||||||
|
this.domain.reverse(); // since yaxis in svg start from top
|
||||||
|
}
|
||||||
|
this.scale = scaleLinear().domain(this.domain).range(this.getRange());
|
||||||
|
log.trace('Linear axis final domain, range: ', this.domain, this.getRange());
|
||||||
}
|
}
|
||||||
|
|
||||||
getScaleValue(value: number): number {
|
getScaleValue(value: number): number {
|
||||||
return this.scale(value);
|
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 [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
AxisDataType,
|
AxisConfig,
|
||||||
BandAxisDataType,
|
AxisDataType,
|
||||||
BoundingRect,
|
BandAxisDataType,
|
||||||
LinearAxisDataType,
|
LinearAxisDataType
|
||||||
XYChartConfig,
|
|
||||||
XYChartData,
|
|
||||||
} from '../../Interfaces.js';
|
} from '../../Interfaces.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';
|
||||||
|
|
||||||
|
export type AxisPosition = 'left' | 'bottom';
|
||||||
|
|
||||||
export interface IAxis extends ChartComponent {
|
export interface IAxis extends ChartComponent {
|
||||||
getScaleValue(value: string | number): number;
|
getScaleValue(value: string | number): number;
|
||||||
|
setAxisPosition(axisPosition: AxisPosition): void;
|
||||||
setRange(range: [number, number]): void;
|
setRange(range: [number, number]): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,9 +25,10 @@ function isBandAxisData(data: any): data is BandAxisDataType {
|
|||||||
return data.categories && Array.isArray(data.categories);
|
return data.categories && Array.isArray(data.categories);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAxis(data: AxisDataType, chartConfig: XYChartConfig): IAxis {
|
export function getAxis(data: AxisDataType, axisConfig: AxisConfig): IAxis {
|
||||||
|
const textDimansionCalculator = new TextDimensionCalculator();
|
||||||
if (isBandAxisData(data)) {
|
if (isBandAxisData(data)) {
|
||||||
return new BandAxis(chartConfig, data.categories);
|
return new BandAxis(axisConfig, data.categories, data.title, textDimansionCalculator);
|
||||||
}
|
}
|
||||||
return new LinearAxis(chartConfig, [data.min, data.max]);
|
return new LinearAxis(axisConfig, [data.min, data.max], data.title, textDimansionCalculator);
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
import { defaultConfig } from '../../../config.js';
|
import { defaultConfig } from '../../../config.js';
|
||||||
import { log } from '../../../logger.js';
|
import { log } from '../../../logger.js';
|
||||||
import {
|
import {
|
||||||
ChartPlotEnum,
|
ChartPlotEnum,
|
||||||
DrawableElem,
|
DrawableElem,
|
||||||
XYChartConfig,
|
OrientationEnum,
|
||||||
XYChartData,
|
XYChartConfig,
|
||||||
OrientationEnum,
|
XYChartData
|
||||||
XYChartYAxisPosition,
|
|
||||||
} from './Interfaces.js';
|
} from './Interfaces.js';
|
||||||
import { Orchestrator } from './Orchestrator.js';
|
import { Orchestrator } from './Orchestrator.js';
|
||||||
|
|
||||||
@ -17,22 +16,33 @@ export class XYChartBuilder {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.config = {
|
this.config = {
|
||||||
width: 500,
|
width: 700,
|
||||||
height: 500,
|
height: 500,
|
||||||
fontFamily: defaultConfig.fontFamily || 'Sans',
|
fontFamily: defaultConfig.fontFamily || 'Sans',
|
||||||
titleFontSize: 16,
|
titleFontSize: 16,
|
||||||
titleFill: '#000000',
|
titleFill: '#000000',
|
||||||
titlePadding: 5,
|
titlePadding: 5,
|
||||||
xAxisFontSize: 14,
|
showtitle: true,
|
||||||
xAxisTitleFontSize: 16,
|
yAxis: {
|
||||||
yAxisFontSize: 14,
|
showLabel: true,
|
||||||
yAxisTitleFontSize: 16,
|
labelFontSize: 14,
|
||||||
yAxisPosition: XYChartYAxisPosition.LEFT,
|
lablePadding: 5,
|
||||||
showChartTitle: true,
|
labelColor: '#000000',
|
||||||
showXAxisLable: true,
|
showTitle: true,
|
||||||
showXAxisTitle: true,
|
titleFontSize: 16,
|
||||||
showYAxisLabel: true,
|
titlePadding: 5,
|
||||||
showYAxisTitle: true,
|
titleColor: '#000000',
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
showLabel: true,
|
||||||
|
labelFontSize: 14,
|
||||||
|
lablePadding: 5,
|
||||||
|
labelColor: '#000000',
|
||||||
|
showTitle: true,
|
||||||
|
titleFontSize: 16,
|
||||||
|
titlePadding: 5,
|
||||||
|
titleColor: '#000000',
|
||||||
|
},
|
||||||
chartOrientation: OrientationEnum.HORIZONTAL,
|
chartOrientation: OrientationEnum.HORIZONTAL,
|
||||||
plotReservedSpacePercent: 50,
|
plotReservedSpacePercent: 50,
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { select, scaleOrdinal, scaleLinear, axisBottom, line } from 'd3';
|
import { select } from 'd3';
|
||||||
|
import { Diagram } from '../../Diagram.js';
|
||||||
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 {
|
import {
|
||||||
DrawableElem,
|
DrawableElem,
|
||||||
TextElem,
|
TextElem,
|
||||||
TextHorizontalPos,
|
TextHorizontalPos,
|
||||||
TextVerticalPos,
|
TextVerticalPos,
|
||||||
} from './chartBuilder/Interfaces.js';
|
} 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) => {
|
||||||
@ -16,7 +16,7 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTextAnchor(verticalPos: TextVerticalPos) {
|
function getTextAnchor(verticalPos: TextVerticalPos) {
|
||||||
return verticalPos === 'left' ? 'start' : 'middle';
|
return verticalPos === 'left' ? 'start' : verticalPos === 'right' ? 'end' : 'middle';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTextTransformation(data: TextElem) {
|
function getTextTransformation(data: TextElem) {
|
||||||
@ -32,16 +32,13 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
|||||||
if (securityLevel === 'sandbox') {
|
if (securityLevel === 'sandbox') {
|
||||||
sandboxElement = select('#i' + id);
|
sandboxElement = select('#i' + id);
|
||||||
}
|
}
|
||||||
const root =
|
const root = sandboxElement ? sandboxElement : select('body');
|
||||||
sandboxElement
|
|
||||||
? sandboxElement
|
|
||||||
: select('body');
|
|
||||||
|
|
||||||
const svg = root.select(`[id="${id}"]`);
|
const svg = root.select(`[id="${id}"]`);
|
||||||
|
|
||||||
const group = svg.append('g').attr('class', 'main');
|
const group = svg.append('g').attr('class', 'main');
|
||||||
|
|
||||||
const width = 500;
|
const width = 700;
|
||||||
const height = 500;
|
const height = 500;
|
||||||
|
|
||||||
// @ts-ignore: TODO Fix ts errors
|
// @ts-ignore: TODO Fix ts errors
|
||||||
@ -74,7 +71,6 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
|||||||
|
|
||||||
const shapeGroup = group.append('g').attr('class', shape.groupText);
|
const shapeGroup = group.append('g').attr('class', shape.groupText);
|
||||||
|
|
||||||
|
|
||||||
switch (shape.type) {
|
switch (shape.type) {
|
||||||
case 'rect':
|
case 'rect':
|
||||||
shapeGroup
|
shapeGroup
|
||||||
@ -82,13 +78,13 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
|||||||
.data(shape.data)
|
.data(shape.data)
|
||||||
.enter()
|
.enter()
|
||||||
.append('rect')
|
.append('rect')
|
||||||
.attr('x', data => data.x)
|
.attr('x', (data) => data.x)
|
||||||
.attr('y', data => data.y)
|
.attr('y', (data) => data.y)
|
||||||
.attr('width', data => data.width)
|
.attr('width', (data) => data.width)
|
||||||
.attr('height', data => data.height)
|
.attr('height', (data) => data.height)
|
||||||
.attr('fill', data => data.fill)
|
.attr('fill', (data) => data.fill)
|
||||||
.attr('stroke', data => data.strokeFill)
|
.attr('stroke', (data) => data.strokeFill)
|
||||||
.attr('stroke-width', data => data.strokeWidth);
|
.attr('stroke-width', (data) => data.strokeWidth);
|
||||||
break;
|
break;
|
||||||
case 'text':
|
case 'text':
|
||||||
shapeGroup
|
shapeGroup
|
||||||
@ -98,12 +94,12 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
|||||||
.append('text')
|
.append('text')
|
||||||
.attr('x', 0)
|
.attr('x', 0)
|
||||||
.attr('y', 0)
|
.attr('y', 0)
|
||||||
.attr('fill', data => data.fill)
|
.attr('fill', (data) => data.fill)
|
||||||
.attr('font-size', data => data.fontSize)
|
.attr('font-size', (data) => data.fontSize)
|
||||||
.attr('dominant-baseline', data => getDominantBaseLine(data.horizontalPos))
|
.attr('dominant-baseline', (data) => getDominantBaseLine(data.horizontalPos))
|
||||||
.attr('text-anchor', data => getTextAnchor(data.verticalPos))
|
.attr('text-anchor', (data) => getTextAnchor(data.verticalPos))
|
||||||
.attr('transform', data => getTextTransformation(data))
|
.attr('transform', (data) => getTextTransformation(data))
|
||||||
.text(data => data.text);
|
.text((data) => data.text);
|
||||||
break;
|
break;
|
||||||
case 'path':
|
case 'path':
|
||||||
shapeGroup
|
shapeGroup
|
||||||
@ -111,10 +107,10 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
|||||||
.data(shape.data)
|
.data(shape.data)
|
||||||
.enter()
|
.enter()
|
||||||
.append('path')
|
.append('path')
|
||||||
.attr('d', data => data.path)
|
.attr('d', (data) => data.path)
|
||||||
.attr('fill', data => data.fill? data.fill: "none")
|
.attr('fill', (data) => (data.fill ? data.fill : 'none'))
|
||||||
.attr('stroke', data => data.strokeFill)
|
.attr('stroke', (data) => data.strokeFill)
|
||||||
.attr('stroke-width', data => data.strokeWidth)
|
.attr('stroke-width', (data) => data.strokeWidth);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user