@xrn07/figure-renderer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/parse.d.ts +11 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +39 -0
- package/dist/react.d.ts +14 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +21 -0
- package/dist/render.d.ts +10 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +41 -0
- package/dist/renderers/bioTable.d.ts +3 -0
- package/dist/renderers/bioTable.d.ts.map +1 -0
- package/dist/renderers/bioTable.js +28 -0
- package/dist/renderers/circuit.d.ts +3 -0
- package/dist/renderers/circuit.d.ts.map +1 -0
- package/dist/renderers/circuit.js +237 -0
- package/dist/renderers/coordinatePlane.d.ts +3 -0
- package/dist/renderers/coordinatePlane.d.ts.map +1 -0
- package/dist/renderers/coordinatePlane.js +142 -0
- package/dist/renderers/forceDiagram.d.ts +3 -0
- package/dist/renderers/forceDiagram.d.ts.map +1 -0
- package/dist/renderers/forceDiagram.js +130 -0
- package/dist/renderers/geometry.d.ts +3 -0
- package/dist/renderers/geometry.d.ts.map +1 -0
- package/dist/renderers/geometry.js +173 -0
- package/dist/renderers/graph.d.ts +3 -0
- package/dist/renderers/graph.d.ts.map +1 -0
- package/dist/renderers/graph.js +137 -0
- package/dist/renderers/numberLine.d.ts +3 -0
- package/dist/renderers/numberLine.d.ts.map +1 -0
- package/dist/renderers/numberLine.js +98 -0
- package/dist/renderers/rayDiagram.d.ts +3 -0
- package/dist/renderers/rayDiagram.d.ts.map +1 -0
- package/dist/renderers/rayDiagram.js +181 -0
- package/dist/renderers/venn.d.ts +3 -0
- package/dist/renderers/venn.d.ts.map +1 -0
- package/dist/renderers/venn.js +60 -0
- package/dist/types.d.ts +225 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/utils/arrowMarker.d.ts +13 -0
- package/dist/utils/arrowMarker.d.ts.map +1 -0
- package/dist/utils/arrowMarker.js +48 -0
- package/dist/utils/coordTransform.d.ts +29 -0
- package/dist/utils/coordTransform.d.ts.map +1 -0
- package/dist/utils/coordTransform.js +38 -0
- package/dist/utils/gridLines.d.ts +23 -0
- package/dist/utils/gridLines.d.ts.map +1 -0
- package/dist/utils/gridLines.js +124 -0
- package/dist/utils/label.d.ts +30 -0
- package/dist/utils/label.d.ts.map +1 -0
- package/dist/utils/label.js +82 -0
- package/dist/utils/path.d.ts +21 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +72 -0
- package/dist/utils/shapes.d.ts +29 -0
- package/dist/utils/shapes.d.ts.map +1 -0
- package/dist/utils/shapes.js +78 -0
- package/dist/utils/svgTag.d.ts +11 -0
- package/dist/utils/svgTag.d.ts.map +1 -0
- package/dist/utils/svgTag.js +29 -0
- package/package.json +70 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform data coordinates to SVG coordinates
|
|
3
|
+
* Maps data space to SVG pixel space
|
|
4
|
+
*/
|
|
5
|
+
export interface TransformOptions {
|
|
6
|
+
xRange: [number, number];
|
|
7
|
+
yRange: [number, number];
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
padding?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create coordinate transformation function
|
|
14
|
+
*/
|
|
15
|
+
export declare function createCoordTransform(options: TransformOptions): {
|
|
16
|
+
/**
|
|
17
|
+
* Transform data x to SVG x
|
|
18
|
+
*/
|
|
19
|
+
x: (dataX: number) => number;
|
|
20
|
+
/**
|
|
21
|
+
* Transform data y to SVG y (inverted, since SVG y goes down)
|
|
22
|
+
*/
|
|
23
|
+
y: (dataY: number) => number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Quick transform for single coordinate
|
|
27
|
+
*/
|
|
28
|
+
export declare function transformCoord(value: number, range: [number, number], size: number, padding?: number, invert?: boolean): number;
|
|
29
|
+
//# sourceMappingURL=coordTransform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordTransform.d.ts","sourceRoot":"","sources":["../../src/utils/coordTransform.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,gBAAgB;IAY1D;;OAEG;eACQ,MAAM;IAIjB;;OAEG;eACQ,MAAM;EAIpB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EACvB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,MAAW,EACpB,MAAM,GAAE,OAAe,GACtB,MAAM,CAQR"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Coordinate transformation utilities
|
|
2
|
+
/**
|
|
3
|
+
* Create coordinate transformation function
|
|
4
|
+
*/
|
|
5
|
+
export function createCoordTransform(options) {
|
|
6
|
+
const { xRange, yRange, width, height, padding = 40 } = options;
|
|
7
|
+
const xMin = xRange[0];
|
|
8
|
+
const xMax = xRange[1];
|
|
9
|
+
const yMin = yRange[0];
|
|
10
|
+
const yMax = yRange[1];
|
|
11
|
+
const usableWidth = width - 2 * padding;
|
|
12
|
+
const usableHeight = height - 2 * padding;
|
|
13
|
+
return {
|
|
14
|
+
/**
|
|
15
|
+
* Transform data x to SVG x
|
|
16
|
+
*/
|
|
17
|
+
x: (dataX) => {
|
|
18
|
+
return padding + ((dataX - xMin) / (xMax - xMin)) * usableWidth;
|
|
19
|
+
},
|
|
20
|
+
/**
|
|
21
|
+
* Transform data y to SVG y (inverted, since SVG y goes down)
|
|
22
|
+
*/
|
|
23
|
+
y: (dataY) => {
|
|
24
|
+
return height - padding - ((dataY - yMin) / (yMax - yMin)) * usableHeight;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Quick transform for single coordinate
|
|
30
|
+
*/
|
|
31
|
+
export function transformCoord(value, range, size, padding = 40, invert = false) {
|
|
32
|
+
const [min, max] = range;
|
|
33
|
+
const usableSize = size - 2 * padding;
|
|
34
|
+
if (invert) {
|
|
35
|
+
return size - padding - ((value - min) / (max - min)) * usableSize;
|
|
36
|
+
}
|
|
37
|
+
return padding + ((value - min) / (max - min)) * usableSize;
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface GridOptions {
|
|
2
|
+
xRange: [number, number];
|
|
3
|
+
yRange: [number, number];
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
xSteps?: number;
|
|
7
|
+
ySteps?: number;
|
|
8
|
+
stroke?: string;
|
|
9
|
+
strokeWidth?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generate grid lines for coordinate systems
|
|
13
|
+
*/
|
|
14
|
+
export declare function gridLines(options: GridOptions): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate axes with labels
|
|
17
|
+
*/
|
|
18
|
+
export declare function axes(options: GridOptions & {
|
|
19
|
+
showLabels?: boolean;
|
|
20
|
+
labelStep?: number;
|
|
21
|
+
}): string;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=gridLines.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gridLines.d.ts","sourceRoot":"","sources":["../../src/utils/gridLines.ts"],"names":[],"mappings":"AAKA,UAAU,WAAW;IACnB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAgDtD;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAkEhG"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Grid line generation utility
|
|
2
|
+
import { svgTag } from './svgTag';
|
|
3
|
+
import { createCoordTransform } from './coordTransform';
|
|
4
|
+
/**
|
|
5
|
+
* Generate grid lines for coordinate systems
|
|
6
|
+
*/
|
|
7
|
+
export function gridLines(options) {
|
|
8
|
+
const { xRange, yRange, width, height, xSteps = 10, ySteps = 10, stroke = '#e0e0e0', strokeWidth = 1, } = options;
|
|
9
|
+
const transform = createCoordTransform({ xRange, yRange, width, height });
|
|
10
|
+
const xDelta = (xRange[1] - xRange[0]) / xSteps;
|
|
11
|
+
const yDelta = (yRange[1] - yRange[0]) / ySteps;
|
|
12
|
+
const lines = [];
|
|
13
|
+
// Vertical lines
|
|
14
|
+
for (let i = 0; i <= xSteps; i++) {
|
|
15
|
+
const x = xRange[0] + i * xDelta;
|
|
16
|
+
const svgX = transform.x(x);
|
|
17
|
+
lines.push(svgTag('line', {
|
|
18
|
+
x1: svgX,
|
|
19
|
+
y1: transform.y(yRange[0]),
|
|
20
|
+
x2: svgX,
|
|
21
|
+
y2: transform.y(yRange[1]),
|
|
22
|
+
stroke,
|
|
23
|
+
'stroke-width': strokeWidth,
|
|
24
|
+
}, ''));
|
|
25
|
+
}
|
|
26
|
+
// Horizontal lines
|
|
27
|
+
for (let i = 0; i <= ySteps; i++) {
|
|
28
|
+
const y = yRange[0] + i * yDelta;
|
|
29
|
+
const svgY = transform.y(y);
|
|
30
|
+
lines.push(svgTag('line', {
|
|
31
|
+
x1: transform.x(xRange[0]),
|
|
32
|
+
y1: svgY,
|
|
33
|
+
x2: transform.x(xRange[1]),
|
|
34
|
+
y2: svgY,
|
|
35
|
+
stroke,
|
|
36
|
+
'stroke-width': strokeWidth,
|
|
37
|
+
}, ''));
|
|
38
|
+
}
|
|
39
|
+
return svgTag('g', { class: 'grid' }, lines.join(''));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate axes with labels
|
|
43
|
+
*/
|
|
44
|
+
export function axes(options) {
|
|
45
|
+
const { xRange, yRange, width, height, showLabels = true, labelStep } = options;
|
|
46
|
+
const transform = createCoordTransform({ xRange, yRange, width, height });
|
|
47
|
+
const xZero = transform.x(0);
|
|
48
|
+
const yZero = transform.y(0);
|
|
49
|
+
const elements = [];
|
|
50
|
+
// X axis (draw at y=0 if it's in range, otherwise at bottom)
|
|
51
|
+
const xAxisY = yRange[0] <= 0 && yRange[1] >= 0 ? yZero : height - 40;
|
|
52
|
+
elements.push(svgTag('line', {
|
|
53
|
+
x1: 40,
|
|
54
|
+
y1: xAxisY,
|
|
55
|
+
x2: width - 40,
|
|
56
|
+
y2: xAxisY,
|
|
57
|
+
stroke: '#000',
|
|
58
|
+
'stroke-width': 2,
|
|
59
|
+
'marker-end': 'url(#arrow-default)',
|
|
60
|
+
}, ''));
|
|
61
|
+
// Y axis (draw at x=0 if it's in range, otherwise at left)
|
|
62
|
+
const yAxisX = xRange[0] <= 0 && xRange[1] >= 0 ? xZero : 40;
|
|
63
|
+
elements.push(svgTag('line', {
|
|
64
|
+
x1: yAxisX,
|
|
65
|
+
y1: height - 40,
|
|
66
|
+
x2: yAxisX,
|
|
67
|
+
y2: 40,
|
|
68
|
+
stroke: '#000',
|
|
69
|
+
'stroke-width': 2,
|
|
70
|
+
'marker-end': 'url(#arrow-default)',
|
|
71
|
+
}, ''));
|
|
72
|
+
// Add axis labels
|
|
73
|
+
if (showLabels) {
|
|
74
|
+
const xLabelStep = labelStep || calculateNiceStep(xRange[0], xRange[1]);
|
|
75
|
+
const yLabelStep = labelStep || calculateNiceStep(yRange[0], yRange[1]);
|
|
76
|
+
// X-axis labels
|
|
77
|
+
for (let x = Math.ceil(xRange[0] / xLabelStep) * xLabelStep; x <= xRange[1]; x += xLabelStep) {
|
|
78
|
+
if (Math.abs(x) < 0.001)
|
|
79
|
+
continue; // Skip origin
|
|
80
|
+
const svgX = transform.x(x);
|
|
81
|
+
elements.push(svgTag('text', {
|
|
82
|
+
x: svgX,
|
|
83
|
+
y: xAxisY + 15,
|
|
84
|
+
'text-anchor': 'middle',
|
|
85
|
+
'font-size': 12,
|
|
86
|
+
fill: '#333',
|
|
87
|
+
}, String(x)));
|
|
88
|
+
}
|
|
89
|
+
// Y-axis labels
|
|
90
|
+
for (let y = Math.ceil(yRange[0] / yLabelStep) * yLabelStep; y <= yRange[1]; y += yLabelStep) {
|
|
91
|
+
if (Math.abs(y) < 0.001)
|
|
92
|
+
continue; // Skip origin
|
|
93
|
+
const svgY = transform.y(y);
|
|
94
|
+
elements.push(svgTag('text', {
|
|
95
|
+
x: yAxisX - 10,
|
|
96
|
+
y: svgY + 4,
|
|
97
|
+
'text-anchor': 'end',
|
|
98
|
+
'font-size': 12,
|
|
99
|
+
fill: '#333',
|
|
100
|
+
}, String(y)));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return svgTag('g', { class: 'axes' }, elements.join(''));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Calculate a nice step size for axis labels
|
|
107
|
+
*/
|
|
108
|
+
function calculateNiceStep(min, max) {
|
|
109
|
+
const range = max - min;
|
|
110
|
+
const roughStep = range / 5;
|
|
111
|
+
// Nice steps: 1, 2, 5, 10, 0.5, 0.2, 0.1, etc.
|
|
112
|
+
const magnitude = Math.pow(10, Math.floor(Math.log10(roughStep)));
|
|
113
|
+
const normalized = roughStep / magnitude;
|
|
114
|
+
let niceNormalized;
|
|
115
|
+
if (normalized < 1.5)
|
|
116
|
+
niceNormalized = 1;
|
|
117
|
+
else if (normalized < 3)
|
|
118
|
+
niceNormalized = 2;
|
|
119
|
+
else if (normalized < 7)
|
|
120
|
+
niceNormalized = 5;
|
|
121
|
+
else
|
|
122
|
+
niceNormalized = 10;
|
|
123
|
+
return niceNormalized * magnitude;
|
|
124
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
interface LabelOptions {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
text: string;
|
|
5
|
+
align?: 'left' | 'center' | 'right';
|
|
6
|
+
baseline?: 'top' | 'middle' | 'bottom' | 'auto';
|
|
7
|
+
color?: string;
|
|
8
|
+
fontSize?: number;
|
|
9
|
+
fontWeight?: 'normal' | 'bold';
|
|
10
|
+
rotation?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate positioned text element for labels
|
|
14
|
+
*/
|
|
15
|
+
export declare function label(options: LabelOptions): string;
|
|
16
|
+
/**
|
|
17
|
+
* Create a text label with background for better visibility
|
|
18
|
+
*/
|
|
19
|
+
export declare function labeledBox(options: LabelOptions & {
|
|
20
|
+
backgroundColor?: string;
|
|
21
|
+
padding?: number;
|
|
22
|
+
}): string;
|
|
23
|
+
/**
|
|
24
|
+
* Generate multi-line label
|
|
25
|
+
*/
|
|
26
|
+
export declare function multilineLabel(options: Omit<LabelOptions, 'text'> & {
|
|
27
|
+
lines: string[];
|
|
28
|
+
}): string;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=label.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../src/utils/label.ts"],"names":[],"mappings":"AAIA,UAAU,YAAY;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CA4BnD;AAYD;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG;IAAE,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAoDzG;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,MAAM,CAOhG"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Label positioning utility
|
|
2
|
+
import { svgTag } from './svgTag';
|
|
3
|
+
/**
|
|
4
|
+
* Generate positioned text element for labels
|
|
5
|
+
*/
|
|
6
|
+
export function label(options) {
|
|
7
|
+
const { x, y, text, align = 'center', baseline = 'middle', color = '#000', fontSize = 14, fontWeight = 'normal', rotation = 0, } = options;
|
|
8
|
+
const attrs = {
|
|
9
|
+
x: String(x),
|
|
10
|
+
y: String(y),
|
|
11
|
+
'text-anchor': align,
|
|
12
|
+
'dominant-baseline': baseline,
|
|
13
|
+
fill: color,
|
|
14
|
+
'font-size': String(fontSize),
|
|
15
|
+
'font-weight': fontWeight,
|
|
16
|
+
transform: rotation ? `rotate(${rotation} ${x} ${y})` : undefined,
|
|
17
|
+
};
|
|
18
|
+
return `<text ${Object.entries(attrs)
|
|
19
|
+
.filter(([_, v]) => v !== undefined)
|
|
20
|
+
.map(([k, v]) => `${k}="${v}"`)
|
|
21
|
+
.join(' ')}>${escapeText(text)}</text>`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Escape text content for SVG
|
|
25
|
+
*/
|
|
26
|
+
function escapeText(text) {
|
|
27
|
+
return text
|
|
28
|
+
.replace(/&/g, '&')
|
|
29
|
+
.replace(/</g, '<')
|
|
30
|
+
.replace(/>/g, '>');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a text label with background for better visibility
|
|
34
|
+
*/
|
|
35
|
+
export function labeledBox(options) {
|
|
36
|
+
const { x, y, text, backgroundColor = 'white', padding = 4, fontSize = 14, align = 'center', ...rest } = options;
|
|
37
|
+
// Estimate text width (rough approximation)
|
|
38
|
+
const textWidth = text.length * fontSize * 0.6;
|
|
39
|
+
const textHeight = fontSize;
|
|
40
|
+
// Calculate box dimensions
|
|
41
|
+
let boxX, boxY, boxWidth, boxHeight;
|
|
42
|
+
switch (align) {
|
|
43
|
+
case 'left':
|
|
44
|
+
boxX = x - padding;
|
|
45
|
+
boxY = y - textHeight / 2 - padding;
|
|
46
|
+
boxWidth = textWidth + padding * 2;
|
|
47
|
+
boxHeight = textHeight + padding * 2;
|
|
48
|
+
break;
|
|
49
|
+
case 'right':
|
|
50
|
+
boxX = x - textWidth - padding;
|
|
51
|
+
boxY = y - textHeight / 2 - padding;
|
|
52
|
+
boxWidth = textWidth + padding * 2;
|
|
53
|
+
boxHeight = textHeight + padding * 2;
|
|
54
|
+
break;
|
|
55
|
+
default: // center
|
|
56
|
+
boxX = x - textWidth / 2 - padding;
|
|
57
|
+
boxY = y - textHeight / 2 - padding;
|
|
58
|
+
boxWidth = textWidth + padding * 2;
|
|
59
|
+
boxHeight = textHeight + padding * 2;
|
|
60
|
+
}
|
|
61
|
+
const boxSvg = svgTag('rect', {
|
|
62
|
+
x: boxX,
|
|
63
|
+
y: boxY,
|
|
64
|
+
width: boxWidth,
|
|
65
|
+
height: boxHeight,
|
|
66
|
+
fill: backgroundColor,
|
|
67
|
+
stroke: 'none',
|
|
68
|
+
rx: 2,
|
|
69
|
+
}, '');
|
|
70
|
+
const textSvg = label({ x, y, text, fontSize, align, ...rest });
|
|
71
|
+
return boxSvg + textSvg;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generate multi-line label
|
|
75
|
+
*/
|
|
76
|
+
export function multilineLabel(options) {
|
|
77
|
+
const { x, y, lines, fontSize = 14, ...rest } = options;
|
|
78
|
+
const lineHeight = fontSize * 1.2;
|
|
79
|
+
return lines
|
|
80
|
+
.map((line, i) => label({ x, y: y + i * lineHeight, text: line, fontSize, ...rest }))
|
|
81
|
+
.join('');
|
|
82
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate path data string from array of points
|
|
3
|
+
*/
|
|
4
|
+
export declare function pathFromPoints(points: [number, number][], closed?: boolean): string;
|
|
5
|
+
/**
|
|
6
|
+
* Generate path data for a circle
|
|
7
|
+
*/
|
|
8
|
+
export declare function circlePath(cx: number, cy: number, r: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Generate path data for an arc
|
|
11
|
+
*/
|
|
12
|
+
export declare function arcPath(cx: number, cy: number, r: number, startAngle: number, endAngle: number): string;
|
|
13
|
+
/**
|
|
14
|
+
* Generate path data for an arrow line
|
|
15
|
+
*/
|
|
16
|
+
export declare function arrowLine(x1: number, y1: number, x2: number, y2: number, arrowSize?: number): string;
|
|
17
|
+
/**
|
|
18
|
+
* Generate path data for dashed line
|
|
19
|
+
*/
|
|
20
|
+
export declare function dashedLine(x1: number, y1: number, x2: number, y2: number, dashLength?: number, gapLength?: number): string;
|
|
21
|
+
//# sourceMappingURL=path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAE,OAAe,GAAG,MAAM,CAgB1F;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAIpE;AAED;;GAEG;AACH,wBAAgB,OAAO,CACrB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,MAAM,EACT,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,MAAM,CAYR;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,SAAS,GAAE,MAAW,GACrB,MAAM,CAUR;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,UAAU,GAAE,MAAW,EACvB,SAAS,GAAE,MAAU,GACpB,MAAM,CAuBR"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// SVG path generation utilities
|
|
2
|
+
/**
|
|
3
|
+
* Generate path data string from array of points
|
|
4
|
+
*/
|
|
5
|
+
export function pathFromPoints(points, closed = false) {
|
|
6
|
+
if (points.length === 0)
|
|
7
|
+
return '';
|
|
8
|
+
const [startX, startY] = points[0];
|
|
9
|
+
let d = `M ${startX} ${startY}`;
|
|
10
|
+
for (let i = 1; i < points.length; i++) {
|
|
11
|
+
const [x, y] = points[i];
|
|
12
|
+
d += ` L ${x} ${y}`;
|
|
13
|
+
}
|
|
14
|
+
if (closed) {
|
|
15
|
+
d += ' Z';
|
|
16
|
+
}
|
|
17
|
+
return d;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generate path data for a circle
|
|
21
|
+
*/
|
|
22
|
+
export function circlePath(cx, cy, r) {
|
|
23
|
+
return `M ${cx - r} ${cy}
|
|
24
|
+
a ${r} ${r} 0 1 0 ${r * 2} 0
|
|
25
|
+
a ${r} ${r} 0 1 0 -${r * 2} 0`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generate path data for an arc
|
|
29
|
+
*/
|
|
30
|
+
export function arcPath(cx, cy, r, startAngle, endAngle) {
|
|
31
|
+
const startRad = (startAngle * Math.PI) / 180;
|
|
32
|
+
const endRad = (endAngle * Math.PI) / 180;
|
|
33
|
+
const x1 = cx + r * Math.cos(startRad);
|
|
34
|
+
const y1 = cy - r * Math.sin(startRad);
|
|
35
|
+
const x2 = cx + r * Math.cos(endRad);
|
|
36
|
+
const y2 = cy - r * Math.sin(endRad);
|
|
37
|
+
const largeArc = endAngle - startAngle > 180 ? 1 : 0;
|
|
38
|
+
return `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y2} Z`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generate path data for an arrow line
|
|
42
|
+
*/
|
|
43
|
+
export function arrowLine(x1, y1, x2, y2, arrowSize = 10) {
|
|
44
|
+
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
45
|
+
// Arrow head points
|
|
46
|
+
const ax1 = x2 - arrowSize * Math.cos(angle - Math.PI / 6);
|
|
47
|
+
const ay1 = y2 - arrowSize * Math.sin(angle - Math.PI / 6);
|
|
48
|
+
const ax2 = x2 - arrowSize * Math.cos(angle + Math.PI / 6);
|
|
49
|
+
const ay2 = y2 - arrowSize * Math.sin(angle + Math.PI / 6);
|
|
50
|
+
return `M ${x1} ${y1} L ${x2} ${y2} L ${ax1} ${ay1} M ${x2} ${y2} L ${ax2} ${ay2}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate path data for dashed line
|
|
54
|
+
*/
|
|
55
|
+
export function dashedLine(x1, y1, x2, y2, dashLength = 10, gapLength = 5) {
|
|
56
|
+
const dx = x2 - x1;
|
|
57
|
+
const dy = y2 - y1;
|
|
58
|
+
const length = Math.sqrt(dx * dx + dy * dy);
|
|
59
|
+
const angle = Math.atan2(dy, dx);
|
|
60
|
+
let d = '';
|
|
61
|
+
let currentDist = 0;
|
|
62
|
+
while (currentDist < length) {
|
|
63
|
+
const startX = x1 + currentDist * Math.cos(angle);
|
|
64
|
+
const startY = y1 + currentDist * Math.sin(angle);
|
|
65
|
+
const endDist = Math.min(currentDist + dashLength, length);
|
|
66
|
+
const endX = x1 + endDist * Math.cos(angle);
|
|
67
|
+
const endY = y1 + endDist * Math.sin(angle);
|
|
68
|
+
d += `M ${startX} ${startY} L ${endX} ${endY} `;
|
|
69
|
+
currentDist = endDist + gapLength;
|
|
70
|
+
}
|
|
71
|
+
return d.trim();
|
|
72
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Draw a circle
|
|
3
|
+
*/
|
|
4
|
+
export declare function circle(cx: number, cy: number, r: number, attrs?: Record<string, string | number>): string;
|
|
5
|
+
/**
|
|
6
|
+
* Draw a rectangle
|
|
7
|
+
*/
|
|
8
|
+
export declare function rect(x: number, y: number, width: number, height: number, attrs?: Record<string, string | number>): string;
|
|
9
|
+
/**
|
|
10
|
+
* Draw a line
|
|
11
|
+
*/
|
|
12
|
+
export declare function line(x1: number, y1: number, x2: number, y2: number, attrs?: Record<string, string | number>): string;
|
|
13
|
+
/**
|
|
14
|
+
* Draw a polygon from points
|
|
15
|
+
*/
|
|
16
|
+
export declare function polygon(points: [number, number][], attrs?: Record<string, string | number>): string;
|
|
17
|
+
/**
|
|
18
|
+
* Draw a path
|
|
19
|
+
*/
|
|
20
|
+
export declare function path(d: string, attrs?: Record<string, string | number>): string;
|
|
21
|
+
/**
|
|
22
|
+
* Draw text
|
|
23
|
+
*/
|
|
24
|
+
export declare function text(x: number, y: number, content: string, attrs?: Record<string, string | number>): string;
|
|
25
|
+
/**
|
|
26
|
+
* Draw an ellipse
|
|
27
|
+
*/
|
|
28
|
+
export declare function ellipse(cx: number, cy: number, rx: number, ry: number, attrs?: Record<string, string | number>): string;
|
|
29
|
+
//# sourceMappingURL=shapes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shapes.d.ts","sourceRoot":"","sources":["../../src/utils/shapes.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,MAAM,CACpB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,MAAM,EACT,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,IAAI,CAClB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAQR;AAED;;GAEG;AACH,wBAAgB,IAAI,CAClB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAQR;AAED;;GAEG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAC1B,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,IAAI,CAClB,CAAC,EAAE,MAAM,EACT,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAKR;AAED;;GAEG;AACH,wBAAgB,IAAI,CAClB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,OAAO,CACrB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC1C,MAAM,CAQR"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Common geometric shapes utilities
|
|
2
|
+
import { svgTag } from './svgTag';
|
|
3
|
+
/**
|
|
4
|
+
* Draw a circle
|
|
5
|
+
*/
|
|
6
|
+
export function circle(cx, cy, r, attrs = {}) {
|
|
7
|
+
return svgTag('circle', {
|
|
8
|
+
cx,
|
|
9
|
+
cy,
|
|
10
|
+
r,
|
|
11
|
+
...attrs,
|
|
12
|
+
}, '');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Draw a rectangle
|
|
16
|
+
*/
|
|
17
|
+
export function rect(x, y, width, height, attrs = {}) {
|
|
18
|
+
return svgTag('rect', {
|
|
19
|
+
x,
|
|
20
|
+
y,
|
|
21
|
+
width,
|
|
22
|
+
height,
|
|
23
|
+
...attrs,
|
|
24
|
+
}, '');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Draw a line
|
|
28
|
+
*/
|
|
29
|
+
export function line(x1, y1, x2, y2, attrs = {}) {
|
|
30
|
+
return svgTag('line', {
|
|
31
|
+
x1,
|
|
32
|
+
y1,
|
|
33
|
+
x2,
|
|
34
|
+
y2,
|
|
35
|
+
...attrs,
|
|
36
|
+
}, '');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Draw a polygon from points
|
|
40
|
+
*/
|
|
41
|
+
export function polygon(points, attrs = {}) {
|
|
42
|
+
const pointsStr = points.map(([x, y]) => `${x},${y}`).join(' ');
|
|
43
|
+
return svgTag('polygon', {
|
|
44
|
+
points: pointsStr,
|
|
45
|
+
...attrs,
|
|
46
|
+
}, '');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Draw a path
|
|
50
|
+
*/
|
|
51
|
+
export function path(d, attrs = {}) {
|
|
52
|
+
return svgTag('path', {
|
|
53
|
+
d,
|
|
54
|
+
...attrs,
|
|
55
|
+
}, '');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Draw text
|
|
59
|
+
*/
|
|
60
|
+
export function text(x, y, content, attrs = {}) {
|
|
61
|
+
return svgTag('text', {
|
|
62
|
+
x,
|
|
63
|
+
y,
|
|
64
|
+
...attrs,
|
|
65
|
+
}, content);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Draw an ellipse
|
|
69
|
+
*/
|
|
70
|
+
export function ellipse(cx, cy, rx, ry, attrs = {}) {
|
|
71
|
+
return svgTag('ellipse', {
|
|
72
|
+
cx,
|
|
73
|
+
cy,
|
|
74
|
+
rx,
|
|
75
|
+
ry,
|
|
76
|
+
...attrs,
|
|
77
|
+
}, '');
|
|
78
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type SVGAttributes = Record<string, string | number | boolean | undefined>;
|
|
2
|
+
/**
|
|
3
|
+
* Type-safe SVG element generator
|
|
4
|
+
* @param tag - SVG element name (e.g., 'rect', 'circle', 'path')
|
|
5
|
+
* @param attrs - Object containing element attributes
|
|
6
|
+
* @param content - Inner content (string or nested elements)
|
|
7
|
+
* @returns SVG element string
|
|
8
|
+
*/
|
|
9
|
+
export declare function svgTag(tag: string, attrs: SVGAttributes, content?: string): string;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=svgTag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svgTag.d.ts","sourceRoot":"","sources":["../../src/utils/svgTag.ts"],"names":[],"mappings":"AAEA,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;AAE3E;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,MAAW,GAAG,MAAM,CAUtF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SVG element builder utility
|
|
2
|
+
/**
|
|
3
|
+
* Type-safe SVG element generator
|
|
4
|
+
* @param tag - SVG element name (e.g., 'rect', 'circle', 'path')
|
|
5
|
+
* @param attrs - Object containing element attributes
|
|
6
|
+
* @param content - Inner content (string or nested elements)
|
|
7
|
+
* @returns SVG element string
|
|
8
|
+
*/
|
|
9
|
+
export function svgTag(tag, attrs, content = '') {
|
|
10
|
+
const attributes = Object.entries(attrs)
|
|
11
|
+
.filter(([_, value]) => value !== undefined)
|
|
12
|
+
.map(([key, value]) => `${key}="${escapeAttribute(String(value))}"`)
|
|
13
|
+
.join(' ');
|
|
14
|
+
if (content) {
|
|
15
|
+
return `<${tag} ${attributes}>${content}</${tag}>`;
|
|
16
|
+
}
|
|
17
|
+
return `<${tag} ${attributes} />`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Escape HTML attribute values
|
|
21
|
+
*/
|
|
22
|
+
function escapeAttribute(value) {
|
|
23
|
+
return value
|
|
24
|
+
.replace(/&/g, '&')
|
|
25
|
+
.replace(/</g, '<')
|
|
26
|
+
.replace(/>/g, '>')
|
|
27
|
+
.replace(/"/g, '"')
|
|
28
|
+
.replace(/'/g, ''');
|
|
29
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xrn07/figure-renderer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-dependency pure TypeScript figure renderer - pure SVG string generation",
|
|
5
|
+
"main": "./dist/render.js",
|
|
6
|
+
"module": "./dist/render.js",
|
|
7
|
+
"types": "./dist/render.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/render.d.ts",
|
|
11
|
+
"import": "./dist/render.js"
|
|
12
|
+
},
|
|
13
|
+
"./react": {
|
|
14
|
+
"types": "./dist/react.d.ts",
|
|
15
|
+
"import": "./dist/react.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"test": "vitest",
|
|
26
|
+
"test:coverage": "vitest --coverage",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"prepublishOnly": "npm run build && npm test",
|
|
29
|
+
"version": "npm run build",
|
|
30
|
+
"postversion": "git push --follow-tags"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"svg",
|
|
34
|
+
"renderer",
|
|
35
|
+
"figure",
|
|
36
|
+
"diagram",
|
|
37
|
+
"visualization",
|
|
38
|
+
"typescript",
|
|
39
|
+
"education",
|
|
40
|
+
"circuit",
|
|
41
|
+
"graph",
|
|
42
|
+
"geometry",
|
|
43
|
+
"zero-dependency"
|
|
44
|
+
],
|
|
45
|
+
"author": "Haki",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/haki/figure-renderer.git"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/haki/figure-renderer#readme",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/haki/figure-renderer/issues"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"react": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^20.0.0",
|
|
65
|
+
"@types/react": "^18.3.31",
|
|
66
|
+
"typescript": "^5.0.0",
|
|
67
|
+
"vitest": "^1.0.0",
|
|
68
|
+
"zod": "^3.0.0"
|
|
69
|
+
}
|
|
70
|
+
}
|