@opendata-ai/openchart-core 6.11.0 → 6.13.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 +83 -10
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/helpers/__tests__/spec-builders.test.ts +4 -4
- package/src/index.ts +1 -2
- package/src/layout/__tests__/chrome.test.ts +15 -0
- package/src/layout/chrome.ts +6 -4
- package/src/types/index.ts +2 -0
- package/src/types/layout.ts +8 -0
- package/src/types/spec.ts +88 -10
package/package.json
CHANGED
|
@@ -112,7 +112,7 @@ describe('lineChart', () => {
|
|
|
112
112
|
const xChannel: EncodingChannel = {
|
|
113
113
|
field: 'date',
|
|
114
114
|
type: 'temporal',
|
|
115
|
-
axis: {
|
|
115
|
+
axis: { title: 'Year' },
|
|
116
116
|
};
|
|
117
117
|
const yChannel: EncodingChannel = {
|
|
118
118
|
field: 'value',
|
|
@@ -173,7 +173,7 @@ describe('barChart', () => {
|
|
|
173
173
|
const catChannel: EncodingChannel = {
|
|
174
174
|
field: 'name',
|
|
175
175
|
type: 'ordinal',
|
|
176
|
-
axis: {
|
|
176
|
+
axis: { title: 'Fruit' },
|
|
177
177
|
};
|
|
178
178
|
|
|
179
179
|
const spec = barChart(categoricalData, catChannel, 'count');
|
|
@@ -320,7 +320,7 @@ describe('mixed FieldRef usage', () => {
|
|
|
320
320
|
field: 'value',
|
|
321
321
|
type: 'quantitative',
|
|
322
322
|
aggregate: 'mean',
|
|
323
|
-
axis: {
|
|
323
|
+
axis: { title: 'Average Value', format: ',.1f' },
|
|
324
324
|
};
|
|
325
325
|
|
|
326
326
|
const spec = lineChart(timeSeriesData, 'date', yChannel, {
|
|
@@ -331,7 +331,7 @@ describe('mixed FieldRef usage', () => {
|
|
|
331
331
|
expect(spec.encoding.x?.type).toBe('temporal');
|
|
332
332
|
// y was a full object, so it's passed through
|
|
333
333
|
expect(spec.encoding.y?.aggregate).toBe('mean');
|
|
334
|
-
expect(spec.encoding.y?.axis?.
|
|
334
|
+
expect(spec.encoding.y?.axis?.title).toBe('Average Value');
|
|
335
335
|
// color was a full object
|
|
336
336
|
expect(spec.encoding.color?.type).toBe('nominal');
|
|
337
337
|
});
|
package/src/index.ts
CHANGED
|
@@ -17,8 +17,7 @@ export * from './types/index';
|
|
|
17
17
|
// Colors: palette collections, contrast utilities, color-blindness simulation
|
|
18
18
|
//
|
|
19
19
|
// Individual named palettes (SEQUENTIAL_BLUE, DIVERGING_RED_BLUE, etc.) are
|
|
20
|
-
// available via SEQUENTIAL_PALETTES and DIVERGING_PALETTES
|
|
21
|
-
// from '@opendata-ai/openchart-core/src/colors/palettes'.
|
|
20
|
+
// available via the SEQUENTIAL_PALETTES and DIVERGING_PALETTES collections.
|
|
22
21
|
// ---------------------------------------------------------------------------
|
|
23
22
|
|
|
24
23
|
export type {
|
|
@@ -190,4 +190,19 @@ describe('computeChrome', () => {
|
|
|
190
190
|
expect(result.title).toBeDefined();
|
|
191
191
|
// Just verify it runs without error; exact values depend on measure fn
|
|
192
192
|
});
|
|
193
|
+
|
|
194
|
+
it('returns zero bottom height when watermark is false and no bottom chrome', () => {
|
|
195
|
+
const chrome: Chrome = { title: 'Title' };
|
|
196
|
+
const result = computeChrome(chrome, theme, 600, undefined, 'full', undefined, false);
|
|
197
|
+
expect(result.bottomHeight).toBe(0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('does not reserve brand width for bottom text when watermark is false', () => {
|
|
201
|
+
const chrome: Chrome = { source: 'Source: World Bank' };
|
|
202
|
+
const withWatermark = computeChrome(chrome, theme, 600, undefined, 'full', undefined, true);
|
|
203
|
+
const withoutWatermark = computeChrome(chrome, theme, 600, undefined, 'full', undefined, false);
|
|
204
|
+
|
|
205
|
+
// Without watermark, source text gets full width (not reduced by BRAND_RESERVE_WIDTH)
|
|
206
|
+
expect(withoutWatermark.source!.maxWidth).toBeGreaterThan(withWatermark.source!.maxWidth);
|
|
207
|
+
});
|
|
193
208
|
});
|
package/src/layout/chrome.ts
CHANGED
|
@@ -148,6 +148,7 @@ export function computeChrome(
|
|
|
148
148
|
measureText?: MeasureTextFn,
|
|
149
149
|
chromeMode: ChromeMode = 'full',
|
|
150
150
|
padding?: number,
|
|
151
|
+
watermark: boolean = true,
|
|
151
152
|
): ResolvedChrome {
|
|
152
153
|
if (!chrome || chromeMode === 'hidden') {
|
|
153
154
|
// Brand watermark is also skipped at cramped sizes (height < 200px triggers
|
|
@@ -217,7 +218,7 @@ export function computeChrome(
|
|
|
217
218
|
// renders for wide-enough charts. Reserve space so it doesn't overflow.
|
|
218
219
|
if (chromeMode === 'compact') {
|
|
219
220
|
let compactBottom = 0;
|
|
220
|
-
if (width >= BRAND_MIN_WIDTH) {
|
|
221
|
+
if (watermark && width >= BRAND_MIN_WIDTH) {
|
|
221
222
|
const brandHeight = estimateTextHeight(BRAND_FONT_SIZE, 1);
|
|
222
223
|
compactBottom = theme.spacing.chartToFooter + brandHeight + pad;
|
|
223
224
|
}
|
|
@@ -230,7 +231,8 @@ export function computeChrome(
|
|
|
230
231
|
|
|
231
232
|
// Bottom elements: source, byline, footer
|
|
232
233
|
// Reserve space on the right for the brand watermark so text doesn't overlap it
|
|
233
|
-
const
|
|
234
|
+
const shouldReserveBrandWidth = watermark && width >= BRAND_MIN_WIDTH;
|
|
235
|
+
const bottomMaxWidth = maxWidth - (shouldReserveBrandWidth ? BRAND_RESERVE_WIDTH : 0);
|
|
234
236
|
const bottomElements: Partial<Pick<ResolvedChrome, 'source' | 'byline' | 'footer'>> = {};
|
|
235
237
|
let bottomHeight = 0;
|
|
236
238
|
|
|
@@ -300,7 +302,7 @@ export function computeChrome(
|
|
|
300
302
|
// Ensure bottom height accommodates the brand watermark, which renders
|
|
301
303
|
// at the same Y as the first bottom chrome item but is taller (20px font
|
|
302
304
|
// vs 12px source). Without this, the brand overflows the SVG viewBox.
|
|
303
|
-
if (width >= BRAND_MIN_WIDTH) {
|
|
305
|
+
if (watermark && width >= BRAND_MIN_WIDTH) {
|
|
304
306
|
const brandHeight = estimateTextHeight(BRAND_FONT_SIZE, 1);
|
|
305
307
|
// firstItemY is chartToFooter (the Y offset of the first bottom item).
|
|
306
308
|
// The brand needs brandHeight below that point; bottom chrome content
|
|
@@ -313,7 +315,7 @@ export function computeChrome(
|
|
|
313
315
|
|
|
314
316
|
// Add bottom padding
|
|
315
317
|
bottomHeight += pad;
|
|
316
|
-
} else if (width >= BRAND_MIN_WIDTH) {
|
|
318
|
+
} else if (watermark && width >= BRAND_MIN_WIDTH) {
|
|
317
319
|
// No bottom chrome items, but brand watermark still renders.
|
|
318
320
|
// Reserve space: chartToFooter gap + brand text height + padding.
|
|
319
321
|
const brandHeight = estimateTextHeight(BRAND_FONT_SIZE, 1);
|
package/src/types/index.ts
CHANGED
|
@@ -84,6 +84,7 @@ export type {
|
|
|
84
84
|
// Spec types (user input)
|
|
85
85
|
export type {
|
|
86
86
|
AggregateOp,
|
|
87
|
+
AggregateTransform,
|
|
87
88
|
AnimationConfig,
|
|
88
89
|
AnimationEase,
|
|
89
90
|
AnimationPhaseConfig,
|
|
@@ -114,6 +115,7 @@ export type {
|
|
|
114
115
|
FieldType,
|
|
115
116
|
FilterPredicate,
|
|
116
117
|
FilterTransform,
|
|
118
|
+
FoldTransform,
|
|
117
119
|
GradientDef,
|
|
118
120
|
GradientStop,
|
|
119
121
|
GraphEdge,
|
package/src/types/layout.ts
CHANGED
|
@@ -667,6 +667,8 @@ export interface ChartLayout {
|
|
|
667
667
|
dimensions: { width: number; height: number };
|
|
668
668
|
/** Resolved animation config. Present only when animation is enabled. */
|
|
669
669
|
animation?: ResolvedAnimation;
|
|
670
|
+
/** Whether the tryOpenData.ai watermark is enabled. */
|
|
671
|
+
watermark: boolean;
|
|
670
672
|
}
|
|
671
673
|
|
|
672
674
|
// ---------------------------------------------------------------------------
|
|
@@ -843,6 +845,8 @@ export interface TableLayout {
|
|
|
843
845
|
theme: ResolvedTheme;
|
|
844
846
|
/** Resolved animation config. Present only when animation is enabled. */
|
|
845
847
|
animation?: ResolvedAnimation;
|
|
848
|
+
/** Whether the tryOpenData.ai watermark is enabled. */
|
|
849
|
+
watermark: boolean;
|
|
846
850
|
}
|
|
847
851
|
|
|
848
852
|
// ---------------------------------------------------------------------------
|
|
@@ -1020,6 +1024,8 @@ export interface SankeyLayout {
|
|
|
1020
1024
|
dimensions: { width: number; height: number };
|
|
1021
1025
|
/** Resolved animation config. Present only when animation is enabled. */
|
|
1022
1026
|
animation?: ResolvedAnimation;
|
|
1027
|
+
/** Whether the tryOpenData.ai watermark is enabled. */
|
|
1028
|
+
watermark: boolean;
|
|
1023
1029
|
}
|
|
1024
1030
|
|
|
1025
1031
|
// ---------------------------------------------------------------------------
|
|
@@ -1054,6 +1060,8 @@ export interface CompileOptions {
|
|
|
1054
1060
|
* before calling compile. The engine always receives a resolved boolean.
|
|
1055
1061
|
*/
|
|
1056
1062
|
darkMode?: boolean;
|
|
1063
|
+
/** Whether to show the tryOpenData.ai watermark. Defaults to true. */
|
|
1064
|
+
watermark?: boolean;
|
|
1057
1065
|
/**
|
|
1058
1066
|
* Real text measurement function provided by the adapter.
|
|
1059
1067
|
* Uses a hidden canvas or DOM element for accurate text dimensions.
|
package/src/types/spec.ts
CHANGED
|
@@ -195,7 +195,18 @@ export interface MarkDef {
|
|
|
195
195
|
export type FieldType = 'quantitative' | 'temporal' | 'nominal' | 'ordinal';
|
|
196
196
|
|
|
197
197
|
/** Aggregate function applied to a field before encoding. */
|
|
198
|
-
export type AggregateOp =
|
|
198
|
+
export type AggregateOp =
|
|
199
|
+
| 'count'
|
|
200
|
+
| 'sum'
|
|
201
|
+
| 'mean'
|
|
202
|
+
| 'median'
|
|
203
|
+
| 'min'
|
|
204
|
+
| 'max'
|
|
205
|
+
| 'variance'
|
|
206
|
+
| 'stdev'
|
|
207
|
+
| 'distinct'
|
|
208
|
+
| 'q1'
|
|
209
|
+
| 'q3';
|
|
199
210
|
|
|
200
211
|
/** Axis configuration for an encoding channel. */
|
|
201
212
|
export interface AxisConfig {
|
|
@@ -227,12 +238,6 @@ export interface AxisConfig {
|
|
|
227
238
|
titlePadding?: number;
|
|
228
239
|
/** Padding between tick labels and axis. */
|
|
229
240
|
labelPadding?: number;
|
|
230
|
-
|
|
231
|
-
// --- Deprecated aliases (will be removed) ---
|
|
232
|
-
/** @deprecated Use `title` instead. */
|
|
233
|
-
label?: string;
|
|
234
|
-
/** @deprecated Use `labelAngle` instead. */
|
|
235
|
-
tickAngle?: number;
|
|
236
241
|
}
|
|
237
242
|
|
|
238
243
|
/** Scale configuration for an encoding channel. */
|
|
@@ -309,10 +314,47 @@ export interface EncodingChannel {
|
|
|
309
314
|
scale?: ScaleConfig;
|
|
310
315
|
/**
|
|
311
316
|
* Stacking behavior for quantitative channels (Vega-Lite aligned).
|
|
312
|
-
* - undefined | true | 'zero': stack (default
|
|
317
|
+
* - undefined | true | 'zero': stack from zero baseline (default)
|
|
318
|
+
* - 'normalize': stack and normalize to fraction of total (0-1 per category)
|
|
319
|
+
* - 'center': center stacks around zero (streamgraph style)
|
|
313
320
|
* - null | false: no stacking (grouped/dodged side-by-side)
|
|
314
321
|
*/
|
|
315
|
-
stack?: boolean | 'zero' | null;
|
|
322
|
+
stack?: boolean | 'zero' | 'normalize' | 'center' | null;
|
|
323
|
+
/**
|
|
324
|
+
* Encoding-level bin shorthand (Vega-Lite aligned).
|
|
325
|
+
* When set, auto-generates a BinTransform during normalization and updates
|
|
326
|
+
* the field reference to the binned output (convention: `bin_<fieldName>`).
|
|
327
|
+
* - true: bin with default params
|
|
328
|
+
* - BinParams: bin with custom params (maxbins, step, etc.)
|
|
329
|
+
*/
|
|
330
|
+
bin?: boolean | BinParams;
|
|
331
|
+
/**
|
|
332
|
+
* Encoding-level timeUnit shorthand (Vega-Lite aligned).
|
|
333
|
+
* When set, auto-generates a TimeUnitTransform during normalization and
|
|
334
|
+
* updates the field reference to the output (convention: `<timeUnit>_<fieldName>`).
|
|
335
|
+
*/
|
|
336
|
+
timeUnit?: TimeUnit;
|
|
337
|
+
/**
|
|
338
|
+
* Sort order for categorical (nominal/ordinal) scale domains (Vega-Lite aligned).
|
|
339
|
+
* - 'ascending': sort domain values ascending (default VL behavior)
|
|
340
|
+
* - 'descending': sort domain values descending
|
|
341
|
+
* - null: use data order (no sorting)
|
|
342
|
+
* - undefined: ascending (VL default)
|
|
343
|
+
*/
|
|
344
|
+
sort?: 'ascending' | 'descending' | null;
|
|
345
|
+
/**
|
|
346
|
+
* Display title override (Vega-Lite aligned).
|
|
347
|
+
* Used as the label in tooltips instead of the raw field name.
|
|
348
|
+
* Also usable for axis titles, but `axis.title` takes precedence there.
|
|
349
|
+
*/
|
|
350
|
+
title?: string;
|
|
351
|
+
/**
|
|
352
|
+
* Format string for values (Vega-Lite aligned).
|
|
353
|
+
* For quantitative fields: d3-format string (e.g. ",.0f", "$,.2f").
|
|
354
|
+
* For temporal fields: d3-time-format string (e.g. "%Y", "%b %d").
|
|
355
|
+
* Used in tooltips; `axis.format` takes precedence for axis tick labels.
|
|
356
|
+
*/
|
|
357
|
+
format?: string;
|
|
316
358
|
}
|
|
317
359
|
|
|
318
360
|
/**
|
|
@@ -804,6 +846,8 @@ export interface ChartSpec {
|
|
|
804
846
|
theme?: ThemeConfig;
|
|
805
847
|
/** Dark mode behavior. Defaults to "off". */
|
|
806
848
|
darkMode?: DarkMode;
|
|
849
|
+
/** Whether to show the tryOpenData.ai watermark. Defaults to true. */
|
|
850
|
+
watermark?: boolean;
|
|
807
851
|
/** Series names to hide from rendering. Hidden series remain in the legend but are visually dimmed. */
|
|
808
852
|
hiddenSeries?: string[];
|
|
809
853
|
/** Per-series visual overrides, keyed by series name (the color field value). */
|
|
@@ -844,6 +888,8 @@ export interface TableSpec {
|
|
|
844
888
|
theme?: ThemeConfig;
|
|
845
889
|
/** Dark mode behavior. */
|
|
846
890
|
darkMode?: DarkMode;
|
|
891
|
+
/** Whether to show the tryOpenData.ai watermark. Defaults to true. */
|
|
892
|
+
watermark?: boolean;
|
|
847
893
|
/** Enable client-side search/filter. */
|
|
848
894
|
search?: boolean;
|
|
849
895
|
/** Pagination configuration. True for defaults, or an object with pageSize. */
|
|
@@ -911,6 +957,8 @@ export interface GraphSpec {
|
|
|
911
957
|
theme?: ThemeConfig;
|
|
912
958
|
/** Dark mode behavior. */
|
|
913
959
|
darkMode?: DarkMode;
|
|
960
|
+
/** Whether to show the tryOpenData.ai watermark. Defaults to true. */
|
|
961
|
+
watermark?: boolean;
|
|
914
962
|
}
|
|
915
963
|
|
|
916
964
|
// ---------------------------------------------------------------------------
|
|
@@ -964,6 +1012,8 @@ export interface LayerSpec {
|
|
|
964
1012
|
theme?: ThemeConfig;
|
|
965
1013
|
/** Dark mode behavior. Defaults to "off". */
|
|
966
1014
|
darkMode?: DarkMode;
|
|
1015
|
+
/** Whether to show the tryOpenData.ai watermark. Defaults to true. */
|
|
1016
|
+
watermark?: boolean;
|
|
967
1017
|
/** Resolution strategy for shared scales/axes/legends. */
|
|
968
1018
|
resolve?: ResolveConfig;
|
|
969
1019
|
/** Hidden series names. */
|
|
@@ -1030,6 +1080,8 @@ export interface SankeySpec {
|
|
|
1030
1080
|
theme?: ThemeConfig;
|
|
1031
1081
|
/** Dark mode behavior. Defaults to "off". */
|
|
1032
1082
|
darkMode?: DarkMode;
|
|
1083
|
+
/** Whether to show the tryOpenData.ai watermark. Defaults to true. */
|
|
1084
|
+
watermark?: boolean;
|
|
1033
1085
|
/** Animation configuration for entrance animations. */
|
|
1034
1086
|
animation?: AnimationSpec;
|
|
1035
1087
|
/**
|
|
@@ -1188,8 +1240,34 @@ export interface TimeUnitTransform {
|
|
|
1188
1240
|
as: string;
|
|
1189
1241
|
}
|
|
1190
1242
|
|
|
1243
|
+
/**
|
|
1244
|
+
* Aggregate transform: group rows and compute summary statistics (VL aligned).
|
|
1245
|
+
* Produces one row per group with the groupby fields and computed aggregates.
|
|
1246
|
+
*/
|
|
1247
|
+
export interface AggregateTransform {
|
|
1248
|
+
aggregate: Array<{ op: AggregateOp; field: string; as: string }>;
|
|
1249
|
+
groupby: string[];
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Fold transform: unpivot wide-format columns into key/value rows (VL aligned).
|
|
1254
|
+
* For each input row, produces N output rows (one per fold field) with all
|
|
1255
|
+
* non-fold fields copied plus a key column (field name) and value column (field value).
|
|
1256
|
+
*/
|
|
1257
|
+
export interface FoldTransform {
|
|
1258
|
+
fold: string[];
|
|
1259
|
+
/** Output field names for [key, value]. Defaults to ['key', 'value']. */
|
|
1260
|
+
as?: [string, string];
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1191
1263
|
/** Discriminated union of all transform types. */
|
|
1192
|
-
export type Transform =
|
|
1264
|
+
export type Transform =
|
|
1265
|
+
| FilterTransform
|
|
1266
|
+
| BinTransform
|
|
1267
|
+
| CalculateTransform
|
|
1268
|
+
| TimeUnitTransform
|
|
1269
|
+
| AggregateTransform
|
|
1270
|
+
| FoldTransform;
|
|
1193
1271
|
|
|
1194
1272
|
// ---------------------------------------------------------------------------
|
|
1195
1273
|
// Conditional encoding (Vega-Lite aligned)
|