@opendata-ai/openchart-engine 8.0.0-rc.5 → 8.0.0-rc.6
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.js +4 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/geo/__tests__/compile-map.test.ts +49 -0
- package/src/geo/compile-map.ts +12 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-engine",
|
|
3
|
-
"version": "8.0.0-rc.
|
|
3
|
+
"version": "8.0.0-rc.6",
|
|
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",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"typecheck": "tsc --noEmit"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@opendata-ai/openchart-core": "8.0.0-rc.
|
|
51
|
+
"@opendata-ai/openchart-core": "8.0.0-rc.6",
|
|
52
52
|
"d3-array": "^3.2.0",
|
|
53
53
|
"d3-format": "^3.1.2",
|
|
54
54
|
"d3-interpolate": "^3.0.0",
|
|
@@ -296,6 +296,55 @@ describe('compileMap', () => {
|
|
|
296
296
|
expect(layout.continuousLegend).toBeNull();
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
+
it('categorical legend defaults to top and sits above the map area', () => {
|
|
300
|
+
const layout = compileMap(
|
|
301
|
+
{
|
|
302
|
+
type: 'map',
|
|
303
|
+
geo: { features: MINI_TOPO, projection: 'mercator' },
|
|
304
|
+
data: [
|
|
305
|
+
{ fips: '06', category: 'west' },
|
|
306
|
+
{ fips: '48', category: 'south' },
|
|
307
|
+
],
|
|
308
|
+
encoding: {
|
|
309
|
+
key: { field: 'fips', type: 'nominal' },
|
|
310
|
+
color: { field: 'category', type: 'nominal' },
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
DEFAULT_OPTIONS,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const legend = layout.categoricalLegend!;
|
|
317
|
+
expect(legend.position).toBe('top');
|
|
318
|
+
// The map area shifts down to make room for a top legend; the legend row
|
|
319
|
+
// must land above it, not inside it (the old bottom-anchored legend
|
|
320
|
+
// overlapped the map because the two disagreed about the layout).
|
|
321
|
+
expect(legend.bounds.y + legend.bounds.height).toBeLessThanOrEqual(layout.area.y);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('honors position: bottom for categorical legend', () => {
|
|
325
|
+
const layout = compileMap(
|
|
326
|
+
{
|
|
327
|
+
type: 'map',
|
|
328
|
+
geo: { features: MINI_TOPO, projection: 'mercator' },
|
|
329
|
+
data: [
|
|
330
|
+
{ fips: '06', category: 'west' },
|
|
331
|
+
{ fips: '48', category: 'south' },
|
|
332
|
+
],
|
|
333
|
+
encoding: {
|
|
334
|
+
key: { field: 'fips', type: 'nominal' },
|
|
335
|
+
color: { field: 'category', type: 'nominal' },
|
|
336
|
+
},
|
|
337
|
+
legend: { position: 'bottom' },
|
|
338
|
+
},
|
|
339
|
+
DEFAULT_OPTIONS,
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const legend = layout.categoricalLegend!;
|
|
343
|
+
expect(legend.position).toBe('bottom');
|
|
344
|
+
// Bottom legend starts at or below the map area's bottom edge.
|
|
345
|
+
expect(legend.bounds.y).toBeGreaterThanOrEqual(layout.area.y + layout.area.height);
|
|
346
|
+
});
|
|
347
|
+
|
|
299
348
|
it('assigns neutral fill to features without data', () => {
|
|
300
349
|
const layout = compileMap(
|
|
301
350
|
{
|
package/src/geo/compile-map.ts
CHANGED
|
@@ -360,6 +360,7 @@ export function compileMap(spec: unknown, options: CompileOptions): MapLayout {
|
|
|
360
360
|
fullArea,
|
|
361
361
|
mapAreaHeight,
|
|
362
362
|
legendReserveGap,
|
|
363
|
+
legendPosition,
|
|
363
364
|
showLegend,
|
|
364
365
|
droppedFeatures,
|
|
365
366
|
});
|
|
@@ -1010,6 +1011,7 @@ interface CategoricalOptions {
|
|
|
1010
1011
|
fullArea: { x: number; y: number; width: number; height: number };
|
|
1011
1012
|
mapAreaHeight: number;
|
|
1012
1013
|
legendReserveGap: number;
|
|
1014
|
+
legendPosition: 'top' | 'bottom';
|
|
1013
1015
|
showLegend: boolean;
|
|
1014
1016
|
droppedFeatures: string[];
|
|
1015
1017
|
}
|
|
@@ -1031,6 +1033,7 @@ function buildCategoricalMarks(opts: CategoricalOptions): {
|
|
|
1031
1033
|
fullArea,
|
|
1032
1034
|
mapAreaHeight,
|
|
1033
1035
|
legendReserveGap,
|
|
1036
|
+
legendPosition,
|
|
1034
1037
|
showLegend,
|
|
1035
1038
|
droppedFeatures,
|
|
1036
1039
|
} = opts;
|
|
@@ -1142,8 +1145,15 @@ function buildCategoricalMarks(opts: CategoricalOptions): {
|
|
|
1142
1145
|
shape: 'square' as const,
|
|
1143
1146
|
}));
|
|
1144
1147
|
|
|
1148
|
+
// Honor legend.position like the continuous path does. The map drawing
|
|
1149
|
+
// area (mapAreaY in compileMap) already shifts down for top legends, so
|
|
1150
|
+
// the legend row here must agree with it — a bottom-anchored legend with
|
|
1151
|
+
// a top-shifted map is how legends used to land on top of Alaska.
|
|
1145
1152
|
const legendX = fullArea.x;
|
|
1146
|
-
const legendY =
|
|
1153
|
+
const legendY =
|
|
1154
|
+
legendPosition === 'bottom'
|
|
1155
|
+
? fullArea.y + mapAreaHeight + legendReserveGap
|
|
1156
|
+
: fullArea.y + legendReserveGap;
|
|
1147
1157
|
const legendWidth = fullArea.width;
|
|
1148
1158
|
const swatchSize = 10;
|
|
1149
1159
|
const swatchGap = 6;
|
|
@@ -1151,7 +1161,7 @@ function buildCategoricalMarks(opts: CategoricalOptions): {
|
|
|
1151
1161
|
|
|
1152
1162
|
legend = {
|
|
1153
1163
|
type: 'categorical',
|
|
1154
|
-
position:
|
|
1164
|
+
position: legendPosition,
|
|
1155
1165
|
bounds: { x: legendX, y: legendY, width: legendWidth, height: swatchSize + 6 },
|
|
1156
1166
|
labelStyle,
|
|
1157
1167
|
entries,
|