mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Added axis tick and plot border
This commit is contained in:
parent
183bc0a978
commit
cc1d6af232
@ -35,11 +35,15 @@ export interface AxisConfig {
|
||||
showLabel: boolean;
|
||||
labelFontSize: number;
|
||||
lablePadding: number;
|
||||
labelColor: string;
|
||||
labelFill: string;
|
||||
showTitle: boolean;
|
||||
titleFontSize: number;
|
||||
titlePadding: number;
|
||||
titleColor: string;
|
||||
titleFill: string;
|
||||
showTick: boolean;
|
||||
tickLength: number;
|
||||
tickWidth: number;
|
||||
tickFill: string;
|
||||
}
|
||||
|
||||
export interface XYChartConfig {
|
||||
@ -52,6 +56,7 @@ export interface XYChartConfig {
|
||||
showtitle: boolean;
|
||||
xAxis: AxisConfig;
|
||||
yAxis: AxisConfig;
|
||||
plotBorderWidth: number;
|
||||
chartOrientation: OrientationEnum;
|
||||
plotReservedSpacePercent: number;
|
||||
}
|
||||
@ -139,17 +144,17 @@ export interface PathElem {
|
||||
|
||||
export type DrawableElem =
|
||||
| {
|
||||
groupText: string;
|
||||
groupTexts: string[];
|
||||
type: 'rect';
|
||||
data: RectElem[];
|
||||
}
|
||||
| {
|
||||
groupText: string;
|
||||
groupTexts: string[];
|
||||
type: 'text';
|
||||
data: TextElem[];
|
||||
}
|
||||
| {
|
||||
groupText: string;
|
||||
groupTexts: string[];
|
||||
type: 'path';
|
||||
data: PathElem[];
|
||||
};
|
||||
|
@ -67,6 +67,15 @@ export class Orchestrator {
|
||||
chartHeight += availableHeight;
|
||||
availableHeight = 0;
|
||||
}
|
||||
const plotBorderWidthHalf = this.chartConfig.plotBorderWidth/2;
|
||||
chartX += plotBorderWidthHalf;
|
||||
chartY += plotBorderWidthHalf;
|
||||
chartWidth -= this.chartConfig.plotBorderWidth;
|
||||
chartHeight -= this.chartConfig.plotBorderWidth;
|
||||
this.componentStore.plot.calculateSpace({
|
||||
width: chartWidth,
|
||||
height: chartHeight,
|
||||
});
|
||||
|
||||
log.trace(
|
||||
`Final chart dimansion: x = ${chartX}, y = ${chartY}, width = ${chartWidth}, height = ${chartHeight}`
|
||||
|
@ -57,7 +57,7 @@ export class ChartTitle implements ChartComponent {
|
||||
const drawableElem: DrawableElem[] = [];
|
||||
if (this.boundingRect.height > 0 && this.boundingRect.width > 0) {
|
||||
drawableElem.push({
|
||||
groupText: 'chart-title',
|
||||
groupTexts: ['chart-title'],
|
||||
type: 'text',
|
||||
data: [
|
||||
{
|
||||
|
@ -1,20 +1,15 @@
|
||||
import {
|
||||
Dimension,
|
||||
Point,
|
||||
DrawableElem,
|
||||
BoundingRect,
|
||||
AxisConfig,
|
||||
} from '../../Interfaces.js';
|
||||
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 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 showTick = false;
|
||||
protected innerPadding = 0;
|
||||
|
||||
constructor(
|
||||
@ -25,7 +20,6 @@ export abstract class BaseAxis implements IAxis {
|
||||
this.range = [0, 10];
|
||||
this.boundingRect = { x: 0, y: 0, width: 0, height: 0 };
|
||||
this.axisPosition = 'left';
|
||||
|
||||
}
|
||||
|
||||
setRange(range: [number, number]): void {
|
||||
@ -54,7 +48,7 @@ export abstract class BaseAxis implements IAxis {
|
||||
);
|
||||
}
|
||||
|
||||
private calculateSpaceIfDrawnVertical(availableSpace: Dimension) {
|
||||
private calculateSpaceIfDrawnHorizontally(availableSpace: Dimension) {
|
||||
let availableHeight = availableSpace.height;
|
||||
if (this.axisConfig.showLabel) {
|
||||
const spaceRequired = this.getLabelDimension();
|
||||
@ -66,6 +60,10 @@ export abstract class BaseAxis implements IAxis {
|
||||
this.showLabel = true;
|
||||
}
|
||||
}
|
||||
if (this.axisConfig.showTick && availableHeight >= this.axisConfig.tickLength) {
|
||||
this.showTick = true;
|
||||
availableHeight -= this.axisConfig.tickLength;
|
||||
}
|
||||
if (this.axisConfig.showTitle) {
|
||||
const spaceRequired = this.textDimensionCalculator.getDimension(
|
||||
[this.title],
|
||||
@ -82,7 +80,7 @@ export abstract class BaseAxis implements IAxis {
|
||||
this.boundingRect.height = availableSpace.height - availableHeight;
|
||||
}
|
||||
|
||||
private calculateSpaceIfDrawnHorizontally(availableSpace: Dimension) {
|
||||
private calculateSpaceIfDrawnVertical(availableSpace: Dimension) {
|
||||
let availableWidth = availableSpace.width;
|
||||
if (this.axisConfig.showLabel) {
|
||||
const spaceRequired = this.getLabelDimension();
|
||||
@ -94,6 +92,10 @@ export abstract class BaseAxis implements IAxis {
|
||||
this.showLabel = true;
|
||||
}
|
||||
}
|
||||
if (this.axisConfig.showTick && availableWidth >= this.axisConfig.tickLength) {
|
||||
this.showTick = true;
|
||||
availableWidth -= this.axisConfig.tickLength;
|
||||
}
|
||||
if (this.axisConfig.showTitle) {
|
||||
const spaceRequired = this.textDimensionCalculator.getDimension(
|
||||
[this.title],
|
||||
@ -116,9 +118,9 @@ export abstract class BaseAxis implements IAxis {
|
||||
return { width: 0, height: 0 };
|
||||
}
|
||||
if (this.axisPosition === 'left') {
|
||||
this.calculateSpaceIfDrawnHorizontally(availableSpace);
|
||||
} else {
|
||||
this.calculateSpaceIfDrawnVertical(availableSpace);
|
||||
} else {
|
||||
this.calculateSpaceIfDrawnHorizontally(availableSpace);
|
||||
}
|
||||
this.recalculateScale();
|
||||
return {
|
||||
@ -137,12 +139,16 @@ export abstract class BaseAxis implements IAxis {
|
||||
if (this.showLabel) {
|
||||
drawableElement.push({
|
||||
type: 'text',
|
||||
groupText: 'left-axis-label',
|
||||
groupTexts: ['left-axis', 'label'],
|
||||
data: this.getTickValues().map((tick) => ({
|
||||
text: tick.toString(),
|
||||
x: this.boundingRect.x + this.boundingRect.width - this.axisConfig.lablePadding,
|
||||
x:
|
||||
this.boundingRect.x +
|
||||
this.boundingRect.width -
|
||||
this.axisConfig.lablePadding -
|
||||
this.axisConfig.tickLength,
|
||||
y: this.getScaleValue(tick),
|
||||
fill: this.axisConfig.labelColor,
|
||||
fill: this.axisConfig.labelFill,
|
||||
fontSize: this.axisConfig.labelFontSize,
|
||||
rotation: 0,
|
||||
verticalPos: 'right',
|
||||
@ -150,21 +156,35 @@ export abstract class BaseAxis implements IAxis {
|
||||
})),
|
||||
});
|
||||
}
|
||||
if (this.showTick) {
|
||||
const x = this.boundingRect.x + this.boundingRect.width;
|
||||
drawableElement.push({
|
||||
type: 'path',
|
||||
groupTexts: ['left-axis', 'ticks'],
|
||||
data: this.getTickValues().map((tick) => ({
|
||||
path: `M ${x},${this.getScaleValue(tick)} L ${x - this.axisConfig.tickLength},${this.getScaleValue(tick)}`,
|
||||
strokeFill: this.axisConfig.tickFill,
|
||||
strokeWidth: this.axisConfig.tickWidth,
|
||||
})),
|
||||
});
|
||||
}
|
||||
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',
|
||||
}]
|
||||
})
|
||||
groupTexts: ['left-axis', 'title'],
|
||||
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.titleFill,
|
||||
fontSize: this.axisConfig.titleFontSize,
|
||||
rotation: 270,
|
||||
verticalPos: 'center',
|
||||
horizontalPos: 'top',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return drawableElement;
|
||||
}
|
||||
@ -173,12 +193,12 @@ export abstract class BaseAxis implements IAxis {
|
||||
if (this.showLabel) {
|
||||
drawableElement.push({
|
||||
type: 'text',
|
||||
groupText: 'right-axis-lable',
|
||||
groupTexts: ['bottom-axis', 'label'],
|
||||
data: this.getTickValues().map((tick) => ({
|
||||
text: tick.toString(),
|
||||
x: this.getScaleValue(tick),
|
||||
y: this.boundingRect.y + this.axisConfig.lablePadding,
|
||||
fill: this.axisConfig.labelColor,
|
||||
y: this.boundingRect.y + this.axisConfig.lablePadding + this.axisConfig.tickLength,
|
||||
fill: this.axisConfig.labelFill,
|
||||
fontSize: this.axisConfig.labelFontSize,
|
||||
rotation: 0,
|
||||
verticalPos: 'center',
|
||||
@ -186,21 +206,35 @@ export abstract class BaseAxis implements IAxis {
|
||||
})),
|
||||
});
|
||||
}
|
||||
if (this.showTick) {
|
||||
const y = this.boundingRect.y;
|
||||
drawableElement.push({
|
||||
type: 'path',
|
||||
groupTexts: ['bottom-axis', 'ticks'],
|
||||
data: this.getTickValues().map((tick) => ({
|
||||
path: `M ${this.getScaleValue(tick)},${y} L ${this.getScaleValue(tick)},${y + this.axisConfig.tickLength}`,
|
||||
strokeFill: this.axisConfig.tickFill,
|
||||
strokeWidth: this.axisConfig.tickWidth,
|
||||
})),
|
||||
});
|
||||
}
|
||||
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',
|
||||
}]
|
||||
})
|
||||
groupTexts: ['bottom-axis', 'title'],
|
||||
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.titleFill,
|
||||
fontSize: this.axisConfig.titleFontSize,
|
||||
rotation: 0,
|
||||
verticalPos: 'center',
|
||||
horizontalPos: 'bottom',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return drawableElement;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ScaleLinear, scaleLinear } from 'd3';
|
||||
import { AxisConfig, Dimension } from '../../Interfaces.js';
|
||||
import { log } from '../../../../../logger.js';
|
||||
import { AxisConfig } from '../../Interfaces.js';
|
||||
import { ITextDimensionCalculator } from '../../TextDimensionCalculator.js';
|
||||
import { BaseAxis } from './BaseAxis.js';
|
||||
import { log } from '../../../../../logger.js';
|
||||
|
||||
export class LinearAxis extends BaseAxis {
|
||||
private scale: ScaleLinear<number, number>;
|
||||
@ -24,10 +24,11 @@ export class LinearAxis extends BaseAxis {
|
||||
}
|
||||
|
||||
recalculateScale(): void {
|
||||
const domain = [...this.domain]; // copy the array so if reverse is called twise it shouldnot cancel the reverse effect
|
||||
if (this.axisPosition === 'left') {
|
||||
this.domain.reverse(); // since yaxis in svg start from top
|
||||
domain.reverse(); // since yaxis in svg start from top
|
||||
}
|
||||
this.scale = scaleLinear().domain(this.domain).range(this.getRange());
|
||||
this.scale = scaleLinear().domain(domain).range(this.getRange());
|
||||
log.trace('Linear axis final domain, range: ', this.domain, this.getRange());
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ export class LinePlot {
|
||||
}
|
||||
return [
|
||||
{
|
||||
groupText: 'line-plot',
|
||||
groupTexts: ['plot', 'line-plot'],
|
||||
type: 'path',
|
||||
data: [
|
||||
{
|
||||
|
@ -0,0 +1,21 @@
|
||||
import { BoundingRect, DrawableElem } from '../../Interfaces.js';
|
||||
export class PlotBorder {
|
||||
constructor(private boundingRect: BoundingRect) {}
|
||||
|
||||
getDrawableElement(): DrawableElem[] {
|
||||
const {x, y, width, height} = this.boundingRect;
|
||||
return [
|
||||
{
|
||||
groupTexts: ['plot', 'chart-border'],
|
||||
type: 'path',
|
||||
data: [
|
||||
{
|
||||
path: `M ${x},${y} L ${x + width},${y} L ${x + width},${y + height} L ${x},${y + height} L ${x},${y}`,
|
||||
strokeFill: '#000000',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import {
|
||||
import { IAxis } from '../axis/index.js';
|
||||
import { ChartComponent } from './../Interfaces.js';
|
||||
import { LinePlot } from './LinePlot.js';
|
||||
import { PlotBorder } from './PlotBorder.js';
|
||||
|
||||
|
||||
export interface IPlot extends ChartComponent {
|
||||
@ -59,7 +60,9 @@ export class Plot implements IPlot {
|
||||
if(!(this.xAxis && this.yAxis)) {
|
||||
throw Error("Axes must be passed to render Plots");
|
||||
}
|
||||
const drawableElem: DrawableElem[] = [];
|
||||
const drawableElem: DrawableElem[] = [
|
||||
...new PlotBorder(this.boundingRect).getDrawableElement()
|
||||
];
|
||||
for(const plot of this.chartData.plots) {
|
||||
switch(plot.type) {
|
||||
case ChartPlotEnum.LINE: {
|
||||
|
@ -23,25 +23,34 @@ export class XYChartBuilder {
|
||||
titleFill: '#000000',
|
||||
titlePadding: 5,
|
||||
showtitle: true,
|
||||
plotBorderWidth: 2,
|
||||
yAxis: {
|
||||
showLabel: true,
|
||||
labelFontSize: 14,
|
||||
lablePadding: 5,
|
||||
labelColor: '#000000',
|
||||
labelFill: '#000000',
|
||||
showTitle: true,
|
||||
titleFontSize: 16,
|
||||
titlePadding: 5,
|
||||
titleColor: '#000000',
|
||||
titleFill: '#000000',
|
||||
showTick: true,
|
||||
tickLength: 5,
|
||||
tickWidth: 2,
|
||||
tickFill: '#000000',
|
||||
},
|
||||
xAxis: {
|
||||
showLabel: true,
|
||||
labelFontSize: 14,
|
||||
lablePadding: 5,
|
||||
labelColor: '#000000',
|
||||
labelFill: '#000000',
|
||||
showTitle: true,
|
||||
titleFontSize: 16,
|
||||
titlePadding: 5,
|
||||
titleColor: '#000000',
|
||||
titleFill: '#000000',
|
||||
showTick: true,
|
||||
tickLength: 5,
|
||||
tickWidth: 2,
|
||||
tickFill: '#000000',
|
||||
},
|
||||
chartOrientation: OrientationEnum.HORIZONTAL,
|
||||
plotReservedSpacePercent: 50,
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { select } from 'd3';
|
||||
import { select, Selection } from 'd3';
|
||||
import { Diagram } from '../../Diagram.js';
|
||||
import * as configApi from '../../config.js';
|
||||
import { log } from '../../logger.js';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||
import {
|
||||
DrawableElem,
|
||||
TextElem,
|
||||
TextHorizontalPos,
|
||||
TextVerticalPos,
|
||||
DrawableElem,
|
||||
TextElem,
|
||||
TextHorizontalPos,
|
||||
TextVerticalPos,
|
||||
} from './chartBuilder/Interfaces.js';
|
||||
|
||||
export const draw = (txt: string, id: string, _version: string, diagObj: Diagram) => {
|
||||
@ -54,6 +54,25 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
const shapes: DrawableElem[] = diagObj.db.getDrawableElem();
|
||||
|
||||
const groups: Record<string, any> = {};
|
||||
|
||||
function getGroup(gList: string[]) {
|
||||
let elem = group;
|
||||
let prefix = '';
|
||||
for (let i = 0; i < gList.length; i++) {
|
||||
let parent = group;
|
||||
if (i > 0 && groups[prefix]) {
|
||||
parent = groups[prefix];
|
||||
}
|
||||
prefix += gList[i];
|
||||
elem = groups[prefix];
|
||||
if (!elem) {
|
||||
elem = groups[prefix] = parent.append('g').attr('class', gList[i]);
|
||||
}
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
for (const shape of shapes) {
|
||||
if (shape.data.length === 0) {
|
||||
log.trace(
|
||||
@ -69,7 +88,7 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
||||
`Drawing shape of type: ${shape.type} with data: ${JSON.stringify(shape.data, null, 2)}`
|
||||
);
|
||||
|
||||
const shapeGroup = group.append('g').attr('class', shape.groupText);
|
||||
const shapeGroup = getGroup(shape.groupTexts);
|
||||
|
||||
switch (shape.type) {
|
||||
case 'rect':
|
||||
|
Loading…
x
Reference in New Issue
Block a user