ggaction 0.0.17 → 0.0.19
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/CHANGELOG.md +96 -0
- package/README.md +6 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +3 -3
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +20 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +8 -7
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/derived.js +11 -1
- package/src/actions/data/edit.js +3 -1
- package/src/actions/data/index.js +4 -0
- package/src/actions/data/regression.js +25 -16
- package/src/actions/data/regressionStatistics.js +73 -0
- package/src/actions/data/shared.js +8 -4
- package/src/actions/data/stack.js +3 -1
- package/src/actions/distributions/revision.js +24 -18
- package/src/actions/encodings/appearance.js +37 -29
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +1 -1
- package/src/actions/encodings/color/index.js +1 -1
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +3 -2
- package/src/actions/encodings/ranged.js +17 -0
- package/src/actions/encodings/remove.js +14 -8
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +26 -24
- package/src/actions/encodings/text.js +6 -3
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +176 -247
- package/src/actions/facets/guides.js +2 -1
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axes.js +30 -11
- package/src/actions/guides/axes/axis.js +72 -10
- package/src/actions/guides/axes/components.js +13 -13
- package/src/actions/guides/axes/edit.js +36 -24
- package/src/actions/guides/axes/labels.js +155 -71
- package/src/actions/guides/axes/lines.js +27 -24
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +13 -8
- package/src/actions/guides/axes/tickGroups.js +11 -7
- package/src/actions/guides/axes/ticks.js +36 -24
- package/src/actions/guides/axes/titles.js +107 -69
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeAxes.js +33 -24
- package/src/actions/guides/facadeLegend.js +12 -4
- package/src/actions/guides/grids/grid.js +8 -2
- package/src/actions/guides/grids/resolve.js +102 -58
- package/src/actions/guides/legends/blocks.js +46 -20
- package/src/actions/guides/legends/categorical/actions.js +95 -19
- package/src/actions/guides/legends/categorical/components.js +15 -18
- package/src/actions/guides/legends/categorical/index.js +8 -0
- package/src/actions/guides/legends/categorical/layout.js +45 -20
- package/src/actions/guides/legends/categorical/options.js +39 -22
- package/src/actions/guides/legends/categorical/recipes.js +7 -3
- package/src/actions/guides/legends/categorical/resolve.js +11 -4
- package/src/actions/guides/legends/categorical/symbols.js +38 -18
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/colorSize.js +110 -0
- package/src/actions/guides/legends/combine.js +37 -0
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +73 -46
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +33 -21
- package/src/actions/guides/legends/creation.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +8 -7
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/lifecycle.js +2 -1
- package/src/actions/guides/legends/remove.js +9 -3
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +65 -47
- package/src/actions/guides/legends/strokeWidth.js +13 -29
- package/src/actions/guides/legends/target.js +2 -79
- package/src/actions/guides/legends/transition.js +44 -11
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/guides/tickValues.js +8 -5
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +10 -4
- package/src/actions/marks/bar/edit.js +29 -14
- package/src/actions/marks/bar/materialize.js +15 -7
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/edit.js +1 -1
- package/src/actions/marks/point/materialize.js +5 -1
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +12 -10
- package/src/actions/marks/rule/actions.js +7 -4
- package/src/actions/marks/text/actions.js +17 -5
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/primitives/createGraphics.js +3 -2
- package/src/actions/primitives/editGraphics.js +15 -11
- package/src/actions/primitives/graphicProperties.js +1 -6
- package/src/actions/primitives/semanticAction.js +27 -1
- package/src/actions/primitives/semanticValidation/guide.js +11 -1
- package/src/actions/primitives/semanticValidation/layer.js +8 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +18 -6
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +9 -4
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -5
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +12 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +8 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +126 -45
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/compositionState.js +6 -3
- package/src/core/font.js +9 -2
- package/src/core/graphicProperties.js +1 -0
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +4 -0
- package/src/core/resourceReferences.js +5 -2
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +35 -0
- package/src/core/vocabulary.js +7 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/axisTextPlacement.js +1 -0
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +2 -12
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/colorSizeLegend.js +39 -0
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +115 -26
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +13 -9
- package/src/grammar/displayLabels.js +36 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +58 -30
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/facets/legendIntent.js +65 -0
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/guideDash.js +1 -0
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendBlocks.js +1 -0
- package/src/grammar/legendBorder.js +9 -0
- package/src/grammar/legendClip.js +19 -0
- package/src/grammar/legendCombinations.js +45 -0
- package/src/grammar/legendGradient.js +27 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendOpacity.js +6 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/pointShapes.js +16 -2
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +16 -2
- package/src/grammar/regression/models.js +142 -54
- package/src/grammar/regression/parameters.js +23 -4
- package/src/grammar/regression/statistics.js +64 -0
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -197
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +14 -0
- package/src/grammar/schemas/graphic.js +6 -0
- package/src/grammar/schemas/graphicBounds.js +31 -7
- package/src/grammar/schemas/semanticPath.js +19 -3
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +4 -27
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +22 -11
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +15 -2
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/guideCollisions.js +9 -1
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +22 -1
- package/src/materialization/compositionSnapshot.js +2 -7
- package/src/materialization/coordinateBounds.js +21 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/edit.js +30 -0
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/owner.js +125 -0
- package/src/materialization/facetGuides/ownerLayout.js +71 -0
- package/src/materialization/facetGuides/placement.js +71 -14
- package/src/materialization/facetGuides/preparation.js +22 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +5 -1
- package/src/materialization/graphicTranslation.js +73 -0
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendBorder.js +28 -0
- package/src/materialization/legendClip.js +76 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +2 -2
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +14 -15
- package/src/materialization/scales/map.js +6 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +7 -3
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +13 -4
- package/src/materialization/textLines.js +15 -0
- package/src/materialization/typography.js +10 -2
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +220 -0
- package/src/persistence.js +19 -6
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +22 -11
- package/src/renderers/canvas/isolated.js +166 -0
- package/src/renderers/canvas/limits.js +3 -0
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/pdf.js +2 -2
- package/src/renderers/png.js +2 -2
- package/src/renderers/svg.js +14 -4
- package/src/selectors/axes.js +1 -0
- package/src/selectors/graphicCapacity.js +38 -0
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +3 -0
- package/types/index.d.ts +12 -0
- package/types/program.d.ts +536 -129
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cartesianAxisOwners, axisTargetArgs } from "../../selectors/axes.js";
|
|
1
2
|
import { DEFAULT_TICK_COUNT } from "../guides/tickValues.js";
|
|
2
3
|
import { findLayer } from "../../selectors/layers.js";
|
|
3
4
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
@@ -296,39 +297,44 @@ export function clearCartesianPositions(program, id) {
|
|
|
296
297
|
return next;
|
|
297
298
|
}
|
|
298
299
|
|
|
299
|
-
function rebindAxis(program, channel, {
|
|
300
|
+
function rebindAxis(program, channel, options) {
|
|
301
|
+
return cartesianAxisOwners(program).filter(owner => owner.channel === channel)
|
|
302
|
+
.reduce((next, owner) => rebindAxisOwner(next, channel, owner.id, options), program);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function rebindAxisOwner(program, channel, axisId, {
|
|
300
306
|
previousScale,
|
|
301
307
|
nextScale,
|
|
302
308
|
previousTitle,
|
|
303
309
|
nextTitle
|
|
304
310
|
}) {
|
|
305
|
-
const semantic = program.semanticSpec.guides.axis?.[
|
|
311
|
+
const semantic = program.semanticSpec.guides.axis?.[axisId];
|
|
306
312
|
if (semantic?.scale !== previousScale) return program;
|
|
307
313
|
let next = program.editSemantic({
|
|
308
|
-
property: `guide.axis.${
|
|
314
|
+
property: `guide.axis.${axisId}.scale`,
|
|
309
315
|
value: nextScale
|
|
310
316
|
});
|
|
311
317
|
if (semantic.title === previousTitle) {
|
|
312
318
|
next = next.editSemantic({
|
|
313
|
-
property: `guide.axis.${
|
|
319
|
+
property: `guide.axis.${axisId}.title`,
|
|
314
320
|
value: nextTitle
|
|
315
321
|
});
|
|
316
322
|
}
|
|
317
323
|
const scale = next.resolvedScales[nextScale];
|
|
318
324
|
const ticks = normalizeTickConfig(
|
|
319
|
-
next.guideConfigs.axis?.[
|
|
325
|
+
next.guideConfigs.axis?.[axisId]?.ticks,
|
|
320
326
|
scale
|
|
321
327
|
);
|
|
322
328
|
if (ticks !== undefined) {
|
|
323
|
-
next = next._withGuideConfig(
|
|
329
|
+
next = next._withGuideConfig(axisId, "ticks", {
|
|
324
330
|
...ticks,
|
|
325
331
|
scale: nextScale
|
|
326
332
|
});
|
|
327
333
|
}
|
|
328
|
-
const labels = next.guideConfigs.axis?.[
|
|
334
|
+
const labels = next.guideConfigs.axis?.[axisId]?.labels;
|
|
329
335
|
if (labels !== undefined) {
|
|
330
|
-
const tickConfig = next.guideConfigs.axis[
|
|
331
|
-
next = next._withGuideConfig(
|
|
336
|
+
const tickConfig = next.guideConfigs.axis[axisId].ticks;
|
|
337
|
+
next = next._withGuideConfig(axisId, "labels", {
|
|
332
338
|
...labels,
|
|
333
339
|
scale: nextScale,
|
|
334
340
|
mode: tickConfig.mode,
|
|
@@ -338,30 +344,30 @@ function rebindAxis(program, channel, {
|
|
|
338
344
|
: { count: tickConfig.count })
|
|
339
345
|
});
|
|
340
346
|
if (tickConfig.mode === "values") {
|
|
341
|
-
const config = next.guideConfigs.axis[
|
|
347
|
+
const config = next.guideConfigs.axis[axisId].labels;
|
|
342
348
|
if (Object.hasOwn(config, "count")) {
|
|
343
349
|
const { count: _count, ...withoutCount } = config;
|
|
344
|
-
next = next._withGuideConfig(
|
|
350
|
+
next = next._withGuideConfig(axisId, "labels", withoutCount);
|
|
345
351
|
}
|
|
346
352
|
} else {
|
|
347
|
-
const config = next.guideConfigs.axis[
|
|
353
|
+
const config = next.guideConfigs.axis[axisId].labels;
|
|
348
354
|
if (Object.hasOwn(config, "values")) {
|
|
349
355
|
const { values: _values, ...withoutValues } = config;
|
|
350
|
-
next = next._withGuideConfig(
|
|
356
|
+
next = next._withGuideConfig(axisId, "labels", withoutValues);
|
|
351
357
|
}
|
|
352
358
|
}
|
|
353
359
|
}
|
|
354
|
-
const title = next.guideConfigs.axis?.[
|
|
360
|
+
const title = next.guideConfigs.axis?.[axisId]?.title;
|
|
355
361
|
if (title !== undefined) {
|
|
356
|
-
next = next._withGuideConfig(
|
|
362
|
+
next = next._withGuideConfig(axisId, "title", {
|
|
357
363
|
...title,
|
|
358
364
|
scale: nextScale
|
|
359
365
|
});
|
|
360
366
|
}
|
|
361
367
|
const methods = axisMethods(channel);
|
|
362
|
-
if (ticks !== undefined) next = next[methods.ticks]();
|
|
363
|
-
if (labels !== undefined) next = next[methods.labels]();
|
|
364
|
-
if (title !== undefined) next = next[methods.title]();
|
|
368
|
+
if (ticks !== undefined) next = next[methods.ticks](axisId === channel ? undefined : axisTargetArgs(axisId, channel));
|
|
369
|
+
if (labels !== undefined) next = next[methods.labels](axisId === channel ? undefined : axisTargetArgs(axisId, channel));
|
|
370
|
+
if (title !== undefined) next = next[methods.title](axisId === channel ? undefined : axisTargetArgs(axisId, channel));
|
|
365
371
|
return next;
|
|
366
372
|
}
|
|
367
373
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { canMaterializeBar } from "../../materialization/marks/index.js";
|
|
1
2
|
import { action } from "../../core/action.js";
|
|
2
3
|
import { validatePathSeriesAppearance } from "../../grammar/pathSeries.js";
|
|
3
4
|
import { assertEncodingSelectionCompatibility } from "../../materialization/selection/compatibility.js";
|
|
4
5
|
import {
|
|
5
|
-
readNominalField,
|
|
6
|
-
readQuantitativeField,
|
|
7
6
|
readScaleField,
|
|
8
7
|
validateOpacityValue
|
|
9
8
|
} from "../../grammar/scales/index.js";
|
|
@@ -42,11 +41,11 @@ function encodeAppearanceField(program, channel, args, operation) {
|
|
|
42
41
|
["point"],
|
|
43
42
|
"point mark"
|
|
44
43
|
);
|
|
45
|
-
const
|
|
44
|
+
const fieldTypes = channel === "shape" ? ["nominal"] : ["quantitative", "nominal", "ordinal"];
|
|
46
45
|
const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
|
|
47
|
-
const fieldType = args.fieldType ??
|
|
48
|
-
if (fieldType
|
|
49
|
-
throw new Error(`${operation} requires a ${
|
|
46
|
+
const fieldType = args.fieldType ?? fieldTypes[0];
|
|
47
|
+
if (!fieldTypes.includes(fieldType)) {
|
|
48
|
+
throw new Error(`${operation} requires a ${fieldTypes.join(" or ")} field.`);
|
|
50
49
|
}
|
|
51
50
|
if (channel === "size" && program.markConfigs[target]?.radius !== undefined) {
|
|
52
51
|
throw new Error("encodeSize cannot be combined with a constant radius.");
|
|
@@ -59,17 +58,12 @@ function encodeAppearanceField(program, channel, args, operation) {
|
|
|
59
58
|
const scale = resolveAppearanceScaleDefinition(
|
|
60
59
|
program,
|
|
61
60
|
channel,
|
|
62
|
-
requestedScale
|
|
61
|
+
requestedScale,
|
|
62
|
+
fieldType
|
|
63
63
|
);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
68
|
-
} else if (channel === "shape") {
|
|
69
|
-
readNominalField(dataset.values, args.field);
|
|
70
|
-
} else {
|
|
71
|
-
readQuantitativeField(dataset.values, args.field);
|
|
72
|
-
}
|
|
64
|
+
readScaleField(dataset.values, args.field, fieldType, {
|
|
65
|
+
allowUnknown: Object.hasOwn(scale, "unknown")
|
|
66
|
+
});
|
|
73
67
|
|
|
74
68
|
let next = setEncodingProperties(program, target, channel, {
|
|
75
69
|
field: args.field,
|
|
@@ -152,7 +146,7 @@ const removePointRadius = /* @__PURE__ */ action(
|
|
|
152
146
|
const encodeSize = /* @__PURE__ */ action(
|
|
153
147
|
{
|
|
154
148
|
op: "encodeSize",
|
|
155
|
-
description: "Encode a
|
|
149
|
+
description: "Encode a field as equal-area point size."
|
|
156
150
|
},
|
|
157
151
|
function (args = {}) {
|
|
158
152
|
return encodeAppearanceField(this, "size", args, "encodeSize");
|
|
@@ -184,6 +178,17 @@ const clearOpacityEncoding = /* @__PURE__ */ action(
|
|
|
184
178
|
}
|
|
185
179
|
);
|
|
186
180
|
|
|
181
|
+
function withoutConstantOpacity(program, target) {
|
|
182
|
+
const { opacity, ...config } = program.markConfigs[target] ?? {};
|
|
183
|
+
void opacity;
|
|
184
|
+
if (config.barAppearance?.opacity !== undefined) {
|
|
185
|
+
const { opacity: barOpacity, ...appearance } = config.barAppearance;
|
|
186
|
+
void barOpacity;
|
|
187
|
+
config.barAppearance = appearance;
|
|
188
|
+
}
|
|
189
|
+
return program._withMarkConfig(target, config);
|
|
190
|
+
}
|
|
191
|
+
|
|
187
192
|
const encodeOpacity = /* @__PURE__ */ action(
|
|
188
193
|
{
|
|
189
194
|
op: "encodeOpacity",
|
|
@@ -202,14 +207,13 @@ const encodeOpacity = /* @__PURE__ */ action(
|
|
|
202
207
|
const { id: target, dataset: sourceDataset, layer } = resolveTarget(
|
|
203
208
|
this,
|
|
204
209
|
args.target,
|
|
205
|
-
["point", "rule", "line"],
|
|
206
|
-
"point, rule or
|
|
210
|
+
["point", "rule", "line", "bar"],
|
|
211
|
+
"point, rule, line or bar mark"
|
|
207
212
|
);
|
|
208
213
|
if (hasValue) {
|
|
209
214
|
validateOpacityValue(args.value, "encodeOpacity");
|
|
210
215
|
assertEncodingSelectionCompatibility(this, target, ["opacity"]);
|
|
211
|
-
const
|
|
212
|
-
void opacity;
|
|
216
|
+
const config = withoutConstantOpacity(this, target).markConfigs[target];
|
|
213
217
|
const withoutLegend = this.guideConfigs.legend?.opacity?.target === target
|
|
214
218
|
? this.removeOpacityLegend()
|
|
215
219
|
: this;
|
|
@@ -220,6 +224,11 @@ const encodeOpacity = /* @__PURE__ */ action(
|
|
|
220
224
|
if (layer.mark.type === "line") {
|
|
221
225
|
return rematerializeEncoding(next, target, "opacity", undefined, layer);
|
|
222
226
|
}
|
|
227
|
+
if (layer.mark.type === "bar") {
|
|
228
|
+
return canMaterializeBar(next, findLayer(next, target))
|
|
229
|
+
? applyDetachedScaleRematerialization(next.rematerializeBarMark({ id: target }), [layer])
|
|
230
|
+
: next;
|
|
231
|
+
}
|
|
223
232
|
const materialized = layer.mark.type === "rule"
|
|
224
233
|
? next.rematerializeRuleMark({ id: target })
|
|
225
234
|
: next.rematerializePointMark({ id: target });
|
|
@@ -244,17 +253,13 @@ const encodeOpacity = /* @__PURE__ */ action(
|
|
|
244
253
|
...layer, encoding: { ...layer.encoding, opacity: { field: args.field, fieldType } }
|
|
245
254
|
});
|
|
246
255
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});
|
|
251
|
-
} else {
|
|
252
|
-
readQuantitativeField(dataset.values, args.field);
|
|
253
|
-
}
|
|
256
|
+
readScaleField(dataset.values, args.field, fieldType, {
|
|
257
|
+
allowUnknown: Object.hasOwn(scale, "unknown")
|
|
258
|
+
});
|
|
254
259
|
const { opacity, ...config } = this.markConfigs[target] ?? {};
|
|
255
260
|
void opacity;
|
|
256
261
|
let next = setEncodingProperties(
|
|
257
|
-
this
|
|
262
|
+
withoutConstantOpacity(this, target),
|
|
258
263
|
target,
|
|
259
264
|
"opacity",
|
|
260
265
|
{ field: args.field, fieldType, scale: scale.id }
|
|
@@ -262,6 +267,9 @@ const encodeOpacity = /* @__PURE__ */ action(
|
|
|
262
267
|
next = applyEncodingScale(next, scale, requestedScale, {
|
|
263
268
|
reassignment: previous?.scale === scale.id
|
|
264
269
|
});
|
|
270
|
+
if (layer.mark.type === "bar" && !canMaterializeBar(next, findLayer(next, target))) {
|
|
271
|
+
return next.rematerializeScale({ id: scale.id });
|
|
272
|
+
}
|
|
265
273
|
return rematerializeEncoding(next, target, "opacity", scale.id, layer);
|
|
266
274
|
}
|
|
267
275
|
);
|
|
@@ -4,11 +4,33 @@ import {
|
|
|
4
4
|
BAR_GRAINS,
|
|
5
5
|
resolveBarColorLayout,
|
|
6
6
|
resolveBarOffsetChannel,
|
|
7
|
+
resolveBarChannels,
|
|
7
8
|
resolveBarGrain
|
|
8
9
|
} from "../../grammar/bars/policy.js";
|
|
9
10
|
import { normalizeBarWidth } from "../../grammar/bars/geometry.js";
|
|
11
|
+
import { applyMaterializationPlan } from "../../materialization/dependencies.js";
|
|
12
|
+
import {
|
|
13
|
+
getScaleConsumerMarkSteps,
|
|
14
|
+
getSourceDependentMarkSteps
|
|
15
|
+
} from "../../materialization/marks/index.js";
|
|
16
|
+
|
|
17
|
+
function rematerializeWidth(program, target, layer, width) {
|
|
18
|
+
const next = program._withMarkConfig(target, {
|
|
19
|
+
...program.markConfigs[target], barWidth: width
|
|
20
|
+
}).rematerializeBarMark({ id: target });
|
|
21
|
+
const category = resolveBarChannels(layer)?.category;
|
|
22
|
+
const encoding = layer.encoding?.[category];
|
|
23
|
+
if (encoding?.fieldType !== "temporal") return next;
|
|
24
|
+
const marks = getScaleConsumerMarkSteps(next, [encoding.scale])
|
|
25
|
+
.filter(step => step.args.id !== target);
|
|
26
|
+
return applyMaterializationPlan(next, [
|
|
27
|
+
...marks,
|
|
28
|
+
...[target, ...marks.map(step => step.args.id)]
|
|
29
|
+
.flatMap(id => getSourceDependentMarkSteps(next, id))
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
10
32
|
|
|
11
|
-
const OPTIONS = Object.freeze(["band", "pixels", "target"]);
|
|
33
|
+
const OPTIONS = Object.freeze(["band", "pixels", "interval", "target"]);
|
|
12
34
|
|
|
13
35
|
const encodeBarWidth = /* @__PURE__ */ action(
|
|
14
36
|
{
|
|
@@ -26,22 +48,30 @@ const encodeBarWidth = /* @__PURE__ */ action(
|
|
|
26
48
|
const layout = resolveBarColorLayout(layer);
|
|
27
49
|
const width = normalizeBarWidth(
|
|
28
50
|
args,
|
|
29
|
-
this.markConfigs[target]?.barWidth
|
|
51
|
+
this.markConfigs[target]?.barWidth ?? (layer.mark.orientation === undefined ? undefined : { pixels: 5 })
|
|
30
52
|
);
|
|
31
53
|
const grouped = layout === "group";
|
|
32
54
|
const offsetChannel = resolveBarOffsetChannel(layer);
|
|
33
55
|
const grain = resolveBarGrain(layer);
|
|
56
|
+
if (layer.mark.orientation !== undefined && width.band !== undefined) {
|
|
57
|
+
throw new Error("Numeric-center bars require pixel width.");
|
|
58
|
+
}
|
|
34
59
|
if (grain === undefined && this.markConfigs[target]?.boxPlot === undefined && (
|
|
35
60
|
layer.encoding?.x === undefined || layer.encoding?.y === undefined
|
|
36
61
|
)) {
|
|
37
62
|
return this._withMarkConfig(target, { ...this.markConfigs[target], barWidth: width });
|
|
38
63
|
}
|
|
64
|
+
if (width.interval !== undefined && grain !== undefined) {
|
|
65
|
+
const category = resolveBarChannels(layer)?.category;
|
|
66
|
+
if (grain !== BAR_GRAINS.aggregate || grouped || layer.encoding?.[category]?.fieldType !== "temporal") {
|
|
67
|
+
throw new Error("Bar interval width requires ungrouped aggregate temporal categories.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
39
70
|
if (grain === BAR_GRAINS.histogram) {
|
|
40
71
|
throw new Error("encodeBarWidth requires an aggregate or ranged category slot, not histogram bins.");
|
|
41
72
|
}
|
|
42
|
-
if (
|
|
43
|
-
return this
|
|
44
|
-
.rematerializeBarMark({ id: target });
|
|
73
|
+
if ([BAR_GRAINS.ranged, BAR_GRAINS.centered].includes(grain)) {
|
|
74
|
+
return rematerializeWidth(this, target, layer, width);
|
|
45
75
|
}
|
|
46
76
|
if (
|
|
47
77
|
grain !== BAR_GRAINS.aggregate ||
|
|
@@ -56,12 +86,7 @@ const encodeBarWidth = /* @__PURE__ */ action(
|
|
|
56
86
|
);
|
|
57
87
|
}
|
|
58
88
|
|
|
59
|
-
return this
|
|
60
|
-
._withMarkConfig(target, {
|
|
61
|
-
...this.markConfigs[target],
|
|
62
|
-
barWidth: width
|
|
63
|
-
})
|
|
64
|
-
.rematerializeBarMark({ id: target });
|
|
89
|
+
return rematerializeWidth(this, target, layer, width);
|
|
65
90
|
}
|
|
66
91
|
);
|
|
67
92
|
|
|
@@ -39,7 +39,7 @@ export function encodeContinuousColor(program, args) {
|
|
|
39
39
|
const { id: target, dataset: sourceDataset, layer } = resolveTarget(
|
|
40
40
|
program,
|
|
41
41
|
args.target,
|
|
42
|
-
["point", "bar", "rect"],
|
|
42
|
+
["point", "bar", "rect", "text"],
|
|
43
43
|
"continuous color mark"
|
|
44
44
|
);
|
|
45
45
|
const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
|
|
@@ -63,7 +63,7 @@ const encodeColor = /* @__PURE__ */ action(
|
|
|
63
63
|
const { id: target, dataset: sourceDataset, layer } = resolveTarget(
|
|
64
64
|
this,
|
|
65
65
|
args.target,
|
|
66
|
-
["point", "line", "bar", "area", "arc", "rect"],
|
|
66
|
+
["point", "line", "bar", "area", "arc", "rect", "text"],
|
|
67
67
|
"color mark"
|
|
68
68
|
);
|
|
69
69
|
const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
|
|
@@ -20,6 +20,9 @@ export function resolveColorScaleOptions(args) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function assertNoConstantColor(program, layer) {
|
|
23
|
+
if (layer.mark.type === "text" && layer.source !== undefined) {
|
|
24
|
+
throw new Error("Source-owned text uses inheritColor or constant fill; field color requires row-backed text.");
|
|
25
|
+
}
|
|
23
26
|
const config = program.markConfigs[layer.id];
|
|
24
27
|
const hasConstant = {
|
|
25
28
|
point: config?.fill !== undefined,
|
|
@@ -27,7 +30,8 @@ export function assertNoConstantColor(program, layer) {
|
|
|
27
30
|
area: config?.errorBand?.fill !== undefined,
|
|
28
31
|
bar: config?.barAppearance?.fill !== undefined,
|
|
29
32
|
arc: config?.fill !== undefined,
|
|
30
|
-
rect: config?.fillExplicit === true
|
|
33
|
+
rect: config?.fillExplicit === true,
|
|
34
|
+
text: config?.fillExplicit === true
|
|
31
35
|
}[layer.mark.type] ?? false;
|
|
32
36
|
if (hasConstant) {
|
|
33
37
|
throw new Error(
|
|
@@ -58,15 +62,18 @@ export function resolveColorLayout(layer, requested, barGrain) {
|
|
|
58
62
|
layer.mark.type === "bar"
|
|
59
63
|
? barGrain === BAR_GRAINS.histogram
|
|
60
64
|
? "stack"
|
|
61
|
-
:
|
|
65
|
+
: [BAR_GRAINS.ranged, BAR_GRAINS.centered].includes(barGrain) ? "overlay" : "group"
|
|
62
66
|
: layer.mark.type === "area"
|
|
63
67
|
? "overlay"
|
|
64
68
|
: undefined
|
|
65
69
|
);
|
|
66
70
|
|
|
67
|
-
if (["point", "line", "rect"].includes(layer.mark.type) && layout !== undefined) {
|
|
71
|
+
if (["point", "line", "rect", "text"].includes(layer.mark.type) && layout !== undefined) {
|
|
68
72
|
throw new Error(`Color layout is not supported for ${layer.mark.type} marks.`);
|
|
69
73
|
}
|
|
74
|
+
if (barGrain === BAR_GRAINS.centered && layout !== "overlay") {
|
|
75
|
+
throw new Error("Numeric-center bars support only overlay layout.");
|
|
76
|
+
}
|
|
70
77
|
if (layer.mark.type === "area" && layout === "group") {
|
|
71
78
|
throw new Error('Area color layout does not support "group".');
|
|
72
79
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
+
export { requireStringValue as requireDensityField } from "../../../core/validation.js";
|
|
1
2
|
import { isPlainObject } from "../../../core/immutable.js";
|
|
2
3
|
import { findSemanticScale } from "../../../selectors/scales.js";
|
|
3
4
|
|
|
4
|
-
export function requireDensityField(value, label) {
|
|
5
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
6
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
7
|
-
}
|
|
8
|
-
return value;
|
|
9
|
-
}
|
|
10
5
|
|
|
11
6
|
export function resolveDensityScaleOptions(value, defaults, label) {
|
|
12
7
|
if (value !== undefined && !isPlainObject(value)) {
|
|
@@ -11,11 +11,6 @@ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
|
|
|
11
11
|
throw new Error(`Arc ${channel} encoding does not support stack.`);
|
|
12
12
|
}
|
|
13
13
|
if (channel === "radius") {
|
|
14
|
-
if (layer.encoding?.theta?.fieldType === "quantitative") {
|
|
15
|
-
throw new Error(
|
|
16
|
-
"Arc radius encoding cannot be combined with quantitative theta."
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
14
|
if (args.mapping !== undefined) {
|
|
20
15
|
validateRadialMapping(args.mapping);
|
|
21
16
|
if (fieldType !== "quantitative" || !["count", "sum"].includes(args.aggregate)) {
|
|
@@ -30,7 +25,16 @@ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
|
|
|
30
25
|
return { ...emptyPositionPolicy(), aggregate: args.aggregate };
|
|
31
26
|
}
|
|
32
27
|
if (args.aggregate !== undefined) {
|
|
33
|
-
throw new Error("Arc radius
|
|
28
|
+
if (!["count", "sum"].includes(args.aggregate)) throw new Error("Arc radius aggregate supports count or sum.");
|
|
29
|
+
const theta = layer.encoding?.theta;
|
|
30
|
+
if (theta !== undefined && !["count", "sum"].includes(theta.aggregate)) {
|
|
31
|
+
throw new Error("Grouped arc radius requires aggregated categorical theta.");
|
|
32
|
+
}
|
|
33
|
+
if (args.aggregate === "count" && Object.hasOwn(args, "field")) {
|
|
34
|
+
throw new Error("Arc radius count does not accept a field.");
|
|
35
|
+
}
|
|
36
|
+
if (args.weight !== undefined) throw new Error("Arc radius encoding does not support weight.");
|
|
37
|
+
return { ...emptyPositionPolicy(), aggregate: args.aggregate };
|
|
34
38
|
}
|
|
35
39
|
if (args.weight !== undefined) {
|
|
36
40
|
throw new Error("Arc radius encoding does not support weight.");
|
|
@@ -38,10 +42,8 @@ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
|
|
|
38
42
|
return emptyPositionPolicy();
|
|
39
43
|
}
|
|
40
44
|
if (fieldType === "quantitative") {
|
|
41
|
-
if (layer.encoding?.radius !== undefined) {
|
|
42
|
-
throw new Error(
|
|
43
|
-
"Quantitative arc theta cannot be combined with radius encoding."
|
|
44
|
-
);
|
|
45
|
+
if (layer.encoding?.radius?.aggregate !== undefined) {
|
|
46
|
+
throw new Error("Grouped arc radius requires aggregated categorical theta.");
|
|
45
47
|
}
|
|
46
48
|
if (args.aggregate !== undefined) {
|
|
47
49
|
throw new Error("Quantitative arc theta does not support aggregate.");
|
|
@@ -26,16 +26,16 @@ export function resolveAreaPositionPolicy({
|
|
|
26
26
|
const density = dataset.transform?.find(transform => transform.type === "density");
|
|
27
27
|
if (["nominal", "ordinal"].includes(fieldType)) {
|
|
28
28
|
if (
|
|
29
|
-
density
|
|
29
|
+
density !== undefined && (density.placement?.type !== "category" ||
|
|
30
30
|
density.placement.channel !== channel ||
|
|
31
|
-
density.placement.categoryField !== field
|
|
31
|
+
density.placement.categoryField !== field)
|
|
32
32
|
) {
|
|
33
33
|
throw new Error(
|
|
34
34
|
"Categorical area position requires a matching category density placement."
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
if (args.aggregate !== undefined || args.bin !== undefined || args.stack !== undefined) {
|
|
38
|
-
throw new Error("Categorical
|
|
38
|
+
throw new Error("Categorical area position does not support aggregate, bin, or stack.");
|
|
39
39
|
}
|
|
40
40
|
return emptyPositionPolicy();
|
|
41
41
|
}
|
|
@@ -26,7 +26,15 @@ function resolveBarChannelPolicy({
|
|
|
26
26
|
let stack;
|
|
27
27
|
const xEncoding = layer.encoding?.x;
|
|
28
28
|
const opposite = layer.encoding?.[channel === "x" ? "y" : "x"];
|
|
29
|
-
const
|
|
29
|
+
const pendingRange = program.markConfigs[layer.id]?.boxPlot !== undefined ||
|
|
30
|
+
layer.encoding?.[`${channel}2`] !== undefined;
|
|
31
|
+
|
|
32
|
+
if (layer.mark?.orientation !== undefined) {
|
|
33
|
+
if (fieldType !== "quantitative" || ["bin", "aggregate", "stack", "weight"].some(key => args[key] !== undefined)) {
|
|
34
|
+
throw new Error("Oriented numeric bars require raw quantitative positions without bin, aggregate, stack, or weight.");
|
|
35
|
+
}
|
|
36
|
+
return { bin, aggregate, stack };
|
|
37
|
+
}
|
|
30
38
|
|
|
31
39
|
if (["nominal", "ordinal", "temporal"].includes(fieldType)) {
|
|
32
40
|
if (args.aggregate !== undefined || args.bin !== undefined || args.stack !== undefined) {
|
|
@@ -73,11 +81,11 @@ function resolveBarChannelPolicy({
|
|
|
73
81
|
}
|
|
74
82
|
aggregate = args.aggregate ?? (
|
|
75
83
|
["nominal", "ordinal", "temporal"].includes(opposite?.fieldType) &&
|
|
76
|
-
!
|
|
84
|
+
!pendingRange
|
|
77
85
|
? "mean"
|
|
78
86
|
: undefined
|
|
79
87
|
);
|
|
80
|
-
if (
|
|
88
|
+
if (pendingRange && args.aggregate === undefined) {
|
|
81
89
|
return { bin, aggregate, stack };
|
|
82
90
|
}
|
|
83
91
|
stack = Object.hasOwn(args, "stack") ? args.stack : opposite === undefined ? undefined : null;
|
|
@@ -106,7 +114,8 @@ export function resolveBarPositionPolicy(context) {
|
|
|
106
114
|
const policy = resolveBarChannelPolicy(context);
|
|
107
115
|
const oppositeChannel = channel === "x" ? "y" : "x";
|
|
108
116
|
const opposite = layer.encoding?.[oppositeChannel];
|
|
109
|
-
const
|
|
117
|
+
const pendingRange = program.markConfigs[layer.id]?.boxPlot !== undefined ||
|
|
118
|
+
layer.encoding?.[`${channel}2`] !== undefined;
|
|
110
119
|
const candidate = {
|
|
111
120
|
...layer,
|
|
112
121
|
encoding: {
|
|
@@ -118,7 +127,7 @@ export function resolveBarPositionPolicy(context) {
|
|
|
118
127
|
// Absence of an aggregate/stack is sufficient to represent an unresolved role.
|
|
119
128
|
let companion;
|
|
120
129
|
if (
|
|
121
|
-
!
|
|
130
|
+
!pendingRange && resolveBarGrain(layer) === undefined &&
|
|
122
131
|
opposite?.fieldType === "quantitative" && opposite.bin === undefined
|
|
123
132
|
) {
|
|
124
133
|
const resolved = resolveBarChannelPolicy({
|
|
@@ -138,7 +147,7 @@ export function resolveBarPositionPolicy(context) {
|
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
const orientation = resolveBarOrientation(candidate);
|
|
141
|
-
if (opposite !== undefined && orientation === undefined && !
|
|
150
|
+
if (opposite !== undefined && orientation === undefined && !pendingRange) {
|
|
142
151
|
throw new Error(
|
|
143
152
|
`Bar ${channel} encoding requires a quantitative field opposite a categorical position.`
|
|
144
153
|
);
|
|
@@ -68,6 +68,13 @@ export function resolveLinePositionPolicy({
|
|
|
68
68
|
stack: undefined
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
|
+
if (["nominal", "ordinal"].includes(fieldType)) {
|
|
72
|
+
if (layer.encoding?.y !== undefined && layer.encoding.y.aggregate === undefined &&
|
|
73
|
+
layer.encoding.y.fieldType !== "quantitative") {
|
|
74
|
+
throw new Error("Categorical line x requires a quantitative value or aggregate y encoding.");
|
|
75
|
+
}
|
|
76
|
+
return emptyPositionPolicy();
|
|
77
|
+
}
|
|
71
78
|
const directPair =
|
|
72
79
|
layer.encoding?.y?.aggregate === undefined &&
|
|
73
80
|
["quantitative", "temporal"].includes(layer.encoding?.y?.fieldType) &&
|
|
@@ -82,7 +89,7 @@ export function resolveLinePositionPolicy({
|
|
|
82
89
|
)
|
|
83
90
|
) {
|
|
84
91
|
throw new Error(
|
|
85
|
-
"Line x encoding requires a temporal field or a compatible
|
|
92
|
+
"Line x encoding requires a categorical or temporal field, or a compatible quantitative field."
|
|
86
93
|
);
|
|
87
94
|
}
|
|
88
95
|
return emptyPositionPolicy();
|
|
@@ -93,15 +100,19 @@ export function resolveLinePositionPolicy({
|
|
|
93
100
|
if (layer.encoding?.x?.bin !== undefined && args.aggregate === undefined) {
|
|
94
101
|
throw new Error("Binned line x encoding requires an aggregate y encoding.");
|
|
95
102
|
}
|
|
96
|
-
|
|
103
|
+
if (["nominal", "ordinal"].includes(layer.encoding?.x?.fieldType) &&
|
|
104
|
+
args.aggregate === undefined && fieldType !== "quantitative") {
|
|
105
|
+
throw new Error("Categorical line x requires a quantitative value or aggregate y encoding.");
|
|
106
|
+
}
|
|
107
|
+
const continuousPair =
|
|
97
108
|
fieldType === "quantitative" &&
|
|
98
|
-
layer.encoding?.x?.fieldType
|
|
109
|
+
["quantitative", "temporal", "nominal", "ordinal"].includes(layer.encoding?.x?.fieldType) &&
|
|
99
110
|
layer.encoding.x.bin === undefined;
|
|
100
111
|
const prospectiveDirect =
|
|
101
112
|
args.aggregate === undefined &&
|
|
102
113
|
(fieldType === "temporal" ||
|
|
103
114
|
(fieldType === "quantitative" &&
|
|
104
|
-
(layer.encoding?.x === undefined ||
|
|
115
|
+
(layer.encoding?.x === undefined || continuousPair)));
|
|
105
116
|
if (interval || (windowOutput && args.aggregate === undefined) || prospectiveDirect) {
|
|
106
117
|
if (!["quantitative", "temporal"].includes(fieldType)) {
|
|
107
118
|
throw new Error(
|
|
@@ -125,12 +136,12 @@ export function resolveLinePositionPolicy({
|
|
|
125
136
|
|
|
126
137
|
if (layer.encoding?.x !== undefined) {
|
|
127
138
|
const compatibleAggregateX =
|
|
128
|
-
layer.encoding.x.fieldType
|
|
139
|
+
["temporal", "nominal", "ordinal"].includes(layer.encoding.x.fieldType) ||
|
|
129
140
|
(layer.encoding.x.fieldType === "quantitative" &&
|
|
130
141
|
layer.encoding.x.bin !== undefined);
|
|
131
142
|
if (!compatibleAggregateX) {
|
|
132
143
|
throw new Error(
|
|
133
|
-
"Aggregate line y encoding requires a temporal or binned quantitative x encoding."
|
|
144
|
+
"Aggregate line y encoding requires a temporal, categorical, or binned quantitative x encoding."
|
|
134
145
|
);
|
|
135
146
|
}
|
|
136
147
|
}
|
|
@@ -142,7 +142,8 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
142
142
|
? undefined
|
|
143
143
|
: args.mapping ?? findSemanticScale(program, previous?.scale)?.radialMapping
|
|
144
144
|
: undefined;
|
|
145
|
-
const countRadius =
|
|
145
|
+
const countRadius = channel === "radius" && layer.mark.type === "arc" &&
|
|
146
|
+
!clearRadialMapping && (args.aggregate ?? previous?.aggregate) === "count";
|
|
146
147
|
const requestedFieldType = args.fieldType ?? previous?.fieldType ?? (
|
|
147
148
|
["rule", "rect", "text", "point", "tick"].includes(layer.mark.type) && hasDatum
|
|
148
149
|
? inferDatumFieldType(args.datum, operation)
|
|
@@ -179,7 +180,7 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
179
180
|
} else readScaleField(dataset.values, field, fieldType, { temporalUnit });
|
|
180
181
|
}
|
|
181
182
|
const effectiveArgs = { ...args };
|
|
182
|
-
if (clearRadialMapping) delete effectiveArgs.mapping;
|
|
183
|
+
if (clearRadialMapping && args.aggregate === undefined) delete effectiveArgs.mapping;
|
|
183
184
|
else if (mapping !== undefined) effectiveArgs.mapping = mapping;
|
|
184
185
|
const directQuantitativeArcTheta =
|
|
185
186
|
layer.mark.type === "arc" &&
|
|
@@ -127,6 +127,9 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
|
|
|
127
127
|
|
|
128
128
|
let next = program;
|
|
129
129
|
if (layer.mark.type === "bar") {
|
|
130
|
+
if (layer.mark.orientation !== undefined && primaryChannel !== (layer.mark.orientation === "vertical" ? "y" : "x")) {
|
|
131
|
+
throw new Error("Numeric bar endpoints must use the measure channel.");
|
|
132
|
+
}
|
|
130
133
|
if (layer.layout?.mode !== undefined && layer.layout.mode !== "overlay") throw new Error("Ranged bars support only overlay layout; clear series layout before assigning endpoints.");
|
|
131
134
|
for (const property of ["aggregate", "stack", "bin"]) {
|
|
132
135
|
if (layer.encoding?.[primaryChannel]?.[property] !== undefined) {
|
|
@@ -210,6 +213,20 @@ function rangeAction(channel) {
|
|
|
210
213
|
const { layer, dataset } = resolveTarget(this, args.target, ["area", "bar", "rect", "rule"], "ranged mark");
|
|
211
214
|
let next = this;
|
|
212
215
|
let lower = { field: args.lower }, upper = { field: args.upper };
|
|
216
|
+
if (layer.mark.type === "bar") {
|
|
217
|
+
if (layer.mark.orientation !== undefined && channel !== (layer.mark.orientation === "vertical" ? "y" : "x")) {
|
|
218
|
+
throw new Error("Numeric bar endpoints must use the measure channel.");
|
|
219
|
+
}
|
|
220
|
+
common.target = layer.id;
|
|
221
|
+
const resolved = resolvePositionEncoding(this, channel, {
|
|
222
|
+
...common, ...lower,
|
|
223
|
+
...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
|
|
224
|
+
...(args.scale === undefined ? {} : { scale: args.scale })
|
|
225
|
+
}, op);
|
|
226
|
+
next = setEncodingProperties(next, layer.id, `${channel}2`, {
|
|
227
|
+
...upper, fieldType: common.fieldType, scale: resolved.scale.id
|
|
228
|
+
});
|
|
229
|
+
}
|
|
213
230
|
if (layer.mark.type === "area") {
|
|
214
231
|
common.target = layer.id;
|
|
215
232
|
const prepared = prepareAreaRange(this, layer, dataset, channel, args, common);
|