@fjader/svelte 0.2.2 → 0.2.3

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.
Files changed (118) hide show
  1. package/dist/components/axes/BandAxisTicks.svelte +63 -24
  2. package/dist/components/axes/BandAxisTicks.svelte.d.ts +5 -5
  3. package/dist/components/charts/ChartContainer.svelte +2 -1
  4. package/dist/components/guides/legend/ContinuousColorLegend.svelte +2 -1
  5. package/dist/components/guides/legend/OrdinalColorLegend.svelte +92 -34
  6. package/dist/components/marks/AreaMarkX.svelte +19 -6
  7. package/dist/components/marks/AreaMarkY.svelte +19 -6
  8. package/dist/components/marks/BarMarkX.svelte +3 -0
  9. package/dist/components/marks/BarMarkY.svelte +3 -0
  10. package/dist/components/marks/DotsMark.svelte +3 -0
  11. package/dist/components/marks/LineMark.svelte +3 -0
  12. package/dist/components/marks/ParallelCoordinatesMark.svelte +6 -0
  13. package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +3 -0
  14. package/dist/components/marks/RectMark.svelte +3 -0
  15. package/dist/components/marks/RectXMark.svelte +3 -0
  16. package/dist/components/marks/RectYMark.svelte +3 -0
  17. package/dist/components/marks/RuleMarkX.svelte +10 -2
  18. package/dist/components/marks/RuleMarkY.svelte +10 -2
  19. package/dist/components/marks/TextMark.svelte +68 -4
  20. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +15 -1
  21. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +116 -27
  22. package/dist/components/marks/lifecycle/domain-queries.js +13 -11
  23. package/dist/components/marks/lifecycle/transform-scale-key.d.ts +16 -0
  24. package/dist/components/marks/lifecycle/transform-scale-key.js +27 -0
  25. package/dist/components/marks/utils/area-outline.d.ts +24 -0
  26. package/dist/components/marks/utils/area-outline.js +10 -0
  27. package/dist/components/marks/utils/reactive-config.d.ts +1 -1
  28. package/dist/components/marks/utils/reactive-config.js +3 -0
  29. package/dist/components/rendering/Areas.svelte +30 -6
  30. package/dist/components/rendering/Areas.svelte.d.ts +2 -0
  31. package/dist/components/rendering/BaseGPURenderer.svelte.js +4 -1
  32. package/dist/components/rendering/Dots.svelte +24 -3
  33. package/dist/components/rendering/Dots.svelte.d.ts +2 -0
  34. package/dist/components/rendering/GridLine.svelte +2 -1
  35. package/dist/components/rendering/Lines.svelte +49 -7
  36. package/dist/components/rendering/Lines.svelte.d.ts +9 -0
  37. package/dist/components/rendering/ParallelLines.svelte +26 -7
  38. package/dist/components/rendering/ParallelLines.svelte.d.ts +2 -0
  39. package/dist/components/rendering/Rects.svelte +20 -5
  40. package/dist/components/rendering/Rects.svelte.d.ts +2 -0
  41. package/dist/components/rendering/RuleX.svelte +20 -5
  42. package/dist/components/rendering/RuleX.svelte.d.ts +2 -0
  43. package/dist/components/rendering/RuleY.svelte +20 -5
  44. package/dist/components/rendering/RuleY.svelte.d.ts +2 -0
  45. package/dist/components/rendering/activity-reads.d.ts +93 -0
  46. package/dist/components/rendering/activity-reads.js +135 -0
  47. package/dist/components/rendering/uniform-sizes.d.ts +26 -0
  48. package/dist/components/rendering/uniform-sizes.js +28 -0
  49. package/dist/config/ChartConfig.svelte.d.ts +2 -0
  50. package/dist/gpu/ColorScaleShaderLib.d.ts +13 -1
  51. package/dist/gpu/ColorScaleShaderLib.js +56 -5
  52. package/dist/gpu/GPUCoordinator.svelte.d.ts +8 -0
  53. package/dist/gpu/GPUCoordinator.svelte.js +68 -32
  54. package/dist/gpu/ResolveShader.d.ts +2 -2
  55. package/dist/gpu/ResolveShader.js +2 -2
  56. package/dist/gpu/ScaleTemplateSystem.d.ts +19 -0
  57. package/dist/gpu/ScaleTemplateSystem.js +86 -7
  58. package/dist/gpu/SizeScaleShaderLib.d.ts +4 -4
  59. package/dist/gpu/SizeScaleShaderLib.js +4 -4
  60. package/dist/gpu/identity-slot-packing.d.ts +157 -40
  61. package/dist/gpu/identity-slot-packing.js +262 -62
  62. package/dist/gpu/tracked-scales.d.ts +14 -0
  63. package/dist/gpu/tracked-scales.js +18 -0
  64. package/dist/index.js +1 -0
  65. package/dist/interactors/BandLabelToggle.svelte +5 -9
  66. package/dist/interactors/BandLabelToggle.svelte.d.ts +3 -3
  67. package/dist/interactors/DerivedInterval1D.svelte +26 -54
  68. package/dist/interactors/Interval1D.svelte +2 -1
  69. package/dist/interactors/Interval2D.svelte +2 -1
  70. package/dist/interactors/ParallelAxisSource.svelte +4 -4
  71. package/dist/interactors/ParallelAxisSource.svelte.d.ts +2 -1
  72. package/dist/interactors/ParallelBrushes.svelte +8 -9
  73. package/dist/interactors/ParallelBrushes.svelte.d.ts +3 -3
  74. package/dist/interactors/Toggle.svelte +17 -36
  75. package/dist/interactors/Toggle.svelte.d.ts +4 -3
  76. package/dist/interactors/Tooltip.svelte +2 -1
  77. package/dist/interactors/TooltipManager.svelte.js +8 -3
  78. package/dist/interactors/relay-clauses.d.ts +23 -0
  79. package/dist/interactors/relay-clauses.js +60 -0
  80. package/dist/query-engine/CrossfilterSelection.svelte.d.ts +88 -15
  81. package/dist/query-engine/CrossfilterSelection.svelte.js +94 -20
  82. package/dist/query-engine/FieldActivity.svelte.d.ts +79 -0
  83. package/dist/query-engine/FieldActivity.svelte.js +207 -0
  84. package/dist/query-engine/FilterGroup.svelte.d.ts +53 -29
  85. package/dist/query-engine/FilterGroup.svelte.js +181 -122
  86. package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +42 -0
  87. package/dist/query-engine/FilterGroupRegistry.svelte.js +103 -1
  88. package/dist/query-engine/MarkHandle.svelte.d.ts +9 -1
  89. package/dist/query-engine/MarkHandle.svelte.js +12 -2
  90. package/dist/query-engine/QueryEngine.d.ts +52 -16
  91. package/dist/query-engine/QueryEngine.js +90 -28
  92. package/dist/query-engine/categorical-identity.d.ts +99 -27
  93. package/dist/query-engine/categorical-identity.js +210 -51
  94. package/dist/query-engine/index.d.ts +2 -1
  95. package/dist/query-engine/index.js +1 -0
  96. package/dist/query-engine/preagg/Preagg.d.ts +1 -1
  97. package/dist/query-engine/preagg/Preagg.js +11 -9
  98. package/dist/query-engine/preagg/plan.d.ts +9 -5
  99. package/dist/query-engine/preagg/plan.js +13 -11
  100. package/dist/query-engine/toggle-predicate.js +12 -1
  101. package/dist/scales/ActivityTable.d.ts +99 -0
  102. package/dist/scales/ActivityTable.js +211 -0
  103. package/dist/scales/BandScaleState.svelte.d.ts +28 -7
  104. package/dist/scales/BandScaleState.svelte.js +55 -20
  105. package/dist/scales/ChartScales.svelte.d.ts +2 -2
  106. package/dist/scales/ColorScaleState.svelte.d.ts +36 -8
  107. package/dist/scales/ColorScaleState.svelte.js +118 -24
  108. package/dist/scales/DomainResolver.d.ts +2 -0
  109. package/dist/scales/DomainResolver.js +15 -8
  110. package/dist/scales/ScaleRegistry.svelte.d.ts +9 -0
  111. package/dist/scales/ScaleRegistry.svelte.js +22 -1
  112. package/dist/scales/TimeScaleState.svelte.js +7 -1
  113. package/dist/spec/index.js +1 -0
  114. package/dist/spec/types.d.ts +2 -0
  115. package/dist/transforms/index.d.ts +27 -0
  116. package/dist/transforms/index.js +97 -3
  117. package/dist/types.d.ts +30 -0
  118. package/package.json +1 -1
