@meonode/canvas 1.4.0 → 1.5.1

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/README.md CHANGED
@@ -52,41 +52,41 @@ import {Root, Box, Text} from '@meonode/canvas';
52
52
  import {writeFile} from 'fs/promises';
53
53
 
54
54
  async function generateImage() {
55
- const canvas = await Root({
56
- width: 500,
57
- height: 300,
58
- fonts: [
59
- {
60
- family: 'Roboto',
61
- paths: ['./fonts/Roboto-Regular.ttf', './fonts/Roboto-Bold.ttf'],
62
- },
63
- ],
55
+ const canvas = await Root({
56
+ width: 500,
57
+ height: 300,
58
+ fonts: [
59
+ {
60
+ family: 'Roboto',
61
+ paths: ['./fonts/Roboto-Regular.ttf', './fonts/Roboto-Bold.ttf'],
62
+ },
63
+ ],
64
+ children: [
65
+ Box({
66
+ width: '100%',
67
+ height: '100%',
68
+ backgroundColor: '#f0f0f0',
69
+ padding: 20,
64
70
  children: [
65
- Box({
66
- width: '100%',
67
- height: '100%',
68
- backgroundColor: '#f0f0f0',
69
- padding: 20,
70
- children: [
71
- Text('Hello, World!', {
72
- fontSize: 32,
73
- fontWeight: 'bold',
74
- fontFamily: 'Roboto',
75
- color: '#333',
76
- }),
77
- Text('This is a basic example of using @meonode/canvas.', {
78
- fontSize: 18,
79
- fontFamily: 'Roboto',
80
- color: '#666',
81
- margin: {Top: 10},
82
- }),
83
- ],
84
- }),
71
+ Text('Hello, World!', {
72
+ fontSize: 32,
73
+ fontWeight: 'bold',
74
+ fontFamily: 'Roboto',
75
+ color: '#333',
76
+ }),
77
+ Text('This is a basic example of using @meonode/canvas.', {
78
+ fontSize: 18,
79
+ fontFamily: 'Roboto',
80
+ color: '#666',
81
+ margin: {Top: 10},
82
+ }),
85
83
  ],
86
- });
84
+ }),
85
+ ],
86
+ });
87
87
 
88
- const buffer = await canvas.toBuffer('png');
89
- await writeFile('output.png', buffer);
88
+ const buffer = await canvas.toBuffer('png');
89
+ await writeFile('output.png', buffer);
90
90
  }
91
91
 
92
92
  generateImage().catch(console.error);
@@ -102,109 +102,109 @@ import {Root, Column, Row, Text, Image, Style} from '@meonode/canvas';
102
102
  import {writeFile} from 'fs/promises';
103
103
 
