@plainviz/render-svg 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/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/render.d.ts +17 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +87 -0
- package/package.json +35 -0
- package/src/index.ts +2 -0
- package/src/render.ts +119 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { render, renderBarChart } from './render';
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlainViz SVG Renderer
|
|
3
|
+
* Renders IR to SVG string (pure, no DOM dependency)
|
|
4
|
+
*/
|
|
5
|
+
import type { PlainVizIR } from '@plainviz/core';
|
|
6
|
+
export interface RenderOptions {
|
|
7
|
+
width?: number;
|
|
8
|
+
height?: number;
|
|
9
|
+
padding?: number;
|
|
10
|
+
colors?: string[];
|
|
11
|
+
backgroundColor?: string;
|
|
12
|
+
textColor?: string;
|
|
13
|
+
gridColor?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function renderBarChart(ir: PlainVizIR, opts?: RenderOptions): string;
|
|
16
|
+
export declare function render(ir: PlainVizIR, opts?: RenderOptions): string;
|
|
17
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA6BD,wBAAgB,cAAc,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,GAAE,aAAkB,GAAG,MAAM,CA4D/E;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,GAAE,aAAkB,GAAG,MAAM,CAYvE"}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlainViz SVG Renderer
|
|
3
|
+
* Renders IR to SVG string (pure, no DOM dependency)
|
|
4
|
+
*/
|
|
5
|
+
const DEFAULT_COLORS = [
|
|
6
|
+
'#89b4fa', // Blue
|
|
7
|
+
'#a6e3a1', // Green
|
|
8
|
+
'#f9e2af', // Yellow
|
|
9
|
+
'#f38ba8', // Red
|
|
10
|
+
'#cba6f7', // Mauve
|
|
11
|
+
'#fab387', // Peach
|
|
12
|
+
];
|
|
13
|
+
const DEFAULT_OPTIONS = {
|
|
14
|
+
width: 500,
|
|
15
|
+
height: 300,
|
|
16
|
+
padding: 60,
|
|
17
|
+
colors: DEFAULT_COLORS,
|
|
18
|
+
backgroundColor: '#1e1e2e',
|
|
19
|
+
textColor: '#cdd6f4',
|
|
20
|
+
gridColor: '#313244',
|
|
21
|
+
};
|
|
22
|
+
function escapeXml(str) {
|
|
23
|
+
return str
|
|
24
|
+
.replace(/&/g, '&')
|
|
25
|
+
.replace(/</g, '<')
|
|
26
|
+
.replace(/>/g, '>')
|
|
27
|
+
.replace(/"/g, '"');
|
|
28
|
+
}
|
|
29
|
+
export function renderBarChart(ir, opts = {}) {
|
|
30
|
+
const options = { ...DEFAULT_OPTIONS, ...opts };
|
|
31
|
+
const { width, height, padding, colors, backgroundColor, textColor, gridColor } = options;
|
|
32
|
+
const chartWidth = width - padding * 2;
|
|
33
|
+
const chartHeight = height - padding * 2 - 30; // Reserve space for title
|
|
34
|
+
const maxValue = Math.max(...ir.values);
|
|
35
|
+
const barCount = ir.values.length;
|
|
36
|
+
const barWidth = (chartWidth / barCount) * 0.6;
|
|
37
|
+
const barGap = (chartWidth / barCount) * 0.4;
|
|
38
|
+
const lines = [];
|
|
39
|
+
// SVG header
|
|
40
|
+
lines.push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" style="background-color: ${backgroundColor};">`);
|
|
41
|
+
// Title
|
|
42
|
+
if (ir.title) {
|
|
43
|
+
lines.push(` <text x="${width / 2}" y="25" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" font-weight="bold" fill="${textColor}">${escapeXml(ir.title)}</text>`);
|
|
44
|
+
}
|
|
45
|
+
const chartTop = padding;
|
|
46
|
+
const chartBottom = height - padding;
|
|
47
|
+
// Y-axis
|
|
48
|
+
lines.push(` <line x1="${padding}" y1="${chartTop}" x2="${padding}" y2="${chartBottom}" stroke="${gridColor}" stroke-width="1"/>`);
|
|
49
|
+
// X-axis
|
|
50
|
+
lines.push(` <line x1="${padding}" y1="${chartBottom}" x2="${width - padding}" y2="${chartBottom}" stroke="${gridColor}" stroke-width="1"/>`);
|
|
51
|
+
// Grid lines (4 horizontal)
|
|
52
|
+
for (let i = 1; i <= 4; i++) {
|
|
53
|
+
const y = chartBottom - (chartHeight / 4) * i;
|
|
54
|
+
lines.push(` <line x1="${padding}" y1="${y}" x2="${width - padding}" y2="${y}" stroke="${gridColor}" stroke-width="1" stroke-dasharray="3,3"/>`);
|
|
55
|
+
// Y-axis labels
|
|
56
|
+
const label = Math.round((maxValue / 4) * i);
|
|
57
|
+
lines.push(` <text x="${padding - 8}" y="${y + 4}" text-anchor="end" font-family="system-ui, sans-serif" font-size="11" fill="#6c7086">${label}</text>`);
|
|
58
|
+
}
|
|
59
|
+
// Bars
|
|
60
|
+
ir.values.forEach((value, i) => {
|
|
61
|
+
const barHeight = (value / maxValue) * chartHeight;
|
|
62
|
+
const x = padding + (chartWidth / barCount) * i + barGap / 2;
|
|
63
|
+
const y = chartBottom - barHeight;
|
|
64
|
+
const color = colors[i % colors.length];
|
|
65
|
+
// Bar
|
|
66
|
+
lines.push(` <rect x="${x}" y="${y}" width="${barWidth}" height="${barHeight}" rx="4" fill="${color}"/>`);
|
|
67
|
+
// Value label
|
|
68
|
+
lines.push(` <text x="${x + barWidth / 2}" y="${y - 8}" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="${textColor}">${value}</text>`);
|
|
69
|
+
// X-axis label
|
|
70
|
+
lines.push(` <text x="${x + barWidth / 2}" y="${chartBottom + 18}" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="#6c7086">${escapeXml(ir.labels[i])}</text>`);
|
|
71
|
+
});
|
|
72
|
+
lines.push('</svg>');
|
|
73
|
+
return lines.join('\n');
|
|
74
|
+
}
|
|
75
|
+
export function render(ir, opts = {}) {
|
|
76
|
+
switch (ir.type) {
|
|
77
|
+
case 'bar':
|
|
78
|
+
return renderBarChart(ir, opts);
|
|
79
|
+
case 'line':
|
|
80
|
+
case 'pie':
|
|
81
|
+
case 'area':
|
|
82
|
+
// TODO: implement other chart types
|
|
83
|
+
throw new Error(`Chart type '${ir.type}' not yet implemented`);
|
|
84
|
+
default:
|
|
85
|
+
throw new Error(`Unknown chart type: ${ir.type}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plainviz/render-svg",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "PlainViz SVG renderer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"dev": "tsc --watch"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@plainviz/core": "workspace:*"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"typescript": "^5.7.2"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"plainviz",
|
|
30
|
+
"svg",
|
|
31
|
+
"render",
|
|
32
|
+
"chart"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|
package/src/index.ts
ADDED
package/src/render.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlainViz SVG Renderer
|
|
3
|
+
* Renders IR to SVG string (pure, no DOM dependency)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PlainVizIR } from '@plainviz/core';
|
|
7
|
+
|
|
8
|
+
export interface RenderOptions {
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
padding?: number;
|
|
12
|
+
colors?: string[];
|
|
13
|
+
backgroundColor?: string;
|
|
14
|
+
textColor?: string;
|
|
15
|
+
gridColor?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DEFAULT_COLORS = [
|
|
19
|
+
'#89b4fa', // Blue
|
|
20
|
+
'#a6e3a1', // Green
|
|
21
|
+
'#f9e2af', // Yellow
|
|
22
|
+
'#f38ba8', // Red
|
|
23
|
+
'#cba6f7', // Mauve
|
|
24
|
+
'#fab387', // Peach
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const DEFAULT_OPTIONS: Required<RenderOptions> = {
|
|
28
|
+
width: 500,
|
|
29
|
+
height: 300,
|
|
30
|
+
padding: 60,
|
|
31
|
+
colors: DEFAULT_COLORS,
|
|
32
|
+
backgroundColor: '#1e1e2e',
|
|
33
|
+
textColor: '#cdd6f4',
|
|
34
|
+
gridColor: '#313244',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function escapeXml(str: string): string {
|
|
38
|
+
return str
|
|
39
|
+
.replace(/&/g, '&')
|
|
40
|
+
.replace(/</g, '<')
|
|
41
|
+
.replace(/>/g, '>')
|
|
42
|
+
.replace(/"/g, '"');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function renderBarChart(ir: PlainVizIR, opts: RenderOptions = {}): string {
|
|
46
|
+
const options = { ...DEFAULT_OPTIONS, ...opts };
|
|
47
|
+
const { width, height, padding, colors, backgroundColor, textColor, gridColor } = options;
|
|
48
|
+
|
|
49
|
+
const chartWidth = width - padding * 2;
|
|
50
|
+
const chartHeight = height - padding * 2 - 30; // Reserve space for title
|
|
51
|
+
const maxValue = Math.max(...ir.values);
|
|
52
|
+
const barCount = ir.values.length;
|
|
53
|
+
const barWidth = (chartWidth / barCount) * 0.6;
|
|
54
|
+
const barGap = (chartWidth / barCount) * 0.4;
|
|
55
|
+
|
|
56
|
+
const lines: string[] = [];
|
|
57
|
+
|
|
58
|
+
// SVG header
|
|
59
|
+
lines.push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" style="background-color: ${backgroundColor};">`);
|
|
60
|
+
|
|
61
|
+
// Title
|
|
62
|
+
if (ir.title) {
|
|
63
|
+
lines.push(` <text x="${width / 2}" y="25" text-anchor="middle" font-family="system-ui, sans-serif" font-size="16" font-weight="bold" fill="${textColor}">${escapeXml(ir.title)}</text>`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const chartTop = padding;
|
|
67
|
+
const chartBottom = height - padding;
|
|
68
|
+
|
|
69
|
+
// Y-axis
|
|
70
|
+
lines.push(` <line x1="${padding}" y1="${chartTop}" x2="${padding}" y2="${chartBottom}" stroke="${gridColor}" stroke-width="1"/>`);
|
|
71
|
+
|
|
72
|
+
// X-axis
|
|
73
|
+
lines.push(` <line x1="${padding}" y1="${chartBottom}" x2="${width - padding}" y2="${chartBottom}" stroke="${gridColor}" stroke-width="1"/>`);
|
|
74
|
+
|
|
75
|
+
// Grid lines (4 horizontal)
|
|
76
|
+
for (let i = 1; i <= 4; i++) {
|
|
77
|
+
const y = chartBottom - (chartHeight / 4) * i;
|
|
78
|
+
lines.push(` <line x1="${padding}" y1="${y}" x2="${width - padding}" y2="${y}" stroke="${gridColor}" stroke-width="1" stroke-dasharray="3,3"/>`);
|
|
79
|
+
|
|
80
|
+
// Y-axis labels
|
|
81
|
+
const label = Math.round((maxValue / 4) * i);
|
|
82
|
+
lines.push(` <text x="${padding - 8}" y="${y + 4}" text-anchor="end" font-family="system-ui, sans-serif" font-size="11" fill="#6c7086">${label}</text>`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Bars
|
|
86
|
+
ir.values.forEach((value, i) => {
|
|
87
|
+
const barHeight = (value / maxValue) * chartHeight;
|
|
88
|
+
const x = padding + (chartWidth / barCount) * i + barGap / 2;
|
|
89
|
+
const y = chartBottom - barHeight;
|
|
90
|
+
const color = colors[i % colors.length];
|
|
91
|
+
|
|
92
|
+
// Bar
|
|
93
|
+
lines.push(` <rect x="${x}" y="${y}" width="${barWidth}" height="${barHeight}" rx="4" fill="${color}"/>`);
|
|
94
|
+
|
|
95
|
+
// Value label
|
|
96
|
+
lines.push(` <text x="${x + barWidth / 2}" y="${y - 8}" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="${textColor}">${value}</text>`);
|
|
97
|
+
|
|
98
|
+
// X-axis label
|
|
99
|
+
lines.push(` <text x="${x + barWidth / 2}" y="${chartBottom + 18}" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" fill="#6c7086">${escapeXml(ir.labels[i])}</text>`);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
lines.push('</svg>');
|
|
103
|
+
|
|
104
|
+
return lines.join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function render(ir: PlainVizIR, opts: RenderOptions = {}): string {
|
|
108
|
+
switch (ir.type) {
|
|
109
|
+
case 'bar':
|
|
110
|
+
return renderBarChart(ir, opts);
|
|
111
|
+
case 'line':
|
|
112
|
+
case 'pie':
|
|
113
|
+
case 'area':
|
|
114
|
+
// TODO: implement other chart types
|
|
115
|
+
throw new Error(`Chart type '${ir.type}' not yet implemented`);
|
|
116
|
+
default:
|
|
117
|
+
throw new Error(`Unknown chart type: ${ir.type}`);
|
|
118
|
+
}
|
|
119
|
+
}
|