@@ -1,21 +1,25 @@
1
1
  <script lang="ts">
2
+ import { getContext } from 'svelte';
2
3
  import type { BandScaleState, BandTick } from '../../scales/BandScaleState.svelte';
4
+ import type { ChartContext } from '../../types';
5
+ import { categoryLabel } from '../../query-engine/categorical-identity';
6
+ import type { FilterGroupRegistry } from '../../query-engine/FilterGroupRegistry.svelte';
3
7
 
4
8
  /**
5
9
  * The labels on a band axis, wherever that axis is drawn.
6
10
  *
7
11
  * Three components rendered this: AxisBottom, AxisLeft and AxisCenter. The
8
- * arithmetic was already shared — `tickPosition`, `tickOpacity` and
9
- * `isTickDimmed` all live on the scale — so what was actually duplicated
12
+ * arithmetic was already shared — `tickPosition` and `tickOpacity` live on
13
+ * the scale — so what was actually duplicated
10
14
  * was the markup around it, and what differs between the three is a handful
11
15
  * of numbers: which way the ticks point, how long they are, how far the
12
16
  * label sits, and where it anchors.
13
17
  *
14
18
  * Those are props here, so the structure is written once. The structure is
15
19
  * the part with the reasons attached: the keyed `{#each}` so a reorder
16
- * MOVES a label rather than replacing it, the opacity wrapper the scale
17
- * drives per frame so a label travels and fades with its bar, and the
18
- * dimming class inside it so a selection does not fight the transition.
20
+ * MOVES a label rather than replacing it, and the opacity wrapper the scale
21
+ * drives per frame so a label travels, enters, leaves and dims for a
22
+ * selection with its bar, all on the scale's clocks.
19
23
  *
20
24
  * A band label is positioned through `tickPosition`, which reads the
21
25
  * category's CURRENT fractional slot, not through the d3 scale. The d3
@@ -54,26 +58,66 @@
54
58
  textAnchor = 'middle'
55
59
  }: Props = $props();
56
60
 
61
+ const chart = getContext<ChartContext | undefined>('chart');
62
+ const filterGroups = getContext<FilterGroupRegistry | undefined>('filter-groups');
63
+
64
+ /**
65
+ * The filter groups whose selection the labels show: those of the chart's
66
+ * marks. A selection dims the axis's marks in their own group, so a label
67
+ * dims when any of those groups dims its category; a background mark in a
68
+ * group with nothing selected changes nothing.
69
+ */
70
+ const groups = $derived.by(() => {
71
+ if (!chart || !filterGroups) return [];
72
+ const indices: number[] = [];
73
+ for (const entry of chart.config.entries.values()) {
74
+ // A 'filter' mark never dims; its labels follow it.
75
+ if (entry.selectionResponse === 'filter') continue;
76
+ const index = filterGroups.get(entry.selection).index;
77
+ if (!indices.includes(index)) indices.push(index);
78
+ }
79
+ return indices;
80
+ });
81
+
82
+ /**
83
+ * The field the axis's selection is kept under: its identity key, or its
84
+ * column before. As for the legend, the two differ for a computed field,
85
+ * so its labels briefly lose their dimming while a reload empties identity.
86
+ */
87
+ const key = $derived(scale.identityKey ?? scale.sourceField);
88
+
89
+ /**
90
+ * A label's opacity: the scale's own (travel, clipping) times its selection
91
+ * fade. Read by id where the tick's key is one — the only way to reach an
92
+ * Other bucket, whose label is not a value — and by value before identity.
93
+ */
94
+ function opacityOf(tick: BandTick): number {
95
+ const selected = !filterGroups
96
+ ? 1
97
+ : scale.hasIdentity
98
+ ? filterGroups.opacityOfId(key, groups, tick.key)
99
+ : filterGroups.opacityOfValue(key, groups, tick.value);
100
+ return scale.tickOpacity(tick.key) * selected;
101
+ }
102
+
57
103
  const tickEnd = $derived(base + dir * tickLength);