104
104
  async function generateComplexImage() {
105
- const canvas = await Root({
106
- width: 800,
107
- height: 600,
108
- fonts: [
109
- {
110
- family: 'Roboto',
111
- paths: ['./fonts/Roboto-Regular.ttf', './fonts/Roboto-Bold.ttf'],
112
- },
113
- {
114
- family: 'Open Sans',
115
- paths: ['./fonts/OpenSans-Regular.ttf'],
116
- },
117
- ],
105
+ const canvas = await Root({
106
+ width: 800,
107
+ height: 600,
108
+ fonts: [
109
+ {
110
+ family: 'Roboto',
111
+ paths: ['./fonts/Roboto-Regular.ttf', './fonts/Roboto-Bold.ttf'],
112
+ },
113
+ {
114
+ family: 'Open Sans',
115
+ paths: ['./fonts/OpenSans-Regular.ttf'],
116
+ },
117
+ ],
118
+ children: [
119
+ Column({
120
+ width: '100%',
121
+ height: '100%',
122
+ backgroundColor: '#f0f0f0',
123
+ padding: 20,
124
+ justifyContent: Style.Justify.SpaceBetween,
118
125
  children: [
119
- Column({
126
+ // Header Section
127
+ Row({
128
+ width: '100%',
129
+ alignItems: Style.Align.Center,
130
+ marginBottom: 20,
131
+ children: [
132
+ Image({
133
+ src: 'https://via.placeholder.com/80x80/FF0000/FFFFFF?text=Logo',
134
+ width: 80,
135
+ height: 80,
136
+ borderRadius: 40,
137
+ marginRight: 20,
138
+ objectFit: 'cover',
139
+ }),
140
+ Text('Welcome to MeoNode Canvas!', {
141
+ fontSize: 40,
142
+ fontWeight: 'bold',
143
+ fontFamily: 'Roboto',
144
+ color: '#333',
145
+ }),
146
+ ],
147
+ }),
148
+
149
+ // Main Content Section
150
+ Column({
151
+ flexGrow: 1,
152
+ width: '100%',
153
+ backgroundColor: '#ffffff',
154
+ borderRadius: 10,
155
+ padding: 30,
156
+ boxShadow: {blur: 10, color: 'rgba(0,0,0,0.1)'},
157
+ children: [
158
+ Text('A New Way to Render Graphics', {
159
+ fontSize: 28,
160
+ fontWeight: 'bold',
161
+ fontFamily: 'Open Sans',
162
+ color: '#555',
163
+ marginBottom: 15,
164
+ }),
165
+ Text(
166
+ `This example demonstrates a more complex layout using various components.
167
+ We have a header with a logo and title, a main content area with text,
168
+ and a footer. Notice how flexbox properties are used to arrange elements.`,
169
+ {
170
+ fontSize: 18,
171
+ fontFamily: 'Open Sans',
172
+ color: '#777',
173
+ lineHeight: 24,
174
+ },
175
+ ),
176
+ Image({
177
+ src: 'https://via.placeholder.com/600x200/007bff/ffffff?text=Feature+Image',
120
178
  width: '100%',
121
- height: '100%',
122
- backgroundColor: '#f0f0f0',
123
- padding: 20,
124
- justifyContent: Style.Justify.SpaceBetween,
125
- children: [
126
- // Header Section
127
- Row({
128
- width: '100%',
129
- alignItems: Style.Align.Center,
130
- marginBottom: 20,
131
- children: [
132
- Image({
133
- src: 'https://via.placeholder.com/80x80/FF0000/FFFFFF?text=Logo',
134
- width: 80,
135
- height: 80,
136
- borderRadius: 40,
137
- marginRight: 20,
138
- objectFit: 'cover',
139
- }),
140
- Text('Welcome to MeoNode Canvas!', {
141
- fontSize: 40,
142
- fontWeight: 'bold',
143
- fontFamily: 'Roboto',
144
- color: '#333',
145
- }),
146
- ],
147
- }),
148
-
149
- // Main Content Section
150
- Column({
151
- flexGrow: 1,
152
- width: '100%',
153
- backgroundColor: '#ffffff',
154
- borderRadius: 10,
155
- padding: 30,
156
- boxShadow: {blur: 10, color: 'rgba(0,0,0,0.1)'},
157
- children: [
158
- Text('A New Way to Render Graphics', {
159
- fontSize: 28,
160
- fontWeight: 'bold',
161
- fontFamily: 'Open Sans',
162
- color: '#555',
163
- marginBottom: 15,
164
- }),
165
- Text(
166
- `This example demonstrates a more complex layout using various components.
167
- We have a header with a logo and title, a main content area with text,
168
- and a footer. Notice how flexbox properties are used to arrange elements.`,
169
- {
170
- fontSize: 18,
171
- fontFamily: 'Open Sans',
172
- color: '#777',
173
- lineHeight: 24,
174
- },
175
- ),
176
- Image({
177
- src: 'https://via.placeholder.com/600x200/007bff/ffffff?text=Feature+Image',
178
- width: '100%',
179
- height: 200,
180
- marginTop: 20,
181
- borderRadius: 8,
182
- objectFit: 'contain',
183
- objectPosition: {Top: '50%', Left: '50%'},
184
- }),
185
- ],
186
- }),
187
-
188
- // Footer Section
189
- Row({
190
- width: '100%',
191
- marginTop: 20,
192
- justifyContent: Style.Justify.Center,
193
- children: [
194
- Text('© 2025 MeoNode Canvas. All rights reserved.', {
195
- fontSize: 14,
196
- fontFamily: 'Open Sans',
197
- color: '#999',
198
- }),
199
- ],
200
- }),
201
- ],
202
- }),
179
+ height: 200,
180
+ marginTop: 20,
181
+ borderRadius: 8,
182
+ objectFit: 'contain',
183
+ objectPosition: {Top: '50%', Left: '50%'},
184
+ }),
185
+ ],
186
+ }),
187
+
188
+ // Footer Section
189
+ Row({
190
+ width: '100%',
191
+ marginTop: 20,
192
+ justifyContent: Style.Justify.Center,
193
+ children: [
194
+ Text('© 2025 MeoNode Canvas. All rights reserved.', {
195
+ fontSize: 14,
196
+ fontFamily: 'Open Sans',
197
+ color: '#999',
198
+ }),
199
+ ],
200
+ }),
203
201
  ],
204
- });
202
+ }),
203
+ ],
204
+ });
205
205
 
206
- const buffer = await canvas.toBuffer('png');
207
- await writeFile('complex_output.png', buffer);
206
+ const buffer = await canvas.toBuffer('png');
207
+ await writeFile('complex_output.png', buffer);
208
208
  }
