ggaction 0.0.13 → 0.0.15
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 +78 -0
- package/README.md +20 -11
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +8 -3
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +11 -3
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/categoryOrder/index.js +15 -4
- package/src/actions/charts/area.js +5 -1
- package/src/actions/charts/bar.js +2 -1
- package/src/actions/charts/density.js +6 -2
- package/src/actions/charts/ecdf.js +4 -1
- package/src/actions/charts/endpoints.js +25 -3
- package/src/actions/charts/heatmap.js +2 -1
- package/src/actions/charts/histogram.js +4 -3
- package/src/actions/charts/horizon.js +3 -1
- package/src/actions/charts/interval-regression.js +5 -2
- package/src/actions/charts/line.js +3 -1
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +10 -2
- package/src/actions/charts/polar.js +15 -4
- package/src/actions/charts/radar.js +8 -2
- package/src/actions/charts/radial.js +10 -2
- package/src/actions/charts/rug-strip.js +6 -2
- package/src/actions/charts/scatter.js +3 -1
- package/src/actions/composition/actions.js +13 -4
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +1 -1
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +2 -2
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +47 -17
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +14 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +14 -0
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +16 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/summary.js +1 -1
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +2 -1
- package/src/actions/encodings/density.js +5 -2
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +20 -13
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +10 -2
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/remove.js +9 -4
- package/src/actions/encodings/ruleAppearance.js +1 -23
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/errorBands/create.js +23 -3
- package/src/actions/errorBands/edit.js +21 -6
- package/src/actions/errorBars/create.js +35 -7
- package/src/actions/errorBars/edit.js +19 -4
- package/src/actions/facets/actions.js +237 -57
- package/src/actions/facets/derive.js +91 -26
- package/src/actions/gradientPlots/components.js +10 -2
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +3 -1
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/labels.js +58 -12
- package/src/actions/guides/axes/lines.js +6 -2
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/tickGroups.js +2 -1
- package/src/actions/guides/axes/ticks.js +6 -2
- package/src/actions/guides/axes/titles.js +11 -3
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +19 -3
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +134 -48
- package/src/actions/guides/legends/categorical/components.js +17 -12
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +97 -15
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +85 -30
- package/src/actions/guides/legends/continuous/common.js +30 -12
- package/src/actions/guides/legends/continuous/gradient.js +10 -7
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +8 -3
- package/src/actions/guides/legends/continuous/opacity.js +8 -5
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +158 -59
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +54 -19
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +13 -3
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +93 -40
- package/src/actions/guides/legends/strokeWidth.js +34 -28
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +4 -4
- package/src/actions/guides/polar/axes/labels.js +24 -5
- package/src/actions/guides/polar/axes/lines.js +10 -4
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +1 -1
- package/src/actions/guides/polar/axes/titles.js +1 -1
- package/src/actions/guides/polar/grids.js +1 -1
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +14 -3
- package/src/actions/marks/arc/actions.js +77 -19
- package/src/actions/marks/area/actions.js +34 -9
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +22 -2
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +37 -14
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +18 -6
- package/src/actions/marks/point/materialize.js +49 -15
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +601 -30
- package/src/actions/marks/remove.js +71 -1
- package/src/actions/marks/rule/actions.js +47 -10
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +74 -9
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +154 -6
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +2 -1
- package/src/actions/primitives/semanticAction.js +83 -10
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +10 -2
- package/src/actions/regression/edit.js +21 -7
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +54 -22
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +66 -11
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +4 -3
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +68 -13
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +315 -67
- package/src/actions/theme/state.js +160 -0
- package/src/actions/violinPlots/create.js +5 -2
- package/src/actions/violinPlots/edit.js +1 -0
- package/src/core/action.js +10 -0
- package/src/core/compositionState.js +6 -1
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +340 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +343 -0
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +19 -4
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/svg.js +19 -0
- package/src/selectors/datasets.js +32 -1
- package/src/selectors/layers.js +4 -0
- package/src/theme/defaults.js +101 -0
- package/types/basic.d.ts +6 -0
- package/types/index.d.ts +85 -0
- package/types/program.d.ts +910 -117
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { numericExtent } from "../../../grammar/numeric.js";
|
|
11
11
|
import {
|
|
12
12
|
readNominalField,
|
|
13
|
+
readScaleField,
|
|
13
14
|
readTemporalField
|
|
14
15
|
} from "../../../grammar/scales/index.js";
|
|
15
16
|
import { findSemanticScale } from "../../../selectors/scales.js";
|
|
@@ -52,9 +53,19 @@ function aggregateCellDefinitions(program, layer, dataset) {
|
|
|
52
53
|
const offsetChannel = resolveBarOffsetChannel(layer);
|
|
53
54
|
const offsetScale = program.resolvedScales[layer.encoding?.[offsetChannel]?.scale];
|
|
54
55
|
const layout = resolveBarColorLayout(layer);
|
|
56
|
+
const stroke = layer.encoding?.stroke;
|
|
55
57
|
|
|
56
58
|
function definition(cell, start, end) {
|
|
57
59
|
const members = aggregateMembers(dataset.values, layer, cell, channels);
|
|
60
|
+
const strokeValues = stroke === undefined ? undefined : readScaleField(
|
|
61
|
+
members,
|
|
62
|
+
stroke.field,
|
|
63
|
+
stroke.fieldType,
|
|
64
|
+
{ temporalUnit: stroke.temporalUnit }
|
|
65
|
+
);
|
|
66
|
+
if (strokeValues !== undefined && new Set(strokeValues).size !== 1) {
|
|
67
|
+
throw new Error("Bar stroke requires one value within each aggregate cell.");
|
|
68
|
+
}
|
|
58
69
|
return {
|
|
59
70
|
fields: uniqueFields(members),
|
|
60
71
|
channels: {
|
|
@@ -62,6 +73,7 @@ function aggregateCellDefinitions(program, layer, dataset) {
|
|
|
62
73
|
[channels.measure]: start,
|
|
63
74
|
[`${channels.measure}2`]: end,
|
|
64
75
|
...(cell.color === undefined ? {} : { color: cell.color }),
|
|
76
|
+
...(strokeValues === undefined ? {} : { stroke: strokeValues[0] }),
|
|
65
77
|
...(layer.encoding?.[offsetChannel] === undefined
|
|
66
78
|
? {}
|
|
67
79
|
: { [offsetChannel]: cell.series ?? cell.color })
|
|
@@ -88,8 +100,18 @@ function histogramDefinitions(program, layer, dataset) {
|
|
|
88
100
|
resolvedScales: program.resolvedScales
|
|
89
101
|
});
|
|
90
102
|
const colorScale = program.resolvedScales[colorEncoding?.scale];
|
|
103
|
+
const stroke = layer.encoding?.stroke;
|
|
91
104
|
return segments.map(segment => {
|
|
92
105
|
const colorValue = segment.colorValue ?? colorScale?.domain[segment.category];
|
|
106
|
+
const strokeValues = stroke === undefined ? undefined : readScaleField(
|
|
107
|
+
segment.members,
|
|
108
|
+
stroke.field,
|
|
109
|
+
stroke.fieldType,
|
|
110
|
+
{ temporalUnit: stroke.temporalUnit }
|
|
111
|
+
);
|
|
112
|
+
if (strokeValues !== undefined && new Set(strokeValues).size !== 1) {
|
|
113
|
+
throw new Error("Histogram stroke requires one value within each bin/series cell.");
|
|
114
|
+
}
|
|
93
115
|
return {
|
|
94
116
|
fields: uniqueFields(segment.members),
|
|
95
117
|
channels: {
|
|
@@ -97,7 +119,8 @@ function histogramDefinitions(program, layer, dataset) {
|
|
|
97
119
|
x2: segment.end,
|
|
98
120
|
y: segment.stackStart,
|
|
99
121
|
y2: segment.stackEnd,
|
|
100
|
-
...(colorValue === undefined ? {} : { color: colorValue })
|
|
122
|
+
...(colorValue === undefined ? {} : { color: colorValue }),
|
|
123
|
+
...(strokeValues === undefined ? {} : { stroke: strokeValues[0] })
|
|
101
124
|
},
|
|
102
125
|
members: segment.members
|
|
103
126
|
};
|
|
@@ -188,14 +211,18 @@ export function resolveBarItems(program, layer, dataset, selectionGrain) {
|
|
|
188
211
|
if (definitions === undefined) {
|
|
189
212
|
throw new Error(`Bar mark "${layer.id}" is incomplete for selection.`);
|
|
190
213
|
}
|
|
214
|
+
const graphicTypes = Object.hasOwn(
|
|
215
|
+
program.markConfigs[layer.id]?.barAppearance ?? {},
|
|
216
|
+
"cornerRadius"
|
|
217
|
+
) ? ["rect", "path"] : "rect";
|
|
191
218
|
if (selectionGrain === "stack") {
|
|
192
219
|
return finalizeItems(
|
|
193
220
|
program,
|
|
194
221
|
layer,
|
|
195
222
|
"stack",
|
|
196
223
|
barStackDefinitions(layer, grain, definitions),
|
|
197
|
-
|
|
224
|
+
graphicTypes
|
|
198
225
|
);
|
|
199
226
|
}
|
|
200
|
-
return finalizeItems(program, layer, grain, definitions,
|
|
227
|
+
return finalizeItems(program, layer, grain, definitions, graphicTypes);
|
|
201
228
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "../../../grammar/markSelection.js";
|
|
7
7
|
import { unionConcreteGraphicBounds } from
|
|
8
8
|
"../../../grammar/schemas/graphicBounds.js";
|
|
9
|
+
import { readRectItemGeometry } from "../../../grammar/roundedRect.js";
|
|
9
10
|
|
|
10
11
|
export function itemKey(layer, grain, index) {
|
|
11
12
|
return `${layer.id}/${grain}/${index}`;
|
|
@@ -54,14 +55,15 @@ export function uniqueFields(rows) {
|
|
|
54
55
|
|
|
55
56
|
function requireResolvedGraphic(program, layer, type) {
|
|
56
57
|
const graphic = program.graphicSpec.objects[layer.id];
|
|
58
|
+
const types = Array.isArray(type) ? type : [type];
|
|
57
59
|
const compatibleCollection = graphic?.type === "collection" &&
|
|
58
|
-
graphic.items?.every(child => child.type
|
|
60
|
+
graphic.items?.every(child => types.includes(child.type));
|
|
59
61
|
if (
|
|
60
|
-
(graphic?.type
|
|
62
|
+
(!types.includes(graphic?.type) && !compatibleCollection) ||
|
|
61
63
|
!Array.isArray(graphic.items)
|
|
62
64
|
) {
|
|
63
65
|
throw new Error(
|
|
64
|
-
`Mark "${layer.id}" requires a materialized ${
|
|
66
|
+
`Mark "${layer.id}" requires a materialized ${types.join("/")} collection for selection.`
|
|
65
67
|
);
|
|
66
68
|
}
|
|
67
69
|
return graphic;
|
|
@@ -78,6 +80,13 @@ export function concreteProperties(properties) {
|
|
|
78
80
|
);
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
function selectableGraphicProperties(item) {
|
|
84
|
+
return {
|
|
85
|
+
...concreteProperties(item?.properties),
|
|
86
|
+
...(readRectItemGeometry(item) ?? {})
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
81
90
|
function sharedConcreteProperties(items) {
|
|
82
91
|
if (items.length === 0) return {};
|
|
83
92
|
const shared = concreteProperties(items[0].properties);
|
|
@@ -118,7 +127,10 @@ export function finalizeItems(program, layer, grain, definitions, graphicType) {
|
|
|
118
127
|
const items = indices.map(childIndex => graphic.items[childIndex]);
|
|
119
128
|
const properties = definition.properties ?? (
|
|
120
129
|
items.length === 1
|
|
121
|
-
?
|
|
130
|
+
? selectableGraphicProperties({
|
|
131
|
+
type: items[0].type ?? graphic.type,
|
|
132
|
+
properties: items[0].properties
|
|
133
|
+
})
|
|
122
134
|
: {
|
|
123
135
|
...sharedConcreteProperties(items),
|
|
124
136
|
...collectionBounds(program, items)
|
|
@@ -131,6 +143,9 @@ export function finalizeItems(program, layer, grain, definitions, graphicType) {
|
|
|
131
143
|
fields: cloneAndFreeze(definition.fields),
|
|
132
144
|
channels: cloneAndFreeze(definition.channels),
|
|
133
145
|
properties: cloneAndFreeze(properties),
|
|
146
|
+
...(definition.geometry === undefined
|
|
147
|
+
? {}
|
|
148
|
+
: { geometry: cloneAndFreeze(definition.geometry) }),
|
|
134
149
|
members: Object.freeze([...definition.members]),
|
|
135
150
|
graphicIds: Object.freeze(items.map((child, childOffset) =>
|
|
136
151
|
child.id ?? graphicId(layer, indices[childOffset])
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { rectUsesFields } from "../../../grammar/rects.js";
|
|
2
|
+
import { readRectItemGeometry } from "../../../grammar/roundedRect.js";
|
|
2
3
|
import { resolveRectRows } from "../../rect.js";
|
|
3
4
|
import {
|
|
4
5
|
channelMapFromRow,
|
|
@@ -9,6 +10,17 @@ import {
|
|
|
9
10
|
uniqueFields
|
|
10
11
|
} from "./common.js";
|
|
11
12
|
|
|
13
|
+
function rectSelectionProperties(graphic, index) {
|
|
14
|
+
const item = graphic.items[index];
|
|
15
|
+
return {
|
|
16
|
+
...concreteProperties(item?.properties),
|
|
17
|
+
...(readRectItemGeometry({
|
|
18
|
+
type: item?.type ?? graphic.type,
|
|
19
|
+
properties: item?.properties
|
|
20
|
+
}) ?? {})
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
function resolveGradientPlotItems(program, layer, dataset) {
|
|
13
25
|
const graphic = program.graphicSpec.objects[layer.id];
|
|
14
26
|
return finalizeItems(
|
|
@@ -19,7 +31,7 @@ function resolveGradientPlotItems(program, layer, dataset) {
|
|
|
19
31
|
key: itemKey(layer, "rect", index),
|
|
20
32
|
fields: ownFields(row),
|
|
21
33
|
channels: channelMapFromRow(row, layer),
|
|
22
|
-
properties:
|
|
34
|
+
properties: rectSelectionProperties(graphic, index),
|
|
23
35
|
members: [row]
|
|
24
36
|
})),
|
|
25
37
|
"rect"
|
|
@@ -28,7 +40,13 @@ function resolveGradientPlotItems(program, layer, dataset) {
|
|
|
28
40
|
|
|
29
41
|
export function resolveRectItems(program, layer, dataset) {
|
|
30
42
|
const graphic = program.graphicSpec.objects[layer.id];
|
|
31
|
-
|
|
43
|
+
const rounded = Object.hasOwn(program.markConfigs[layer.id] ?? {}, "cornerRadius");
|
|
44
|
+
const compatibleCollection = rounded && graphic?.type === "collection" &&
|
|
45
|
+
graphic.items?.every(item => ["rect", "path"].includes(item.type));
|
|
46
|
+
if (
|
|
47
|
+
(graphic?.type !== "rect" && !compatibleCollection) ||
|
|
48
|
+
!Array.isArray(graphic.items)
|
|
49
|
+
) {
|
|
32
50
|
throw new Error(`Rect mark "${layer.id}" is incomplete for selection.`);
|
|
33
51
|
}
|
|
34
52
|
if (program.markConfigs[layer.id]?.gradientPlot?.materialized === true) {
|
|
@@ -40,8 +58,14 @@ export function resolveRectItems(program, layer, dataset) {
|
|
|
40
58
|
key: itemKey(layer, "rect", item.sourceIndex),
|
|
41
59
|
fields: hasField ? ownFields(item.row) : uniqueFields(dataset.values),
|
|
42
60
|
channels: channelMapFromRow(item.row, layer),
|
|
43
|
-
properties:
|
|
61
|
+
properties: rectSelectionProperties(graphic, graphicIndex),
|
|
44
62
|
members: hasField ? [item.row] : dataset.values
|
|
45
63
|
}));
|
|
46
|
-
return finalizeItems(
|
|
64
|
+
return finalizeItems(
|
|
65
|
+
program,
|
|
66
|
+
layer,
|
|
67
|
+
"rect",
|
|
68
|
+
definitions,
|
|
69
|
+
rounded ? ["rect", "path"] : "rect"
|
|
70
|
+
);
|
|
47
71
|
}
|
|
@@ -10,7 +10,7 @@ function translateCommand(command, offset) {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function transformPathHighlightProperties(properties, style) {
|
|
13
|
-
const { offset, ...appearance } = style;
|
|
13
|
+
const { offset = { x: 0, y: 0 }, ...appearance } = style;
|
|
14
14
|
return {
|
|
15
15
|
...properties,
|
|
16
16
|
...appearance,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createPointShapeGraphic } from "../../grammar/pointShapes.js";
|
|
2
2
|
import { finiteMidpoint } from "../../grammar/numeric.js";
|
|
3
|
+
import { requestedStrokeDetails } from "../../grammar/strokeStyle.js";
|
|
3
4
|
|
|
4
5
|
function pointCenterAndArea(child) {
|
|
5
6
|
const properties = child.properties;
|
|
@@ -75,7 +76,8 @@ export function transformPointHighlightChild(child, style) {
|
|
|
75
76
|
? (child.properties.opacity === undefined ? {} : { opacity: child.properties.opacity })
|
|
76
77
|
: { opacity: style.opacity }),
|
|
77
78
|
...(style.stroke === undefined ? {} : { stroke: style.stroke }),
|
|
78
|
-
...(style.strokeWidth === undefined ? {} : { strokeWidth: style.strokeWidth })
|
|
79
|
+
...(style.strokeWidth === undefined ? {} : { strokeWidth: style.strokeWidth }),
|
|
80
|
+
...requestedStrokeDetails(child.properties, "Point highlight source")
|
|
79
81
|
};
|
|
80
82
|
if (style.shape !== undefined) {
|
|
81
83
|
return createPointShapeGraphic({
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { resolveBarChannels } from "../grammar/bars/policy.js";
|
|
2
|
+
import {
|
|
3
|
+
assertMarkLabelPlacementSupport,
|
|
4
|
+
normalizeMarkLabelPlacement,
|
|
5
|
+
resolveMarkLabelPlacement
|
|
6
|
+
} from "../layout/labels.js";
|
|
2
7
|
import { resolveMarkLabelValues } from "../grammar/markLabels.js";
|
|
3
8
|
import { formatTextValue } from "../grammar/text.js";
|
|
4
9
|
import { normalizePositionDatum } from "../grammar/positionDatum.js";
|
|
5
10
|
import { mapOrdinalPositionValues } from "../grammar/scales/index.js";
|
|
6
11
|
import { findDataset } from "../selectors/datasets.js";
|
|
12
|
+
import { findCoordinate } from "../selectors/coordinates.js";
|
|
7
13
|
import { findLayer } from "../selectors/layers.js";
|
|
14
|
+
import { unionConcreteGraphicBounds } from
|
|
15
|
+
"../grammar/schemas/graphicBounds.js";
|
|
16
|
+
import { polarDirection } from "../grammar/polar.js";
|
|
8
17
|
import { resolveMarkItems } from "./selection/policies/index.js";
|
|
18
|
+
import { resolveMarkSelection } from "./selection/state.js";
|
|
9
19
|
import { mapScaleConsumerValues } from "./scales/map.js";
|
|
10
20
|
import { resolveRowEncodingValues } from "./rowEncoding.js";
|
|
11
21
|
|
|
@@ -83,6 +93,172 @@ function sourceAnchor(program, source, item) {
|
|
|
83
93
|
return { x: item.properties.x, y: item.properties.y };
|
|
84
94
|
}
|
|
85
95
|
|
|
96
|
+
function explicitIntervalAxis(source) {
|
|
97
|
+
const axes = ["x", "y"].filter(axis =>
|
|
98
|
+
source.encoding?.[axis] !== undefined &&
|
|
99
|
+
source.encoding?.[`${axis}2`] !== undefined
|
|
100
|
+
);
|
|
101
|
+
return axes.length === 1 ? axes[0] : undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function sourcePlacementContext(program, source) {
|
|
105
|
+
const coordinateType = findCoordinate(program, source.coordinate)?.type;
|
|
106
|
+
return {
|
|
107
|
+
markType: source.mark.type,
|
|
108
|
+
coordinateType,
|
|
109
|
+
intervalAxis: source.mark.type === "bar"
|
|
110
|
+
? resolveBarChannels(source).measure
|
|
111
|
+
: source.mark.type === "rect"
|
|
112
|
+
? explicitIntervalAxis(source)
|
|
113
|
+
: undefined
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function validateSourceMarkLabelPlacement(program, sourceId, value) {
|
|
118
|
+
const source = findLayer(program, sourceId);
|
|
119
|
+
if (source === undefined) {
|
|
120
|
+
throw new Error(`Unknown mark label placement source "${sourceId}".`);
|
|
121
|
+
}
|
|
122
|
+
const placement = normalizeMarkLabelPlacement(value);
|
|
123
|
+
assertMarkLabelPlacementSupport(sourcePlacementContext(program, source), placement);
|
|
124
|
+
return placement;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function rectangleBounds(item) {
|
|
128
|
+
const { x, y, width, height } = item.properties;
|
|
129
|
+
if (![x, y, width, height].every(Number.isFinite)) {
|
|
130
|
+
throw new TypeError("Interval mark label placement requires finite rectangle geometry.");
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
left: Math.min(x, x + width),
|
|
134
|
+
right: Math.max(x, x + width),
|
|
135
|
+
top: Math.min(y, y + height),
|
|
136
|
+
bottom: Math.max(y, y + height)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function mappedChannel(program, source, item, channel) {
|
|
141
|
+
const value = item.channels[channel];
|
|
142
|
+
const primary = channel === "x2" ? "x" : channel === "y2" ? "y" : channel;
|
|
143
|
+
const scale = program.resolvedScales[
|
|
144
|
+
source.encoding?.[channel]?.scale ?? source.encoding?.[primary]?.scale
|
|
145
|
+
];
|
|
146
|
+
return value === undefined || scale === undefined
|
|
147
|
+
? undefined
|
|
148
|
+
: mapScaleConsumerValues([value], scale, channel)[0];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function scaleIncreaseDirection(program, source, axis) {
|
|
152
|
+
const scale = program.resolvedScales[source.encoding?.[axis]?.scale];
|
|
153
|
+
const domain = scale?.domain;
|
|
154
|
+
if (!Array.isArray(domain) || domain.length < 2) {
|
|
155
|
+
return axis === "x" ? { x: 1, y: 0 } : { x: 0, y: -1 };
|
|
156
|
+
}
|
|
157
|
+
const mapped = mapScaleConsumerValues(
|
|
158
|
+
[domain[0], domain.at(-1)],
|
|
159
|
+
scale,
|
|
160
|
+
axis
|
|
161
|
+
);
|
|
162
|
+
const delta = mapped[1] - mapped[0];
|
|
163
|
+
if (!Number.isFinite(delta) || delta === 0) {
|
|
164
|
+
return axis === "x" ? { x: 1, y: 0 } : { x: 0, y: -1 };
|
|
165
|
+
}
|
|
166
|
+
return axis === "x"
|
|
167
|
+
? { x: Math.sign(delta), y: 0 }
|
|
168
|
+
: { x: 0, y: Math.sign(delta) };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function intervalPlacementGeometry(program, source, item, axis) {
|
|
172
|
+
const bounds = rectangleBounds(item);
|
|
173
|
+
const primary = mappedChannel(program, source, item, axis);
|
|
174
|
+
const secondary = mappedChannel(program, source, item, `${axis}2`);
|
|
175
|
+
if (![primary, secondary].every(Number.isFinite)) {
|
|
176
|
+
throw new TypeError(
|
|
177
|
+
`${source.mark.type === "bar" ? "Bar" : "Rect"} semantic label placement requires resolved ${axis}/${axis}2 endpoints.`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
const cross = axis === "x"
|
|
181
|
+
? (bounds.top + bounds.bottom) / 2
|
|
182
|
+
: (bounds.left + bounds.right) / 2;
|
|
183
|
+
const start = axis === "x" ? { x: primary, y: cross } : { x: cross, y: primary };
|
|
184
|
+
const end = axis === "x" ? { x: secondary, y: cross } : { x: cross, y: secondary };
|
|
185
|
+
const delta = { x: end.x - start.x, y: end.y - start.y };
|
|
186
|
+
const length = Math.hypot(delta.x, delta.y);
|
|
187
|
+
const endOutward = length > 0
|
|
188
|
+
? { x: delta.x / length, y: delta.y / length }
|
|
189
|
+
: scaleIncreaseDirection(program, source, axis);
|
|
190
|
+
return {
|
|
191
|
+
kind: "interval",
|
|
192
|
+
center: {
|
|
193
|
+
x: (bounds.left + bounds.right) / 2,
|
|
194
|
+
y: (bounds.top + bounds.bottom) / 2
|
|
195
|
+
},
|
|
196
|
+
start: {
|
|
197
|
+
point: start,
|
|
198
|
+
outward: { x: -endOutward.x, y: -endOutward.y }
|
|
199
|
+
},
|
|
200
|
+
end: { point: end, outward: endOutward },
|
|
201
|
+
bounds
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function pointPlacementGeometry(program, source, item) {
|
|
206
|
+
const center = { x: item.properties.x, y: item.properties.y };
|
|
207
|
+
const coordinateType = findCoordinate(program, source.coordinate)?.type;
|
|
208
|
+
if (coordinateType !== "polar") return { kind: "point", center };
|
|
209
|
+
const thetaScale = program.resolvedScales[source.encoding?.theta?.scale];
|
|
210
|
+
const theta = item.channels.theta === undefined || thetaScale === undefined
|
|
211
|
+
? undefined
|
|
212
|
+
: mapScaleConsumerValues([item.channels.theta], thetaScale, "theta")[0];
|
|
213
|
+
const outward = Number.isFinite(theta) ? polarDirection(theta) : { x: 0, y: -1 };
|
|
214
|
+
const objectBounds = unionConcreteGraphicBounds(
|
|
215
|
+
program.graphicSpec,
|
|
216
|
+
item.graphicIds
|
|
217
|
+
);
|
|
218
|
+
const corners = objectBounds === undefined ? [] : [
|
|
219
|
+
{ x: objectBounds.left, y: objectBounds.top },
|
|
220
|
+
{ x: objectBounds.right, y: objectBounds.top },
|
|
221
|
+
{ x: objectBounds.right, y: objectBounds.bottom },
|
|
222
|
+
{ x: objectBounds.left, y: objectBounds.bottom }
|
|
223
|
+
];
|
|
224
|
+
const support = Math.max(0, ...corners.map(point =>
|
|
225
|
+
(point.x - center.x) * outward.x + (point.y - center.y) * outward.y
|
|
226
|
+
));
|
|
227
|
+
return {
|
|
228
|
+
kind: "point",
|
|
229
|
+
center,
|
|
230
|
+
end: {
|
|
231
|
+
point: {
|
|
232
|
+
x: center.x + outward.x * support,
|
|
233
|
+
y: center.y + outward.y * support
|
|
234
|
+
},
|
|
235
|
+
outward
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function sourcePlacementGeometry(program, source, item) {
|
|
241
|
+
if (source.mark.type === "bar") {
|
|
242
|
+
return intervalPlacementGeometry(
|
|
243
|
+
program,
|
|
244
|
+
source,
|
|
245
|
+
item,
|
|
246
|
+
resolveBarChannels(source).measure
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
if (source.mark.type === "rect") {
|
|
250
|
+
const axis = explicitIntervalAxis(source);
|
|
251
|
+
return axis === undefined
|
|
252
|
+
? { kind: "point", center: sourceAnchor(program, source, item) }
|
|
253
|
+
: intervalPlacementGeometry(program, source, item, axis);
|
|
254
|
+
}
|
|
255
|
+
if (source.mark.type === "arc") return item.geometry;
|
|
256
|
+
if (source.mark.type === "point") {
|
|
257
|
+
return pointPlacementGeometry(program, source, item);
|
|
258
|
+
}
|
|
259
|
+
return { kind: "point", center: sourceAnchor(program, source, item) };
|
|
260
|
+
}
|
|
261
|
+
|
|
86
262
|
function contentValue(encoding, rowOrItem, source) {
|
|
87
263
|
return Object.hasOwn(encoding, "field")
|
|
88
264
|
? source === undefined
|
|
@@ -91,8 +267,7 @@ function contentValue(encoding, rowOrItem, source) {
|
|
|
91
267
|
: encoding.datum;
|
|
92
268
|
}
|
|
93
269
|
|
|
94
|
-
function
|
|
95
|
-
const text = formatTextValue(value, format);
|
|
270
|
+
function concreteTextItem(config, position, text, { offsets = true } = {}) {
|
|
96
271
|
const x = position.x;
|
|
97
272
|
const y = position.y;
|
|
98
273
|
if (text === undefined || !Number.isFinite(x) || !Number.isFinite(y)) {
|
|
@@ -101,8 +276,8 @@ function concreteItem(config, position, value, format) {
|
|
|
101
276
|
return {
|
|
102
277
|
type: "text",
|
|
103
278
|
properties: {
|
|
104
|
-
x: x + config.dx,
|
|
105
|
-
y: y + config.dy,
|
|
279
|
+
x: x + (offsets ? config.dx : 0),
|
|
280
|
+
y: y + (offsets ? config.dy : 0),
|
|
106
281
|
text,
|
|
107
282
|
fill: config.fill,
|
|
108
283
|
opacity: config.opacity,
|
|
@@ -116,6 +291,10 @@ function concreteItem(config, position, value, format) {
|
|
|
116
291
|
};
|
|
117
292
|
}
|
|
118
293
|
|
|
294
|
+
function concreteItem(config, position, value, format) {
|
|
295
|
+
return concreteTextItem(config, position, formatTextValue(value, format));
|
|
296
|
+
}
|
|
297
|
+
|
|
119
298
|
function relativeLuminance(color) {
|
|
120
299
|
if (typeof color !== "string" || !/^#[0-9a-f]{6}$/i.test(color)) {
|
|
121
300
|
return undefined;
|
|
@@ -147,18 +326,90 @@ function resolveSourceTextItems(program, layer, config) {
|
|
|
147
326
|
if (source === undefined) {
|
|
148
327
|
throw new Error(`Text mark "${layer.id}" requires source layer "${layer.source}".`);
|
|
149
328
|
}
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
329
|
+
const requested = program.markConfigs[layer.id]?.labelAuthoring?.selection ?? {
|
|
330
|
+
kind: "all"
|
|
331
|
+
};
|
|
332
|
+
let allItems;
|
|
333
|
+
let items;
|
|
334
|
+
if (requested.kind === "all") {
|
|
335
|
+
allItems = resolveMarkItems(program, source.id);
|
|
336
|
+
items = allItems;
|
|
337
|
+
} else if (requested.kind === "inline") {
|
|
338
|
+
const resolved = resolveMarkSelection(program, source.id, requested.selector);
|
|
339
|
+
allItems = resolved.items;
|
|
340
|
+
const selected = new Set(resolved.keys);
|
|
341
|
+
items = allItems.filter(item => selected.has(item.key));
|
|
342
|
+
} else if (requested.kind === "named") {
|
|
343
|
+
const definition = program.materializationConfigs.selections?.[requested.id];
|
|
344
|
+
if (definition === undefined) {
|
|
345
|
+
throw new Error(`Unknown label selection "${requested.id}".`);
|
|
346
|
+
}
|
|
347
|
+
if (definition.target !== source.id) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`Label selection "${requested.id}" must target source mark "${source.id}".`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const resolved = resolveMarkSelection(program, source.id, definition.selector);
|
|
353
|
+
allItems = resolved.items;
|
|
354
|
+
const selected = new Set(resolved.keys);
|
|
355
|
+
items = allItems.filter(item => selected.has(item.key));
|
|
356
|
+
} else {
|
|
357
|
+
throw new Error(`Unknown label selection kind "${requested.kind}".`);
|
|
358
|
+
}
|
|
359
|
+
const resolvedValues = layer.encoding.text.content === undefined ? undefined
|
|
360
|
+
: resolveMarkLabelValues(source, allItems, layer.encoding.text);
|
|
361
|
+
const values = resolvedValues === undefined ? undefined : new Map(
|
|
362
|
+
allItems.map((item, index) => [item.key, resolvedValues[index]])
|
|
363
|
+
);
|
|
364
|
+
return items.flatMap(item => {
|
|
365
|
+
let resolvedConfig = sourceTextConfig(config, source, item);
|
|
366
|
+
const text = formatTextValue(
|
|
367
|
+
values === undefined
|
|
368
|
+
? contentValue(layer.encoding.text, item, source)
|
|
369
|
+
: values.get(item.key),
|
|
159
370
|
layer.encoding.text.format
|
|
160
371
|
);
|
|
161
|
-
|
|
372
|
+
if (text === undefined) return [];
|
|
373
|
+
const placement = program.markConfigs[layer.id]?.labelAuthoring?.placement;
|
|
374
|
+
if (placement === undefined) {
|
|
375
|
+
const anchor = sourceAnchor(program, source, item);
|
|
376
|
+
const concrete = concreteTextItem(resolvedConfig, anchor, text);
|
|
377
|
+
return concrete === undefined ? [] : [{ graphic: concrete, anchor }];
|
|
378
|
+
}
|
|
379
|
+
assertMarkLabelPlacementSupport(sourcePlacementContext(program, source), placement);
|
|
380
|
+
const resolved = resolveMarkLabelPlacement({
|
|
381
|
+
placement,
|
|
382
|
+
geometry: sourcePlacementGeometry(program, source, item),
|
|
383
|
+
text: {
|
|
384
|
+
text,
|
|
385
|
+
fontSize: resolvedConfig.fontSize,
|
|
386
|
+
fontFamily: resolvedConfig.fontFamily,
|
|
387
|
+
fontWeight: resolvedConfig.fontWeight,
|
|
388
|
+
textAlign: resolvedConfig.align,
|
|
389
|
+
textBaseline: resolvedConfig.baseline,
|
|
390
|
+
rotation: resolvedConfig.rotation,
|
|
391
|
+
dx: resolvedConfig.dx,
|
|
392
|
+
dy: resolvedConfig.dy
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
if (!resolved.visible) return [];
|
|
396
|
+
if (
|
|
397
|
+
resolved.anchor.startsWith("outside") &&
|
|
398
|
+
config.fillExplicit !== true
|
|
399
|
+
) {
|
|
400
|
+
resolvedConfig = config;
|
|
401
|
+
}
|
|
402
|
+
const anchor = { x: resolved.sourceX, y: resolved.sourceY };
|
|
403
|
+
return [{
|
|
404
|
+
graphic: concreteTextItem(
|
|
405
|
+
resolvedConfig,
|
|
406
|
+
{ x: resolved.x, y: resolved.y },
|
|
407
|
+
text,
|
|
408
|
+
{ offsets: false }
|
|
409
|
+
),
|
|
410
|
+
anchor,
|
|
411
|
+
placement: resolved
|
|
412
|
+
}];
|
|
162
413
|
});
|
|
163
414
|
}
|
|
164
415
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { requireFiniteProperty } from "./validation.js";
|
|
2
2
|
import { validateConcreteGraphicProperties } from
|
|
3
3
|
"../../grammar/schemas/concreteGraphic.js";
|
|
4
|
+
import { applyCanvasStroke } from "./stroke.js";
|
|
4
5
|
|
|
5
6
|
function drawCircle(context, child, collectionId) {
|
|
6
7
|
const properties = child.properties ?? {};
|
|
@@ -43,9 +44,7 @@ function drawCircle(context, child, collectionId) {
|
|
|
43
44
|
`Graphic "${graphicId}" requires a non-negative strokeWidth.`
|
|
44
45
|
);
|
|
45
46
|
}
|
|
46
|
-
context
|
|
47
|
-
context.lineWidth = strokeWidth;
|
|
48
|
-
context.setLineDash([]);
|
|
47
|
+
applyCanvasStroke(context, properties);
|
|
49
48
|
context.stroke();
|
|
50
49
|
}
|
|
51
50
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { requireFiniteProperty } from "./validation.js";
|
|
2
2
|
import { validateConcreteGraphicProperties } from
|
|
3
3
|
"../../grammar/schemas/concreteGraphic.js";
|
|
4
|
+
import { applyCanvasStroke } from "./stroke.js";
|
|
4
5
|
|
|
5
6
|
function drawLine(context, child, collectionId) {
|
|
6
7
|
const properties = child.properties ?? {};
|
|
@@ -32,10 +33,8 @@ function drawLine(context, child, collectionId) {
|
|
|
32
33
|
throw new Error(`Graphic "${graphicId}" requires opacity from 0 to 1.`);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
context.strokeStyle = properties.stroke;
|
|
36
|
-
context.lineWidth = strokeWidth;
|
|
37
36
|
context.globalAlpha = opacity;
|
|
38
|
-
context
|
|
37
|
+
applyCanvasStroke(context, properties, properties.strokeDash ?? []);
|
|
39
38
|
context.beginPath();
|
|
40
39
|
context.moveTo(x1, y1);
|
|
41
40
|
context.lineTo(x2, y2);
|
|
@@ -9,10 +9,10 @@ import { validateConcreteGraphicProperties } from
|
|
|
9
9
|
const MAX_NATIVE_VALUE = 16_777_216;
|
|
10
10
|
const NATIVE_PROPERTIES = {
|
|
11
11
|
canvas: ["x", "y", "width", "height"],
|
|
12
|
-
circle: ["x", "y", "radius", "strokeWidth"],
|
|
13
|
-
rect: ["x", "y", "width", "height", "strokeWidth"],
|
|
14
|
-
line: ["x1", "y1", "x2", "y2", "strokeWidth"],
|
|
15
|
-
path: ["strokeWidth"],
|
|
12
|
+
circle: ["x", "y", "radius", "strokeWidth", "miterLimit"],
|
|
13
|
+
rect: ["x", "y", "width", "height", "strokeWidth", "miterLimit"],
|
|
14
|
+
line: ["x1", "y1", "x2", "y2", "strokeWidth", "miterLimit"],
|
|
15
|
+
path: ["strokeWidth", "miterLimit"],
|
|
16
16
|
text: ["x", "y", "fontSize"]
|
|
17
17
|
};
|
|
18
18
|
const COMMAND_PROPERTIES = ["x", "y", "x1", "y1", "x2", "y2"];
|
|
@@ -3,6 +3,7 @@ import { validateConcreteGraphicProperties } from
|
|
|
3
3
|
"../../grammar/schemas/concreteGraphic.js";
|
|
4
4
|
import { resolvePathCommandBounds } from "../../grammar/schemas/graphicBounds.js";
|
|
5
5
|
import { applyCanvasFill } from "./fill.js";
|
|
6
|
+
import { applyCanvasStroke } from "./stroke.js";
|
|
6
7
|
|
|
7
8
|
function drawPath(context, child, collectionId) {
|
|
8
9
|
const properties = child.properties ?? {};
|
|
@@ -82,9 +83,7 @@ function drawPath(context, child, collectionId) {
|
|
|
82
83
|
context.fill();
|
|
83
84
|
}
|
|
84
85
|
if (hasStroke) {
|
|
85
|
-
context
|
|
86
|
-
context.lineWidth = strokeWidth;
|
|
87
|
-
context.setLineDash(strokeDash);
|
|
86
|
+
applyCanvasStroke(context, properties, strokeDash);
|
|
88
87
|
context.stroke();
|
|
89
88
|
}
|
|
90
89
|
}
|
|
@@ -2,6 +2,7 @@ import { requireFiniteProperty } from "./validation.js";
|
|
|
2
2
|
import { validateConcreteGraphicProperties } from
|
|
3
3
|
"../../grammar/schemas/concreteGraphic.js";
|
|
4
4
|
import { applyCanvasFill } from "./fill.js";
|
|
5
|
+
import { applyCanvasStroke } from "./stroke.js";
|
|
5
6
|
|
|
6
7
|
function drawRect(context, child, collectionId) {
|
|
7
8
|
const properties = child.properties ?? {};
|
|
@@ -42,9 +43,7 @@ function drawRect(context, child, collectionId) {
|
|
|
42
43
|
bottom: y + height
|
|
43
44
|
}, graphicId);
|
|
44
45
|
context.fillRect(x, y, width, height);
|
|
45
|
-
context
|
|
46
|
-
context.lineWidth = strokeWidth;
|
|
47
|
-
context.setLineDash([]);
|
|
46
|
+
applyCanvasStroke(context, properties);
|
|
48
47
|
context.beginPath();
|
|
49
48
|
context.moveTo(x, y);
|
|
50
49
|
context.lineTo(x + width, y);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { resolveStrokeDetails } from "../../grammar/strokeStyle.js";
|
|
2
|
+
|
|
3
|
+
export function applyCanvasStroke(context, properties, strokeDash = []) {
|
|
4
|
+
const details = resolveStrokeDetails(properties);
|
|
5
|
+
context.strokeStyle = properties.stroke;
|
|
6
|
+
context.lineWidth = properties.strokeWidth;
|
|
7
|
+
context.lineCap = details.lineCap;
|
|
8
|
+
context.lineJoin = details.lineJoin;
|
|
9
|
+
context.miterLimit = details.miterLimit;
|
|
10
|
+
context.setLineDash(strokeDash);
|
|
11
|
+
}
|