58
104
  const labelAt = $derived(base + dir * labelGap);
59
105
  </script>
60
106
 
61
107
  {#each ticks as tick (tick.key)}
62
108
  {@const pos = scale.tickPosition(tick.key, rangeSize, axis)}
63
- <g opacity={scale.tickOpacity(tick.key)}>
64
- <g class:tick-dimmed={scale.isTickDimmed(tick)}>
65
- {#if axis === 'x'}
66
- <line x1={pos} x2={pos} y1={base} y2={tickEnd} />
67
- <text text-anchor={textAnchor} x={pos} y={labelAt}>
68
- {tick.value}
69
- </text>
70
- {:else}
71
- <line x1={base} x2={tickEnd} y1={pos} y2={pos} />
72
- <text text-anchor={textAnchor} dominant-baseline="middle" x={labelAt} y={pos}>
73
- {tick.value}
74
- </text>
75
- {/if}
76
- </g>
109
+ <g opacity={opacityOf(tick)}>
110
+ {#if axis === 'x'}
111
+ <line x1={pos} x2={pos} y1={base} y2={tickEnd} />
112
+ <text text-anchor={textAnchor} x={pos} y={labelAt}>
113
+ {categoryLabel(tick.value)}
114
+ </text>
115
+ {:else}
116
+ <line x1={base} x2={tickEnd} y1={pos} y2={pos} />
117
+ <text text-anchor={textAnchor} dominant-baseline="middle" x={labelAt} y={pos}>
118
+ {categoryLabel(tick.value)}
119
+ </text>
120
+ {/if}
77
121
  </g>
78
122
  {/each}
79
123
 
@@ -87,9 +131,4 @@
87
131
  font-family: var(--fjader-font, system-ui, sans-serif);
88
132
  font-size: 10px;
89
133
  }
90
-
91
- .tick-dimmed {
92
- opacity: 0.25;
93
- transition: opacity 0.15s ease;
94
- }
95
134
  </style>
@@ -3,17 +3,17 @@ import type { BandScaleState, BandTick } from '../../scales/BandScaleState.svelt
3
3
  * The labels on a band axis, wherever that axis is drawn.
4
4
  *
5
5
  * Three components rendered this: AxisBottom, AxisLeft and AxisCenter. The
6
- * arithmetic was already shared — `tickPosition`, `tickOpacity` and
7
- * `isTickDimmed` all live on the scale — so what was actually duplicated
6
+ * arithmetic was already shared — `tickPosition` and `tickOpacity` live on
7
+ * the scale — so what was actually duplicated
8
8
  * was the markup around it, and what differs between the three is a handful
9
9
  * of numbers: which way the ticks point, how long they are, how far the
10
10
  * label sits, and where it anchors.
11
11
  *
12
12
  * Those are props here, so the structure is written once. The structure is
13
13
  * the part with the reasons attached: the keyed `{#each}` so a reorder
14
- * MOVES a label rather than replacing it, the opacity wrapper the scale
15
- * drives per frame so a label travels and fades with its bar, and the
16
- * dimming class inside it so a selection does not fight the transition.
14
+ * MOVES a label rather than replacing it, and the opacity wrapper the scale
15
+ * drives per frame so a label travels, enters, leaves and dims for a
16
+ * selection with its bar, all on the scale's clocks.
17
17
  *
18
18
  * A band label is positioned through `tickPosition`, which reads the
19
19
  * category's CURRENT fractional slot, not through the d3 scale. The d3
@@ -13,6 +13,7 @@
13
13
  import { identityKeyFor } from '../../query-engine/identity-key';
14
14
  import { checkFacetCells } from './facet-limits';
15
15
  import ChartCell from './ChartCell.svelte';
16
+ import { categoryLabel } from '../../query-engine/categorical-identity';
16
17
 
17
18
  interface Props {
18
19
  xLabel?: string | null;
@@ -481,7 +482,7 @@
481
482
  <ChartCell
482
483
  cellChartId={facetChartIds[i]}
483
484
  facetIndex={i}
484
- facetLabel={String(cat)}
485
+ facetLabel={String(categoryLabel(cat))}
485
486
  margin={{ top: 20, right: 10, bottom: 35, left: 55 }}
486
487
  {scales}
487
488
  {chartConfig}
@@ -166,7 +166,8 @@
166
166
 
167
167
  const brushHandle: SourceHandle | null = filterGroup
168
168
  ? filterGroup.register(sourceId, {
169
- excludeMarkIds: () => new Set<string>(),
169
+ // No chart: an interval cannot dim, so the chart it sits in filters by
170
+ // it rather than showing nothing.
170
171
  // The brush maps the bar linearly onto [min, max], whatever the color
171
172
  // transform, so its pixel bins do too: barLength pixels over the domain.
172
173
  activeDims: () =>
@@ -1,9 +1,11 @@
1
1
  <script lang="ts">
2
2
  import { getContext, onDestroy } from 'svelte';
3
- import type { ScaleRegistry } from '../../../scales/ScaleRegistry.svelte';
4
3
  import type { ColorScaleState } from '../../../scales/ColorScaleState.svelte';
5
- import type { ActiveValueScale } from '../../../query-engine/FilterGroup.svelte';
6
4
  import type { FilterGroupRegistry } from '../../../query-engine/FilterGroupRegistry.svelte';
5
+ import type { ChartContext } from '../../../types';
6
+ import type { BandScaleState } from '../../../scales/BandScaleState.svelte';
7
+ import type { DimValue } from '../../../query-engine/categorical-identity';
8
+ import { categoryLabel } from '../../../query-engine/categorical-identity';
7
9
 
8
10
  interface Props {
9
11
  scaleState: ColorScaleState;
@@ -31,42 +33,102 @@
31
33
 
32
34
  const filterGroups = getContext<FilterGroupRegistry | undefined>('filter-groups');
33
35
  const filterGroup = filterGroups?.get(selection);
34
- const scaleRegistry = getContext<ScaleRegistry>('scale-registry');
35
36
 
36
- // Reactive: re-renders when domain or scheme changes
37
- const items = $derived(
38
- scaleState.domain.map((value) => ({
37
+ const chartContext = getContext<ChartContext | undefined>('chart');
38
+
39
+ /**
40
+ * The chart's band axis on this legend's field, if it has one. The legend then
41
+ * lists what that axis shows (a top N and its Other, a table's own categories)
42
+ * rather than every category of the field, which is what the colour scale's
43
+ * domain holds: it is shared per field across charts.
44
+ */
45
+ const bandOnField = $derived.by(() => {
46
+ // Neither identityKey is reactive state, and both are null until identity
47
+ // lands, which is after this first runs: read the signals that change
48
+ // with them, or this stays null for good.
49
+ void scaleState.ranksRevision;
50
+ const key = scaleState.identityKey;
51
+ if (!chartContext || !key) return null;
52
+ for (const axis of ['x', 'y'] as const) {
53
+ const type = chartContext.scaleTypes?.[axis];
54
+ if (type !== 'band' && type !== 'point') continue;
55
+ const band = chartContext[`${axis}ScaleState`] as BandScaleState | null;
56
+ if (!band) continue;
57
+ void band._version;
58
+ if (band.identityKey === key) return band;
59
+ }
60
+ return null;
61
+ });
62
+
63
+ interface Item {
64
+ /** The category's dim id, when the legend follows a band axis. */
65
+ id: number | null;
66
+ value: DimValue;
67
+ color: string;
68
+ /** An Other bucket: shown, not clickable (it names no value). */
69
+ bucket: boolean;
70
+ }
71
+
72
+ // Reactive: re-renders when the domain, the axis or the palette ranks change.
73
+ const items = $derived.by((): Item[] => {
74
+ const band = bandOnField;
75
+ if (band) {
76
+ void band._version;
77
+ const ids = band.displayOrder;
78
+ const values = band.effectiveDomain;
79
+ const identity = scaleState.identity;
80
+ return Array.from(ids, (id, i) => ({
81
+ id,
82
+ value: values[i],
83
+ color: scaleState.getColor(values[i] as string | number | null),
84
+ bucket: identity?.synthetic.has(id) ?? false
85
+ }));
86
+ }
87
+ return scaleState.domain.map((value) => ({
88
+ id: null,
39
89
  value,
40
- color: scaleState.getColor(value)
41
- }))
42
- );
90
+ color: scaleState.getColor(value),
91
+ bucket: false
92
+ }));
93
+ });
43
94
 
44
95
  // --- Toggle interaction ---
45
96
  const isInteractive = $derived(filterGroup != null && field != null);
46
97
 
47
- // Scales to dim: this color scale + any band scale for the same field.
48
- // Projection itself is handled by FilterGroup, shared with Toggle/brushes.
49
- function getLegendScaleStates(): ActiveValueScale[] {
50
- const band = scaleRegistry?.getScaleByField(field!, 'band');
51
- return [scaleState, band].filter(Boolean) as unknown as ActiveValueScale[];
98
+ /**
99
+ * The field's identity key: what its selection is kept under. The scale's
100
+ * own once it has identity; the field before, as the toggle keys it. For a
101
+ * plain column the two are the same. For a computed field (an expression)
102
+ * they differ, so while a reload empties the identity the legend reads the
103
+ * selection under the field's name and its highlight is briefly lost.
104
+ */
105
+ const key = $derived(scaleState.identityKey ?? field ?? scaleState.field);
106
+ const groups = $derived(filterGroup ? [filterGroup.index] : []);
107
+
108
+ /**
109
+ * How visible an entry is now: its group's selection fade on the field, on
110
+ * the same clock and with the same floor as the marks.
111
+ */
112
+ function opacityOf(item: Item): number {
113
+ if (!filterGroups) return 1;
114
+ // By id where there is one: the only way to reach an Other bucket.
115
+ return item.id !== null
116
+ ? filterGroups.opacityOfId(key, groups, item.id)
117
+ : filterGroups.opacityOfValue(key, groups, item.value);
52
118
  }
53
119
 
54
120
  // The selection is shared with any Toggle on the same field through its clause.
55
- // A legend is not a chart, so it excludes no marks: every chart filters.
121
+ // A legend inside a chart skips that chart, as a Toggle there does: its marks
122
+ // keyed by the field dim, the rest keep their data. A standalone legend is in
123
+ // no chart, so every chart in its group is routed by its marks alone.
56
124
  const toggle =
57
125
  filterGroup && field
58
- ? filterGroup.registerToggle(field, {
59
- excludeMarkIds: () => new Set<string>(),
60
- scales: getLegendScaleStates
61
- })
126
+ ? filterGroup.registerToggle(field, { chart: chartContext?.config ?? null })
62
127
  : null;
63
128
  onDestroy(() => toggle?.destroy());
64
129
 
65
- // Derive visual state from the toggle's selection
66
- const selectedSet = $derived(toggle?.active ? toggle.values : null);
67
-
68
130
  // Shift/meta-click adds or removes; selecting every value clears instead.
69
- function handleClick(value: string | number, event: MouseEvent) {
131
+ function handleClick(value: DimValue, event: MouseEvent) {
70
132
  toggle?.toggle([value], event.shiftKey || event.metaKey, scaleState.domain.length);
71
133
  }
72
134
  </script>
@@ -85,11 +147,12 @@
85
147
  class:legend-columns={columns}
86
148
  class:legend-vertical={orientation === 'vertical'}
87
149
  >
88
- {#each items as { value, color } (value)}
89
- {#if isInteractive}
150
+ {#each items as item (item.id ?? item.value)}
151
+ {@const { value, color } = item}
152
+ {#if isInteractive && !item.bucket}
90
153
  <button
91
154
  class="legend-item legend-item-interactive"
92
- class:legend-item-dimmed={selectedSet != null && !selectedSet.has(value)}
155
+ style:opacity={opacityOf(item)}
93
156
  onclick={(e) => handleClick(value, e)}
94
157
  >
95
158
  <span
@@ -99,10 +162,10 @@
99
162
  style:height="{swatchSize}px"
100
163
  style:border-radius="{swatchRadius}px"
101
164
  ></span>
102
- <span class="legend-label">{value}</span>
165
+ <span class="legend-label">{categoryLabel(value)}</span>
103
166
  </button>
104
167
  {:else}
105
- <span class="legend-item">
168
+ <span class="legend-item" style:opacity={opacityOf(item)}>
106
169
  <span
107
170
  class="legend-swatch"
108
171
  style:background={color}
@@ -110,7 +173,7 @@
110
173
  style:height="{swatchSize}px"
111
174
  style:border-radius="{swatchRadius}px"
112
175
  ></span>
113
- <span class="legend-label">{value}</span>
176
+ <span class="legend-label">{categoryLabel(value)}</span>
114
177
  </span>
115
178
  {/if}
116
179
  {/each}
@@ -157,7 +220,6 @@
157
220
  align-items: center;
158
221
  gap: 4px;
159
222
  white-space: nowrap;
160
- transition: opacity 0.15s ease;
161
223
  }
162
224
 
163
225
  .legend-item-interactive {
@@ -170,10 +232,6 @@
170
232
  color: inherit;
171
233
  }
172
234
 
173
- .legend-item-dimmed {
174
- opacity: 0.2;
175
- }
176
-
177
235
  .legend-swatch {
178
236
  display: inline-block;
179
237
  flex-shrink: 0;
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { outlineColor } from './utils/area-outline';
2
3
  import { getContext, onDestroy, untrack } from 'svelte';
3
4
  import { MarkLifecycleV2 } from './lifecycle/MarkLifecycleV2.svelte';
4
5
  import { Areas, Lines } from '../rendering';
@@ -46,6 +47,7 @@
46
47
  let {
47
48
  table,
48
49
  selection = undefined,
50
+ selectionResponse = undefined,
49
51
  y,
50
52
  x,
51
53
  x1: x1Prop,
@@ -98,6 +100,7 @@
98
100
  strokeWidth: () => strokeWidth,
99
101
  transforms: () => transforms,
100
102
  selection: () => selection,
103
+ selectionResponse: () => selectionResponse,
101
104
  limit: () => limit,
102
105
  preferredXScaleType: () => preferredXScaleType,
103
106
  preferredYScaleType: () => preferredYScaleType,
@@ -157,6 +160,9 @@
157
160
  chartContext
158
161
  );
159
162
 
163
+ /** The outline's colour, and the fill's activity it dims by (see area-outline.ts). */
164
+ const outline = $derived(outlineColor(entry.stroke, markColor));
165
+
160
166
  // Detect if x2 is an aggregate expression
161
167
  const hasAggregateX = $derived.by(() => {
162
168
  return (
@@ -366,6 +372,7 @@
366
372
  colorValueData={markColor.data}
367
373
  colorMode={markColor.colorMode}
368
374
  colorScaleIndex={markColor.colorScaleIndex}
375
+ groupIndex={setup?.groupIndex ?? 0}
369
376
  constantColor={markColor.constantColor}
370
377
  fillOpacity={entry.fillOpacity ?? 0.5}
371
378
  dotRadius={effectiveDotRadius}
@@ -381,9 +388,12 @@
381
388
  {yData}
382
389
  {seriesData}
383
390
  curve={effectiveCurve}
384
- colorValueData={entry.stroke ? null : markColor.data}
385
- colorMode={entry.stroke ? 0 : markColor.colorMode}
386
- colorScaleIndex={entry.stroke ? 0 : markColor.colorScaleIndex}
391
+ colorValueData={outline.colorValueData}
392
+ colorMode={outline.colorMode}
393
+ colorScaleIndex={outline.colorScaleIndex}
394
+ activityColorMode={outline.activityColorMode}
395
+ activityColorScaleIndex={outline.activityColorScaleIndex}
396
+ groupIndex={setup?.groupIndex ?? 0}
387
397
  constantColor={strokeConstantColor}
388
398
  strokeWidth={entry.strokeWidth ?? 0}
389
399
  zIndex={1}
@@ -396,9 +406,12 @@
396
406
  {yData}
397
407
  {seriesData}
398
408
  curve={effectiveCurve}
399
- colorValueData={entry.stroke ? null : markColor.data}
400
- colorMode={entry.stroke ? 0 : markColor.colorMode}
401
- colorScaleIndex={entry.stroke ? 0 : markColor.colorScaleIndex}
409
+ colorValueData={outline.colorValueData}
410
+ colorMode={outline.colorMode}
411
+ colorScaleIndex={outline.colorScaleIndex}
412
+ activityColorMode={outline.activityColorMode}
413
+ activityColorScaleIndex={outline.activityColorScaleIndex}
414
+ groupIndex={setup?.groupIndex ?? 0}
402
415
  constantColor={strokeConstantColor}
403
416
  strokeWidth={entry.strokeWidth ?? 0}
404
417
  zIndex={1}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { outlineColor } from './utils/area-outline';
2
3
  import { getContext, onDestroy, untrack } from 'svelte';
3
4
  import { MarkLifecycleV2 } from './lifecycle/MarkLifecycleV2.svelte';
4
5
  import { Areas, Lines } from '../rendering';
@@ -46,6 +47,7 @@
46
47
  let {
47
48
  table,
48
49
  selection = undefined,
50
+ selectionResponse = undefined,
49
51
  x,
50
52
  y,
51
53
  y1: y1Prop,
@@ -98,6 +100,7 @@
98
100
  strokeWidth: () => strokeWidth,
99
101
  transforms: () => transforms,
100
102
  selection: () => selection,
103
+ selectionResponse: () => selectionResponse,
101
104
  limit: () => limit,
102
105
  preferredXScaleType: () => preferredXScaleType,
103
106
  preferredYScaleType: () => preferredYScaleType,
@@ -157,6 +160,9 @@
157
160
  chartContext
158
161
  );
159
162
 
163
+ /** The outline's colour, and the fill's activity it dims by (see area-outline.ts). */
164
+ const outline = $derived(outlineColor(entry.stroke, markColor));
165
+
160
166
  // Detect if y2 is an aggregate expression (count, sum, mean, etc.)
161
167
  const hasAggregateY = $derived.by(() => {
162
168
  return (
@@ -366,6 +372,7 @@
366
372
  colorValueData={markColor.data}
367
373
  colorMode={markColor.colorMode}
368
374
  colorScaleIndex={markColor.colorScaleIndex}
375
+ groupIndex={setup?.groupIndex ?? 0}
369
376
  constantColor={markColor.constantColor}
370
377
  fillOpacity={entry.fillOpacity ?? 0.5}
371
378
  dotRadius={effectiveDotRadius}
@@ -381,9 +388,12 @@
381
388
  yData={yTopData}
382
389
  {seriesData}
383
390
  curve={effectiveCurve}
384
- colorValueData={entry.stroke ? null : markColor.data}
385
- colorMode={entry.stroke ? 0 : markColor.colorMode}
386
- colorScaleIndex={entry.stroke ? 0 : markColor.colorScaleIndex}
391
+ colorValueData={outline.colorValueData}
392
+ colorMode={outline.colorMode}
393
+ colorScaleIndex={outline.colorScaleIndex}
394
+ activityColorMode={outline.activityColorMode}
395
+ activityColorScaleIndex={outline.activityColorScaleIndex}
396
+ groupIndex={setup?.groupIndex ?? 0}
387
397
  constantColor={strokeConstantColor}
388
398
  strokeWidth={entry.strokeWidth ?? 0}
389
399
  zIndex={1}
@@ -396,9 +406,12 @@
396
406
  yData={yBottomData}
397
407
  {seriesData}
398
408
  curve={effectiveCurve}
399
- colorValueData={entry.stroke ? null : markColor.data}
400
- colorMode={entry.stroke ? 0 : markColor.colorMode}
401
- colorScaleIndex={entry.stroke ? 0 : markColor.colorScaleIndex}
409
+ colorValueData={outline.colorValueData}
410
+ colorMode={outline.colorMode}
411
+ colorScaleIndex={outline.colorScaleIndex}
412
+ activityColorMode={outline.activityColorMode}
413
+ activityColorScaleIndex={outline.activityColorScaleIndex}
414
+ groupIndex={setup?.groupIndex ?? 0}
402
415
  constantColor={strokeConstantColor}
403
416
  strokeWidth={entry.strokeWidth ?? 0}
404
417
  zIndex={1}
@@ -40,6 +40,7 @@
40
40
  let {
41
41
  table,
42
42
  selection = undefined,
43
+ selectionResponse = undefined,
43
44
  x,
44
45
  x1: x1Prop,
45
46
  x2: x2Prop,
@@ -81,6 +82,7 @@
81
82
  opacity: () => opacity,
82
83
  transforms: () => transforms,
83
84
  selection: () => selection,
85
+ selectionResponse: () => selectionResponse,
84
86
  limit: () => limit,
85
87
  padding: () => padding,
86
88
  paddingInner: () => paddingInner,
@@ -303,6 +305,7 @@
303
305
  alphaData={opacityData}
304
306
  colorMode={markColor.colorMode}
305
307
  colorScaleIndex={markColor.colorScaleIndex}
308
+ groupIndex={setup?.groupIndex ?? 0}
306
309
  constantColor={markColor.constantColor}
307
310
  facetData={setup?.facetData}
308
311
  dataVersion={setup?.dataVersion || 0}
@@ -39,6 +39,7 @@
39
39
  let {
40
40
  table,
41
41
  selection = undefined,
42
+ selectionResponse = undefined,
42
43
  x,
43
44
  y,
44
45
  y1: y1Prop,
@@ -80,6 +81,7 @@
80
81
  opacity: () => opacity,
81
82
  transforms: () => transforms,
82
83
  selection: () => selection,
84
+ selectionResponse: () => selectionResponse,
83
85
  limit: () => limit,
84
86
  padding: () => padding,
85
87
  paddingInner: () => paddingInner,
@@ -304,6 +306,7 @@
304
306
  alphaData={opacityData}
305
307
  colorMode={markColor.colorMode}
306
308
  colorScaleIndex={markColor.colorScaleIndex}
309
+ groupIndex={setup?.groupIndex ?? 0}
307
310
  constantColor={markColor.constantColor}
308
311
  facetData={setup?.facetData}
309
312
  dataVersion={setup?.dataVersion || 0}
@@ -42,6 +42,7 @@
42
42
  let {
43
43
  table,
44
44
  selection = undefined,
45
+ selectionResponse = undefined,
45
46
  x,
46
47
  y,
47
48
  fill = null,
@@ -80,6 +81,7 @@
80
81
  radiusRange: () => radiusRange,
81
82
  transforms: () => transforms,
82
83
  selection: () => selection,
84
+ selectionResponse: () => selectionResponse,
83
85
  limit: () => limit,
84
86
  preferredXScaleType: () => preferredXScaleType,
85
87
  preferredYScaleType: () => preferredYScaleType,
@@ -306,6 +308,7 @@
306
308
  colorValueData={markColor.data}
307
309
  colorMode={markColor.colorMode}
308
310
  colorScaleIndex={markColor.colorScaleIndex}
311
+ groupIndex={setup?.groupIndex ?? 0}
309
312
  constantColor={markColor.constantColor}
310
313
  sizeMode={markSize.sizeMode}
311
314
  sizeScaleIndex={markSize.sizeScaleIndex}
@@ -70,6 +70,7 @@
70
70
  let {
71
71
  table,
72
72
  selection = undefined,
73
+ selectionResponse = undefined,
73
74
  x,
74
75
  y,
75
76
  stroke = null,
@@ -122,6 +123,7 @@
122
123
  dotRadius: () => dotRadius,
123
124
  transforms: () => transforms,
124
125
  selection: () => selection,
126
+ selectionResponse: () => selectionResponse,
125
127
  limit: () => limit,
126
128
  preferredXScaleType: () => preferredXScaleType,
127
129
  preferredYScaleType: () => preferredYScaleType,
@@ -570,6 +572,7 @@
570
572
  colorValueData={markColor.data}
571
573
  colorMode={markColor.colorMode}
572
574
  colorScaleIndex={markColor.colorScaleIndex}
575
+ groupIndex={setup?.groupIndex ?? 0}
573
576
  constantColor={markColor.constantColor}
574
577
  strokeWidth={entry.strokeWidth ?? 2}
575
578
  dotRadius={effectiveDotRadius}
@@ -11,6 +11,7 @@
11
11
  </script>
12
12
 
13
13
  <script lang="ts">
14
+ import type { SelectionResponse } from '../rendering/activity-reads';
14
15
  import { getContext, onDestroy, untrack } from 'svelte';
15
16
  import { MarkLifecycleV2 } from './lifecycle/MarkLifecycleV2.svelte';
16
17
  import { ParallelLines } from '../rendering';
@@ -31,6 +32,8 @@
31
32
  strokeWidth?: number;
32
33
  curve?: CurveType;
33
34
  selection?: string;
35
+ /** How this mark responds to selections in its group (see BaseMarkProps). */
36
+ selectionResponse?: SelectionResponse;
34
37
  transforms?: Transform[];
35
38
  }
36
39
 
@@ -43,6 +46,7 @@
43
46
  strokeWidth = 2,
44
47
  curve = 'linear',
45
48
  selection = undefined,
49
+ selectionResponse = undefined,
46
50
  transforms = []
47
51
  }: Props = $props();
48
52
 
@@ -59,6 +63,7 @@
59
63
  stroke: () => stroke,
60
64
  scheme: () => scheme,
61
65
  selection: () => selection,
66
+ selectionResponse: () => selectionResponse,
62
67
  transforms: () => transforms
63
68
  });
64
69
 
@@ -188,6 +193,7 @@
188
193
  colorValueData={markColor.data}
189
194
  colorMode={markColor.colorMode}
190
195
  colorScaleIndex={markColor.colorScaleIndex}
196
+ groupIndex={setup?.groupIndex ?? 0}
191
197
  constantColor={markColor.constantColor}
192
198
  axisOrder={currentAxisOrder}
193
199
  axisScaleIds={currentScaleIds}
@@ -9,6 +9,7 @@
9
9
  export declare const parallelParity: {
10
10
  reverseScales: boolean;
11
11
  };
12
+ import type { SelectionResponse } from '../rendering/activity-reads';
12
13
  import type { FieldRef, ColorScheme, CurveType, Transform } from '../../types';
13
14
  interface Props {
14
15
  table: string;
@@ -19,6 +20,8 @@ interface Props {
19
20
  strokeWidth?: number;
20
21
  curve?: CurveType;
21
22
  selection?: string;
23
+ /** How this mark responds to selections in its group (see BaseMarkProps). */
24
+ selectionResponse?: SelectionResponse;
22
25
  transforms?: Transform[];
23
26
  }
24
27
  declare const ParallelCoordinatesMark: import("svelte").Component<Props, {}, "">;