209
209
 
210
210
  generateComplexImage().catch(console.error);
@@ -221,39 +221,39 @@ import {Root, Chart} from '@meonode/canvas';
221
221
  import {writeFile} from 'fs/promises';
222
222
 
223
223
  async function generateBarChart() {
224
- const canvas = await Root({
225
- width: 600,
226
- height: 400,
227
- children: [
228
- Chart({
229
- type: 'bar',
230
- width: '100%',
231
- height: '100%',
232
- data: {
233
- labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
234
- datasets: [
235
- {
236
- label: 'Sales',
237
- data: [120, 150, 180, 90, 200],
238
- color: '#36A2EB',
239
- },
240
- ],
241
- },
242
- options: {
243
- gridOptions: {show: true, style: 'dashed'},
244
- axisColor: '#333',
245
- labelColor: '#333',
246
- showValues: true,
247
- valueFontSize: 12,
248
- showYAxis: true,
249
- yAxisColor: '#666',
250
- },
251
- }),
252
- ],
253
- });
224
+ const canvas = await Root({
225
+ width: 600,
226
+ height: 400,
227
+ children: [
228
+ Chart({
229
+ type: 'bar',
230
+ width: '100%',
231
+ height: '100%',
232
+ data: {
233
+ labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
234
+ datasets: [
235
+ {
236
+ label: 'Sales',
237
+ data: [120, 150, 180, 90, 200],
238
+ color: '#36A2EB',
239
+ },
240
+ ],
241
+ },
242
+ options: {
243
+ gridOptions: {show: true, style: 'dashed'},
244
+ axisColor: '#333',
245
+ labelColor: '#333',
246
+ showValues: true,
247
+ valueFontSize: 12,
248
+ showYAxis: true,
249
+ yAxisColor: '#666',
250
+ },
251
+ }),
252
+ ],
253
+ });
254
254
 
255
- const buffer = await canvas.toBuffer('png');
256
- await writeFile('bar_chart.png', buffer);
255
+ const buffer = await canvas.toBuffer('png');
256
+ await writeFile('bar_chart.png', buffer);
257
257
  }
258
258
 
259
259
  generateBarChart().catch(console.error);
@@ -266,37 +266,37 @@ import {Root, Chart, Row, Box, Text} from '@meonode/canvas';
266
266
  import {writeFile} from 'fs/promises';
