⚗️ Add radar renderer tests

This commit is contained in:
Thomas Di Cizerone
2025-03-16 22:04:30 +01:00
parent 5d1b27132a
commit bb5a7a585e
5 changed files with 124 additions and 22 deletions

View File

@@ -1,6 +1,11 @@
import { it, describe, expect } from 'vitest'; import { it, describe, expect } from 'vitest';
import { db } from './db.js'; import { db } from './db.js';
import { parser } from './parser.js'; import { parser } from './parser.js';
import { renderer, relativeRadius, closedRoundCurve } from './renderer.js';
import { Diagram } from '../../Diagram.js';
import mermaidAPI from '../../mermaidAPI.js';
import { a } from 'vitest/dist/chunks/suite.qtkXWc6R.js';
import { buildRadarStyleOptions } from './styles.js';
const { const {
clear, clear,
@@ -135,10 +140,6 @@ describe('radar diagrams', () => {
curve mycurve{1,2,3} curve mycurve{1,2,3}
`; `;
await expect(parser.parse(str)).resolves.not.toThrow(); await expect(parser.parse(str)).resolves.not.toThrow();
// TODO: ✨ Fix this test
// expect(getConfig().marginTop).toBe(80);
// expect(getConfig().axisLabelFactor).toBe(1.25);
}); });
it('should parse radar diagram with theme override', async () => { it('should parse radar diagram with theme override', async () => {
@@ -149,8 +150,6 @@ describe('radar diagrams', () => {
curve mycurve{1,2,3} curve mycurve{1,2,3}
`; `;
await expect(parser.parse(str)).resolves.not.toThrow(); await expect(parser.parse(str)).resolves.not.toThrow();
// TODO: ✨ Add tests for theme override
}); });
it('should handle radar diagram with radar style override', async () => { it('should handle radar diagram with radar style override', async () => {
@@ -161,7 +160,99 @@ describe('radar diagrams', () => {
curve mycurve{1,2,3} curve mycurve{1,2,3}
`; `;
await expect(parser.parse(str)).resolves.not.toThrow(); await expect(parser.parse(str)).resolves.not.toThrow();
});
// TODO: ✨ Add tests for style override describe('renderer', () => {
describe('relativeRadius', () => {
it('should calculate relative radius', () => {
expect(relativeRadius(5, 0, 10, 100)).toBe(50);
});
it('should handle min value', () => {
expect(relativeRadius(0, 0, 10, 100)).toBe(0);
});
it('should handle max value', () => {
expect(relativeRadius(10, 0, 10, 100)).toBe(100);
});
it('should clip values below min', () => {
expect(relativeRadius(-5, 0, 10, 100)).toBe(0);
});
it('should clip values above max', () => {
expect(relativeRadius(15, 0, 10, 100)).toBe(100);
});
it('should handle negative min', () => {
expect(relativeRadius(5, -10, 10, 100)).toBe(75);
});
});
describe('closedRoundCurve', () => {
it('should construct a polygon if tension is 0', () => {
const points = [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
{ x: 100, y: 100 },
{ x: 0, y: 100 },
];
const tension = 0;
const path = closedRoundCurve(points, tension);
expect(path).toMatchInlineSnapshot(
`"M0,0 C0,0 100,0 100,0 C100,0 100,100 100,100 C100,100 0,100 0,100 C0,100 0,0 0,0 Z"`
);
});
it('should construct a simple round curve', () => {
const points = [
{ x: 0, y: 0 },
{ x: 100, y: 100 },
];
const tension = 0.5;
const path = closedRoundCurve(points, tension);
expect(path).toMatchInlineSnapshot(`"M0,0 C0,0 100,100 100,100 C100,100 0,0 0,0 Z"`);
});
it('should construct a closed round curve', () => {
const points = [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
{ x: 100, y: 100 },
{ x: 0, y: 100 },
];
const tension = 0.5;
const path = closedRoundCurve(points, tension);
expect(path).toMatchInlineSnapshot(
`"M0,0 C50,-50 50,-50 100,0 C150,50 150,50 100,100 C50,150 50,150 0,100 C-50,50 -50,50 0,0 Z"`
);
});
});
describe('draw', () => {
it('should draw a simple radar diagram', async () => {
const str = `radar-beta
axis A,B,C
curve mycurve{1,2,3}`;
await mermaidAPI.parse(str);
const diagram = await Diagram.fromText(str);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
});
it('should draw a complex radar diagram', async () => {
const str = `radar-beta
title Radar diagram
accTitle: Radar accTitle
accDescr: Radar accDescription
axis A["Axis A"], B["Axis B"] ,C["Axis C"]
curve mycurve["My Curve"]{1,2,3}
curve mycurve2["My Curve 2"]{ C: 1, A: 2, B: 3 }
graticule polygon
`;
await mermaidAPI.parse(str);
const diagram = await Diagram.fromText(str);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
});
});
}); });
}); });

