@opendata-ai/openchart-engine 6.11.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 +7 -0
- package/dist/index.js +76 -33
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__test-fixtures__/specs.ts +3 -0
- package/src/compile.ts +24 -12
- 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/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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-engine",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.12.0",
|
|
4
4
|
"description": "Headless compiler for openchart: spec validation, data compilation, scales, and layout",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Riley Hilliard",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"typecheck": "tsc --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@opendata-ai/openchart-core": "6.
|
|
48
|
+
"@opendata-ai/openchart-core": "6.12.0",
|
|
49
49
|
"d3-array": "^3.2.0",
|
|
50
50
|
"d3-format": "^3.1.2",
|
|
51
51
|
"d3-interpolate": "^3.0.0",
|
|
@@ -73,6 +73,7 @@ export function makeLineSpec(): NormalizedChartSpec {
|
|
|
73
73
|
labels: { density: 'auto', format: '', prefix: '' },
|
|
74
74
|
hiddenSeries: [],
|
|
75
75
|
seriesStyles: {},
|
|
76
|
+
watermark: true,
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
|
|
@@ -102,6 +103,7 @@ export function makeBarSpec(): NormalizedChartSpec {
|
|
|
102
103
|
labels: { density: 'auto', format: '', prefix: '' },
|
|
103
104
|
hiddenSeries: [],
|
|
104
105
|
seriesStyles: {},
|
|
106
|
+
watermark: true,
|
|
105
107
|
};
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -133,5 +135,6 @@ export function makeScatterSpec(): NormalizedChartSpec {
|
|
|
133
135
|
labels: { density: 'auto', format: '', prefix: '' },
|
|
134
136
|
hiddenSeries: [],
|
|
135
137
|
seriesStyles: {},
|
|
138
|
+
watermark: true,
|
|
136
139
|
};
|
|
137
140
|
}
|
package/src/compile.ts
CHANGED
|
@@ -222,6 +222,10 @@ export function compileChart(spec: unknown, options: CompileOptions): ChartLayou
|
|
|
222
222
|
|
|
223
223
|
let chartSpec = normalized as NormalizedChartSpec;
|
|
224
224
|
|
|
225
|
+
// Resolve watermark: explicit spec value wins, then options fallback, then default true.
|
|
226
|
+
const rawWatermark = (spec as Record<string, unknown>).watermark;
|
|
227
|
+
const watermark = rawWatermark !== undefined ? chartSpec.watermark : (options.watermark ?? true);
|
|
228
|
+
|
|
225
229
|
// Run data transforms (filter, bin, calculate, timeUnit) before any other data processing.
|
|
226
230
|
// Transforms are defined on the original spec, not the normalized spec, since
|
|
227
231
|
// NormalizedChartSpec doesn't carry the transform field.
|
|
@@ -306,10 +310,10 @@ export function compileChart(spec: unknown, options: CompileOptions): ChartLayou
|
|
|
306
310
|
width: options.width,
|
|
307
311
|
height: options.height,
|
|
308
312
|
};
|
|
309
|
-
const legendLayout = computeLegend(chartSpec, strategy, theme, preliminaryArea);
|
|
313
|
+
const legendLayout = computeLegend(chartSpec, strategy, theme, preliminaryArea, watermark);
|
|
310
314
|
|
|
311
315
|
// Compute dimensions (accounts for chrome + legend + responsive strategy)
|
|
312
|
-
const dims = computeDimensions(chartSpec, options, legendLayout, theme, strategy);
|
|
316
|
+
const dims = computeDimensions(chartSpec, options, legendLayout, theme, strategy, watermark);
|
|
313
317
|
const chartArea = dims.chartArea;
|
|
314
318
|
|
|
315
319
|
// Recompute legend bounds relative to actual chart area.
|
|
@@ -332,7 +336,7 @@ export function compileChart(spec: unknown, options: CompileOptions): ChartLayou
|
|
|
332
336
|
break;
|
|
333
337
|
}
|
|
334
338
|
}
|
|
335
|
-
const finalLegend = computeLegend(chartSpec, strategy, theme, legendArea);
|
|
339
|
+
const finalLegend = computeLegend(chartSpec, strategy, theme, legendArea, watermark);
|
|
336
340
|
|
|
337
341
|
// Apply data filtering after legend (so legend retains all series), but before
|
|
338
342
|
// scale computation (so hidden/clipped data doesn't affect domains or marks).
|
|
@@ -444,14 +448,16 @@ export function compileChart(spec: unknown, options: CompileOptions): ChartLayou
|
|
|
444
448
|
// Add brand watermark as an obstacle so annotations avoid overlapping it.
|
|
445
449
|
// The brand is right-aligned on the same baseline as the first bottom chrome element,
|
|
446
450
|
// offset below the chart area by x-axis extent (tick labels + axis title).
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
451
|
+
if (watermark) {
|
|
452
|
+
const brandPadding = theme.spacing.padding;
|
|
453
|
+
const brandX = dims.total.width - brandPadding - BRAND_RESERVE_WIDTH;
|
|
454
|
+
const xAxisExtent = axes.x?.label ? 48 : axes.x ? 26 : 0;
|
|
455
|
+
const firstBottomChrome = dims.chrome.source ?? dims.chrome.byline ?? dims.chrome.footer;
|
|
456
|
+
const brandY = firstBottomChrome
|
|
457
|
+
? chartArea.y + chartArea.height + xAxisExtent + firstBottomChrome.y
|
|
458
|
+
: chartArea.y + chartArea.height + xAxisExtent + theme.spacing.chartToFooter;
|
|
459
|
+
obstacles.push({ x: brandX, y: brandY, width: BRAND_RESERVE_WIDTH, height: 30 });
|
|
460
|
+
}
|
|
455
461
|
const annotations: ResolvedAnnotation[] = computeAnnotations(
|
|
456
462
|
chartSpec,
|
|
457
463
|
scales,
|
|
@@ -547,6 +553,7 @@ export function compileChart(spec: unknown, options: CompileOptions): ChartLayou
|
|
|
547
553
|
height: options.height,
|
|
548
554
|
},
|
|
549
555
|
animation: resolvedAnimation,
|
|
556
|
+
watermark,
|
|
550
557
|
};
|
|
551
558
|
}
|
|
552
559
|
|
|
@@ -656,6 +663,7 @@ function buildPrimarySpec(leaves: ChartSpec[], layerSpec: LayerSpec): ChartSpec
|
|
|
656
663
|
responsive: layerSpec.responsive ?? leaves[0].responsive,
|
|
657
664
|
theme: layerSpec.theme ?? leaves[0].theme,
|
|
658
665
|
darkMode: layerSpec.darkMode ?? leaves[0].darkMode,
|
|
666
|
+
watermark: layerSpec.watermark ?? leaves[0].watermark,
|
|
659
667
|
hiddenSeries: layerSpec.hiddenSeries ?? leaves[0].hiddenSeries,
|
|
660
668
|
};
|
|
661
669
|
|
|
@@ -697,7 +705,11 @@ export function compileTable(spec: unknown, options: CompileTableOptions): Table
|
|
|
697
705
|
theme = adaptTheme(theme);
|
|
698
706
|
}
|
|
699
707
|
|
|
700
|
-
|
|
708
|
+
// Resolve watermark: spec-level wins, then options, then default true
|
|
709
|
+
const rawWatermark = (spec as Record<string, unknown>).watermark;
|
|
710
|
+
const watermark = rawWatermark !== undefined ? tableSpec.watermark : (options.watermark ?? true);
|
|
711
|
+
|
|
712
|
+
return compileTableLayout({ ...tableSpec, watermark }, options, theme);
|
|
701
713
|
}
|
|
702
714
|
|
|
703
715
|
// ---------------------------------------------------------------------------
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ChartSpec,
|
|
3
3
|
GraphSpec,
|
|
4
|
+
LayerSpec,
|
|
4
5
|
RangeAnnotation,
|
|
5
6
|
RefLineAnnotation,
|
|
7
|
+
SankeySpec,
|
|
6
8
|
TableSpec,
|
|
7
9
|
TextAnnotation,
|
|
8
10
|
} from '@opendata-ai/openchart-core';
|
|
9
11
|
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import type { NormalizedSankeySpec } from '../../sankey/types';
|
|
10
13
|
import { normalizeSpec } from '../normalize';
|
|
11
14
|
import type { NormalizedChartSpec, NormalizedGraphSpec, NormalizedTableSpec } from '../types';
|
|
12
15
|
|
|
@@ -52,6 +55,17 @@ describe('normalizeSpec', () => {
|
|
|
52
55
|
});
|
|
53
56
|
});
|
|
54
57
|
|
|
58
|
+
it('watermark defaults to true when not specified', () => {
|
|
59
|
+
const result = normalizeSpec(lineSpec) as NormalizedChartSpec;
|
|
60
|
+
expect(result.watermark).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('watermark respects explicit false', () => {
|
|
64
|
+
const spec: ChartSpec = { ...lineSpec, watermark: false };
|
|
65
|
+
const result = normalizeSpec(spec) as NormalizedChartSpec;
|
|
66
|
+
expect(result.watermark).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
55
69
|
it('preserves explicit values', () => {
|
|
56
70
|
const spec: ChartSpec = {
|
|
57
71
|
...lineSpec,
|
|
@@ -205,6 +219,27 @@ describe('normalizeSpec', () => {
|
|
|
205
219
|
expect(result.responsive).toBe(true);
|
|
206
220
|
expect(result.darkMode).toBe('off');
|
|
207
221
|
});
|
|
222
|
+
|
|
223
|
+
it('watermark defaults to true when not specified', () => {
|
|
224
|
+
const spec: TableSpec = {
|
|
225
|
+
type: 'table',
|
|
226
|
+
data: [{ name: 'Alice', age: 30 }],
|
|
227
|
+
columns: [{ key: 'name' }, { key: 'age' }],
|
|
228
|
+
};
|
|
229
|
+
const result = normalizeSpec(spec) as NormalizedTableSpec;
|
|
230
|
+
expect(result.watermark).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('watermark respects explicit false', () => {
|
|
234
|
+
const spec: TableSpec = {
|
|
235
|
+
type: 'table',
|
|
236
|
+
data: [{ name: 'Alice', age: 30 }],
|
|
237
|
+
columns: [{ key: 'name' }, { key: 'age' }],
|
|
238
|
+
watermark: false,
|
|
239
|
+
};
|
|
240
|
+
const result = normalizeSpec(spec) as NormalizedTableSpec;
|
|
241
|
+
expect(result.watermark).toBe(false);
|
|
242
|
+
});
|
|
208
243
|
});
|
|
209
244
|
|
|
210
245
|
describe('graph spec normalization', () => {
|
|
@@ -221,5 +256,80 @@ describe('normalizeSpec', () => {
|
|
|
221
256
|
expect(result.annotations).toEqual([]);
|
|
222
257
|
expect(result.darkMode).toBe('off');
|
|
223
258
|
});
|
|
259
|
+
|
|
260
|
+
it('watermark defaults to true when not specified', () => {
|
|
261
|
+
const spec: GraphSpec = {
|
|
262
|
+
type: 'graph',
|
|
263
|
+
nodes: [{ id: 'a' }, { id: 'b' }],
|
|
264
|
+
edges: [{ source: 'a', target: 'b' }],
|
|
265
|
+
};
|
|
266
|
+
const result = normalizeSpec(spec) as NormalizedGraphSpec;
|
|
267
|
+
expect(result.watermark).toBe(true);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('watermark respects explicit false', () => {
|
|
271
|
+
const spec: GraphSpec = {
|
|
272
|
+
type: 'graph',
|
|
273
|
+
nodes: [{ id: 'a' }, { id: 'b' }],
|
|
274
|
+
edges: [{ source: 'a', target: 'b' }],
|
|
275
|
+
watermark: false,
|
|
276
|
+
};
|
|
277
|
+
const result = normalizeSpec(spec) as NormalizedGraphSpec;
|
|
278
|
+
expect(result.watermark).toBe(false);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe('sankey spec normalization', () => {
|
|
283
|
+
const baseSankeySpec: SankeySpec = {
|
|
284
|
+
type: 'sankey',
|
|
285
|
+
data: [{ source: 'A', target: 'B', value: 10 }],
|
|
286
|
+
encoding: {
|
|
287
|
+
source: { field: 'source' },
|
|
288
|
+
target: { field: 'target' },
|
|
289
|
+
value: { field: 'value' },
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
it('watermark defaults to true when not specified', () => {
|
|
294
|
+
const result = normalizeSpec(baseSankeySpec) as NormalizedSankeySpec;
|
|
295
|
+
expect(result.watermark).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('watermark respects explicit false', () => {
|
|
299
|
+
const spec: SankeySpec = { ...baseSankeySpec, watermark: false };
|
|
300
|
+
const result = normalizeSpec(spec) as NormalizedSankeySpec;
|
|
301
|
+
expect(result.watermark).toBe(false);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe('layer spec normalization', () => {
|
|
306
|
+
const baseLeaf: ChartSpec = {
|
|
307
|
+
mark: 'line',
|
|
308
|
+
data: [{ x: 1, y: 2 }],
|
|
309
|
+
encoding: {
|
|
310
|
+
x: { field: 'x', type: 'quantitative' },
|
|
311
|
+
y: { field: 'y', type: 'quantitative' },
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
it('watermark defaults to true for layer leaves', () => {
|
|
316
|
+
const layerSpec: LayerSpec = { layer: [baseLeaf] };
|
|
317
|
+
const result = normalizeSpec(layerSpec) as NormalizedChartSpec;
|
|
318
|
+
expect(result.watermark).toBe(true);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('layer-level watermark: false propagates to leaves', () => {
|
|
322
|
+
const layerSpec: LayerSpec = { layer: [baseLeaf], watermark: false };
|
|
323
|
+
const result = normalizeSpec(layerSpec) as NormalizedChartSpec;
|
|
324
|
+
expect(result.watermark).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('leaf-level watermark overrides layer-level', () => {
|
|
328
|
+
const leaf: ChartSpec = { ...baseLeaf, watermark: true };
|
|
329
|
+
const layerSpec: LayerSpec = { layer: [leaf], watermark: false };
|
|
330
|
+
const result = normalizeSpec(layerSpec) as NormalizedChartSpec;
|
|
331
|
+
// Leaf explicitly sets true, which should be preserved
|
|
332
|
+
expect(result.watermark).toBe(true);
|
|
333
|
+
});
|
|
224
334
|
});
|
|
225
335
|
});
|
|
@@ -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/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
|
}
|