267
267
 
268
268
  async function generateDoughnutChart() {
269
- const canvas = await Root({
270
- width: 600,
271
- height: 400,
272
- children: [
273
- Chart({
274
- type: 'doughnut',
275
- width: '100%',
276
- height: '100%',
277
- data: [
278
- {label: 'Red', value: 300, color: '#FF6384'},
279
- {label: 'Blue', value: 50, color: '#36A2EB'},
280
- {label: 'Yellow', value: 100, color: '#FFCE56'},
281
- ],
282
- options: {
283
- innerRadius: 0.7,
284
- sliceBorderRadius: 5,
285
- renderLegendItem: ({item, color}) =>
286
- Row({
287
- alignItems: 'center',
288
- children: [
289
- Box({width: 12, height: 12, backgroundColor: color, borderRadius: 6}),
290
- Text(`${item.label}: ${item.value}`, {fontSize: 16, marginLeft: 8}),
291
- ],
292
- }),
293
- },
294
- }),
269
+ const canvas = await Root({
270
+ width: 600,
271
+ height: 400,
272
+ children: [
273
+ Chart({
274
+ type: 'doughnut',
275
+ width: '100%',
276
+ height: '100%',
277
+ data: [
278
+ {label: 'Red', value: 300, color: '#FF6384'},
279
+ {label: 'Blue', value: 50, color: '#36A2EB'},
280
+ {label: 'Yellow', value: 100, color: '#FFCE56'},
295
281
  ],
296
- });
282
+ options: {
283
+ innerRadius: 0.7,
284
+ sliceBorderRadius: 5,
285
+ renderLegendItem: ({item, color}) =>
286
+ Row({
287
+ alignItems: 'center',
288
+ children: [
289
+ Box({width: 12, height: 12, backgroundColor: color, borderRadius: 6}),
290
+ Text(`${item.label}: ${item.value}`, {fontSize: 16, marginLeft: 8}),
291
+ ],
292
+ }),
293
+ },
294
+ }),
295
+ ],
296
+ });
297
297
 
298
- const buffer = await canvas.toBuffer('png');
299
- await writeFile('doughnut_chart.png', buffer);
298
+ const buffer = await canvas.toBuffer('png');
299
+ await writeFile('doughnut_chart.png', buffer);
300
300
  }
301
301
 
302
302
  generateDoughnutChart().catch(console.error);
@@ -315,25 +315,25 @@ import {Root, Grid, Box, Text} from '@meonode/canvas';
315
315
  import {writeFile} from 'fs/promises';
316
316
 
317
317
  async function generateBasicGrid() {
318
- const canvas = await Root({
319
- width: 400,
320
- height: 300,
318
+ const canvas = await Root({
319
+ width: 400,
320
+ height: 300,
321
+ children: [
322
+ Grid({
323
+ columns: 3,
324
+ templateColumns: [100, 100, 100], // or ['100px', '100px', '100px']
325
+ gap: 10,
321
326
  children: [
322
- Grid({
323
- columns: 3,
324
- templateColumns: [100, 100, 100], // or ['100px', '100px', '100px']
325
- gap: 10,
326
- children: [
327
- Box({backgroundColor: 'red', height: 50, children: [Text('1')]}),
328
- Box({backgroundColor: 'blue', height: 50, children: [Text('2')]}),
329
- Box({backgroundColor: 'green', height: 50, children: [Text('3')]}),
330
- Box({backgroundColor: 'yellow', height: 50, children: [Text('4')]}),
331
- ],
332
- }),
327
+ Box({backgroundColor: 'red', height: 50, children: [Text('1')]}),
328
+ Box({backgroundColor: 'blue', height: 50, children: [Text('2')]}),
329
+ Box({backgroundColor: 'green', height: 50, children: [Text('3')]}),
330
+ Box({backgroundColor: 'yellow', height: 50, children: [Text('4')]}),
333
331
  ],
334
- });
332
+ }),
333
+ ],
334
+ });
335
335
 
336
- await canvas.toFile('grid_basic.png');
336
+ await canvas.toFile('grid_basic.png');
337
337
  }
338
338
 
339
339
  generateBasicGrid();
@@ -345,14 +345,14 @@ Using fractional units (`fr`) allows columns to take up proportional space.
345
345
 
346
346
  ```typescript
347
347
  Grid({
348
- // First column takes 1 part, second takes 2 parts, third takes 1 part
349
- templateColumns: ['1fr', '2fr', '1fr'],
350
- gap: 10,
351
- children: [
352
- Box({backgroundColor: 'red', height: 50, children: [Text('1fr')]}),
353
- Box({backgroundColor: 'blue', height: 50, children: [Text('2fr')]}),
354
- Box({backgroundColor: 'green', height: 50, children: [Text('1fr')]}),
355
- ],
348
+ // First column takes 1 part, second takes 2 parts, third takes 1 part
349
+ templateColumns: ['1fr', '2fr', '1fr'],
350
+ gap: 10,
351
+ children: [
352
+ Box({backgroundColor: 'red', height: 50, children: [Text('1fr')]}),
353
+ Box({backgroundColor: 'blue', height: 50, children: [Text('2fr')]}),
354
+ Box({backgroundColor: 'green', height: 50, children: [Text('1fr')]}),
355
+ ],
356
356
  });
357
357
  ```
358
358
 
@@ -364,27 +364,27 @@ Use `GridItem` (or just passing `gridColumn`/`gridRow` props to any child) to sp
364
364
  import {Grid, GridItem, Box, Text} from '@meonode/canvas';
365
365
 
366
366
  Grid({
367
- templateColumns: ['1fr', '1fr', '1fr'],
368
- gap: 10,
369
- children: [
370
- // Spans all 3 columns
371
- GridItem({
372
- gridColumn: 'span 3',
373
- height: 50,
374
- backgroundColor: '#333',
375
- children: [Text('Header', {color: 'white'})],
376
- }),
377
- // Standard items
378
- Box({backgroundColor: '#eee', height: 100, children: [Text('Content')]}),
379
- Box({backgroundColor: '#ccc', height: 100, children: [Text('Sidebar')]}),
380
- // Spans 2 columns
381
- GridItem({
382
- gridColumn: 'span 2',
383
- height: 50,
384
- backgroundColor: '#555',
385
- children: [Text('Footer', {color: 'white'})],
386
- }),
387
- ],
367
+ templateColumns: ['1fr', '1fr', '1fr'],
368
+ gap: 10,
369
+ children: [
370
+ // Spans all 3 columns
371
+ GridItem({
372
+ gridColumn: 'span 3',
373
+ height: 50,
374
+ backgroundColor: '#333',
375
+ children: [Text('Header', {color: 'white'})],
376
+ }),
377
+ // Standard items
378
+ Box({backgroundColor: '#eee', height: 100, children: [Text('Content')]}),
379
+ Box({backgroundColor: '#ccc', height: 100, children: [Text('Sidebar')]}),
380
+ // Spans 2 columns
381
+ GridItem({
382
+ gridColumn: 'span 2',
383
+ height: 50,
384
+ backgroundColor: '#555',
385
+ children: [Text('Footer', {color: 'white'})],
386
+ }),
387
+ ],
388
388
  });
389
389
  ```
390
390
 
@@ -399,19 +399,19 @@ For example, to set `flexDirection` to `row` or `positionType` to `absolute`, yo
399
399
  import {Box, Style} from '@meonode/canvas';
400
400
 
401
401
  Box({
402
- flexDirection: Style.FlexDirection.Row,
403
- justifyContent: Style.Justify.Center,
404
- alignItems: Style.Align.Center,
405
- children: [
406
- Box({
407
- width: 100,
408
- height: 100,
409
- backgroundColor: 'red',
410
- positionType: Style.PositionType.Absolute,
411
- position: {Top: 10, Left: 10},
412
- }),
413
- // ... other children
414
- ],
402
+ flexDirection: Style.FlexDirection.Row,
403
+ justifyContent: Style.Justify.Center,
404
+ alignItems: Style.Align.Center,
405
+ children: [
406
+ Box({
407
+ width: 100,
408
+ height: 100,
409
+ backgroundColor: 'red',
410
+ positionType: Style.PositionType.Absolute,
411
+ position: {Top: 10, Left: 10},
412
+ }),
413
+ // ... other children
414
+ ],
415
415
  });
416
416
  ```
417
417
 
@@ -584,7 +584,36 @@ The `options` prop is a conditional type that changes based on the chart `type`.
584
584
  | `showYAxis` | `boolean` | If `true`, displays the Y-axis labels on the left. |
585
585
  | `yAxisColor` | `string` | Color of the Y-axis labels. |
586
586
  | `yAxisFontSize` | `number` | Font size of the Y-axis labels. |
587
- | `yAxisLabelFormatter` | `(value: number) => string` | Custom formatter for Y-axis labels. |
587
+ | `yAxisLabelFormatter` | `(value: number) => string` | Custom formatter for Y-axis labels. Smart defaults adjust decimal precision based on value range. |
588
+ | `xAxisLabelFormatter` | `(value: string, index: number) => string` | Custom formatter for X-axis labels. Useful for truncating or transforming labels. |
589
+
590
+ ###### Y-Axis Label Formatter
591
+
592
+ The `yAxisLabelFormatter` enables custom Y-axis label formatting. By default, it uses smart formatting:
593
+ - **Values < 1**: 4 decimals (e.g., `0.0025`)
594
+ - **Values 1-100**: 2 decimals (e.g., `25.43`)
595
+ - **Values 100-1000**: 1 decimal (e.g., `250.5`)
596
+ - **Values 1000-1M**: 0 decimals (e.g., `50000`)
597
+ - **Values ≥ 1M**: 1 decimal with "M" suffix (e.g., `1.5M`)
598
+
599
+ Custom example:
600
+ ```typescript
601
+ options: {
602
+ yAxisLabelFormatter: (value) => `$${value.toFixed(2)}`,
603
+ }
604
+ ```
605
+
606
+ ###### X-Axis Label Formatter
607
+
608
+ The `xAxisLabelFormatter` allows custom X-axis label transformations. It receives the label string and its index.
609
+
610
+ Custom example:
611
+ ```typescript
612
+ options: {
613
+ xAxisLabelFormatter: (label, index) =>
614
+ label.length > 5 ? label.substring(0, 5) + '...' : label,
615
+ }
616
+ ```
588
617
 
589
618
  ##### Pie & Doughnut Chart Options (`pie`, `doughnut`)
590
619
 
@@ -616,4 +645,3 @@ started.
616
645
  ## License
617
646
 
618
647
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
619
-
@@ -501,7 +501,7 @@ export interface RootProps extends BoxProps {
501
501
  * - `${number}fr` (fraction of available space)
502
502
  * - `${number}%` (percentage of container size)
503
503
  */
504
- export type GridTrackSize = number | 'auto' | `${number}fr` | `${number}%`;
504
+ export type GridTrackSize = number | 'auto' | `${number}px` | `${number}fr` | `${number}%`;
505
505
  /**
506
506
  * Defines the properties for a GridNode.
507
507
  */
@@ -755,6 +755,7 @@ interface CartesianChartSpecificOptions {
755
755
  yAxisFontSize?: number;
756
756
  yAxisColor?: string;
757
757
  yAxisLabelFormatter?: (value: number) => string;
758
+ xAxisLabelFormatter?: (value: string, index: number) => string;
758
759
  }
759
760
  interface PieChartSpecificOptions {
760
761
  /**