@opendata-ai/openchart-engine 6.10.0 → 6.12.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 +9 -10
- package/dist/index.js +148 -59
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__test-fixtures__/specs.ts +3 -0
- package/src/charts/bar/compute.ts +9 -5
- package/src/charts/bar/labels.ts +2 -1
- package/src/charts/column/compute.ts +9 -5
- package/src/charts/column/labels.ts +2 -1
- package/src/charts/dot/labels.ts +6 -2
- package/src/charts/line/area.ts +3 -2
- package/src/charts/line/compute.ts +5 -2
- package/src/charts/pie/compute.ts +24 -3
- package/src/charts/rule/index.ts +6 -3
- package/src/charts/scatter/compute.ts +2 -1
- package/src/charts/text/index.ts +6 -3
- package/src/charts/tick/index.ts +6 -3
- package/src/charts/utils.ts +3 -3
- package/src/compile.ts +27 -14
- package/src/compiler/__tests__/normalize.test.ts +110 -0
- package/src/compiler/normalize.ts +20 -1
- package/src/compiler/types.ts +4 -0
- package/src/graphs/compile-graph.ts +8 -0
- package/src/graphs/types.ts +2 -0
- package/src/layout/dimensions.ts +3 -0
- package/src/layout/scales.ts +2 -2
- package/src/legend/compute.ts +3 -1
- package/src/sankey/compile-sankey.ts +12 -2
- package/src/sankey/types.ts +1 -0
- package/src/tables/compile-table.ts +5 -0
- package/src/tooltips/compute.ts +11 -6
|
@@ -215,6 +215,7 @@ function normalizeChartSpec(spec: ChartSpec, warnings: string[]): NormalizedChar
|
|
|
215
215
|
darkMode: spec.darkMode ?? 'off',
|
|
216
216
|
hiddenSeries: spec.hiddenSeries ?? [],
|
|
217
217
|
seriesStyles: spec.seriesStyles ?? {},
|
|
218
|
+
watermark: spec.watermark ?? true,
|
|
218
219
|
};
|
|
219
220
|
}
|
|
220
221
|
|
|
@@ -233,6 +234,7 @@ function normalizeTableSpec(spec: TableSpec, _warnings: string[]): NormalizedTab
|
|
|
233
234
|
compact: spec.compact ?? false,
|
|
234
235
|
responsive: spec.responsive ?? true,
|
|
235
236
|
animation: spec.animation,
|
|
237
|
+
watermark: spec.watermark ?? true,
|
|
236
238
|
};
|
|
237
239
|
}
|
|
238
240
|
|
|
@@ -254,6 +256,7 @@ function normalizeSankeySpec(spec: SankeySpec, _warnings: string[]): NormalizedS
|
|
|
254
256
|
animation: spec.animation,
|
|
255
257
|
valueFormat: spec.valueFormat,
|
|
256
258
|
linkOpacity: spec.linkOpacity,
|
|
259
|
+
watermark: spec.watermark ?? true,
|
|
257
260
|
};
|
|
258
261
|
}
|
|
259
262
|
|
|
@@ -282,6 +285,7 @@ function normalizeGraphSpec(spec: GraphSpec, _warnings: string[]): NormalizedGra
|
|
|
282
285
|
annotations: normalizeAnnotations(spec.annotations),
|
|
283
286
|
theme: spec.theme ?? {},
|
|
284
287
|
darkMode: spec.darkMode ?? 'off',
|
|
288
|
+
watermark: spec.watermark ?? true,
|
|
285
289
|
};
|
|
286
290
|
}
|
|
287
291
|
|
|
@@ -338,6 +342,7 @@ export function flattenLayers(
|
|
|
338
342
|
parentData?: DataRow[],
|
|
339
343
|
parentEncoding?: Encoding,
|
|
340
344
|
parentTransforms?: import('@opendata-ai/openchart-core').Transform[],
|
|
345
|
+
parentWatermark?: boolean,
|
|
341
346
|
): ChartSpec[] {
|
|
342
347
|
const resolvedData = spec.data ?? parentData;
|
|
343
348
|
const resolvedEncoding: Encoding | undefined =
|
|
@@ -345,13 +350,23 @@ export function flattenLayers(
|
|
|
345
350
|
? { ...parentEncoding, ...spec.encoding }
|
|
346
351
|
: (spec.encoding ?? parentEncoding);
|
|
347
352
|
const resolvedTransforms = [...(parentTransforms ?? []), ...(spec.transform ?? [])];
|
|
353
|
+
// Layer-level watermark propagates to children (child can still override)
|
|
354
|
+
const resolvedWatermark = spec.watermark ?? parentWatermark;
|
|
348
355
|
|
|
349
356
|
const leaves: ChartSpec[] = [];
|
|
350
357
|
|
|
351
358
|
for (const child of spec.layer) {
|
|
352
359
|
if (isLayerSpec(child)) {
|
|
353
360
|
// Nested layer: recurse with merged context
|
|
354
|
-
leaves.push(
|
|
361
|
+
leaves.push(
|
|
362
|
+
...flattenLayers(
|
|
363
|
+
child,
|
|
364
|
+
resolvedData,
|
|
365
|
+
resolvedEncoding,
|
|
366
|
+
resolvedTransforms,
|
|
367
|
+
resolvedWatermark,
|
|
368
|
+
),
|
|
369
|
+
);
|
|
355
370
|
} else {
|
|
356
371
|
// Leaf ChartSpec: merge inherited properties
|
|
357
372
|
const mergedData = child.data ?? resolvedData ?? [];
|
|
@@ -365,6 +380,10 @@ export function flattenLayers(
|
|
|
365
380
|
data: mergedData,
|
|
366
381
|
encoding: mergedEncoding,
|
|
367
382
|
transform: mergedTransforms.length > 0 ? mergedTransforms : undefined,
|
|
383
|
+
// Inherit parent watermark if child doesn't explicitly set one
|
|
384
|
+
...(child.watermark === undefined && resolvedWatermark !== undefined
|
|
385
|
+
? { watermark: resolvedWatermark }
|
|
386
|
+
: {}),
|
|
368
387
|
});
|
|
369
388
|
}
|
|
370
389
|
}
|
package/src/compiler/types.ts
CHANGED
|
@@ -78,6 +78,8 @@ export interface NormalizedChartSpec {
|
|
|
78
78
|
responsive: boolean;
|
|
79
79
|
theme: ThemeConfig;
|
|
80
80
|
darkMode: DarkMode;
|
|
81
|
+
/** Whether the tryOpenData.ai watermark is enabled. */
|
|
82
|
+
watermark: boolean;
|
|
81
83
|
/** Series names to hide from rendering. */
|
|
82
84
|
hiddenSeries: string[];
|
|
83
85
|
/** Per-series visual style overrides. */
|
|
@@ -93,6 +95,7 @@ export interface NormalizedTableSpec {
|
|
|
93
95
|
chrome: NormalizedChrome;
|
|
94
96
|
theme: ThemeConfig;
|
|
95
97
|
darkMode: DarkMode;
|
|
98
|
+
watermark: boolean;
|
|
96
99
|
search: boolean;
|
|
97
100
|
pagination: boolean | { pageSize: number };
|
|
98
101
|
stickyFirstColumn: boolean;
|
|
@@ -113,6 +116,7 @@ export interface NormalizedGraphSpec {
|
|
|
113
116
|
annotations: Annotation[];
|
|
114
117
|
theme: ThemeConfig;
|
|
115
118
|
darkMode: DarkMode;
|
|
119
|
+
watermark: boolean;
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
/** Discriminated union of all normalized spec types. */
|
|
@@ -194,6 +194,10 @@ export function compileGraph(spec: unknown, options: CompileOptions): GraphCompi
|
|
|
194
194
|
|
|
195
195
|
const graphSpec = normalized as NormalizedGraphSpec;
|
|
196
196
|
|
|
197
|
+
// Resolve watermark: explicit spec value wins, then options fallback, then default true.
|
|
198
|
+
const rawWatermark = (spec as Record<string, unknown>).watermark;
|
|
199
|
+
const watermark = rawWatermark !== undefined ? graphSpec.watermark : (options.watermark ?? true);
|
|
200
|
+
|
|
197
201
|
// 2. Resolve theme
|
|
198
202
|
const mergedThemeConfig = options.theme
|
|
199
203
|
? { ...graphSpec.theme, ...options.theme }
|
|
@@ -288,6 +292,9 @@ export function compileGraph(spec: unknown, options: CompileOptions): GraphCompi
|
|
|
288
292
|
theme,
|
|
289
293
|
options.width,
|
|
290
294
|
options.measureText,
|
|
295
|
+
'full',
|
|
296
|
+
undefined,
|
|
297
|
+
watermark,
|
|
291
298
|
);
|
|
292
299
|
|
|
293
300
|
// 12. Return compilation
|
|
@@ -304,6 +311,7 @@ export function compileGraph(spec: unknown, options: CompileOptions): GraphCompi
|
|
|
304
311
|
height: options.height,
|
|
305
312
|
},
|
|
306
313
|
simulationConfig,
|
|
314
|
+
watermark,
|
|
307
315
|
};
|
|
308
316
|
}
|
|
309
317
|
|
package/src/graphs/types.ts
CHANGED
package/src/layout/dimensions.ts
CHANGED
|
@@ -96,6 +96,7 @@ export function computeDimensions(
|
|
|
96
96
|
legendLayout: LegendLayout,
|
|
97
97
|
theme: ResolvedTheme,
|
|
98
98
|
strategy?: LayoutStrategy,
|
|
99
|
+
watermark: boolean = true,
|
|
99
100
|
): LayoutDimensions {
|
|
100
101
|
const { width, height } = options;
|
|
101
102
|
|
|
@@ -111,6 +112,7 @@ export function computeDimensions(
|
|
|
111
112
|
options.measureText,
|
|
112
113
|
chromeMode,
|
|
113
114
|
padding,
|
|
115
|
+
watermark,
|
|
114
116
|
);
|
|
115
117
|
|
|
116
118
|
// Start with the total rect
|
|
@@ -334,6 +336,7 @@ export function computeDimensions(
|
|
|
334
336
|
options.measureText,
|
|
335
337
|
fallbackMode as 'compact' | 'hidden',
|
|
336
338
|
padding,
|
|
339
|
+
watermark,
|
|
337
340
|
);
|
|
338
341
|
|
|
339
342
|
// Recalculate top/bottom margins with stripped chrome
|
package/src/layout/scales.ts
CHANGED
|
@@ -99,8 +99,8 @@ export interface ResolvedScales {
|
|
|
99
99
|
y?: ResolvedScale;
|
|
100
100
|
color?: ResolvedScale;
|
|
101
101
|
size?: ResolvedScale;
|
|
102
|
-
/** Default color for single-series charts (first categorical palette color). */
|
|
103
|
-
defaultColor?: string;
|
|
102
|
+
/** Default color for single-series charts (first categorical palette color or markDef.fill gradient). */
|
|
103
|
+
defaultColor?: string | import('@opendata-ai/openchart-core').GradientDef;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// ---------------------------------------------------------------------------
|
package/src/legend/compute.ts
CHANGED
|
@@ -150,6 +150,7 @@ export function computeLegend(
|
|
|
150
150
|
strategy: LayoutStrategy,
|
|
151
151
|
theme: ResolvedTheme,
|
|
152
152
|
chartArea: Rect,
|
|
153
|
+
watermark: boolean = true,
|
|
153
154
|
): LegendLayout {
|
|
154
155
|
// Legend explicitly hidden via show: false, or height strategy says no legend
|
|
155
156
|
if (spec.legend?.show === false || strategy.legendMaxHeight === 0) {
|
|
@@ -259,7 +260,8 @@ export function computeLegend(
|
|
|
259
260
|
|
|
260
261
|
// Top/bottom-positioned legend: horizontal flow with overflow protection.
|
|
261
262
|
// Reserve space on the right so legend entries don't overlap the brand watermark.
|
|
262
|
-
const availableWidth =
|
|
263
|
+
const availableWidth =
|
|
264
|
+
chartArea.width - LEGEND_PADDING * 2 - (watermark ? BRAND_RESERVE_WIDTH : 0);
|
|
263
265
|
|
|
264
266
|
// Apply symbolLimit first if set (minimum 1), then fit remaining entries to available rows.
|
|
265
267
|
if (spec.legend?.symbolLimit != null) {
|
|
@@ -211,6 +211,10 @@ export function compileSankey(spec: unknown, options: CompileOptions): SankeyLay
|
|
|
211
211
|
|
|
212
212
|
const sankeySpec = normalized as NormalizedSankeySpec;
|
|
213
213
|
|
|
214
|
+
// Resolve watermark: explicit spec value wins, then options fallback, then default true.
|
|
215
|
+
const rawWatermark = (spec as Record<string, unknown>).watermark;
|
|
216
|
+
const watermark = rawWatermark !== undefined ? sankeySpec.watermark : (options.watermark ?? true);
|
|
217
|
+
|
|
214
218
|
// 2. Resolve theme
|
|
215
219
|
const mergedThemeConfig = options.theme
|
|
216
220
|
? { ...sankeySpec.theme, ...options.theme }
|
|
@@ -241,6 +245,9 @@ export function compileSankey(spec: unknown, options: CompileOptions): SankeyLay
|
|
|
241
245
|
theme,
|
|
242
246
|
options.width,
|
|
243
247
|
options.measureText,
|
|
248
|
+
'full',
|
|
249
|
+
undefined,
|
|
250
|
+
watermark,
|
|
244
251
|
);
|
|
245
252
|
|
|
246
253
|
// 4. Compute drawing area (total space minus chrome)
|
|
@@ -254,7 +261,7 @@ export function compileSankey(spec: unknown, options: CompileOptions): SankeyLay
|
|
|
254
261
|
|
|
255
262
|
// Guard against negative dimensions
|
|
256
263
|
if (fullArea.width <= 0 || fullArea.height <= 0) {
|
|
257
|
-
return emptyLayout(fullArea, chrome, theme, options);
|
|
264
|
+
return emptyLayout(fullArea, chrome, theme, options, watermark);
|
|
258
265
|
}
|
|
259
266
|
|
|
260
267
|
// 5. Extract encoding fields
|
|
@@ -298,7 +305,7 @@ export function compileSankey(spec: unknown, options: CompileOptions): SankeyLay
|
|
|
298
305
|
};
|
|
299
306
|
|
|
300
307
|
if (area.height <= 0) {
|
|
301
|
-
return emptyLayout(area, chrome, theme, options);
|
|
308
|
+
return emptyLayout(area, chrome, theme, options, watermark);
|
|
302
309
|
}
|
|
303
310
|
|
|
304
311
|
// 6. Run d3-sankey layout (may re-run once if labels overflow)
|
|
@@ -473,6 +480,7 @@ export function compileSankey(spec: unknown, options: CompileOptions): SankeyLay
|
|
|
473
480
|
height: options.height,
|
|
474
481
|
},
|
|
475
482
|
animation: resolvedAnimation,
|
|
483
|
+
watermark,
|
|
476
484
|
};
|
|
477
485
|
}
|
|
478
486
|
|
|
@@ -631,6 +639,7 @@ function emptyLayout(
|
|
|
631
639
|
chrome: ReturnType<typeof computeChrome>,
|
|
632
640
|
theme: ResolvedTheme,
|
|
633
641
|
options: CompileOptions,
|
|
642
|
+
watermark: boolean,
|
|
634
643
|
): SankeyLayout {
|
|
635
644
|
return {
|
|
636
645
|
area,
|
|
@@ -664,5 +673,6 @@ function emptyLayout(
|
|
|
664
673
|
width: options.width,
|
|
665
674
|
height: options.height,
|
|
666
675
|
},
|
|
676
|
+
watermark,
|
|
667
677
|
};
|
|
668
678
|
}
|
package/src/sankey/types.ts
CHANGED
|
@@ -382,6 +382,7 @@ export function compileTableLayout(
|
|
|
382
382
|
});
|
|
383
383
|
|
|
384
384
|
// 9. Compute chrome
|
|
385
|
+
const watermark = spec.watermark;
|
|
385
386
|
const chrome = computeChrome(
|
|
386
387
|
{
|
|
387
388
|
title: spec.chrome.title,
|
|
@@ -393,6 +394,9 @@ export function compileTableLayout(
|
|
|
393
394
|
theme,
|
|
394
395
|
options.width,
|
|
395
396
|
options.measureText,
|
|
397
|
+
'full',
|
|
398
|
+
undefined,
|
|
399
|
+
watermark,
|
|
396
400
|
);
|
|
397
401
|
|
|
398
402
|
// 10. Build a11y
|
|
@@ -418,5 +422,6 @@ export function compileTableLayout(
|
|
|
418
422
|
},
|
|
419
423
|
theme,
|
|
420
424
|
animation: resolveAnimation(spec.animation),
|
|
425
|
+
watermark,
|
|
421
426
|
};
|
|
422
427
|
}
|
package/src/tooltips/compute.ts
CHANGED
|
@@ -19,7 +19,12 @@ import type {
|
|
|
19
19
|
TooltipContent,
|
|
20
20
|
TooltipField,
|
|
21
21
|
} from '@opendata-ai/openchart-core';
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
buildTemporalFormatter,
|
|
24
|
+
formatDate,
|
|
25
|
+
formatNumber,
|
|
26
|
+
getRepresentativeColor,
|
|
27
|
+
} from '@opendata-ai/openchart-core';
|
|
23
28
|
import { format as d3Format } from 'd3-format';
|
|
24
29
|
|
|
25
30
|
import type { NormalizedChartSpec } from '../compiler/types';
|
|
@@ -155,7 +160,7 @@ function tooltipsForPoint(
|
|
|
155
160
|
markIndex: number,
|
|
156
161
|
): Array<[string, TooltipContent]> {
|
|
157
162
|
const title = getTooltipTitle(mark.data, encoding);
|
|
158
|
-
const fields = buildFields(mark.data, encoding, mark.fill);
|
|
163
|
+
const fields = buildFields(mark.data, encoding, getRepresentativeColor(mark.fill));
|
|
159
164
|
|
|
160
165
|
return [[`point-${markIndex}`, { title, fields }]];
|
|
161
166
|
}
|
|
@@ -166,7 +171,7 @@ function tooltipsForRect(
|
|
|
166
171
|
markIndex: number,
|
|
167
172
|
): Array<[string, TooltipContent]> {
|
|
168
173
|
const title = getTooltipTitle(mark.data, encoding);
|
|
169
|
-
const fields = buildFields(mark.data, encoding, mark.fill);
|
|
174
|
+
const fields = buildFields(mark.data, encoding, getRepresentativeColor(mark.fill));
|
|
170
175
|
|
|
171
176
|
return [[`rect-${markIndex}`, { title, fields }]];
|
|
172
177
|
}
|
|
@@ -187,14 +192,14 @@ function tooltipsForArc(
|
|
|
187
192
|
fields.push({
|
|
188
193
|
label: categoryName,
|
|
189
194
|
value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y.axis?.format),
|
|
190
|
-
color: mark.fill,
|
|
195
|
+
color: getRepresentativeColor(mark.fill),
|
|
191
196
|
});
|
|
192
197
|
}
|
|
193
198
|
} else if (encoding.y) {
|
|
194
199
|
fields.push({
|
|
195
200
|
label: encoding.y.field,
|
|
196
201
|
value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y.axis?.format),
|
|
197
|
-
color: mark.fill,
|
|
202
|
+
color: getRepresentativeColor(mark.fill),
|
|
198
203
|
});
|
|
199
204
|
}
|
|
200
205
|
|
|
@@ -214,7 +219,7 @@ function tooltipsForArea(
|
|
|
214
219
|
for (const dp of mark.dataPoints) {
|
|
215
220
|
dp.tooltip = {
|
|
216
221
|
title: getTooltipTitle(dp.datum, encoding),
|
|
217
|
-
fields: buildFields(dp.datum, encoding, mark.fill),
|
|
222
|
+
fields: buildFields(dp.datum, encoding, getRepresentativeColor(mark.fill)),
|
|
218
223
|
};
|
|
219
224
|
}
|
|
220
225
|
}
|