@teamflojo/floimg-d3 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 +449 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Brett Cooke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# imgflo-d3
|
|
2
|
+
|
|
3
|
+
D3 data visualization generator for imgflo using server-side rendering.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install imgflo imgflo-d3
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import createClient from 'imgflo';
|
|
15
|
+
import d3viz from 'imgflo-d3';
|
|
16
|
+
|
|
17
|
+
const imgflo = createClient();
|
|
18
|
+
imgflo.registerGenerator(d3viz());
|
|
19
|
+
|
|
20
|
+
// Create a bar chart
|
|
21
|
+
const chart = await imgflo.generate({
|
|
22
|
+
generator: 'd3',
|
|
23
|
+
params: {
|
|
24
|
+
width: 600,
|
|
25
|
+
height: 400,
|
|
26
|
+
render: (svg, d3, data) => {
|
|
27
|
+
// Use D3 directly - full API available
|
|
28
|
+
svg.selectAll('rect')
|
|
29
|
+
.data(data)
|
|
30
|
+
.join('rect')
|
|
31
|
+
.attr('x', (d, i) => i * 60)
|
|
32
|
+
.attr('y', d => 400 - d.value * 3)
|
|
33
|
+
.attr('width', 50)
|
|
34
|
+
.attr('height', d => d.value * 3)
|
|
35
|
+
.attr('fill', 'steelblue');
|
|
36
|
+
},
|
|
37
|
+
data: [
|
|
38
|
+
{ value: 30 },
|
|
39
|
+
{ value: 80 },
|
|
40
|
+
{ value: 45 },
|
|
41
|
+
{ value: 60 },
|
|
42
|
+
{ value: 20 }
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Upload to S3
|
|
48
|
+
await imgflo.save(chart, './output/bar.svg');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Examples
|
|
52
|
+
|
|
53
|
+
### Bar Chart
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const barChart = await imgflo.generate({
|
|
57
|
+
generator: 'd3',
|
|
58
|
+
params: {
|
|
59
|
+
width: 800,
|
|
60
|
+
height: 400,
|
|
61
|
+
render: (svg, d3, data) => {
|
|
62
|
+
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
|
|
63
|
+
const width = 800 - margin.left - margin.right;
|
|
64
|
+
const height = 400 - margin.top - margin.bottom;
|
|
65
|
+
|
|
66
|
+
const g = svg.append('g')
|
|
67
|
+
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
68
|
+
|
|
69
|
+
const x = d3.scaleBand()
|
|
70
|
+
.domain(data.map(d => d.name))
|
|
71
|
+
.range([0, width])
|
|
72
|
+
.padding(0.1);
|
|
73
|
+
|
|
74
|
+
const y = d3.scaleLinear()
|
|
75
|
+
.domain([0, d3.max(data, d => d.value)])
|
|
76
|
+
.range([height, 0]);
|
|
77
|
+
|
|
78
|
+
g.selectAll('rect')
|
|
79
|
+
.data(data)
|
|
80
|
+
.join('rect')
|
|
81
|
+
.attr('x', d => x(d.name))
|
|
82
|
+
.attr('y', d => y(d.value))
|
|
83
|
+
.attr('width', x.bandwidth())
|
|
84
|
+
.attr('height', d => height - y(d.value))
|
|
85
|
+
.attr('fill', 'steelblue');
|
|
86
|
+
|
|
87
|
+
g.append('g')
|
|
88
|
+
.attr('transform', `translate(0,${height})`)
|
|
89
|
+
.call(d3.axisBottom(x));
|
|
90
|
+
|
|
91
|
+
g.append('g')
|
|
92
|
+
.call(d3.axisLeft(y));
|
|
93
|
+
},
|
|
94
|
+
data: [
|
|
95
|
+
{ name: 'Q1', value: 120 },
|
|
96
|
+
{ name: 'Q2', value: 200 },
|
|
97
|
+
{ name: 'Q3', value: 150 },
|
|
98
|
+
{ name: 'Q4', value: 170 }
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Line Chart
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const lineChart = await imgflo.generate({
|
|
108
|
+
generator: 'd3',
|
|
109
|
+
params: {
|
|
110
|
+
width: 800,
|
|
111
|
+
height: 400,
|
|
112
|
+
render: (svg, d3, data) => {
|
|
113
|
+
const margin = { top: 20, right: 20, bottom: 30, left: 50 };
|
|
114
|
+
const width = 800 - margin.left - margin.right;
|
|
115
|
+
const height = 400 - margin.top - margin.bottom;
|
|
116
|
+
|
|
117
|
+
const g = svg.append('g')
|
|
118
|
+
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
119
|
+
|
|
120
|
+
const x = d3.scaleLinear()
|
|
121
|
+
.domain([0, data.length - 1])
|
|
122
|
+
.range([0, width]);
|
|
123
|
+
|
|
124
|
+
const y = d3.scaleLinear()
|
|
125
|
+
.domain([0, d3.max(data, d => d.value)])
|
|
126
|
+
.range([height, 0]);
|
|
127
|
+
|
|
128
|
+
const line = d3.line()
|
|
129
|
+
.x((d, i) => x(i))
|
|
130
|
+
.y(d => y(d.value));
|
|
131
|
+
|
|
132
|
+
g.append('path')
|
|
133
|
+
.datum(data)
|
|
134
|
+
.attr('fill', 'none')
|
|
135
|
+
.attr('stroke', 'steelblue')
|
|
136
|
+
.attr('stroke-width', 2)
|
|
137
|
+
.attr('d', line);
|
|
138
|
+
|
|
139
|
+
g.append('g')
|
|
140
|
+
.attr('transform', `translate(0,${height})`)
|
|
141
|
+
.call(d3.axisBottom(x));
|
|
142
|
+
|
|
143
|
+
g.append('g')
|
|
144
|
+
.call(d3.axisLeft(y));
|
|
145
|
+
},
|
|
146
|
+
data: [
|
|
147
|
+
{ value: 30 },
|
|
148
|
+
{ value: 50 },
|
|
149
|
+
{ value: 45 },
|
|
150
|
+
{ value: 80 },
|
|
151
|
+
{ value: 70 },
|
|
152
|
+
{ value: 90 }
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Pie Chart
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const pieChart = await imgflo.generate({
|
|
162
|
+
generator: 'd3',
|
|
163
|
+
params: {
|
|
164
|
+
width: 500,
|
|
165
|
+
height: 500,
|
|
166
|
+
render: (svg, d3, data) => {
|
|
167
|
+
const width = 500;
|
|
168
|
+
const height = 500;
|
|
169
|
+
const radius = Math.min(width, height) / 2;
|
|
170
|
+
|
|
171
|
+
const g = svg.append('g')
|
|
172
|
+
.attr('transform', `translate(${width / 2},${height / 2})`);
|
|
173
|
+
|
|
174
|
+
const color = d3.scaleOrdinal(d3.schemeCategory10);
|
|
175
|
+
|
|
176
|
+
const pie = d3.pie()
|
|
177
|
+
.value(d => d.value);
|
|
178
|
+
|
|
179
|
+
const arc = d3.arc()
|
|
180
|
+
.innerRadius(0)
|
|
181
|
+
.outerRadius(radius);
|
|
182
|
+
|
|
183
|
+
const arcs = g.selectAll('arc')
|
|
184
|
+
.data(pie(data))
|
|
185
|
+
.join('g')
|
|
186
|
+
.attr('class', 'arc');
|
|
187
|
+
|
|
188
|
+
arcs.append('path')
|
|
189
|
+
.attr('d', arc)
|
|
190
|
+
.attr('fill', (d, i) => color(i));
|
|
191
|
+
|
|
192
|
+
arcs.append('text')
|
|
193
|
+
.attr('transform', d => `translate(${arc.centroid(d)})`)
|
|
194
|
+
.attr('text-anchor', 'middle')
|
|
195
|
+
.text(d => d.data.label);
|
|
196
|
+
},
|
|
197
|
+
data: [
|
|
198
|
+
{ label: 'A', value: 30 },
|
|
199
|
+
{ label: 'B', value: 70 },
|
|
200
|
+
{ label: 'C', value: 45 },
|
|
201
|
+
{ label: 'D', value: 60 }
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Scatter Plot
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const scatterPlot = await imgflo.generate({
|
|
211
|
+
generator: 'd3',
|
|
212
|
+
params: {
|
|
213
|
+
width: 600,
|
|
214
|
+
height: 400,
|
|
215
|
+
render: (svg, d3, data) => {
|
|
216
|
+
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
|
|
217
|
+
const width = 600 - margin.left - margin.right;
|
|
218
|
+
const height = 400 - margin.top - margin.bottom;
|
|
219
|
+
|
|
220
|
+
const g = svg.append('g')
|
|
221
|
+
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
222
|
+
|
|
223
|
+
const x = d3.scaleLinear()
|
|
224
|
+
.domain([0, d3.max(data, d => d.x)])
|
|
225
|
+
.range([0, width]);
|
|
226
|
+
|
|
227
|
+
const y = d3.scaleLinear()
|
|
228
|
+
.domain([0, d3.max(data, d => d.y)])
|
|
229
|
+
.range([height, 0]);
|
|
230
|
+
|
|
231
|
+
g.selectAll('circle')
|
|
232
|
+
.data(data)
|
|
233
|
+
.join('circle')
|
|
234
|
+
.attr('cx', d => x(d.x))
|
|
235
|
+
.attr('cy', d => y(d.y))
|
|
236
|
+
.attr('r', 5)
|
|
237
|
+
.attr('fill', 'steelblue')
|
|
238
|
+
.attr('opacity', 0.7);
|
|
239
|
+
|
|
240
|
+
g.append('g')
|
|
241
|
+
.attr('transform', `translate(0,${height})`)
|
|
242
|
+
.call(d3.axisBottom(x));
|
|
243
|
+
|
|
244
|
+
g.append('g')
|
|
245
|
+
.call(d3.axisLeft(y));
|
|
246
|
+
},
|
|
247
|
+
data: [
|
|
248
|
+
{ x: 10, y: 20 },
|
|
249
|
+
{ x: 20, y: 40 },
|
|
250
|
+
{ x: 30, y: 25 },
|
|
251
|
+
{ x: 40, y: 60 },
|
|
252
|
+
{ x: 50, y: 45 }
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Area Chart
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const areaChart = await imgflo.generate({
|
|
262
|
+
generator: 'd3',
|
|
263
|
+
params: {
|
|
264
|
+
width: 800,
|
|
265
|
+
height: 400,
|
|
266
|
+
render: (svg, d3, data) => {
|
|
267
|
+
const margin = { top: 20, right: 20, bottom: 30, left: 50 };
|
|
268
|
+
const width = 800 - margin.left - margin.right;
|
|
269
|
+
const height = 400 - margin.top - margin.bottom;
|
|
270
|
+
|
|
271
|
+
const g = svg.append('g')
|
|
272
|
+
.attr('transform', `translate(${margin.left},${margin.top})`);
|
|
273
|
+
|
|
274
|
+
const x = d3.scaleLinear()
|
|
275
|
+
.domain([0, data.length - 1])
|
|
276
|
+
.range([0, width]);
|
|
277
|
+
|
|
278
|
+
const y = d3.scaleLinear()
|
|
279
|
+
.domain([0, d3.max(data, d => d.value)])
|
|
280
|
+
.range([height, 0]);
|
|
281
|
+
|
|
282
|
+
const area = d3.area()
|
|
283
|
+
.x((d, i) => x(i))
|
|
284
|
+
.y0(height)
|
|
285
|
+
.y1(d => y(d.value));
|
|
286
|
+
|
|
287
|
+
g.append('path')
|
|
288
|
+
.datum(data)
|
|
289
|
+
.attr('fill', 'steelblue')
|
|
290
|
+
.attr('opacity', 0.7)
|
|
291
|
+
.attr('d', area);
|
|
292
|
+
|
|
293
|
+
g.append('g')
|
|
294
|
+
.attr('transform', `translate(0,${height})`)
|
|
295
|
+
.call(d3.axisBottom(x));
|
|
296
|
+
|
|
297
|
+
g.append('g')
|
|
298
|
+
.call(d3.axisLeft(y));
|
|
299
|
+
},
|
|
300
|
+
data: [
|
|
301
|
+
{ value: 30 },
|
|
302
|
+
{ value: 50 },
|
|
303
|
+
{ value: 45 },
|
|
304
|
+
{ value: 80 },
|
|
305
|
+
{ value: 70 },
|
|
306
|
+
{ value: 90 }
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Heatmap
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
const heatmap = await imgflo.generate({
|
|
316
|
+
generator: 'd3',
|
|
317
|
+
params: {
|
|
318
|
+
width: 600,
|
|
319
|
+
height: 400,
|
|
320
|
+
render: (svg, d3, data) => {
|
|
321
|
+
const cellSize = 50;
|
|
322
|
+
const rows = data.length;
|
|
323
|
+
const cols = data[0].length;
|
|
324
|
+
|
|
325
|
+
const color = d3.scaleSequential(d3.interpolateBlues)
|
|
326
|
+
.domain([0, d3.max(data.flat())]);
|
|
327
|
+
|
|
328
|
+
for (let i = 0; i < rows; i++) {
|
|
329
|
+
for (let j = 0; j < cols; j++) {
|
|
330
|
+
svg.append('rect')
|
|
331
|
+
.attr('x', j * cellSize)
|
|
332
|
+
.attr('y', i * cellSize)
|
|
333
|
+
.attr('width', cellSize)
|
|
334
|
+
.attr('height', cellSize)
|
|
335
|
+
.attr('fill', color(data[i][j]))
|
|
336
|
+
.attr('stroke', 'white');
|
|
337
|
+
|
|
338
|
+
svg.append('text')
|
|
339
|
+
.attr('x', j * cellSize + cellSize / 2)
|
|
340
|
+
.attr('y', i * cellSize + cellSize / 2)
|
|
341
|
+
.attr('text-anchor', 'middle')
|
|
342
|
+
.attr('dominant-baseline', 'middle')
|
|
343
|
+
.attr('fill', data[i][j] > 5 ? 'white' : 'black')
|
|
344
|
+
.text(data[i][j]);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
data: [
|
|
349
|
+
[1, 3, 5, 7],
|
|
350
|
+
[2, 4, 6, 8],
|
|
351
|
+
[9, 7, 5, 3],
|
|
352
|
+
[8, 6, 4, 2]
|
|
353
|
+
]
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Parameters
|
|
359
|
+
|
|
360
|
+
| Parameter | Type | Default | Description |
|
|
361
|
+
|-----------|------|---------|-------------|
|
|
362
|
+
| `width` | number | 800 | Output width in pixels |
|
|
363
|
+
| `height` | number | 600 | Output height in pixels |
|
|
364
|
+
| `backgroundColor` | string | 'white' | Background color |
|
|
365
|
+
| `render` | function | *required* | Function that receives (svg, d3, data) |
|
|
366
|
+
| `renderString` | string | - | String version of render function (for serialization) |
|
|
367
|
+
| `data` | any | [] | Data to pass to render function |
|
|
368
|
+
|
|
369
|
+
## Configuration
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
imgflo.registerGenerator(d3viz({
|
|
373
|
+
width: 1000, // Default width
|
|
374
|
+
height: 600, // Default height
|
|
375
|
+
backgroundColor: '#f0f0f0' // Default background
|
|
376
|
+
}));
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Features
|
|
380
|
+
|
|
381
|
+
- **Full D3 API**: Complete access to all D3 capabilities
|
|
382
|
+
- **Server-side rendering**: No browser required
|
|
383
|
+
- **SVG output**: Scalable vector graphics
|
|
384
|
+
- **Pass-through pattern**: Zero abstraction, pure D3 code
|
|
385
|
+
- **Type-safe**: Full TypeScript support
|
|
386
|
+
|
|
387
|
+
## D3 Documentation
|
|
388
|
+
|
|
389
|
+
For full D3 capabilities and examples:
|
|
390
|
+
- https://d3js.org/
|
|
391
|
+
- https://observablehq.com/@d3/gallery
|
|
392
|
+
|
|
393
|
+
## Performance
|
|
394
|
+
|
|
395
|
+
- **Generation time**: ~10-100ms depending on complexity
|
|
396
|
+
- **Memory**: Minimal (~5-20MB)
|
|
397
|
+
- **No external dependencies**: Pure Node.js
|
|
398
|
+
|
|
399
|
+
## Use Cases
|
|
400
|
+
|
|
401
|
+
### Data Reports
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
// Generate multiple charts for a report
|
|
405
|
+
const charts = await Promise.all([
|
|
406
|
+
imgflo.generate({ generator: 'd3', params: salesChartConfig }),
|
|
407
|
+
imgflo.generate({ generator: 'd3', params: revenueChartConfig }),
|
|
408
|
+
imgflo.generate({ generator: 'd3', params: growthChartConfig })
|
|
409
|
+
]);
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Real-time Dashboards
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
// Update charts with live data
|
|
416
|
+
setInterval(async () => {
|
|
417
|
+
const liveData = await fetchLiveData();
|
|
418
|
+
const chart = await imgflo.generate({
|
|
419
|
+
generator: 'd3',
|
|
420
|
+
params: {
|
|
421
|
+
render: createChartRenderer(),
|
|
422
|
+
data: liveData
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
await imgflo.save(chart, './output/dashboard-live.svg');
|
|
426
|
+
}, 60000);
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### API Endpoints
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
app.get('/api/chart/:type', async (req, res) => {
|
|
433
|
+
const data = await fetchData(req.params.type);
|
|
434
|
+
const chart = await imgflo.generate({
|
|
435
|
+
generator: 'd3',
|
|
436
|
+
params: { render: getRenderer(req.params.type), data }
|
|
437
|
+
});
|
|
438
|
+
res.type('image/svg+xml').send(chart.bytes);
|
|
439
|
+
});
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## License
|
|
443
|
+
|
|
444
|
+
MIT
|
|
445
|
+
|
|
446
|
+
## See Also
|
|
447
|
+
|
|
448
|
+
- [imgflo](https://github.com/bcooke/imgflo) - Core library
|
|
449
|
+
- [D3.js](https://d3js.org/) - Data visualization library
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ImageGenerator, GeneratorSchema } from "@teamflojo/floimg";
|
|
2
|
+
/**
|
|
3
|
+
* Schema for the D3 visualization generator
|
|
4
|
+
*/
|
|
5
|
+
export declare const d3Schema: GeneratorSchema;
|
|
6
|
+
/**
|
|
7
|
+
* D3 data visualization generator using server-side rendering
|
|
8
|
+
*
|
|
9
|
+
* This generator accepts D3 code directly (pass-through pattern).
|
|
10
|
+
* You provide a render function that uses D3 to create visualizations.
|
|
11
|
+
* No imgflo abstraction - you get full D3 capabilities.
|
|
12
|
+
*
|
|
13
|
+
* @see https://d3js.org/ - D3 documentation
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import createClient from '@teamflojo/floimg';
|
|
18
|
+
* import d3viz from '@teamflojo/floimg-d3';
|
|
19
|
+
*
|
|
20
|
+
* const floimg = createClient();
|
|
21
|
+
* floimg.registerGenerator(d3viz());
|
|
22
|
+
*
|
|
23
|
+
* const chart = await floimg.generate({
|
|
24
|
+
* generator: 'd3',
|
|
25
|
+
* params: {
|
|
26
|
+
* width: 600,
|
|
27
|
+
* height: 400,
|
|
28
|
+
* render: (svg, d3, data) => {
|
|
29
|
+
* // Use D3 directly
|
|
30
|
+
* svg.selectAll('rect')
|
|
31
|
+
* .data(data)
|
|
32
|
+
* .join('rect')
|
|
33
|
+
* .attr('x', (d, i) => i * 50)
|
|
34
|
+
* .attr('y', d => 400 - d.value * 10)
|
|
35
|
+
* .attr('width', 40)
|
|
36
|
+
* .attr('height', d => d.value * 10)
|
|
37
|
+
* .attr('fill', 'steelblue');
|
|
38
|
+
* },
|
|
39
|
+
* data: [
|
|
40
|
+
* { value: 10 },
|
|
41
|
+
* { value: 20 },
|
|
42
|
+
* { value: 15 }
|
|
43
|
+
* ]
|
|
44
|
+
* }
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export interface D3Config {
|
|
49
|
+
/** Default width (default: 800) */
|
|
50
|
+
width?: number;
|
|
51
|
+
/** Default height (default: 600) */
|
|
52
|
+
height?: number;
|
|
53
|
+
/** Default background color */
|
|
54
|
+
backgroundColor?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface D3Params extends Record<string, unknown> {
|
|
57
|
+
/** Width of the visualization */
|
|
58
|
+
width?: number;
|
|
59
|
+
/** Height of the visualization */
|
|
60
|
+
height?: number;
|
|
61
|
+
/** Background color */
|
|
62
|
+
backgroundColor?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Render function that receives (svg, d3, data) and creates the visualization
|
|
65
|
+
* The svg is a D3 selection of the root SVG element
|
|
66
|
+
*/
|
|
67
|
+
render?: (svg: any, d3: typeof import('d3'), data: any) => void;
|
|
68
|
+
/**
|
|
69
|
+
* String version of render function (for serialization)
|
|
70
|
+
* Will be eval'd in a safe context
|
|
71
|
+
*/
|
|
72
|
+
renderString?: string;
|
|
73
|
+
/** Data to pass to the render function */
|
|
74
|
+
data?: any;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a D3 generator instance
|
|
78
|
+
*/
|
|
79
|
+
export default function d3viz(config?: D3Config): ImageGenerator;
|
|
80
|
+
export { d3viz };
|
|
81
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAa,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpF;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,eAuCtB,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,WAAW,QAAQ;IACvB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,QAAS,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvD,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,cAAc,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAChE;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,MAAM,GAAE,QAAa,GAAG,cAAc,CA0EnE;AAGD,OAAO,EAAE,KAAK,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema for the D3 visualization generator
|
|
3
|
+
*/
|
|
4
|
+
export const d3Schema = {
|
|
5
|
+
name: "d3",
|
|
6
|
+
description: "Create custom data visualizations using D3.js with server-side rendering",
|
|
7
|
+
category: "Charts",
|
|
8
|
+
parameters: {
|
|
9
|
+
width: {
|
|
10
|
+
type: "number",
|
|
11
|
+
title: "Width",
|
|
12
|
+
description: "Width of the visualization in pixels",
|
|
13
|
+
default: 800,
|
|
14
|
+
minimum: 100,
|
|
15
|
+
maximum: 4096,
|
|
16
|
+
},
|
|
17
|
+
height: {
|
|
18
|
+
type: "number",
|
|
19
|
+
title: "Height",
|
|
20
|
+
description: "Height of the visualization in pixels",
|
|
21
|
+
default: 600,
|
|
22
|
+
minimum: 100,
|
|
23
|
+
maximum: 4096,
|
|
24
|
+
},
|
|
25
|
+
backgroundColor: {
|
|
26
|
+
type: "string",
|
|
27
|
+
title: "Background Color",
|
|
28
|
+
description: "Background color (hex, rgb, or named color)",
|
|
29
|
+
default: "white",
|
|
30
|
+
},
|
|
31
|
+
renderString: {
|
|
32
|
+
type: "string",
|
|
33
|
+
title: "Render Function",
|
|
34
|
+
description: "JavaScript code that receives (svg, d3, data) and creates the visualization",
|
|
35
|
+
},
|
|
36
|
+
data: {
|
|
37
|
+
type: "object",
|
|
38
|
+
title: "Data",
|
|
39
|
+
description: "Data to pass to the render function",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
requiredParameters: ["renderString"],
|
|
43
|
+
};
|
|
44
|
+
import * as d3 from "d3";
|
|
45
|
+
import { JSDOM } from "jsdom";
|
|
46
|
+
/**
|
|
47
|
+
* Create a D3 generator instance
|
|
48
|
+
*/
|
|
49
|
+
export default function d3viz(config = {}) {
|
|
50
|
+
const { width: defaultWidth = 800, height: defaultHeight = 600, backgroundColor: defaultBgColor = 'white', } = config;
|
|
51
|
+
return {
|
|
52
|
+
name: "d3",
|
|
53
|
+
schema: d3Schema,
|
|
54
|
+
async generate(params) {
|
|
55
|
+
const { width = defaultWidth, height = defaultHeight, backgroundColor = defaultBgColor, render, renderString, data = [], } = params;
|
|
56
|
+
if (!render && !renderString) {
|
|
57
|
+
throw new Error("D3 generator requires either 'render' function or 'renderString' parameter");
|
|
58
|
+
}
|
|
59
|
+
// Create a fake DOM using jsdom
|
|
60
|
+
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
|
|
61
|
+
const document = dom.window.document;
|
|
62
|
+
// Create SVG element
|
|
63
|
+
const body = d3.select(document.body);
|
|
64
|
+
const svg = body
|
|
65
|
+
.append('svg')
|
|
66
|
+
.attr('xmlns', 'http://www.w3.org/2000/svg')
|
|
67
|
+
.attr('width', width)
|
|
68
|
+
.attr('height', height)
|
|
69
|
+
.style('background-color', backgroundColor);
|
|
70
|
+
// Execute render function
|
|
71
|
+
try {
|
|
72
|
+
if (render) {
|
|
73
|
+
// Direct function call
|
|
74
|
+
await render(svg, d3, data);
|
|
75
|
+
}
|
|
76
|
+
else if (renderString) {
|
|
77
|
+
// Eval string (for serialization scenarios like MCP)
|
|
78
|
+
const renderFn = new Function('svg', 'd3', 'data', renderString);
|
|
79
|
+
await renderFn(svg, d3, data);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new Error(`D3 render function failed: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
// Get the SVG string
|
|
86
|
+
const svgNode = svg.node();
|
|
87
|
+
if (!svgNode) {
|
|
88
|
+
throw new Error('Failed to create SVG element');
|
|
89
|
+
}
|
|
90
|
+
const svgString = svgNode.outerHTML;
|
|
91
|
+
const bytes = Buffer.from(svgString, 'utf-8');
|
|
92
|
+
return {
|
|
93
|
+
bytes,
|
|
94
|
+
mime: 'image/svg+xml',
|
|
95
|
+
width: width,
|
|
96
|
+
height: height,
|
|
97
|
+
source: "d3",
|
|
98
|
+
metadata: {
|
|
99
|
+
backgroundColor,
|
|
100
|
+
dataPoints: Array.isArray(data) ? data.length : undefined,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// Also export as named export for convenience
|
|
107
|
+
export { d3viz };
|
|
108
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@teamflojo/floimg-d3",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "D3 data visualization generator for floimg using server-side rendering",
|
|
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
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"floimg",
|
|
21
|
+
"d3",
|
|
22
|
+
"data-visualization",
|
|
23
|
+
"charts",
|
|
24
|
+
"svg",
|
|
25
|
+
"image-generation"
|
|
26
|
+
],
|
|
27
|
+
"author": "Brett Cooke",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/TeamFlojo/floimg.git",
|
|
32
|
+
"directory": "packages/floimg-d3"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@teamflojo/floimg": "^0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"d3": "^7.9.0",
|
|
39
|
+
"jsdom": "^25.0.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/d3": "^7.4.3",
|
|
43
|
+
"@types/jsdom": "^21.1.7",
|
|
44
|
+
"@types/node": "^22.10.2",
|
|
45
|
+
"typescript": "^5.7.2",
|
|
46
|
+
"vitest": "^2.1.8",
|
|
47
|
+
"@teamflojo/floimg": "0.1.0"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18.0.0"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsc",
|
|
54
|
+
"dev": "tsc --watch",
|
|
55
|
+
"test": "vitest",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"clean": "rm -rf dist"
|
|
58
|
+
}
|
|
59
|
+
}
|