@fundar/data-chart-telling 0.0.49 → 0.0.51
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/dist/configuration/config.svelte.js +7 -2
- package/dist/configuration/themes/index.d.ts +12 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/layout/legend/DiscreteBadgeItem.svelte +21 -8
- package/dist/layout/legend/DiscreteBadgeItem.svelte.d.ts +1 -0
- package/dist/layout/legend/DiscreteSection.svelte +22 -9
- package/dist/layout/legend/DiscreteSection.svelte.d.ts +2 -1
- package/dist/layout/legend/LegendLayout.svelte +7 -2
- package/dist/layout/scales/ScalesLayout.svelte +87 -10
- package/dist/layout/scales/buildScale.d.ts +18 -1
- package/dist/layout/scales/buildScale.js +49 -2
- package/dist/layout/scales/niceDomain.d.ts +6 -0
- package/dist/layout/scales/niceDomain.js +34 -0
- package/dist/layout/tooltip/controller.svelte.js +23 -11
- package/dist/layout/tooltip/utils.js +3 -0
- package/dist/markers/BarMarker.svelte +145 -84
- package/dist/markers/HoverMarker.svelte +41 -1
- package/dist/markers/HoverMarker.svelte.d.ts +7 -1
- package/dist/plots/bar/Plot.svelte +46 -20
- package/dist/plots/bar/buildBarMarkers.d.ts +4 -14
- package/dist/plots/bar/buildBarMarkers.js +73 -53
- package/dist/plots/bar/hoverPoints.d.ts +1 -1
- package/dist/plots/bar/hoverPoints.js +2 -3
- package/dist/plots/bar/layout.svelte.d.ts +4 -0
- package/dist/plots/bar/layout.svelte.js +62 -17
- package/dist/plots/line/Plot.svelte +29 -14
- package/dist/plots/line/buildLineMarkers.d.ts +1 -0
- package/dist/plots/line/buildLineMarkers.js +3 -4
- package/dist/plots/pyramid/Plot.svelte +10 -6
- package/dist/plots/scatter/Plot.svelte +25 -11
- package/dist/plots/scatter/buildScatterMarkers.d.ts +1 -0
- package/dist/plots/scatter/buildScatterMarkers.js +3 -4
- package/dist/plots/utils/legendDisabled.d.ts +13 -1
- package/dist/plots/utils/legendDisabled.js +27 -0
- package/dist/types/configuration/styling.d.ts +12 -2
- package/dist/types/layout/legend.d.ts +15 -10
- package/dist/types/layout/tooltip.d.ts +2 -1
- package/dist/types/markers/common.d.ts +14 -2
- package/package.json +1 -1
|
@@ -1,92 +1,153 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { BarX, BarY, usePlot } from 'svelteplot';
|
|
3
|
+
import type { BarMarkerConfig } from '../types/markers/common';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
let { marker }: { marker: BarMarkerConfig } = $props();
|
|
5
|
+
/**
|
|
6
|
+
* Renders one `'bar'` marker: `role: 'segment'` (visible bar via
|
|
7
|
+
* `<BarX>`/`<BarY>`), `role: 'hitArea'` (invisible hover hit region for
|
|
8
|
+
* one segment), or `role: 'highlight'` (visible area highlight).
|
|
9
|
+
*/
|
|
10
|
+
let { marker }: { marker: BarMarkerConfig } = $props();
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
const plot = usePlot();
|
|
13
|
+
function categoryScaleFor(isHorizontal: boolean) {
|
|
14
|
+
return isHorizontal ? plot.scales.y : plot.scales.x;
|
|
15
|
+
}
|
|
16
|
+
function valueScaleFor(isHorizontal: boolean) {
|
|
17
|
+
return isHorizontal ? plot.scales.x : plot.scales.y;
|
|
18
|
+
}
|
|
19
|
+
function valueExtentFor(isHorizontal: boolean): [number, number] {
|
|
20
|
+
const range = valueScaleFor(isHorizontal)?.fn?.range?.();
|
|
21
|
+
if (!range || range.length < 2) return [0, 0];
|
|
22
|
+
const a = Number(range[0]);
|
|
23
|
+
const b = Number(range[range.length - 1]);
|
|
24
|
+
return [Math.min(a, b), Math.max(a, b)];
|
|
25
|
+
}
|
|
26
|
+
// A `'hitArea'` marker's own value-axis slice, mapped to pixels.
|
|
27
|
+
function hitAreaValueRangeFor(
|
|
28
|
+
isHorizontal: boolean,
|
|
29
|
+
from: unknown,
|
|
30
|
+
to: unknown
|
|
31
|
+
): [number, number] {
|
|
32
|
+
if (typeof from !== 'number' || typeof to !== 'number') return valueExtentFor(isHorizontal);
|
|
33
|
+
const scale = valueScaleFor(isHorizontal);
|
|
34
|
+
const a = Number(scale?.fn?.(from) ?? 0);
|
|
35
|
+
const b = Number(scale?.fn?.(to) ?? 0);
|
|
36
|
+
return [Math.min(a, b), Math.max(a, b)];
|
|
37
|
+
}
|
|
27
38
|
</script>
|
|
28
39
|
|
|
29
40
|
{#if marker.role === 'segment'}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
41
|
+
{@const categoryScale = categoryScaleFor(marker.isHorizontal)}
|
|
42
|
+
{@const bandwidth = categoryScale?.type === 'band' ? categoryScale.fn.bandwidth() : 0}
|
|
43
|
+
{#if marker.isHorizontal}
|
|
44
|
+
<BarX
|
|
45
|
+
data={marker.data}
|
|
46
|
+
y={marker.y}
|
|
47
|
+
x1={marker.x1}
|
|
48
|
+
x2={marker.x2}
|
|
49
|
+
insetTop={marker.insetTop != null ? marker.insetTop * bandwidth : undefined}
|
|
50
|
+
insetBottom={marker.insetBottom != null ? marker.insetBottom * bandwidth : undefined}
|
|
51
|
+
fill={marker.style?.fill}
|
|
52
|
+
fillOpacity={marker.style?.fillOpacity}
|
|
53
|
+
stroke={marker.style?.stroke}
|
|
54
|
+
strokeWidth={marker.style?.strokeWidth}
|
|
55
|
+
strokeOpacity={marker.style?.strokeOpacity}
|
|
56
|
+
strokeDasharray={marker.style?.strokeDasharray}
|
|
57
|
+
borderRadius={marker.style?.borderRadius}
|
|
58
|
+
/>
|
|
59
|
+
{:else}
|
|
60
|
+
<BarY
|
|
61
|
+
data={marker.data}
|
|
62
|
+
x={marker.x}
|
|
63
|
+
y1={marker.y1}
|
|
64
|
+
y2={marker.y2}
|
|
65
|
+
insetLeft={marker.insetLeft != null ? marker.insetLeft * bandwidth : undefined}
|
|
66
|
+
insetRight={marker.insetRight != null ? marker.insetRight * bandwidth : undefined}
|
|
67
|
+
fill={marker.style?.fill}
|
|
68
|
+
fillOpacity={marker.style?.fillOpacity}
|
|
69
|
+
stroke={marker.style?.stroke}
|
|
70
|
+
strokeWidth={marker.style?.strokeWidth}
|
|
71
|
+
strokeOpacity={marker.style?.strokeOpacity}
|
|
72
|
+
strokeDasharray={marker.style?.strokeDasharray}
|
|
73
|
+
borderRadius={marker.style?.borderRadius}
|
|
74
|
+
/>
|
|
75
|
+
{/if}
|
|
63
76
|
{:else}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
{@const categoryScale = categoryScaleFor(marker.isHorizontal)}
|
|
78
|
+
{#if categoryScale?.type === 'band'}
|
|
79
|
+
{@const bandwidth = categoryScale.fn.bandwidth()}
|
|
80
|
+
{@const bandStart = Number(categoryScale.fn(marker.category as never) ?? 0)}
|
|
81
|
+
{#if marker.role === 'hitArea'}
|
|
82
|
+
{@const [rangeMin, rangeMax] = marker.isHorizontal
|
|
83
|
+
? hitAreaValueRangeFor(marker.isHorizontal, marker.x1, marker.x2)
|
|
84
|
+
: hitAreaValueRangeFor(marker.isHorizontal, marker.y1, marker.y2)}
|
|
85
|
+
{@const insetLeft = (marker.insetLeft ?? 0) * bandwidth}
|
|
86
|
+
{@const insetRight = (marker.insetRight ?? 0) * bandwidth}
|
|
87
|
+
{@const insetTop = (marker.insetTop ?? 0) * bandwidth}
|
|
88
|
+
{@const insetBottom = (marker.insetBottom ?? 0) * bandwidth}
|
|
89
|
+
{#if marker.isHorizontal}
|
|
90
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
91
|
+
<rect
|
|
92
|
+
class="dct-hit-area"
|
|
93
|
+
x={rangeMin + insetLeft}
|
|
94
|
+
y={bandStart + insetBottom}
|
|
95
|
+
width={Math.max(0, rangeMax - rangeMin - insetLeft - insetRight)}
|
|
96
|
+
height={Math.max(0, bandwidth - insetTop - insetBottom)}
|
|
97
|
+
fill="transparent"
|
|
98
|
+
onpointerenter={marker.onpointerenter}
|
|
99
|
+
onpointerleave={marker.onpointerleave}
|
|
100
|
+
/>
|
|
101
|
+
{:else}
|
|
102
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
103
|
+
<rect
|
|
104
|
+
class="dct-hit-area"
|
|
105
|
+
x={bandStart + insetLeft}
|
|
106
|
+
y={rangeMin + insetBottom}
|
|
107
|
+
width={Math.max(0, bandwidth - insetLeft - insetRight)}
|
|
108
|
+
height={Math.max(0, rangeMax - rangeMin - insetTop - insetBottom)}
|
|
109
|
+
fill="transparent"
|
|
110
|
+
onpointerenter={marker.onpointerenter}
|
|
111
|
+
onpointerleave={marker.onpointerleave}
|
|
112
|
+
/>
|
|
113
|
+
{/if}
|
|
114
|
+
{:else}
|
|
115
|
+
{@const [valueMin, valueMax] = valueExtentFor(marker.isHorizontal)}
|
|
116
|
+
{@const thickness = bandwidth + 2}
|
|
117
|
+
{@const center = bandStart + bandwidth / 2}
|
|
118
|
+
{#if marker.isHorizontal}
|
|
119
|
+
<rect
|
|
120
|
+
class="hover-area"
|
|
121
|
+
x={valueMin}
|
|
122
|
+
y={center - thickness / 2}
|
|
123
|
+
width={Math.max(0, valueMax - valueMin)}
|
|
124
|
+
height={thickness}
|
|
125
|
+
fill={marker.style?.fill}
|
|
126
|
+
fill-opacity={marker.style?.fillOpacity}
|
|
127
|
+
stroke={marker.style?.stroke}
|
|
128
|
+
stroke-width={marker.style?.strokeWidth}
|
|
129
|
+
stroke-opacity={marker.style?.strokeOpacity}
|
|
130
|
+
stroke-dasharray={marker.style?.strokeDasharray}
|
|
131
|
+
rx={marker.style?.borderRadius}
|
|
132
|
+
pointer-events="none"
|
|
133
|
+
/>
|
|
134
|
+
{:else}
|
|
135
|
+
<rect
|
|
136
|
+
class="hover-area"
|
|
137
|
+
x={center - thickness / 2}
|
|
138
|
+
y={valueMin}
|
|
139
|
+
width={thickness}
|
|
140
|
+
height={Math.max(0, valueMax - valueMin)}
|
|
141
|
+
fill={marker.style?.fill}
|
|
142
|
+
fill-opacity={marker.style?.fillOpacity}
|
|
143
|
+
stroke={marker.style?.stroke}
|
|
144
|
+
stroke-width={marker.style?.strokeWidth}
|
|
145
|
+
stroke-opacity={marker.style?.strokeOpacity}
|
|
146
|
+
stroke-dasharray={marker.style?.strokeDasharray}
|
|
147
|
+
ry={marker.style?.borderRadius}
|
|
148
|
+
pointer-events="none"
|
|
149
|
+
/>
|
|
150
|
+
{/if}
|
|
151
|
+
{/if}
|
|
152
|
+
{/if}
|
|
92
153
|
{/if}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import DotMarker from './DotMarker.svelte';
|
|
3
3
|
import TextMarker from './TextMarker.svelte';
|
|
4
4
|
import RuleMarker from './RuleMarker.svelte';
|
|
5
|
+
import BarMarker from './BarMarker.svelte';
|
|
5
6
|
import { getConfiguration } from '../configuration/config.svelte';
|
|
6
7
|
import type { MarkerProps } from '../types/markers/props';
|
|
7
8
|
import type { HoverMarkerConfig } from '../types/markers/common';
|
|
@@ -22,10 +23,19 @@
|
|
|
22
23
|
* pattern `BarMarker`/`RuleMarker` follow. `activePoint`/
|
|
23
24
|
* `impliesCrosshairX`/`impliesCrosshairY` are all optional and default to
|
|
24
25
|
* "no crosshair".
|
|
26
|
+
*
|
|
27
|
+
* `areaX`/`areaY` are a separate, always-explicit opt-in (no implied
|
|
28
|
+
* default) for a thickened area highlight in place of the thin crosshair —
|
|
29
|
+
* see `HoverMarkerConfig`'s own doc comment. Rendered via `BarMarker`'s
|
|
30
|
+
* `role: 'highlight'`, so it renders nothing on an axis without a band
|
|
31
|
+
* scale (e.g. a line/scatter plot's continuous axis).
|
|
25
32
|
*/
|
|
26
33
|
interface Props extends MarkerProps<HoverDisplayPoint> {
|
|
27
34
|
/** Narrowed to just the fields this component reads — `HoverMarkerConfig` also carries routing/scoping fields (`series`/`scope`/`strategy`/`sync`) it has no use for. */
|
|
28
|
-
marker: Pick<
|
|
35
|
+
marker: Pick<
|
|
36
|
+
HoverMarkerConfig,
|
|
37
|
+
'ruleX' | 'ruleY' | 'areaX' | 'areaY' | 'showLabels' | 'format' | 'ruleStyle' | 'areaStyle' | 'dotStyle' | 'fontStyle'
|
|
38
|
+
>;
|
|
29
39
|
activePoint?: HoverDisplayPoint | null;
|
|
30
40
|
impliesCrosshairX?: boolean;
|
|
31
41
|
impliesCrosshairY?: boolean;
|
|
@@ -46,9 +56,13 @@
|
|
|
46
56
|
const showCrosshairY = $derived(marker.ruleY !== false && (impliesCrosshairY || marker.ruleY === true));
|
|
47
57
|
const ruleX = $derived(showCrosshairX ? (activePoint?.x ?? null) : null);
|
|
48
58
|
const ruleY = $derived(showCrosshairY ? (activePoint?.y ?? null) : null);
|
|
59
|
+
// areaX/areaY have no implied default — always off unless explicitly set.
|
|
60
|
+
const areaX = $derived(marker.areaX === true ? (activePoint?.x ?? null) : null);
|
|
61
|
+
const areaY = $derived(marker.areaY === true ? (activePoint?.y ?? null) : null);
|
|
49
62
|
const showLabels = $derived(marker.showLabels ?? true);
|
|
50
63
|
const format = $derived(marker.format);
|
|
51
64
|
const ruleStyle = $derived(marker.ruleStyle);
|
|
65
|
+
const areaStyle = $derived({ ...cfg.hover.area, ...marker.areaStyle });
|
|
52
66
|
const dotStyle = $derived(marker.dotStyle);
|
|
53
67
|
const fontStyle = $derived(marker.fontStyle);
|
|
54
68
|
|
|
@@ -93,6 +107,32 @@
|
|
|
93
107
|
}
|
|
94
108
|
</script>
|
|
95
109
|
|
|
110
|
+
{#if areaX != null}
|
|
111
|
+
<BarMarker
|
|
112
|
+
marker={{
|
|
113
|
+
type: 'bar',
|
|
114
|
+
role: 'highlight',
|
|
115
|
+
isHorizontal: false,
|
|
116
|
+
data: [],
|
|
117
|
+
category: areaX,
|
|
118
|
+
style: areaStyle,
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
{/if}
|
|
122
|
+
|
|
123
|
+
{#if areaY != null}
|
|
124
|
+
<BarMarker
|
|
125
|
+
marker={{
|
|
126
|
+
type: 'bar',
|
|
127
|
+
role: 'highlight',
|
|
128
|
+
isHorizontal: true,
|
|
129
|
+
data: [],
|
|
130
|
+
category: areaY,
|
|
131
|
+
style: areaStyle,
|
|
132
|
+
}}
|
|
133
|
+
/>
|
|
134
|
+
{/if}
|
|
135
|
+
|
|
96
136
|
{#if ruleX != null}
|
|
97
137
|
<RuleMarker axis="x" value={ruleX} style={{ ...cfg.hover.rule, ...ruleStyle }} />
|
|
98
138
|
{/if}
|
|
@@ -16,10 +16,16 @@ import type { HoverDisplayPoint } from '../types/layout/tooltip';
|
|
|
16
16
|
* pattern `BarMarker`/`RuleMarker` follow. `activePoint`/
|
|
17
17
|
* `impliesCrosshairX`/`impliesCrosshairY` are all optional and default to
|
|
18
18
|
* "no crosshair".
|
|
19
|
+
*
|
|
20
|
+
* `areaX`/`areaY` are a separate, always-explicit opt-in (no implied
|
|
21
|
+
* default) for a thickened area highlight in place of the thin crosshair —
|
|
22
|
+
* see `HoverMarkerConfig`'s own doc comment. Rendered via `BarMarker`'s
|
|
23
|
+
* `role: 'highlight'`, so it renders nothing on an axis without a band
|
|
24
|
+
* scale (e.g. a line/scatter plot's continuous axis).
|
|
19
25
|
*/
|
|
20
26
|
interface Props extends MarkerProps<HoverDisplayPoint> {
|
|
21
27
|
/** Narrowed to just the fields this component reads — `HoverMarkerConfig` also carries routing/scoping fields (`series`/`scope`/`strategy`/`sync`) it has no use for. */
|
|
22
|
-
marker: Pick<HoverMarkerConfig, 'ruleX' | 'ruleY' | 'showLabels' | 'format' | 'ruleStyle' | 'dotStyle' | 'fontStyle'>;
|
|
28
|
+
marker: Pick<HoverMarkerConfig, 'ruleX' | 'ruleY' | 'areaX' | 'areaY' | 'showLabels' | 'format' | 'ruleStyle' | 'areaStyle' | 'dotStyle' | 'fontStyle'>;
|
|
23
29
|
activePoint?: HoverDisplayPoint | null;
|
|
24
30
|
impliesCrosshairX?: boolean;
|
|
25
31
|
impliesCrosshairY?: boolean;
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
import { paletteColor } from '../../utils/color';
|
|
3
3
|
import { buildSeries } from '../../utils/grouping';
|
|
4
4
|
import { getLegendInteraction } from '../../layout/legend/interaction.svelte';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
disabledFillOverride,
|
|
7
|
+
resolveEffectiveDisabledStyle,
|
|
8
|
+
omitDisabledSeries,
|
|
9
|
+
buildSeriesColorIndex
|
|
10
|
+
} from '../utils/legendDisabled';
|
|
6
11
|
import { resolveAccessor } from '../utils/accessors';
|
|
7
12
|
import { getConfiguration } from '../../configuration/config.svelte';
|
|
8
13
|
import {
|
|
@@ -71,7 +76,15 @@
|
|
|
71
76
|
});
|
|
72
77
|
|
|
73
78
|
const cfg = $derived(getConfiguration());
|
|
74
|
-
const
|
|
79
|
+
const legendInteraction = $derived(getLegendInteraction());
|
|
80
|
+
// Unfiltered series list, for stable per-name color indexing.
|
|
81
|
+
const fullSeries = $derived(series ?? buildSeries(data!, x!, y!, z));
|
|
82
|
+
const colorIndex = $derived(buildSeriesColorIndex(fullSeries));
|
|
83
|
+
function colorForSeries(name: string): string {
|
|
84
|
+
return paletteColor(colorIndex.get(name) ?? 0, cfg.palette);
|
|
85
|
+
}
|
|
86
|
+
// Drops series toggled off with `disabled: { mode: 'omit' }`.
|
|
87
|
+
const resolvedSeries = $derived(omitDisabledSeries(fullSeries, legendInteraction));
|
|
75
88
|
const groupedSeries = $derived(
|
|
76
89
|
// connect: false — bars are discrete; there's no visual gap between
|
|
77
90
|
// adjacent categories to bridge (see buildGroupedSeries's own doc).
|
|
@@ -104,7 +117,7 @@
|
|
|
104
117
|
buildScopedMarkerGroups<TData, Series<TData>, BarSegmentStyle>({
|
|
105
118
|
groupedSeries,
|
|
106
119
|
segments,
|
|
107
|
-
colorFor: (
|
|
120
|
+
colorFor: (series) => colorForSeries(series.name),
|
|
108
121
|
segmentColorFor: (style, defaultColor) => style?.fill ?? defaultColor
|
|
109
122
|
})
|
|
110
123
|
);
|
|
@@ -145,14 +158,27 @@
|
|
|
145
158
|
delete rest.type;
|
|
146
159
|
return rest;
|
|
147
160
|
}
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
161
|
+
// Backs the category axis's `paddingInner`/`paddingOuter` — the gap
|
|
162
|
+
// *between* category groups — with the theme's `bar.paddingInner`/
|
|
163
|
+
// `paddingOuter` default, when the plot doesn't set its own (or the
|
|
164
|
+
// `padding` shorthand). BarPlot-only, unlike svelteplot's own hardcoded
|
|
165
|
+
// 0.15 fallback for every band scale.
|
|
166
|
+
function withDefaultPadding(scale?: AxisScale<TData>): AxisScale<TData> {
|
|
167
|
+
return {
|
|
168
|
+
...scale,
|
|
169
|
+
paddingInner: scale?.paddingInner ?? scale?.padding ?? cfg.bar.paddingInner,
|
|
170
|
+
paddingOuter: scale?.paddingOuter ?? scale?.padding ?? cfg.bar.paddingOuter
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// The single, fully-resolved scales object both BasePlotLayout (ticks,
|
|
174
|
+
// grid, sort, the real svelteplot scale) and `createBarLayout` (the
|
|
175
|
+
// grouped-bar-width estimate) read for everything — avoids keeping a
|
|
176
|
+
// second, separately-patched copy of `scales.x`/`scales.y` in sync with
|
|
177
|
+
// the one actually forwarded.
|
|
152
178
|
const resolvedScales = $derived({
|
|
153
179
|
...scales,
|
|
154
|
-
x: stripCategoricalType(scales.x),
|
|
155
|
-
y: stripCategoricalType(scales.y)
|
|
180
|
+
x: stripCategoricalType(categoricalAxis === 'x' ? withDefaultPadding(scales.x) : scales.x),
|
|
181
|
+
y: stripCategoricalType(categoricalAxis === 'y' ? withDefaultPadding(scales.y) : scales.y)
|
|
156
182
|
});
|
|
157
183
|
|
|
158
184
|
// The accessor feeding each *visual* channel — swapped from the
|
|
@@ -166,7 +192,7 @@
|
|
|
166
192
|
* rendering, value labels, and hover markers below. See `layout.svelte.ts`.
|
|
167
193
|
*/
|
|
168
194
|
const barLayout = createBarLayout<TData>({
|
|
169
|
-
scales: () =>
|
|
195
|
+
scales: () => resolvedScales,
|
|
170
196
|
isHorizontal: () => isHorizontal,
|
|
171
197
|
width: () => width,
|
|
172
198
|
height: () => height,
|
|
@@ -181,7 +207,6 @@
|
|
|
181
207
|
// which drive tick/domain computation and the Pointer's hit-testing) —
|
|
182
208
|
// `row` (see BasePlotLayout/HoverPoint) is what lets the template recover the
|
|
183
209
|
// semantic category regardless of which axis it landed on.
|
|
184
|
-
const legendInteraction = $derived(getLegendInteraction());
|
|
185
210
|
const hoverIsolate = $derived(styles.values?.hoverIsolate ?? false);
|
|
186
211
|
|
|
187
212
|
// A value label hovered while `styles.values.hoverIsolate` is on dims
|
|
@@ -191,18 +216,19 @@
|
|
|
191
216
|
let hoverIsolatedSeries = $state<string | null>(null);
|
|
192
217
|
|
|
193
218
|
function effectiveDisabledFor(name: string): LegendDisabledStyle | undefined {
|
|
194
|
-
return (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
return resolveEffectiveDisabledStyle(
|
|
220
|
+
name,
|
|
221
|
+
legendInteraction,
|
|
222
|
+
cfg.legend.disabledOpacity,
|
|
223
|
+
hoverIsolate,
|
|
224
|
+
hoverIsolatedSeries
|
|
199
225
|
);
|
|
200
226
|
}
|
|
201
227
|
|
|
202
228
|
// A legend-disabled series is dimmed on the bar itself, but never surfaces
|
|
203
229
|
// in hover/tooltip — its points are simply absent from the candidate list.
|
|
204
230
|
const hoverPoints = $derived(
|
|
205
|
-
buildHoverPoints(resolvedSeries, isHorizontal, barLayout,
|
|
231
|
+
buildHoverPoints(resolvedSeries, isHorizontal, barLayout, colorForSeries).filter(
|
|
206
232
|
(p) => !legendInteraction?.disabledSeries.has(p.series!)
|
|
207
233
|
)
|
|
208
234
|
);
|
|
@@ -220,8 +246,8 @@
|
|
|
220
246
|
// (via svelteplot's `RectPath`) actually render that band, which is
|
|
221
247
|
// enough to mismatch the neighbouring category once bands get thin
|
|
222
248
|
// (many categories). The `role: 'hitArea'` bar markers `buildBarMarkers`
|
|
223
|
-
// emits hit-test
|
|
224
|
-
// comment.
|
|
249
|
+
// emits hit-test each series' own segment directly instead — see its
|
|
250
|
+
// own doc comment.
|
|
225
251
|
pointMatching: 'contains' as const
|
|
226
252
|
});
|
|
227
253
|
|
|
@@ -294,7 +320,7 @@
|
|
|
294
320
|
barLayout,
|
|
295
321
|
isHorizontal,
|
|
296
322
|
hoverPoints,
|
|
297
|
-
|
|
323
|
+
colorFor: colorForSeries,
|
|
298
324
|
disabledFillFor: (name) => disabledFillOverride(effectiveDisabledFor(name)),
|
|
299
325
|
active: hover.active,
|
|
300
326
|
setHoverMatch,
|
|
@@ -6,26 +6,16 @@ import type { BarMarkerConfig } from '../../types/markers/common';
|
|
|
6
6
|
import type { BarLayout } from './layout.svelte';
|
|
7
7
|
import type { DiscreteGapFill } from '../utils/gaps';
|
|
8
8
|
/**
|
|
9
|
-
* Every `'bar'` marker for one BarPlot render:
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* row `styles.gaps` fills (distinctly styled, drawn at its interpolated
|
|
13
|
-
* value — see `resolveDiscreteGapFills`), plus one `role: 'hitArea'` marker
|
|
14
|
-
* per category, so a chart never gets segment markers without their hit
|
|
15
|
-
* areas. A row whose value is missing and *not* filled by a gap zone is
|
|
16
|
-
* skipped entirely — no bar drawn, rather than the broken zero/`NaN`
|
|
17
|
-
* geometry a raw missing value would otherwise produce.
|
|
18
|
-
*
|
|
19
|
-
* `'hitArea'` geometry is left unresolved (just `category` + `data`) —
|
|
20
|
-
* resolving it to pixels needs svelteplot's live scale, only available
|
|
21
|
-
* where the marker actually renders.
|
|
9
|
+
* Every `'bar'` marker for one BarPlot render: visible `role: 'segment'`
|
|
10
|
+
* bars (real data + gap fills) plus one `role: 'hitArea'` hit region per row,
|
|
11
|
+
* sized to that row's own segment.
|
|
22
12
|
*/
|
|
23
13
|
export declare function buildBarMarkers<TData extends Record<string, unknown>>(args: {
|
|
24
14
|
groupedSeries: VisualGroup<Series<TData>, BarSegmentStyle>[];
|
|
25
15
|
barLayout: BarLayout<TData>;
|
|
26
16
|
isHorizontal: boolean;
|
|
27
17
|
hoverPoints: HoverPoint<TData>[];
|
|
28
|
-
|
|
18
|
+
colorFor: (seriesName: string) => string;
|
|
29
19
|
disabledFillFor: (seriesName: string) => {
|
|
30
20
|
fill?: string;
|
|
31
21
|
fillOpacity?: number;
|