ggaction 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +35 -13
- package/package.json +18 -7
- package/src/actions/boxPlots/create.js +13 -7
- package/src/actions/boxPlots/edit.js +484 -36
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +11 -4
- package/src/actions/boxPlots/resolve.js +10 -2
- package/src/actions/charts/bar.js +50 -0
- package/src/actions/charts/heatmap.js +205 -0
- package/src/actions/charts/histogram.js +58 -0
- package/src/actions/charts/index.js +15 -0
- package/src/actions/charts/line.js +69 -0
- package/src/actions/charts/parallel.js +76 -0
- package/src/actions/charts/scatter.js +56 -0
- package/src/actions/charts/shared.js +121 -0
- package/src/actions/composition/actions.js +13 -3
- package/src/actions/coordinates/parallel.js +50 -0
- package/src/actions/data/bin2d.js +260 -0
- package/src/actions/data/density.js +54 -13
- package/src/actions/data/derived.js +22 -0
- package/src/actions/data/filter.js +5 -0
- package/src/actions/data/gradientProfile.js +65 -0
- package/src/actions/data/horizon.js +71 -0
- package/src/actions/data/index.js +34 -2
- package/src/actions/data/intervalEdit.js +76 -0
- package/src/actions/data/window.js +50 -0
- package/src/actions/distributions/revision.js +326 -0
- package/src/actions/encodings/appearance.js +35 -1
- package/src/actions/encodings/color/index.js +17 -1
- package/src/actions/encodings/color/layout.js +8 -0
- package/src/actions/encodings/density/resolve.js +109 -0
- package/src/actions/encodings/density.js +330 -90
- package/src/actions/encodings/horizon/resolve.js +283 -0
- package/src/actions/encodings/horizon.js +364 -0
- package/src/actions/encodings/index.js +8 -0
- package/src/actions/encodings/parallel.js +115 -0
- package/src/actions/encodings/pathOrder.js +131 -0
- package/src/actions/encodings/position/apply.js +73 -4
- package/src/actions/encodings/position/index.js +15 -2
- package/src/actions/encodings/position/policies/arc.js +27 -4
- package/src/actions/encodings/position/policies/area.js +33 -2
- package/src/actions/encodings/position/policies/index.js +7 -2
- package/src/actions/encodings/position/policies/line.js +18 -2
- package/src/actions/encodings/position/resolve.js +24 -2
- package/src/actions/encodings/ranged.js +3 -1
- package/src/actions/encodings/remove.js +268 -0
- package/src/actions/encodings/ruleAppearance.js +88 -10
- package/src/actions/errorBands/edit.js +121 -13
- package/src/actions/errorBars/edit.js +45 -8
- package/src/actions/errorBars/options.js +7 -1
- package/src/actions/facets/actions.js +137 -3
- package/src/actions/facets/derive.js +33 -3
- package/src/actions/facets/replay.js +7 -36
- package/src/actions/gradientPlots/components.js +177 -0
- package/src/actions/gradientPlots/create.js +96 -0
- package/src/actions/gradientPlots/edit.js +501 -0
- package/src/actions/gradientPlots/index.js +24 -0
- package/src/actions/gradientPlots/materialize.js +287 -0
- package/src/actions/gradientPlots/options.js +120 -0
- package/src/actions/gradientPlots/paint.js +107 -0
- package/src/actions/gradientPlots/rebind.js +51 -0
- package/src/actions/gradientPlots/resolve.js +81 -0
- package/src/actions/guides/applicability.js +44 -17
- package/src/actions/guides/axes/axes.js +37 -3
- package/src/actions/guides/axes/edit.js +129 -47
- package/src/actions/guides/axes/index.js +2 -0
- package/src/actions/guides/axes/labels.js +11 -6
- package/src/actions/guides/axes/parallel/resolve.js +86 -0
- package/src/actions/guides/axes/parallel.js +228 -0
- package/src/actions/guides/axes/titles.js +11 -8
- package/src/actions/guides/guides.js +60 -5
- package/src/actions/guides/legends/categorical/actions.js +67 -19
- package/src/actions/guides/legends/continuous/common.js +3 -3
- package/src/actions/guides/legends/edit.js +63 -0
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/remove.js +124 -30
- package/src/actions/guides/legends/strokeWidth.js +198 -0
- package/src/actions/index.js +6 -0
- package/src/actions/marks/arc/actions.js +46 -14
- package/src/actions/marks/area/actions.js +60 -136
- package/src/actions/marks/area/materialize.js +163 -0
- package/src/actions/marks/index.js +5 -1
- package/src/actions/marks/line/actions.js +99 -80
- package/src/actions/marks/line/materialize.js +146 -0
- package/src/actions/marks/point/create.js +76 -0
- package/src/actions/marks/point/edit.js +97 -0
- package/src/actions/marks/point/index.js +11 -1
- package/src/actions/marks/point/jitter.js +72 -0
- package/src/actions/marks/point/{actions.js → materialize.js} +183 -172
- package/src/actions/marks/rect/actions.js +3 -0
- package/src/actions/marks/remove.js +13 -2
- package/src/actions/marks/rule/actions.js +29 -4
- package/src/actions/marks/text/actions.js +19 -4
- package/src/actions/marks/text/index.js +7 -1
- package/src/actions/marks/text/layout.js +321 -0
- package/src/actions/primitives/semanticValidation/dataset.js +22 -0
- package/src/actions/primitives/semanticValidation/guide.js +43 -0
- package/src/actions/primitives/semanticValidation/index.js +23 -0
- package/src/actions/primitives/semanticValidation/layer.js +74 -0
- package/src/actions/primitives/semanticValidation/scale.js +74 -0
- package/src/actions/primitives/semanticValidation/shared.js +5 -0
- package/src/actions/primitives/semanticValue.js +1 -204
- package/src/actions/regression/edit.js +247 -88
- package/src/actions/scales/consumers/common.js +18 -0
- package/src/actions/scales/consumers/families.js +14 -3
- package/src/actions/scales/definitions.js +30 -0
- package/src/actions/scales/edit.js +6 -204
- package/src/actions/scales/editPolicy.js +214 -0
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +26 -0
- package/src/actions/selection/actions.js +150 -5
- package/src/actions/selection/index.js +6 -0
- package/src/actions/violinPlots/create.js +198 -0
- package/src/actions/violinPlots/index.js +5 -0
- package/src/core/textMetrics.js +47 -0
- package/src/core/vocabulary.js +14 -2
- package/src/grammar/arcs.js +49 -12
- package/src/grammar/areaSeries.js +41 -5
- package/src/grammar/bin2d.js +333 -0
- package/src/grammar/categoricalDensity.js +192 -0
- package/src/grammar/coordinates.js +1 -1
- package/src/grammar/curveCommands.js +3 -1
- package/src/grammar/density.js +252 -26
- package/src/grammar/facets/dependencies.js +3 -11
- package/src/grammar/facets/index.js +3 -1
- package/src/grammar/gradientProfile.js +213 -0
- package/src/grammar/horizon.js +455 -0
- package/src/grammar/jitter.js +197 -0
- package/src/grammar/lineSeries.js +69 -0
- package/src/grammar/paint.js +88 -0
- package/src/grammar/parallelCoordinates.js +213 -0
- package/src/grammar/pathOrder.js +35 -0
- package/src/grammar/pointShapes.js +24 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/appearance.js +21 -0
- package/src/grammar/schemas/concreteGraphic.js +11 -1
- package/src/grammar/schemas/graphicBounds.js +19 -37
- package/src/grammar/schemas/semanticPath.js +17 -2
- package/src/grammar/transforms.js +100 -11
- package/src/grammar/window.js +339 -0
- package/src/layout/labels.js +270 -0
- package/src/layout/text.js +4 -1
- package/src/layout/title.js +10 -20
- package/src/materialization/dataProvenance.js +56 -2
- package/src/materialization/dependencies.js +9 -14
- package/src/materialization/guides/resources.js +74 -24
- package/src/materialization/horizon.js +29 -0
- package/src/materialization/layout.js +15 -0
- package/src/materialization/marks/capabilities.js +21 -1
- package/src/materialization/marks/index.js +3 -0
- package/src/materialization/marks/pathOrder.js +71 -0
- package/src/materialization/marks/policies.js +6 -2
- package/src/materialization/scaleGuideDependencies.js +8 -0
- package/src/materialization/scales/resolve.js +2 -0
- package/src/materialization/selection/items/path.js +27 -1
- package/src/materialization/selection/items/point.js +1 -5
- package/src/materialization/selection/items/rect.js +21 -0
- package/src/materialization/selection/styles.js +20 -9
- package/src/materialization/text.js +12 -4
- package/src/renderers/canvas/fill.js +33 -0
- package/src/renderers/canvas/index.js +7 -0
- package/src/renderers/canvas/path.js +7 -4
- package/src/renderers/canvas/rect.js +9 -3
- package/src/selectors/datasets.js +4 -0
- package/src/theme/defaults.js +4 -0
- package/types/extension.d.ts +6 -0
- package/types/index.d.ts +77 -0
- package/types/program.d.ts +703 -17
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateOptionObject } from "../../../core/validation.js";
|
|
3
|
+
import { resolvePlotGraphicPlacement } from
|
|
4
|
+
"../../../materialization/graphicHierarchy.js";
|
|
5
|
+
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
6
|
+
"../../../theme/defaults.js";
|
|
7
|
+
import {
|
|
8
|
+
requireParallelAxisLayer,
|
|
9
|
+
resolveParallelAxisTarget,
|
|
10
|
+
resolveParallelAxisValues
|
|
11
|
+
} from "./parallel/resolve.js";
|
|
12
|
+
|
|
13
|
+
const CREATE_OPTIONS = Object.freeze(["target", "coordinate"]);
|
|
14
|
+
const REMATERIALIZE_OPTIONS = Object.freeze(["target"]);
|
|
15
|
+
|
|
16
|
+
export const rematerializeParallelAxes = action(
|
|
17
|
+
{
|
|
18
|
+
op: "rematerializeParallelAxes",
|
|
19
|
+
description: "Recompute concrete Parallel dimension axes."
|
|
20
|
+
},
|
|
21
|
+
function (args = {}) {
|
|
22
|
+
validateOptionObject(
|
|
23
|
+
args,
|
|
24
|
+
REMATERIALIZE_OPTIONS,
|
|
25
|
+
"rematerializeParallelAxes"
|
|
26
|
+
);
|
|
27
|
+
const target = resolveParallelAxisTarget(
|
|
28
|
+
this,
|
|
29
|
+
args.target ?? this.guideConfigs.axis?.parallel?.axes?.target
|
|
30
|
+
);
|
|
31
|
+
const { dimensions } = requireParallelAxisLayer(this, target);
|
|
32
|
+
const { axes, bounds } = resolveParallelAxisValues(this, dimensions);
|
|
33
|
+
const ticks = axes.flatMap(axis => axis.values.map((value, index) => ({
|
|
34
|
+
x: axis.x,
|
|
35
|
+
y: axis.y[index],
|
|
36
|
+
value,
|
|
37
|
+
text: axis.labels[index]
|
|
38
|
+
})));
|
|
39
|
+
let next = this
|
|
40
|
+
.editGraphics({
|
|
41
|
+
target: "parallelAxisLines",
|
|
42
|
+
property: "length",
|
|
43
|
+
value: axes.length
|
|
44
|
+
})
|
|
45
|
+
.editGraphics({
|
|
46
|
+
target: "parallelAxisLines",
|
|
47
|
+
property: "x1",
|
|
48
|
+
value: axes.map(axis => axis.x)
|
|
49
|
+
})
|
|
50
|
+
.editGraphics({ target: "parallelAxisLines", property: "y1", value: bounds.y })
|
|
51
|
+
.editGraphics({
|
|
52
|
+
target: "parallelAxisLines",
|
|
53
|
+
property: "x2",
|
|
54
|
+
value: axes.map(axis => axis.x)
|
|
55
|
+
})
|
|
56
|
+
.editGraphics({
|
|
57
|
+
target: "parallelAxisLines",
|
|
58
|
+
property: "y2",
|
|
59
|
+
value: bounds.y + bounds.height
|
|
60
|
+
})
|
|
61
|
+
.editGraphics({
|
|
62
|
+
target: "parallelAxisLines",
|
|
63
|
+
property: "stroke",
|
|
64
|
+
value: DEFAULT_COLORS.axis
|
|
65
|
+
})
|
|
66
|
+
.editGraphics({
|
|
67
|
+
target: "parallelAxisLines",
|
|
68
|
+
property: "strokeWidth",
|
|
69
|
+
value: 1.25
|
|
70
|
+
})
|
|
71
|
+
.editGraphics({
|
|
72
|
+
target: "parallelAxisTicks",
|
|
73
|
+
property: "length",
|
|
74
|
+
value: ticks.length
|
|
75
|
+
})
|
|
76
|
+
.editGraphics({
|
|
77
|
+
target: "parallelAxisTicks",
|
|
78
|
+
property: "x1",
|
|
79
|
+
value: ticks.map(tick => tick.x - 4)
|
|
80
|
+
})
|
|
81
|
+
.editGraphics({
|
|
82
|
+
target: "parallelAxisTicks",
|
|
83
|
+
property: "y1",
|
|
84
|
+
value: ticks.map(tick => tick.y)
|
|
85
|
+
})
|
|
86
|
+
.editGraphics({
|
|
87
|
+
target: "parallelAxisTicks",
|
|
88
|
+
property: "x2",
|
|
89
|
+
value: ticks.map(tick => tick.x + 4)
|
|
90
|
+
})
|
|
91
|
+
.editGraphics({
|
|
92
|
+
target: "parallelAxisTicks",
|
|
93
|
+
property: "y2",
|
|
94
|
+
value: ticks.map(tick => tick.y)
|
|
95
|
+
})
|
|
96
|
+
.editGraphics({
|
|
97
|
+
target: "parallelAxisTicks",
|
|
98
|
+
property: "stroke",
|
|
99
|
+
value: DEFAULT_COLORS.mutedText
|
|
100
|
+
})
|
|
101
|
+
.editGraphics({
|
|
102
|
+
target: "parallelAxisTicks",
|
|
103
|
+
property: "strokeWidth",
|
|
104
|
+
value: 1
|
|
105
|
+
})
|
|
106
|
+
.editGraphics({
|
|
107
|
+
target: "parallelAxisLabels",
|
|
108
|
+
property: "length",
|
|
109
|
+
value: ticks.length
|
|
110
|
+
})
|
|
111
|
+
.editGraphics({
|
|
112
|
+
target: "parallelAxisLabels",
|
|
113
|
+
property: "x",
|
|
114
|
+
value: ticks.map(tick => tick.x - 9)
|
|
115
|
+
})
|
|
116
|
+
.editGraphics({
|
|
117
|
+
target: "parallelAxisLabels",
|
|
118
|
+
property: "y",
|
|
119
|
+
value: ticks.map(tick => tick.y)
|
|
120
|
+
})
|
|
121
|
+
.editGraphics({
|
|
122
|
+
target: "parallelAxisLabels",
|
|
123
|
+
property: "text",
|
|
124
|
+
value: ticks.map(tick => tick.text)
|
|
125
|
+
})
|
|
126
|
+
.editGraphics({ target: "parallelAxisLabels", property: "fill", value: DEFAULT_COLORS.axis })
|
|
127
|
+
.editGraphics({ target: "parallelAxisLabels", property: "fontSize", value: 11 })
|
|
128
|
+
.editGraphics({ target: "parallelAxisLabels", property: "fontFamily", value: DEFAULT_FONT_FAMILY })
|
|
129
|
+
.editGraphics({ target: "parallelAxisLabels", property: "fontWeight", value: "normal" })
|
|
130
|
+
.editGraphics({ target: "parallelAxisLabels", property: "textAlign", value: "right" })
|
|
131
|
+
.editGraphics({ target: "parallelAxisLabels", property: "textBaseline", value: "middle" })
|
|
132
|
+
.editGraphics({
|
|
133
|
+
target: "parallelAxisTitles",
|
|
134
|
+
property: "length",
|
|
135
|
+
value: axes.length
|
|
136
|
+
})
|
|
137
|
+
.editGraphics({
|
|
138
|
+
target: "parallelAxisTitles",
|
|
139
|
+
property: "x",
|
|
140
|
+
value: axes.map(axis => axis.x)
|
|
141
|
+
})
|
|
142
|
+
.editGraphics({
|
|
143
|
+
target: "parallelAxisTitles",
|
|
144
|
+
property: "y",
|
|
145
|
+
value: bounds.y - 20
|
|
146
|
+
})
|
|
147
|
+
.editGraphics({
|
|
148
|
+
target: "parallelAxisTitles",
|
|
149
|
+
property: "text",
|
|
150
|
+
value: axes.map(axis => axis.title)
|
|
151
|
+
})
|
|
152
|
+
.editGraphics({ target: "parallelAxisTitles", property: "fill", value: DEFAULT_COLORS.axisTitle })
|
|
153
|
+
.editGraphics({ target: "parallelAxisTitles", property: "fontSize", value: 13 })
|
|
154
|
+
.editGraphics({ target: "parallelAxisTitles", property: "fontFamily", value: DEFAULT_FONT_FAMILY })
|
|
155
|
+
.editGraphics({ target: "parallelAxisTitles", property: "fontWeight", value: 600 })
|
|
156
|
+
.editGraphics({ target: "parallelAxisTitles", property: "textAlign", value: "center" })
|
|
157
|
+
.editGraphics({ target: "parallelAxisTitles", property: "textBaseline", value: "middle" });
|
|
158
|
+
return next._withGuideConfig("parallel", "axes", {
|
|
159
|
+
target,
|
|
160
|
+
scales: dimensions.map(dimension => dimension.scale)
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
export const createParallelAxes = action(
|
|
166
|
+
{
|
|
167
|
+
op: "createParallelAxes",
|
|
168
|
+
description: "Create ordinary line and text graphics for Parallel dimensions."
|
|
169
|
+
},
|
|
170
|
+
function (args = {}) {
|
|
171
|
+
validateOptionObject(args, CREATE_OPTIONS, "createParallelAxes");
|
|
172
|
+
const target = resolveParallelAxisTarget(this, args.target);
|
|
173
|
+
const { coordinate, dimensions } = requireParallelAxisLayer(this, target);
|
|
174
|
+
if (args.coordinate !== undefined && args.coordinate !== coordinate.id) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Parallel layer "${target}" uses coordinate "${coordinate.id}".`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
for (const id of [
|
|
180
|
+
"parallelAxisLines", "parallelAxisTicks", "parallelAxisLabels",
|
|
181
|
+
"parallelAxisTitles"
|
|
182
|
+
]) {
|
|
183
|
+
if (this.graphicSpec.objects[id] !== undefined) {
|
|
184
|
+
throw new Error("createParallelAxes requires missing Parallel axes.");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const placement = resolvePlotGraphicPlacement(this);
|
|
188
|
+
let next = this
|
|
189
|
+
.editSemantic({
|
|
190
|
+
property: "guide.axis.parallel.target",
|
|
191
|
+
value: target
|
|
192
|
+
})
|
|
193
|
+
.editSemantic({
|
|
194
|
+
property: "guide.axis.parallel.coordinate",
|
|
195
|
+
value: coordinate.id
|
|
196
|
+
})
|
|
197
|
+
.editSemantic({
|
|
198
|
+
property: "guide.axis.parallel.scales",
|
|
199
|
+
value: dimensions.map(dimension => dimension.scale)
|
|
200
|
+
})
|
|
201
|
+
.createGraphics({ id: "parallelAxisLines", type: "line", length: 0, ...placement })
|
|
202
|
+
.editGraphics({ target: "parallelAxisLines", property: "stroke", value: DEFAULT_COLORS.axis })
|
|
203
|
+
.editGraphics({ target: "parallelAxisLines", property: "strokeWidth", value: 1.25 })
|
|
204
|
+
.createGraphics({ id: "parallelAxisTicks", type: "line", length: 0, ...placement })
|
|
205
|
+
.editGraphics({ target: "parallelAxisTicks", property: "stroke", value: DEFAULT_COLORS.mutedText })
|
|
206
|
+
.editGraphics({ target: "parallelAxisTicks", property: "strokeWidth", value: 1 })
|
|
207
|
+
.createGraphics({ id: "parallelAxisLabels", type: "text", length: 0, ...placement })
|
|
208
|
+
.editGraphics({ target: "parallelAxisLabels", property: "fill", value: DEFAULT_COLORS.axis })
|
|
209
|
+
.editGraphics({ target: "parallelAxisLabels", property: "fontSize", value: 11 })
|
|
210
|
+
.editGraphics({ target: "parallelAxisLabels", property: "fontFamily", value: DEFAULT_FONT_FAMILY })
|
|
211
|
+
.editGraphics({ target: "parallelAxisLabels", property: "fontWeight", value: "normal" })
|
|
212
|
+
.editGraphics({ target: "parallelAxisLabels", property: "textAlign", value: "right" })
|
|
213
|
+
.editGraphics({ target: "parallelAxisLabels", property: "textBaseline", value: "middle" })
|
|
214
|
+
.createGraphics({ id: "parallelAxisTitles", type: "text", length: 0, ...placement })
|
|
215
|
+
.editGraphics({ target: "parallelAxisTitles", property: "fill", value: DEFAULT_COLORS.axisTitle })
|
|
216
|
+
.editGraphics({ target: "parallelAxisTitles", property: "fontSize", value: 13 })
|
|
217
|
+
.editGraphics({ target: "parallelAxisTitles", property: "fontFamily", value: DEFAULT_FONT_FAMILY })
|
|
218
|
+
.editGraphics({ target: "parallelAxisTitles", property: "fontWeight", value: 600 })
|
|
219
|
+
.editGraphics({ target: "parallelAxisTitles", property: "textAlign", value: "center" })
|
|
220
|
+
.editGraphics({ target: "parallelAxisTitles", property: "textBaseline", value: "middle" });
|
|
221
|
+
return next.rematerializeParallelAxes({ target });
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
export function registerParallelAxisActions(ProgramClass) {
|
|
226
|
+
ProgramClass.prototype.createParallelAxes = createParallelAxes;
|
|
227
|
+
ProgramClass.prototype.rematerializeParallelAxes = rematerializeParallelAxes;
|
|
228
|
+
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from "./policy.js";
|
|
25
25
|
import { findCanvasGraphic, resolvePlotGraphicPlacement } from
|
|
26
26
|
"../../../materialization/graphicHierarchy.js";
|
|
27
|
+
import { resolveTextBounds } from "../../../core/textMetrics.js";
|
|
27
28
|
|
|
28
29
|
const CREATE_OPTIONS = Object.freeze([
|
|
29
30
|
"text", "scale", "position", "at", "offset", "rotation", "color",
|
|
@@ -132,17 +133,19 @@ function resolveGeometry(program, channel, config) {
|
|
|
132
133
|
offset: config.offset
|
|
133
134
|
});
|
|
134
135
|
const text = program.semanticSpec.guides.axis?.[channel]?.title ?? "";
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
const titleBounds = resolveTextBounds({
|
|
137
|
+
...geometry,
|
|
138
|
+
text,
|
|
139
|
+
fontSize: config.fontSize,
|
|
140
|
+
textAlign: "center",
|
|
141
|
+
textBaseline: "middle",
|
|
142
|
+
rotation: config.rotation
|
|
143
|
+
});
|
|
141
144
|
const canvas = findCanvasGraphic(program)?.properties;
|
|
142
145
|
const newEdgeFits = config.position === "top"
|
|
143
|
-
?
|
|
146
|
+
? titleBounds.top >= 0
|
|
144
147
|
: config.position === "right"
|
|
145
|
-
?
|
|
148
|
+
? titleBounds.right <= canvas?.width
|
|
146
149
|
: true;
|
|
147
150
|
if (!canvas || !newEdgeFits) {
|
|
148
151
|
throw new Error(`The ${channel}-axis title does not fit the Canvas margin.`);
|
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
resolveAutomaticGridOptions,
|
|
6
6
|
resolveGuideApplicability
|
|
7
7
|
} from "./applicability.js";
|
|
8
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
9
|
+
import { findUpstreamTransform } from
|
|
10
|
+
"../../materialization/dataProvenance.js";
|
|
8
11
|
|
|
9
12
|
const OPTIONS = Object.freeze(["axes", "grid", "legend"]);
|
|
10
13
|
|
|
@@ -21,16 +24,51 @@ function validateOptions(args) {
|
|
|
21
24
|
validateGuideOption(args.legend, "createGuides legend");
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
function inferAxesOptions(program) {
|
|
27
|
+
function inferAxesOptions(program, applicability) {
|
|
25
28
|
const horizontalInterval = program.semanticSpec.layers.some(layer =>
|
|
26
29
|
layer.mark?.type === "rule" &&
|
|
27
30
|
layer.encoding?.x?.fieldType === "quantitative" &&
|
|
28
31
|
layer.encoding?.x2?.fieldType === "quantitative" &&
|
|
29
32
|
["nominal", "ordinal", "temporal"].includes(layer.encoding?.y?.fieldType)
|
|
30
33
|
);
|
|
31
|
-
|
|
34
|
+
const inferred = horizontalInterval
|
|
32
35
|
? { x: { ticksAndLabels: { count: 7 } } }
|
|
33
36
|
: {};
|
|
37
|
+
const directions = applicability.axes.directions;
|
|
38
|
+
const hasStoredY = program.semanticSpec.layers.some(
|
|
39
|
+
layer => layer.encoding?.y !== undefined
|
|
40
|
+
);
|
|
41
|
+
if (!directions.y && hasStoredY) inferred.y = false;
|
|
42
|
+
const horizonLayers = program.semanticSpec.layers.filter(layer =>
|
|
43
|
+
findUpstreamTransform(
|
|
44
|
+
program,
|
|
45
|
+
findDataset(program, layer.data),
|
|
46
|
+
"horizon"
|
|
47
|
+
) !== undefined
|
|
48
|
+
);
|
|
49
|
+
if (horizonLayers.length === 1 && directions.x && !directions.y) {
|
|
50
|
+
const scaleId = horizonLayers[0].encoding?.x?.scale;
|
|
51
|
+
const domain = program.resolvedScales?.[scaleId]?.domain;
|
|
52
|
+
if (
|
|
53
|
+
Array.isArray(domain) &&
|
|
54
|
+
domain.length === 2 &&
|
|
55
|
+
domain.every(Number.isFinite) &&
|
|
56
|
+
domain[0] !== domain[1]
|
|
57
|
+
) {
|
|
58
|
+
const low = Math.min(...domain);
|
|
59
|
+
const high = Math.max(...domain);
|
|
60
|
+
inferred.x = {
|
|
61
|
+
...(inferred.x ?? {}),
|
|
62
|
+
ticksAndLabels: {
|
|
63
|
+
values: Array.from(
|
|
64
|
+
{ length: 6 },
|
|
65
|
+
(_, index) => low + (high - low) * index / 5
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return inferred;
|
|
34
72
|
}
|
|
35
73
|
|
|
36
74
|
function selectOption(explicit, applicable) {
|
|
@@ -39,6 +77,17 @@ function selectOption(explicit, applicable) {
|
|
|
39
77
|
return applicable ? {} : undefined;
|
|
40
78
|
}
|
|
41
79
|
|
|
80
|
+
function mergeInferredOptions(inferred, explicit) {
|
|
81
|
+
if (!isPlainObject(inferred) || !isPlainObject(explicit)) return explicit;
|
|
82
|
+
const merged = { ...inferred };
|
|
83
|
+
for (const [key, value] of Object.entries(explicit)) {
|
|
84
|
+
merged[key] = isPlainObject(value) && isPlainObject(inferred[key])
|
|
85
|
+
? mergeInferredOptions(inferred[key], value)
|
|
86
|
+
: value;
|
|
87
|
+
}
|
|
88
|
+
return merged;
|
|
89
|
+
}
|
|
90
|
+
|
|
42
91
|
const createGuides = action(
|
|
43
92
|
{
|
|
44
93
|
op: "createGuides",
|
|
@@ -47,10 +96,16 @@ const createGuides = action(
|
|
|
47
96
|
function (args = {}) {
|
|
48
97
|
validateOptions(args);
|
|
49
98
|
const applicability = resolveGuideApplicability(this);
|
|
50
|
-
const hasAxes = applicability.axes.cartesian || applicability.axes.polar
|
|
99
|
+
const hasAxes = applicability.axes.cartesian || applicability.axes.polar ||
|
|
100
|
+
applicability.axes.parallel;
|
|
101
|
+
const inferredAxes = applicability.axes.cartesian
|
|
102
|
+
? inferAxesOptions(this, applicability)
|
|
103
|
+
: {};
|
|
51
104
|
const axes = args.axes === undefined && applicability.axes.cartesian
|
|
52
|
-
?
|
|
53
|
-
:
|
|
105
|
+
? inferredAxes
|
|
106
|
+
: args.axes !== undefined && args.axes !== false && hasAxes
|
|
107
|
+
? mergeInferredOptions(inferredAxes, args.axes)
|
|
108
|
+
: selectOption(args.axes, hasAxes);
|
|
54
109
|
const grid = args.grid === undefined &&
|
|
55
110
|
(applicability.grid.cartesian || applicability.grid.polar)
|
|
56
111
|
? resolveAutomaticGridOptions(this)
|
|
@@ -5,6 +5,9 @@ import { normalizeOptions } from "./options.js";
|
|
|
5
5
|
import { findLayer } from "../../../../selectors/layers.js";
|
|
6
6
|
import { findSemanticScale } from "../../../../selectors/scales.js";
|
|
7
7
|
import { isSizeLegendPoint } from "../size.js";
|
|
8
|
+
import { isStrokeWidthLegendLayer } from "../strokeWidth.js";
|
|
9
|
+
import { legendResourcePolicies } from
|
|
10
|
+
"../../../../materialization/guides/resources.js";
|
|
8
11
|
import {
|
|
9
12
|
resolveCurrentDefinition,
|
|
10
13
|
resolveDefinition,
|
|
@@ -80,17 +83,13 @@ export const rematerializeLegend = action(
|
|
|
80
83
|
).some(highlight => highlight.target === config.target);
|
|
81
84
|
if (hasHighlight) next = next.rematerializeLegendHighlights();
|
|
82
85
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
next = next.rematerializeIntervalLegend();
|
|
91
|
-
}
|
|
92
|
-
if (this.guideConfigs.legend?.opacity !== undefined) {
|
|
93
|
-
next = next.rematerializeOpacityLegend();
|
|
86
|
+
for (const policy of legendResourcePolicies()) {
|
|
87
|
+
if (
|
|
88
|
+
policy.rematerializeOp !== undefined &&
|
|
89
|
+
this.guideConfigs.legend?.[policy.kind] !== undefined
|
|
90
|
+
) {
|
|
91
|
+
next = next[policy.rematerializeOp]();
|
|
92
|
+
}
|
|
94
93
|
}
|
|
95
94
|
return next;
|
|
96
95
|
}
|
|
@@ -177,6 +176,8 @@ export const createLegend = action(
|
|
|
177
176
|
throw new TypeError("createLegend channels must be an array.");
|
|
178
177
|
}
|
|
179
178
|
const explicitSize = channels?.length === 1 && channels[0] === "size";
|
|
179
|
+
const explicitStrokeWidth = channels?.length === 1 &&
|
|
180
|
+
channels[0] === "strokeWidth";
|
|
180
181
|
const requestedSizeLayer = args.target === undefined
|
|
181
182
|
? undefined
|
|
182
183
|
: findLayer(this, args.target);
|
|
@@ -216,6 +217,42 @@ export const createLegend = action(
|
|
|
216
217
|
...(count === undefined ? {} : { count })
|
|
217
218
|
});
|
|
218
219
|
}
|
|
220
|
+
const strokeWidthCandidates = this.semanticSpec.layers.filter(
|
|
221
|
+
isStrokeWidthLegendLayer
|
|
222
|
+
);
|
|
223
|
+
const requestedStrokeWidthLayer = args.target === undefined
|
|
224
|
+
? undefined
|
|
225
|
+
: findLayer(this, args.target);
|
|
226
|
+
const hasOtherStrokeWidthLegendCandidate = this.semanticSpec.layers.some(
|
|
227
|
+
layer => ["color", "shape", "strokeDash", "size", "opacity"].some(
|
|
228
|
+
channel => layer.encoding?.[channel]?.scale !== undefined
|
|
229
|
+
)
|
|
230
|
+
);
|
|
231
|
+
const inferredStrokeWidth = channels === undefined && (
|
|
232
|
+
(args.target !== undefined &&
|
|
233
|
+
isStrokeWidthLegendLayer(requestedStrokeWidthLayer) &&
|
|
234
|
+
!hasNonSizeLegendEncoding(requestedStrokeWidthLayer)) ||
|
|
235
|
+
(args.target === undefined && strokeWidthCandidates.length === 1 &&
|
|
236
|
+
!hasOtherStrokeWidthLegendCandidate)
|
|
237
|
+
);
|
|
238
|
+
if (explicitStrokeWidth || inferredStrokeWidth) {
|
|
239
|
+
const { target, count, position, channels: _channels, ...unsupported } = args;
|
|
240
|
+
const unsupportedKeys = Object.keys(unsupported);
|
|
241
|
+
if (unsupportedKeys.length > 0) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`Standalone stroke-width legend does not support option "${unsupportedKeys[0]}".`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (position !== undefined && position !== "right") {
|
|
247
|
+
throw new Error(
|
|
248
|
+
'Standalone stroke-width legends currently require position "right".'
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return this.createStrokeWidthLegend({
|
|
252
|
+
...(target === undefined ? {} : { target }),
|
|
253
|
+
...(count === undefined ? {} : { count })
|
|
254
|
+
});
|
|
255
|
+
}
|
|
219
256
|
const opacityCandidates = this.semanticSpec.layers.filter(layer =>
|
|
220
257
|
layer.mark?.type === "point" && layer.encoding?.opacity?.scale !== undefined
|
|
221
258
|
);
|
|
@@ -323,14 +360,25 @@ export const createLegend = action(
|
|
|
323
360
|
symbol
|
|
324
361
|
});
|
|
325
362
|
if (requestedPoint.encoding?.size?.scale !== undefined) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
363
|
+
const existingSize = next.guideConfigs.legend?.size;
|
|
364
|
+
if (existingSize === undefined) {
|
|
365
|
+
next = next.createSizeLegend({
|
|
366
|
+
target: requestedPoint.id,
|
|
367
|
+
...(count === undefined ? {} : { count }),
|
|
368
|
+
inheritAppearance:
|
|
369
|
+
categoricalArgs.position === "left" ||
|
|
370
|
+
categoricalArgs.labels !== undefined ||
|
|
371
|
+
categoricalArgs.titleStyle !== undefined
|
|
372
|
+
});
|
|
373
|
+
} else if (existingSize.target !== requestedPoint.id) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
"Combined point series legend requires the active size legend to share its target."
|
|
376
|
+
);
|
|
377
|
+
} else if (count !== undefined && count !== existingSize.count) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
"Existing size legend count must be edited before recreating the categorical block."
|
|
380
|
+
);
|
|
381
|
+
}
|
|
334
382
|
next = next.rematerializeLegend();
|
|
335
383
|
}
|
|
336
384
|
return next;
|
|
@@ -49,7 +49,7 @@ const DEFAULT_BORDER = Object.freeze({
|
|
|
49
49
|
export const validatePositive = validatePositiveFinite;
|
|
50
50
|
export const validateNonNegative = validateNonNegativeFinite;
|
|
51
51
|
|
|
52
|
-
function
|
|
52
|
+
export function normalizeLegendTextOptions(value, label, defaults) {
|
|
53
53
|
if (value === undefined) return { ...defaults };
|
|
54
54
|
if (!isPlainObject(value)) {
|
|
55
55
|
throw new TypeError(`${label} must be a plain object.`);
|
|
@@ -132,12 +132,12 @@ export function normalizeContinuousLegend(args, kind) {
|
|
|
132
132
|
count,
|
|
133
133
|
title: args.title,
|
|
134
134
|
inferredTitle: args.title === undefined,
|
|
135
|
-
labels:
|
|
135
|
+
labels: normalizeLegendTextOptions(
|
|
136
136
|
args.labels,
|
|
137
137
|
"createLegend.labels",
|
|
138
138
|
DEFAULT_LABELS
|
|
139
139
|
),
|
|
140
|
-
titleStyle:
|
|
140
|
+
titleStyle: normalizeLegendTextOptions(
|
|
141
141
|
args.titleStyle,
|
|
142
142
|
"createLegend.titleStyle",
|
|
143
143
|
DEFAULT_TITLE
|
|
@@ -3,6 +3,7 @@ import { validateOptionObject } from "../../../core/validation.js";
|
|
|
3
3
|
import { normalizeOptions } from "./categorical/options.js";
|
|
4
4
|
import { symbolGraphic } from "./categorical/layout.js";
|
|
5
5
|
import {
|
|
6
|
+
normalizeLegendTextOptions,
|
|
6
7
|
normalizeContinuousLegend,
|
|
7
8
|
validatePositive
|
|
8
9
|
} from "./continuous/common.js";
|
|
@@ -12,6 +13,10 @@ import { findLayer } from "../../../selectors/layers.js";
|
|
|
12
13
|
import { resolveLegendGraphicPlacement } from
|
|
13
14
|
"../../../materialization/graphicHierarchy.js";
|
|
14
15
|
import { resolveLegendTarget } from "./target.js";
|
|
16
|
+
import {
|
|
17
|
+
STROKE_WIDTH_LEGEND_LABELS,
|
|
18
|
+
STROKE_WIDTH_LEGEND_TITLE_STYLE
|
|
19
|
+
} from "./strokeWidth.js";
|
|
15
20
|
|
|
16
21
|
const OPTIONS = Object.freeze([
|
|
17
22
|
"target", "position", "align", "direction", "columns", "offset",
|
|
@@ -163,6 +168,61 @@ function editInterval(program, previous, args) {
|
|
|
163
168
|
return next.rematerializeIntervalLegend();
|
|
164
169
|
}
|
|
165
170
|
|
|
171
|
+
function editStrokeWidth(program, previous, args) {
|
|
172
|
+
const allowed = ["target", "title", "count", "labels", "titleStyle"];
|
|
173
|
+
for (const key of Object.keys(args)) {
|
|
174
|
+
if (!allowed.includes(key)) {
|
|
175
|
+
throw new Error(`stroke-width legend does not accept ${key}.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const count = args.count ?? previous.count;
|
|
179
|
+
if (!Number.isInteger(count) || count < 2) {
|
|
180
|
+
throw new RangeError(
|
|
181
|
+
"Stroke-width legend count must be an integer of at least 2."
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const layer = findLayer(program, previous.target);
|
|
185
|
+
const titleMode = args.title;
|
|
186
|
+
const inferredTitle = titleMode === "auto"
|
|
187
|
+
? true
|
|
188
|
+
: typeof titleMode === "string" ? false : previous.inferredTitle;
|
|
189
|
+
const titleVisible = titleMode === false
|
|
190
|
+
? false
|
|
191
|
+
: titleMode === undefined ? previous.titleVisible !== false : true;
|
|
192
|
+
const title = titleMode === "auto"
|
|
193
|
+
? layer?.encoding?.strokeWidth?.field
|
|
194
|
+
: typeof titleMode === "string" ? titleMode : previous.title;
|
|
195
|
+
const config = {
|
|
196
|
+
...previous,
|
|
197
|
+
title,
|
|
198
|
+
inferredTitle,
|
|
199
|
+
titleVisible,
|
|
200
|
+
count,
|
|
201
|
+
labels: normalizeLegendTextOptions(
|
|
202
|
+
args.labels,
|
|
203
|
+
"editLegend.labels",
|
|
204
|
+
previous.labels ?? STROKE_WIDTH_LEGEND_LABELS
|
|
205
|
+
),
|
|
206
|
+
titleStyle: normalizeLegendTextOptions(
|
|
207
|
+
args.titleStyle,
|
|
208
|
+
"editLegend.titleStyle",
|
|
209
|
+
previous.titleStyle ?? STROKE_WIDTH_LEGEND_TITLE_STYLE
|
|
210
|
+
)
|
|
211
|
+
};
|
|
212
|
+
let next = program;
|
|
213
|
+
if (titleMode === "auto" || typeof titleMode === "string") {
|
|
214
|
+
next = next.editSemantic({
|
|
215
|
+
property: "guide.legend.strokeWidth.title",
|
|
216
|
+
value: title
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
next = next._withLegendConfig("strokeWidth", config);
|
|
220
|
+
next = reconcileGraphic(next, "strokeWidthLegendTitle", titleVisible, {
|
|
221
|
+
type: "text"
|
|
222
|
+
});
|
|
223
|
+
return next.rematerializeStrokeWidthLegend();
|
|
224
|
+
}
|
|
225
|
+
|
|
166
226
|
function categoricalSymbolIds(config) {
|
|
167
227
|
return new Set(config.symbol.layers.map(layer => symbolGraphic(config, layer.type)));
|
|
168
228
|
}
|
|
@@ -284,6 +344,9 @@ export const editLegend = action(
|
|
|
284
344
|
if (configs.interval?.target === target) {
|
|
285
345
|
return editInterval(this, configs.interval, args);
|
|
286
346
|
}
|
|
347
|
+
if (configs.strokeWidth?.target === target) {
|
|
348
|
+
return editStrokeWidth(this, configs.strokeWidth, args);
|
|
349
|
+
}
|
|
287
350
|
const continuousKind = ["gradient", "opacity"].find(
|
|
288
351
|
kind => configs[kind]?.target === target
|
|
289
352
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerCategoricalLegendActions } from "./categorical/index.js";
|
|
2
2
|
import { registerContinuousLegendActions } from "./continuous/index.js";
|
|
3
3
|
import { registerSizeLegendActions } from "./size.js";
|
|
4
|
+
import { registerStrokeWidthLegendActions } from "./strokeWidth.js";
|
|
4
5
|
import { registerFocusedLegendActions } from "./focused.js";
|
|
5
6
|
import { removeLegend } from "./remove.js";
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ export function registerLegendActions(ProgramClass) {
|
|
|
8
9
|
registerCategoricalLegendActions(ProgramClass);
|
|
9
10
|
registerContinuousLegendActions(ProgramClass);
|
|
10
11
|
registerSizeLegendActions(ProgramClass);
|
|
12
|
+
registerStrokeWidthLegendActions(ProgramClass);
|
|
11
13
|
registerFocusedLegendActions(ProgramClass);
|
|
12
14
|
ProgramClass.prototype.removeLegend = removeLegend;
|
|
13
15
|
}
|