View File

@@ -118,7 +118,6 @@ const drawAxes = (
} }
}; };
export const renderer: DiagramRenderer = { draw };
function drawCurves( function drawCurves(
g: SVGGroup, g: SVGGroup,
axes: RadarAxis[], axes: RadarAxis[],
@@ -159,12 +158,17 @@ function drawCurves(
}); });
} }
function relativeRadius(value: number, minValue: number, maxValue: number, radius: number): number { export function relativeRadius(
value: number,
minValue: number,
maxValue: number,
radius: number
): number {
const clippedValue = Math.min(Math.max(value, minValue), maxValue); const clippedValue = Math.min(Math.max(value, minValue), maxValue);
return (radius * (clippedValue - minValue)) / (maxValue - minValue); return (radius * (clippedValue - minValue)) / (maxValue - minValue);
} }
function closedRoundCurve(points: { x: number; y: number }[], tension: number): string { export function closedRoundCurve(points: { x: number; y: number }[], tension: number): string {
// Catmull-Rom spline helper function // Catmull-Rom spline helper function
const numPoints = points.length; const numPoints = points.length;
let d = `M${points[0].x},${points[0].y}`; let d = `M${points[0].x},${points[0].y}`;
@@ -224,3 +228,5 @@ function drawLegend(
.text(curve.label); .text(curve.label);
}); });
} }
export const renderer: DiagramRenderer = { draw };

View File

@@ -30,12 +30,18 @@ const genIndexStyles = (
return sections; return sections;
}; };
export const styles: DiagramStylesProvider = ({ radar }: { radar?: RadarStyleOptions } = {}) => { export const buildRadarStyleOptions = (radar?: RadarStyleOptions) => {
const defaultThemeVariables = getThemeVariables(); const defaultThemeVariables = getThemeVariables();
const currentConfig = getConfigAPI(); const currentConfig = getConfigAPI();
const themeVariables = cleanAndMerge(defaultThemeVariables, currentConfig.themeVariables); const themeVariables = cleanAndMerge(defaultThemeVariables, currentConfig.themeVariables);
const radarOptions: RadarStyleOptions = cleanAndMerge(themeVariables.radar, radar); const radarOptions: RadarStyleOptions = cleanAndMerge(themeVariables.radar, radar);
return { themeVariables, radarOptions };
};
export const styles: DiagramStylesProvider = ({ radar }: { radar?: RadarStyleOptions } = {}) => {
const { themeVariables, radarOptions } = buildRadarStyleOptions(radar);
return ` return `
.radarTitle { .radarTitle {
font-size: ${themeVariables.fontSize}; font-size: ${themeVariables.fontSize};

View File

@@ -28,16 +28,16 @@ export interface RadarDB extends DiagramDBBase<RadarDiagramConfig> {
} }
export interface RadarStyleOptions { export interface RadarStyleOptions {
axisColor: string; axisColor?: string;
axisStrokeWidth: number; axisStrokeWidth?: number;
axisLabelFontSize: number; axisLabelFontSize?: number;
curveOpacity: number; curveOpacity?: number;
curveStrokeWidth: number; curveStrokeWidth?: number;
graticuleColor: string; graticuleColor?: string;
graticuleOpacity: number; graticuleOpacity?: number;
graticuleStrokeWidth: number; graticuleStrokeWidth?: number;
legendBoxSize: number; legendBoxSize?: number;
legendFontSize: number; legendFontSize?: number;
} }
export interface RadarData { export interface RadarData {

View File

@@ -1679,7 +1679,6 @@ Alice->Bob: Hello Bob, how are you?`;
const diagram = await Diagram.fromText(str); const diagram = await Diagram.fromText(str);
await diagram.renderer.draw(str, 'tst', '1.2.3', diagram); await diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds(); const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0); expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0); expect(bounds.starty).toBe(0);