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
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
findSelectionPolicy,
|
|
20
20
|
requireSelectionPolicy
|
|
21
21
|
} from "../../materialization/selection/policies/index.js";
|
|
22
|
-
import { legendGraphicIds } from "../../materialization/guides/resources.js";
|
|
23
22
|
import {
|
|
24
23
|
normalizeDimOthers,
|
|
25
24
|
validateUnitInterval
|
|
@@ -84,18 +83,7 @@ function categoricalLegendKinds(program, target) {
|
|
|
84
83
|
function resetCategoricalLegendSymbols(program, target) {
|
|
85
84
|
const kinds = categoricalLegendKinds(program, target);
|
|
86
85
|
if (kinds.length === 0) return program;
|
|
87
|
-
|
|
88
|
-
for (const kind of kinds) {
|
|
89
|
-
for (const id of legendGraphicIds(kind).filter(id => id.includes("Symbol"))) {
|
|
90
|
-
const graphic = next.graphicSpec.objects[id];
|
|
91
|
-
if (graphic === undefined) continue;
|
|
92
|
-
next = graphic.type === "collection"
|
|
93
|
-
? next.editGraphics({ target: id, property: "items", value: [] })
|
|
94
|
-
: next.editGraphics({ target: id, property: "length", value: 0 });
|
|
95
|
-
}
|
|
96
|
-
next = next.rematerializeLegendSymbols({ kind });
|
|
97
|
-
}
|
|
98
|
-
return next;
|
|
86
|
+
return program.rematerializeLegend();
|
|
99
87
|
}
|
|
100
88
|
|
|
101
89
|
function targetHighlightEntries(program, target) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { synchronizeFacetLegendAppearance } from "../../materialization/facetGuides/owner.js";
|
|
1
2
|
import { rematerializeTypography } from "../../materialization/typography.js";
|
|
2
|
-
import { themeTokens } from "../../theme/defaults.js";
|
|
3
|
+
import { AXIS_LABEL_FONT_SIZES, AXIS_TITLE_FONT_SIZE, themeTokens } from "../../theme/defaults.js";
|
|
3
4
|
import {
|
|
4
5
|
normalizeThemeState,
|
|
5
6
|
resolveEffectiveThemeTokens,
|
|
@@ -21,7 +22,7 @@ const THEME_STYLE_PROPERTIES = Object.freeze([
|
|
|
21
22
|
"background",
|
|
22
23
|
"fill",
|
|
23
24
|
"stroke",
|
|
24
|
-
"fontFamily"
|
|
25
|
+
"fontFamily", "fontSize"
|
|
25
26
|
]);
|
|
26
27
|
const COLOR_TOKENS = Object.freeze([
|
|
27
28
|
"mark",
|
|
@@ -51,15 +52,38 @@ function tokenFor(value, sourceTokens, targetTokens) {
|
|
|
51
52
|
return matches[0];
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
function
|
|
55
|
+
function axisFamily(name) {
|
|
56
|
+
return name.startsWith("parallel") ? "parallel"
|
|
57
|
+
: /^(theta|radial|radius)/u.test(name) ? "polar" : "cartesian";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function axisTextRole(property, component) {
|
|
61
|
+
if (!["labels", "title"].includes(component)) return undefined;
|
|
62
|
+
const role = component === "labels" ? "axisLabel" : "axisTitle";
|
|
63
|
+
if (property === "fontFamily") return `${role}FontFamily`;
|
|
64
|
+
if (property === "fontSize") return `${role}FontSize`;
|
|
65
|
+
if (["fill", "color"].includes(property)) return role;
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function roleToken(tokens, role, family = "cartesian") {
|
|
70
|
+
if (tokens[role] !== undefined) return tokens[role];
|
|
71
|
+
if (role === "axisLabel") return family === "parallel" ? tokens.axis : tokens.text;
|
|
72
|
+
if (["axisLabelFontFamily", "axisTitleFontFamily"].includes(role)) return tokens.fontFamily;
|
|
73
|
+
if (role === "axisLabelFontSize") return AXIS_LABEL_FONT_SIZES[family];
|
|
74
|
+
if (role === "axisTitleFontSize") return AXIS_TITLE_FONT_SIZE;
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function roleValues(role, sourceTokens, legacy = [], family) {
|
|
55
79
|
return new Set([
|
|
56
|
-
sourceTokens
|
|
57
|
-
themeTokens("light")
|
|
80
|
+
roleToken(sourceTokens, role, family),
|
|
81
|
+
roleToken(themeTokens("light"), role, family),
|
|
58
82
|
...legacy.map(token => themeTokens("light")[token] ?? token)
|
|
59
83
|
]);
|
|
60
84
|
}
|
|
61
85
|
|
|
62
|
-
function mapRoleValue(value, role, sourceTokens, targetTokens, legacy) {
|
|
86
|
+
function mapRoleValue(value, role, sourceTokens, targetTokens, legacy, family) {
|
|
63
87
|
if (Array.isArray(value)) {
|
|
64
88
|
let changed = false;
|
|
65
89
|
const next = value.map(item => {
|
|
@@ -68,17 +92,18 @@ function mapRoleValue(value, role, sourceTokens, targetTokens, legacy) {
|
|
|
68
92
|
role,
|
|
69
93
|
sourceTokens,
|
|
70
94
|
targetTokens,
|
|
71
|
-
legacy
|
|
95
|
+
legacy, family
|
|
72
96
|
);
|
|
73
97
|
changed ||= mapped.changed;
|
|
74
98
|
return mapped.value;
|
|
75
99
|
});
|
|
76
100
|
return { changed, value: changed ? next : value };
|
|
77
101
|
}
|
|
78
|
-
if (!roleValues(role, sourceTokens, legacy).has(value)) {
|
|
102
|
+
if (!roleValues(role, sourceTokens, legacy, family).has(value)) {
|
|
79
103
|
return { changed: false, value };
|
|
80
104
|
}
|
|
81
|
-
|
|
105
|
+
const target = roleToken(targetTokens, role, family);
|
|
106
|
+
return { changed: value !== target, value: target };
|
|
82
107
|
}
|
|
83
108
|
|
|
84
109
|
function mapColorValue(value, sourceTokens, targetTokens) {
|
|
@@ -158,6 +183,9 @@ function componentRole(program, id, property) {
|
|
|
158
183
|
}
|
|
159
184
|
|
|
160
185
|
function graphicRole(program, id, property) {
|
|
186
|
+
const axisPart = /(?:Axis|^axis_.+__)(Labels|Titles?)$/u.exec(id);
|
|
187
|
+
const scoped = axisPart === null ? undefined : axisTextRole(property, axisPart[1] === "Labels" ? "labels" : "title");
|
|
188
|
+
if (scoped !== undefined) return scoped;
|
|
161
189
|
if (property === "fontFamily") return "fontFamily";
|
|
162
190
|
if (id === "canvas" && property === "background") return "background";
|
|
163
191
|
if (id === `${program.compositionSpec?.id}-headers` && property === "fill") {
|
|
@@ -173,11 +201,11 @@ function graphicRole(program, id, property) {
|
|
|
173
201
|
}
|
|
174
202
|
return layer.mark?.type === "text" ? "text" : "mark";
|
|
175
203
|
}
|
|
176
|
-
if (/
|
|
177
|
-
if (/
|
|
204
|
+
if (/(?:Axis|^axis_.+__)Lines?$/u.test(id) && property === "stroke") return "axis";
|
|
205
|
+
if (/(?:Axis|^axis_.+__)Ticks$/u.test(id) && property === "stroke") return "mutedText";
|
|
178
206
|
if (/^parallelAxisLabels$/u.test(id) && property === "fill") return "axis";
|
|
179
|
-
if (/
|
|
180
|
-
if (/
|
|
207
|
+
if (/(?:Axis|^axis_.+__)Labels$/u.test(id) && property === "fill") return "text";
|
|
208
|
+
if (/(?:Axis|^axis_.+__)Titles?$/u.test(id) && property === "fill") return "axisTitle";
|
|
181
209
|
if (/Grid(?:Lines|Circles)?$/u.test(id) && property === "stroke") return "grid";
|
|
182
210
|
if (id === "chartTitle" && property === "fill") return "strongText";
|
|
183
211
|
if (id === "chartSubtitle" && property === "fill") return "mutedText";
|
|
@@ -198,6 +226,7 @@ function graphicRole(program, id, property) {
|
|
|
198
226
|
}
|
|
199
227
|
|
|
200
228
|
function roleLegacy(role) {
|
|
229
|
+
if (role === "axisLabel") return ["text", "axis"];
|
|
201
230
|
if (role === "axis") return ["text"];
|
|
202
231
|
if (role === "axisTitle") return ["text", "strongText"];
|
|
203
232
|
if (role === "strongText") return ["text"];
|
|
@@ -238,7 +267,7 @@ function mapGraphicProperties(
|
|
|
238
267
|
role,
|
|
239
268
|
sourceTokens,
|
|
240
269
|
targetTokens,
|
|
241
|
-
roleLegacy(role)
|
|
270
|
+
roleLegacy(role), axisFamily(id)
|
|
242
271
|
);
|
|
243
272
|
if (mapped.changed) {
|
|
244
273
|
next[property] = mapped.value;
|
|
@@ -304,6 +333,11 @@ function recolorGraphic(
|
|
|
304
333
|
|
|
305
334
|
function configRole(program, path) {
|
|
306
335
|
const property = path.at(-1);
|
|
336
|
+
if (path[0] === "guides" && path[1] === "axis") {
|
|
337
|
+
const component = path[2] === "parallel" ? path.at(-2) : path[3];
|
|
338
|
+
const scoped = axisTextRole(property, component);
|
|
339
|
+
if (scoped !== undefined) return scoped;
|
|
340
|
+
}
|
|
307
341
|
if (property === "fontFamily") return "fontFamily";
|
|
308
342
|
if (path[0] === "facets" && path.includes("headers") &&
|
|
309
343
|
property === "color") {
|
|
@@ -369,7 +403,7 @@ function recolorConfig(
|
|
|
369
403
|
}
|
|
370
404
|
if (value === null || typeof value !== "object") {
|
|
371
405
|
const property = path.at(-1);
|
|
372
|
-
if (!["color", "fill", "stroke", "background", "fontFamily"]
|
|
406
|
+
if (!["color", "fill", "stroke", "background", "fontFamily", "fontSize"]
|
|
373
407
|
.includes(property)) {
|
|
374
408
|
return { changed: false, value };
|
|
375
409
|
}
|
|
@@ -387,7 +421,7 @@ function recolorConfig(
|
|
|
387
421
|
role,
|
|
388
422
|
sourceTokens,
|
|
389
423
|
targetTokens,
|
|
390
|
-
roleLegacy(role)
|
|
424
|
+
roleLegacy(role), axisFamily(String(path[2] ?? ""))
|
|
391
425
|
);
|
|
392
426
|
}
|
|
393
427
|
if (["theme", "highlights", "selections"].includes(path[0])) {
|
|
@@ -643,12 +677,13 @@ function clearRemovedResources(overrides, node) {
|
|
|
643
677
|
clearPrefix(overrides, `g:${mark[1]}.`);
|
|
644
678
|
return;
|
|
645
679
|
}
|
|
646
|
-
const axis = /^guide\.axis\.(
|
|
680
|
+
const axis = /^guide\.axis\.([A-Za-z0-9_-]+)$/u
|
|
647
681
|
.exec(current.args.property);
|
|
648
682
|
if (axis !== null) {
|
|
649
683
|
const prefix = axis[1] === "radius" ? "radial" : axis[1];
|
|
684
|
+
const graphicPrefix = ["x", "y", "theta", "radius"].includes(axis[1]) ? `${prefix}Axis` : `axis_${axis[1]}__`;
|
|
650
685
|
clearPrefix(overrides, `c:guides.axis.${axis[1]}.`);
|
|
651
|
-
clearPrefix(overrides, `g:${
|
|
686
|
+
clearPrefix(overrides, `g:${graphicPrefix}`);
|
|
652
687
|
return;
|
|
653
688
|
}
|
|
654
689
|
const grid = /^guide\.grid\.([^\.]+)$/u.exec(current.args.property);
|
|
@@ -704,7 +739,23 @@ function clearRecreatedOverrides(overrides, op, args) {
|
|
|
704
739
|
}
|
|
705
740
|
}
|
|
706
741
|
|
|
707
|
-
function
|
|
742
|
+
function axisOverrideId(node, channel, args) {
|
|
743
|
+
if (args.target !== undefined || args.id !== undefined) return args.target ?? args.id;
|
|
744
|
+
const ids = new Set();
|
|
745
|
+
const visit = entry => {
|
|
746
|
+
if (["createGraphics", "editGraphics"].includes(entry.op)) {
|
|
747
|
+
const id = entry.args.target ?? entry.args.id;
|
|
748
|
+
const named = /^axis_(.+)__(Line|Ticks|Labels|Title)$/u.exec(id ?? "");
|
|
749
|
+
if (named !== null) ids.add(named[1]);
|
|
750
|
+
else if (new RegExp(`^${channel}Axis(?:Line|Ticks|Labels|Title)$`).test(id ?? "")) ids.add(channel);
|
|
751
|
+
}
|
|
752
|
+
entry.children?.forEach(visit);
|
|
753
|
+
};
|
|
754
|
+
if (node !== undefined) visit(node);
|
|
755
|
+
return ids.size === 1 ? [...ids][0] : channel;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function addAxisOverrides(overrides, op, args, node) {
|
|
708
759
|
const match = /^(?:create|edit)(X|Y|Theta|Radial)Axis(Line|Ticks|Labels|Title)$/u
|
|
709
760
|
.exec(op);
|
|
710
761
|
if (match === null) return;
|
|
@@ -715,22 +766,25 @@ function addAxisOverrides(overrides, op, args) {
|
|
|
715
766
|
Radial: "radius"
|
|
716
767
|
}[match[1]];
|
|
717
768
|
const component = match[2].toLowerCase();
|
|
769
|
+
const axisId = axisOverrideId(node, channel, args);
|
|
718
770
|
const idPrefix = match[1] === "Radial" ? "radial" : channel;
|
|
719
771
|
if (Object.hasOwn(args, "color")) {
|
|
720
|
-
overrides.add(`c:guides.axis.${
|
|
772
|
+
overrides.add(`c:guides.axis.${axisId}.${component}.color`);
|
|
721
773
|
overrides.add(
|
|
722
|
-
`g:${idPrefix}Axis${match[2]}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
|
|
774
|
+
`g:${axisId === channel ? `${idPrefix}Axis` : `axis_${axisId}__`}${match[2]}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
|
|
723
775
|
);
|
|
724
776
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
777
|
+
for (const property of ["fontFamily", "fontSize"]) {
|
|
778
|
+
if (["labels", "title"].includes(component) && Object.hasOwn(args, property)) {
|
|
779
|
+
overrides.add(`c:guides.axis.${axisId}.${component}.${property}`);
|
|
780
|
+
overrides.add(`g:${axisId === channel ? `${idPrefix}Axis` : `axis_${axisId}__`}${match[2]}.${property}`);
|
|
781
|
+
}
|
|
729
782
|
}
|
|
730
783
|
}
|
|
731
784
|
|
|
732
|
-
function addCompleteAxisOverride(overrides, name, args) {
|
|
785
|
+
function addCompleteAxisOverride(overrides, name, args, node) {
|
|
733
786
|
const channel = { X: "x", Y: "y", Theta: "theta", Radial: "radius" }[name];
|
|
787
|
+
const axisId = axisOverrideId(node, channel, args);
|
|
734
788
|
const idPrefix = name === "Radial" ? "radial" : channel;
|
|
735
789
|
const components = {
|
|
736
790
|
line: args.line,
|
|
@@ -745,25 +799,32 @@ function addCompleteAxisOverride(overrides, name, args) {
|
|
|
745
799
|
for (const [component, options] of Object.entries(components)) {
|
|
746
800
|
const suffix = `${component[0].toUpperCase()}${component.slice(1)}`;
|
|
747
801
|
if (Object.hasOwn(options ?? {}, "color")) {
|
|
748
|
-
overrides.add(`c:guides.axis.${
|
|
802
|
+
overrides.add(`c:guides.axis.${axisId}.${component}.color`);
|
|
749
803
|
overrides.add(
|
|
750
|
-
`g:${idPrefix}Axis${suffix}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
|
|
804
|
+
`g:${axisId === channel ? `${idPrefix}Axis` : `axis_${axisId}__`}${suffix}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
|
|
751
805
|
);
|
|
752
806
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
807
|
+
for (const property of ["fontFamily", "fontSize"]) {
|
|
808
|
+
if (["labels", "title"].includes(component) && Object.hasOwn(options ?? {}, property)) {
|
|
809
|
+
overrides.add(`c:guides.axis.${axisId}.${component}.${property}`);
|
|
810
|
+
overrides.add(`g:${axisId === channel ? `${idPrefix}Axis` : `axis_${axisId}__`}${suffix}.${property}`);
|
|
811
|
+
}
|
|
757
812
|
}
|
|
758
813
|
}
|
|
759
814
|
}
|
|
760
815
|
|
|
761
|
-
function addCompleteAxisOverrides(overrides, op, args) {
|
|
816
|
+
function addCompleteAxisOverrides(overrides, op, args, node) {
|
|
817
|
+
if (args.guides?.axes !== null && typeof args.guides?.axes === "object") {
|
|
818
|
+
addCompleteAxisOverrides(overrides, "createAxes", args.guides.axes);
|
|
819
|
+
}
|
|
820
|
+
if (op === "createGuides" && args.axes !== null && typeof args.axes === "object") {
|
|
821
|
+
addCompleteAxisOverrides(overrides, "createAxes", args.axes);
|
|
822
|
+
}
|
|
762
823
|
const match = /^(?:create|edit)(X|Y|Theta|Radial)Axis$/u.exec(op);
|
|
763
|
-
if (match !== null) addCompleteAxisOverride(overrides, match[1], args);
|
|
824
|
+
if (match !== null) addCompleteAxisOverride(overrides, match[1], args, node);
|
|
764
825
|
const group = /^(?:create|edit)(X|Y)AxisTicksAndLabels$/u.exec(op);
|
|
765
826
|
if (group !== null) {
|
|
766
|
-
addCompleteAxisOverride(overrides, group[1], { ticksAndLabels: args });
|
|
827
|
+
addCompleteAxisOverride(overrides, group[1], { target: args.target, ticksAndLabels: args }, node);
|
|
767
828
|
}
|
|
768
829
|
if (op !== "createAxes") return;
|
|
769
830
|
for (const [option, name] of [
|
|
@@ -773,7 +834,7 @@ function addCompleteAxisOverrides(overrides, op, args) {
|
|
|
773
834
|
["radius", "Radial"]
|
|
774
835
|
]) {
|
|
775
836
|
if (args[option] && typeof args[option] === "object") {
|
|
776
|
-
addCompleteAxisOverride(overrides, name,
|
|
837
|
+
for (const entry of Array.isArray(args[option]) ? args[option] : [args[option]]) addCompleteAxisOverride(overrides, name, entry);
|
|
777
838
|
}
|
|
778
839
|
}
|
|
779
840
|
}
|
|
@@ -823,11 +884,10 @@ function addParallelOverrides(overrides, program, op, args) {
|
|
|
823
884
|
`c:guides.axis.parallel.axes.dimensions.${index}.${component}.color`
|
|
824
885
|
);
|
|
825
886
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
);
|
|
887
|
+
for (const property of ["fontFamily", "fontSize"]) {
|
|
888
|
+
if (["labels", "title"].includes(component) && Object.hasOwn(options ?? {}, property)) {
|
|
889
|
+
overrides.add(`c:guides.axis.parallel.axes.dimensions.${index}.${component}.${property}`);
|
|
890
|
+
}
|
|
831
891
|
}
|
|
832
892
|
}
|
|
833
893
|
}
|
|
@@ -921,7 +981,7 @@ function explicitOverrides(program, node) {
|
|
|
921
981
|
const overrides = new Set();
|
|
922
982
|
if (op === "editGraphics" &&
|
|
923
983
|
typeof args.target === "string" &&
|
|
924
|
-
["background", "fill", "stroke", "fontFamily"].includes(args.property)) {
|
|
984
|
+
["background", "fill", "stroke", "fontFamily", "fontSize"].includes(args.property)) {
|
|
925
985
|
overrides.add(`g:${args.target}.${args.property}`);
|
|
926
986
|
}
|
|
927
987
|
if (["createCanvas", "editCanvas"].includes(op) &&
|
|
@@ -937,8 +997,8 @@ function explicitOverrides(program, node) {
|
|
|
937
997
|
addErrorBarOverrides(overrides, program, node);
|
|
938
998
|
addErrorBandOverrides(overrides, program, node);
|
|
939
999
|
addRegressionOverrides(overrides, node);
|
|
940
|
-
addAxisOverrides(overrides, op, args);
|
|
941
|
-
addCompleteAxisOverrides(overrides, op, args);
|
|
1000
|
+
addAxisOverrides(overrides, op, args, node);
|
|
1001
|
+
addCompleteAxisOverrides(overrides, op, args, node);
|
|
942
1002
|
addGridOverrides(overrides, op, args);
|
|
943
1003
|
addParallelOverrides(overrides, program, op, args);
|
|
944
1004
|
if (["createTitle", "editTitle"].includes(op)) {
|
|
@@ -959,9 +1019,21 @@ function explicitOverrides(program, node) {
|
|
|
959
1019
|
if (op === "editLegendBlock") {
|
|
960
1020
|
addLegendBlockOverrides(overrides, program, args);
|
|
961
1021
|
}
|
|
1022
|
+
const parentLegendOptions = op === "editFacetGuides" ? args.legend
|
|
1023
|
+
: ["facet", "facetGrid", "repeatCharts"].includes(op) ? args.guides?.legend : undefined;
|
|
1024
|
+
if (parentLegendOptions !== null && typeof parentLegendOptions === "object") {
|
|
1025
|
+
addLegendOverrides(overrides, program, parentLegendOptions);
|
|
1026
|
+
}
|
|
962
1027
|
if (op === "editFacetHeaders") {
|
|
963
1028
|
addFacetOverrides(overrides, program, args);
|
|
964
1029
|
}
|
|
1030
|
+
if (["facet", "facetGrid", "repeatCharts"].includes(op) && args.headers !== undefined) {
|
|
1031
|
+
const { row, column, ...common } = args.headers;
|
|
1032
|
+
addFacetOverrides(overrides, program, common);
|
|
1033
|
+
for (const [role, patch] of [["row", row], ["column", column]]) {
|
|
1034
|
+
if (patch !== undefined) addFacetOverrides(overrides, program, { ...patch, role });
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
965
1037
|
return overrides;
|
|
966
1038
|
}
|
|
967
1039
|
|
|
@@ -979,6 +1051,12 @@ function collectOverrides(program) {
|
|
|
979
1051
|
return overrides;
|
|
980
1052
|
}
|
|
981
1053
|
|
|
1054
|
+
function axisTypographyChanged(before, after) {
|
|
1055
|
+
if (before === after || after === null || typeof after !== "object") return false;
|
|
1056
|
+
return Object.entries(after).some(([key, value]) => ["fontFamily", "fontSize"].includes(key)
|
|
1057
|
+
? value !== before?.[key] : axisTypographyChanged(before?.[key], value));
|
|
1058
|
+
}
|
|
1059
|
+
|
|
982
1060
|
export function reconcileProgramTheme(program, { source, metadata }) {
|
|
983
1061
|
program = reconcileFollowingRegressions(program);
|
|
984
1062
|
const storedState = program.materializationConfigs.theme;
|
|
@@ -1031,9 +1109,12 @@ export function reconcileProgramTheme(program, { source, metadata }) {
|
|
|
1031
1109
|
next = next._withMaterializationConfig(["facets"], configs.value.facets);
|
|
1032
1110
|
}
|
|
1033
1111
|
|
|
1112
|
+
if (configs.changed) next = synchronizeFacetLegendAppearance(next);
|
|
1113
|
+
|
|
1034
1114
|
const facetsChanged = configs.value.facets !==
|
|
1035
1115
|
themed.materializationConfigs.facets;
|
|
1036
|
-
const typographyChanged = sourceTokens.fontFamily !== targetTokens.fontFamily
|
|
1116
|
+
const typographyChanged = sourceTokens.fontFamily !== targetTokens.fontFamily ||
|
|
1117
|
+
axisTypographyChanged(themed.guideConfigs.axis, configs.value.guides.axis);
|
|
1037
1118
|
if (facetsChanged && next.compositionSpec?.type === "facet") {
|
|
1038
1119
|
next = next.materializeComposition();
|
|
1039
1120
|
} else if (typographyChanged) {
|
|
@@ -158,3 +158,15 @@ export function setThemeStateOverrides(state, overrides) {
|
|
|
158
158
|
overrides: cloneAndFreeze([...overrides].sort())
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
// Resolve active font defaults before guide layout validates their occupied bounds.
|
|
163
|
+
export function axisThemeTypography(state, component) {
|
|
164
|
+
if (resolveEffectiveThemeFrame(state) === undefined) return {};
|
|
165
|
+
const tokens = resolveEffectiveThemeTokens(state);
|
|
166
|
+
const role = component === "labels" ? "axisLabel" : "axisTitle";
|
|
167
|
+
const fontSize = tokens[`${role}FontSize`];
|
|
168
|
+
return {
|
|
169
|
+
fontFamily: tokens[`${role}FontFamily`] ?? tokens.fontFamily,
|
|
170
|
+
...(fontSize === undefined ? {} : { fontSize })
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { paintedTitleComponent, titleTextProperties } from "../../layout/title.js";
|
|
1
2
|
import { withGuideLayoutValidation } from "../../materialization/guides/layout.js";
|
|
2
3
|
import { action } from "../../core/action.js";
|
|
3
4
|
import { noOptions } from "../../core/validation.js";
|
|
@@ -56,24 +57,9 @@ function ensureTextShape(program, id, component) {
|
|
|
56
57
|
return program;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
function distributed(value, count) {
|
|
60
|
-
return count === 1 && Array.isArray(value) ? value[0] : value;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
60
|
function editTextGraphic(program, id, component, style) {
|
|
64
|
-
const count = component.lines.length;
|
|
65
61
|
let next = ensureTextShape(program, id, component);
|
|
66
|
-
for (const [property, value] of Object.entries({
|
|
67
|
-
x: distributed(component.x, count),
|
|
68
|
-
y: distributed(component.y, count),
|
|
69
|
-
text: distributed(component.lines, count),
|
|
70
|
-
fill: style.color,
|
|
71
|
-
fontSize: style.fontSize,
|
|
72
|
-
fontFamily: style.fontFamily,
|
|
73
|
-
fontWeight: style.fontWeight,
|
|
74
|
-
textAlign: component.textAlign,
|
|
75
|
-
textBaseline: "middle"
|
|
76
|
-
})) {
|
|
62
|
+
for (const [property, value] of Object.entries(titleTextProperties(component, style))) {
|
|
77
63
|
next = next.editGraphics({ target: id, property, value });
|
|
78
64
|
}
|
|
79
65
|
if (component.explicitRotation) {
|
|
@@ -99,10 +85,11 @@ function titleTextAction(kind, create) {
|
|
|
99
85
|
if (create && this.graphicSpec.objects[id] !== undefined) {
|
|
100
86
|
throw new Error(`${op} requires a missing chart ${kind} graphic.`);
|
|
101
87
|
}
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
88
|
+
const layout = resolveTitleLayout(this, config)[kind];
|
|
89
|
+
if (layout === undefined) {
|
|
104
90
|
throw new Error(`${op} requires semantic subtitle text.`);
|
|
105
91
|
}
|
|
92
|
+
const component = paintedTitleComponent(layout);
|
|
106
93
|
if (!create) {
|
|
107
94
|
return editTextGraphic(this, id, component, config[`${kind}Style`]);
|
|
108
95
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { textBoundsFitCanvas } from "../../core/textMetrics.js";
|
|
2
|
+
import { resolveGuideCollisionBlocks } from "../../materialization/guides/resources.js";
|
|
3
|
+
import { canvasOverflowError } from "../../layout/canvas.js";
|
|
4
|
+
import { validateFontStyle } from "../../core/font.js";
|
|
1
5
|
import { isPlainObject } from "../../core/immutable.js";
|
|
2
6
|
import {
|
|
3
7
|
validateKeys,
|
|
@@ -19,11 +23,11 @@ import { findCanvasGraphic } from
|
|
|
19
23
|
"../../materialization/graphicHierarchy.js";
|
|
20
24
|
|
|
21
25
|
const OPTIONS = Object.freeze([
|
|
22
|
-
"text", "subtitle", "position", "align", "offset", "gap", "titleStyle",
|
|
26
|
+
"text", "subtitle", "position", "align", "offset", "alongOffset", "gap", "titleStyle",
|
|
23
27
|
"subtitleStyle", "maxWidth", "wrap", "lineHeight"
|
|
24
28
|
]);
|
|
25
29
|
const STYLE_OPTIONS = Object.freeze([
|
|
26
|
-
"color", "fontSize", "fontFamily", "fontWeight"
|
|
30
|
+
"color", "fontSize", "fontFamily", "fontWeight", "fontStyle"
|
|
27
31
|
]);
|
|
28
32
|
const POSITIONS = Object.freeze(["top", "bottom", "left", "right"]);
|
|
29
33
|
const ALIGNS = Object.freeze(["left", "center", "right"]);
|
|
@@ -32,6 +36,7 @@ const DEFAULTS = Object.freeze({
|
|
|
32
36
|
position: "top",
|
|
33
37
|
align: "left",
|
|
34
38
|
offset: 0,
|
|
39
|
+
alongOffset: 0,
|
|
35
40
|
gap: 8,
|
|
36
41
|
titleStyle: Object.freeze({
|
|
37
42
|
color: DEFAULT_COLORS.strongText,
|
|
@@ -47,9 +52,12 @@ const DEFAULTS = Object.freeze({
|
|
|
47
52
|
})
|
|
48
53
|
});
|
|
49
54
|
|
|
50
|
-
export function validateTitleString(value, label) {
|
|
55
|
+
export function validateTitleString(value, label, multiline = false) {
|
|
51
56
|
validateNonEmptyString(value, label);
|
|
52
|
-
if (/\r|\n/u.
|
|
57
|
+
if (multiline && value.split(/\r\n|\r|\n/u).every(line => line === "")) {
|
|
58
|
+
throw new Error(`${label} requires at least one non-empty line.`);
|
|
59
|
+
}
|
|
60
|
+
if (!multiline && /\r|\n/u.test(value)) {
|
|
53
61
|
throw new Error(`${label} does not accept explicit newlines.`);
|
|
54
62
|
}
|
|
55
63
|
return value;
|
|
@@ -70,6 +78,7 @@ function normalizeStyle(value, defaults, label) {
|
|
|
70
78
|
)) {
|
|
71
79
|
throw new TypeError(`${label} fontWeight must be a non-empty string or number.`);
|
|
72
80
|
}
|
|
81
|
+
if (style.fontStyle !== undefined) validateFontStyle(style.fontStyle, label);
|
|
73
82
|
return style;
|
|
74
83
|
}
|
|
75
84
|
|
|
@@ -78,6 +87,7 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
|
|
|
78
87
|
const align = args.align ?? defaults.align;
|
|
79
88
|
const offset = args.offset ?? defaults.offset;
|
|
80
89
|
const gap = args.gap ?? defaults.gap;
|
|
90
|
+
const alongOffset = args.alongOffset ?? defaults.alongOffset ?? 0;
|
|
81
91
|
const titleStyle = normalizeStyle(
|
|
82
92
|
args.titleStyle,
|
|
83
93
|
defaults.titleStyle,
|
|
@@ -100,6 +110,7 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
|
|
|
100
110
|
if (!Number.isFinite(offset)) {
|
|
101
111
|
throw new TypeError("Chart title offset must be a finite number.");
|
|
102
112
|
}
|
|
113
|
+
if (!Number.isFinite(alongOffset)) throw new TypeError("Chart title alongOffset must be a finite number.");
|
|
103
114
|
validateNonNegativeFinite(gap, "Chart title gap");
|
|
104
115
|
if (maxWidth !== undefined) {
|
|
105
116
|
validatePositiveFinite(maxWidth, "Chart title maxWidth");
|
|
@@ -121,6 +132,7 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
|
|
|
121
132
|
position,
|
|
122
133
|
align,
|
|
123
134
|
offset,
|
|
135
|
+
alongOffset,
|
|
124
136
|
gap,
|
|
125
137
|
titleStyle,
|
|
126
138
|
subtitleStyle,
|
|
@@ -132,10 +144,10 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
|
|
|
132
144
|
|
|
133
145
|
export function normalizeTitleOptions(args) {
|
|
134
146
|
validateOptionObject(args, OPTIONS, "createTitle");
|
|
135
|
-
const text = validateTitleString(args.text, "Chart title text");
|
|
147
|
+
const text = validateTitleString(args.text, "Chart title text", true);
|
|
136
148
|
const subtitle = args.subtitle === undefined
|
|
137
149
|
? undefined
|
|
138
|
-
: validateTitleString(args.subtitle, "Chart subtitle");
|
|
150
|
+
: validateTitleString(args.subtitle, "Chart subtitle", true);
|
|
139
151
|
return {
|
|
140
152
|
text,
|
|
141
153
|
subtitle,
|
|
@@ -151,12 +163,12 @@ export function normalizeTitleEditOptions(args, previous, semanticTitle) {
|
|
|
151
163
|
});
|
|
152
164
|
const text = args.text === undefined
|
|
153
165
|
? semanticTitle.text
|
|
154
|
-
: validateTitleString(args.text, "Chart title text");
|
|
166
|
+
: validateTitleString(args.text, "Chart title text", true);
|
|
155
167
|
const subtitle = args.subtitle === false
|
|
156
168
|
? undefined
|
|
157
169
|
: args.subtitle === undefined
|
|
158
170
|
? semanticTitle.subtitle
|
|
159
|
-
: validateTitleString(args.subtitle, "Chart subtitle");
|
|
171
|
+
: validateTitleString(args.subtitle, "Chart subtitle", true);
|
|
160
172
|
if (args.subtitle !== undefined && args.subtitle !== false && typeof args.subtitle !== "string") {
|
|
161
173
|
throw new TypeError("Chart subtitle must be a non-empty string or false.");
|
|
162
174
|
}
|
|
@@ -175,11 +187,8 @@ export function requireTitleConfig(program) {
|
|
|
175
187
|
}
|
|
176
188
|
|
|
177
189
|
function validateLayout(position, titleBounds, plot, canvas) {
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
titleBounds.right > canvas.width || titleBounds.bottom > canvas.height
|
|
181
|
-
) {
|
|
182
|
-
throw new Error(`Chart title requires more ${position}-margin space.`);
|
|
190
|
+
if (!textBoundsFitCanvas(titleBounds, canvas)) {
|
|
191
|
+
throw canvasOverflowError(`Chart title requires more ${position}-margin space.`, [titleBounds], canvas);
|
|
183
192
|
}
|
|
184
193
|
const outsidePlot = {
|
|
185
194
|
top: titleBounds.bottom <= plot.y,
|
|
@@ -205,19 +214,31 @@ export function resolveTitleLayout(program, config) {
|
|
|
205
214
|
throw new Error("Chart title layout requires Canvas bounds and dimensions.");
|
|
206
215
|
}
|
|
207
216
|
const block = buildTitleReadingBlock({
|
|
208
|
-
text: validateTitleString(program.semanticSpec.title.text, "Chart title text"),
|
|
217
|
+
text: validateTitleString(program.semanticSpec.title.text, "Chart title text", true),
|
|
209
218
|
subtitle: program.semanticSpec.title.subtitle === undefined
|
|
210
219
|
? undefined
|
|
211
220
|
: validateTitleString(
|
|
212
221
|
program.semanticSpec.title.subtitle,
|
|
213
|
-
"Chart subtitle"
|
|
222
|
+
"Chart subtitle", true
|
|
214
223
|
)
|
|
215
224
|
}, config, program.materializationConfigs.textMetrics);
|
|
225
|
+
const automatic = program.materializationConfigs.canvas?.plot !== undefined;
|
|
226
|
+
const near = ["top", "left"].includes(config.position);
|
|
216
227
|
const horizontal = ["top", "bottom"].includes(config.position);
|
|
228
|
+
let titleEdge = horizontal ? plot.y + (near ? 0 : plot.height)
|
|
229
|
+
: plot.x + (near ? 0 : plot.width);
|
|
230
|
+
if (automatic) {
|
|
231
|
+
const occupied = resolveGuideCollisionBlocks(program.graphicSpec, program.guideConfigs,
|
|
232
|
+
undefined, program.materializationConfigs.textMetrics).filter(item => item.position === config.position);
|
|
233
|
+
const key = horizontal ? (near ? "top" : "bottom") : (near ? "left" : "right");
|
|
234
|
+
for (const item of occupied) titleEdge = near ? Math.min(titleEdge, item.bounds[key]) : Math.max(titleEdge, item.bounds[key]);
|
|
235
|
+
titleEdge += (near ? -1 : 1) * (16 + config.offset);
|
|
236
|
+
}
|
|
217
237
|
let component;
|
|
218
238
|
if (horizontal) {
|
|
219
|
-
const x = alignedTextAnchor(plot.x, plot.width, config.align);
|
|
220
|
-
const blockTop =
|
|
239
|
+
const x = alignedTextAnchor(plot.x, plot.width, config.align) + (config.alongOffset ?? 0);
|
|
240
|
+
const blockTop = automatic ? titleEdge - (near ? block.height : 0)
|
|
241
|
+
: config.position === "top"
|
|
221
242
|
? 16 + config.offset
|
|
222
243
|
: plot.y + plot.height + config.offset;
|
|
223
244
|
component = (lines, centers) => ({
|
|
@@ -230,8 +251,9 @@ export function resolveTitleLayout(program, config) {
|
|
|
230
251
|
});
|
|
231
252
|
} else {
|
|
232
253
|
const rotation = config.position === "left" ? -Math.PI / 2 : Math.PI / 2;
|
|
233
|
-
const y = alignedTitleAnchor(plot.y, plot.height, block.width, config.align);
|
|
234
|
-
const edge =
|
|
254
|
+
const y = alignedTitleAnchor(plot.y, plot.height, block.width, config.align) + (config.alongOffset ?? 0);
|
|
255
|
+
const edge = automatic ? titleEdge + (near ? -block.height : block.height)
|
|
256
|
+
: config.position === "left"
|
|
235
257
|
? 16 + config.offset
|
|
236
258
|
: canvas.properties.width - 16 + config.offset;
|
|
237
259
|
const mapX = value => config.position === "left" ? edge + value : edge - value;
|
|
@@ -2,7 +2,7 @@ import { action } from "../../core/action.js";
|
|
|
2
2
|
import { validateUserId } from "../../core/identifiers.js";
|
|
3
3
|
import { validateKeys } from "../../core/validation.js";
|
|
4
4
|
import { findDataset } from "../../selectors/datasets.js";
|
|
5
|
-
import { findLayer } from "../../selectors/layers.js";
|
|
5
|
+
import { findLayer, resolveConfiguredOwner } from "../../selectors/layers.js";
|
|
6
6
|
import { rebindDistributionGuides } from "../distributions/revision.js";
|
|
7
7
|
import {
|
|
8
8
|
resolveViolinDensity,
|
|
@@ -15,24 +15,7 @@ const OPTIONS = Object.freeze([
|
|
|
15
15
|
"target", "data", "x", "y", "split", "density"
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
const eligible = program.semanticSpec.layers.filter(
|
|
20
|
-
layer => program.markConfigs[layer.id]?.violinPlot?.materialized === true
|
|
21
|
-
);
|
|
22
|
-
if (requested !== undefined) {
|
|
23
|
-
const id = validateUserId(requested, "Violin-plot owner id");
|
|
24
|
-
const layer = findLayer(program, id);
|
|
25
|
-
if (layer === undefined || !eligible.includes(layer)) {
|
|
26
|
-
throw new Error(`Unknown violin-plot owner "${id}".`);
|
|
27
|
-
}
|
|
28
|
-
return layer;
|
|
29
|
-
}
|
|
30
|
-
const current = findLayer(program, program.context.currentMark);
|
|
31
|
-
if (current !== undefined && eligible.includes(current)) return current;
|
|
32
|
-
if (eligible.length === 1) return eligible[0];
|
|
33
|
-
if (eligible.length === 0) throw new Error(`${OPERATION} requires a violin plot.`);
|
|
34
|
-
throw new Error(`${OPERATION} target is ambiguous; provide target.`);
|
|
35
|
-
}
|
|
18
|
+
|
|
36
19
|
|
|
37
20
|
function currentPosition(owner, current, channel) {
|
|
38
21
|
const categorical = current.orientation === "vertical"
|
|
@@ -84,7 +67,7 @@ export const editViolinPlot = /* @__PURE__ */ action(
|
|
|
84
67
|
if (!OPTIONS.slice(1).some(key => Object.hasOwn(args, key))) {
|
|
85
68
|
throw new Error(`${OPERATION} requires at least one violin-plot option.`);
|
|
86
69
|
}
|
|
87
|
-
const owner =
|
|
70
|
+
const owner = resolveConfiguredOwner(this, args.target, { config: "violinPlot", operation: OPERATION, kind: "violin", materialized: true }, validateUserId);
|
|
88
71
|
const current = this.markConfigs[owner.id].violinPlot;
|
|
89
72
|
const source = Object.hasOwn(args, "data")
|
|
90
73
|
? validateUserId(args.data, "Violin-plot data id")
|