@fundar/data-chart-telling 0.0.25 → 0.0.27
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/charts/BaseChart.svelte +18 -10
- package/dist/charts/BaseChart.svelte.d.ts +4 -2
- package/dist/charts/bar/Chart.svelte +2 -0
- package/dist/charts/heatmap/Chart.svelte +5 -3
- package/dist/charts/heatmap/Chart.svelte.d.ts +1 -2
- package/dist/charts/line/Chart.svelte +2 -0
- package/dist/charts/pyramid/Chart.svelte +5 -3
- package/dist/charts/pyramid/Chart.svelte.d.ts +1 -2
- package/dist/charts/scatter/Chart.svelte +95 -0
- package/dist/charts/scatter/Chart.svelte.d.ts +28 -0
- package/dist/configuration/config.svelte.js +28 -0
- package/dist/configuration/themes/dark.js +4 -0
- package/dist/configuration/themes/index.d.ts +112 -0
- package/dist/index.d.ts +26 -16
- package/dist/index.js +11 -3
- package/dist/layout/coordinates/CoordinatesLayout.svelte +93 -0
- package/dist/layout/{plot/RuleLayout.svelte.d.ts → coordinates/CoordinatesLayout.svelte.d.ts} +13 -16
- package/dist/layout/coordinates/margins.d.ts +16 -0
- package/dist/layout/coordinates/margins.js +11 -0
- package/dist/{plots/geo → layout/coordinates}/projections/argentina.d.ts +1 -1
- package/dist/layout/{geo → coordinates}/resolveProjection.d.ts +1 -1
- package/dist/layout/coordinates/resolveProjection.js +24 -0
- package/dist/{plots/geo → layout/coordinates}/zoom.svelte.d.ts +1 -1
- package/dist/layout/facet/FacetIndicator.svelte +31 -0
- package/dist/layout/facet/FacetIndicator.svelte.d.ts +8 -0
- package/dist/layout/facet/FacetLayout.svelte +249 -24
- package/dist/layout/facet/FacetLayout.svelte.d.ts +7 -1
- package/dist/layout/geometry/GeometryLayout.svelte +231 -0
- package/dist/layout/geometry/GeometryLayout.svelte.d.ts +62 -0
- package/dist/layout/legend/ContinuousSection.svelte +1 -1
- package/dist/layout/legend/ContinuousSection.svelte.d.ts +1 -1
- package/dist/layout/legend/DiscreteSection.svelte +1 -1
- package/dist/layout/legend/DiscreteSection.svelte.d.ts +1 -1
- package/dist/layout/legend/LegendLayout.svelte +1 -1
- package/dist/layout/legend/LegendLayout.svelte.d.ts +1 -1
- package/dist/layout/legend/interaction.svelte.d.ts +9 -1
- package/dist/layout/legend/interaction.svelte.js +9 -1
- package/dist/layout/plot/BasePlotLayout.svelte +142 -155
- package/dist/layout/plot/BasePlotLayout.svelte.d.ts +61 -51
- package/dist/layout/plot/margins.d.ts +0 -16
- package/dist/layout/plot/margins.js +0 -11
- package/dist/layout/{plot/AxisLayout.svelte → scales/ScalesLayout.svelte} +23 -16
- package/dist/layout/{plot/AxisLayout.svelte.d.ts → scales/ScalesLayout.svelte.d.ts} +4 -5
- package/dist/layout/scales/buildScale.d.ts +8 -0
- package/dist/layout/scales/buildScale.js +43 -0
- package/dist/layout/styles/StylesLayout.svelte +75 -0
- package/dist/layout/styles/StylesLayout.svelte.d.ts +15 -0
- package/dist/{plots/geo → layout/styles}/TileLayer.svelte +2 -2
- package/dist/{plots/geo → layout/styles}/TileLayer.svelte.d.ts +1 -1
- package/dist/layout/timeline/Timeline.svelte +1 -1
- package/dist/layout/timeline/Timeline.svelte.d.ts +1 -1
- package/dist/layout/timeline/TimelineLayout.svelte +1 -1
- package/dist/layout/timeline/TimelineLayout.svelte.d.ts +1 -1
- package/dist/layout/tooltip/Tooltip.svelte +16 -20
- package/dist/layout/tooltip/Tooltip.svelte.d.ts +5 -0
- package/dist/layout/tooltip/TooltipLayout.svelte +7 -1
- package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +4 -0
- package/dist/layout/tooltip/controller.svelte.d.ts +4 -5
- package/dist/layout/tooltip/controller.svelte.js +42 -16
- package/dist/layout/tooltip/resolveTooltipPlacement.d.ts +24 -0
- package/dist/layout/tooltip/resolveTooltipPlacement.js +49 -0
- package/dist/layout/tooltip/utils.d.ts +27 -19
- package/dist/layout/tooltip/utils.js +16 -15
- package/dist/markers/BarMarker.svelte +92 -0
- package/dist/markers/BarMarker.svelte.d.ts +7 -0
- package/dist/markers/CellMarker.svelte +21 -0
- package/dist/markers/CellMarker.svelte.d.ts +7 -0
- package/dist/markers/ContourMarker.svelte +28 -0
- package/dist/markers/ContourMarker.svelte.d.ts +7 -0
- package/dist/markers/DeltaMarker.svelte +32 -39
- package/dist/markers/DeltaMarker.svelte.d.ts +4 -5
- package/dist/markers/DotMarker.svelte +35 -6
- package/dist/markers/DotMarker.svelte.d.ts +11 -2
- package/dist/markers/GeoFeaturesMarker.svelte +19 -0
- package/dist/markers/GeoFeaturesMarker.svelte.d.ts +7 -0
- package/dist/markers/GeoHoverMarker.svelte +25 -0
- package/dist/markers/GeoHoverMarker.svelte.d.ts +9 -0
- package/dist/markers/GraticuleMarker.svelte +18 -0
- package/dist/markers/GraticuleMarker.svelte.d.ts +7 -0
- package/dist/markers/HoverMarker.svelte +34 -35
- package/dist/markers/HoverMarker.svelte.d.ts +15 -9
- package/dist/markers/LineMarker.svelte +19 -0
- package/dist/markers/LineMarker.svelte.d.ts +7 -0
- package/dist/markers/LinkMarker.svelte +42 -0
- package/dist/markers/LinkMarker.svelte.d.ts +7 -0
- package/dist/markers/RuleMarker.svelte +24 -0
- package/dist/markers/RuleMarker.svelte.d.ts +10 -0
- package/dist/markers/SphereMarker.svelte +15 -0
- package/dist/markers/SphereMarker.svelte.d.ts +7 -0
- package/dist/markers/TextMarker.svelte +1 -1
- package/dist/markers/TextMarker.svelte.d.ts +1 -1
- package/dist/markers/VectorMarker.svelte +20 -0
- package/dist/markers/VectorMarker.svelte.d.ts +7 -0
- package/dist/markers/inset/InsetMarker.svelte +38 -45
- package/dist/markers/inset/InsetMarker.svelte.d.ts +6 -2
- package/dist/plots/bar/Plot.svelte +96 -87
- package/dist/plots/bar/Plot.svelte.d.ts +4 -5
- package/dist/plots/bar/ValueLabels.svelte +70 -24
- package/dist/plots/bar/ValueLabels.svelte.d.ts +11 -2
- package/dist/plots/bar/buildBarMarkers.d.ts +30 -0
- package/dist/plots/bar/buildBarMarkers.js +100 -0
- package/dist/plots/bar/layout.svelte.d.ts +1 -2
- package/dist/plots/bar/layout.svelte.js +1 -1
- package/dist/plots/geo/Plot.svelte +113 -387
- package/dist/plots/geo/Plot.svelte.d.ts +5 -4
- package/dist/plots/geo/buildGeoMarkers.d.ts +33 -0
- package/dist/plots/geo/buildGeoMarkers.js +47 -0
- package/dist/plots/heatmap/Plot.svelte +27 -52
- package/dist/plots/heatmap/Plot.svelte.d.ts +4 -5
- package/dist/plots/heatmap/ValueLabels.svelte +5 -5
- package/dist/plots/heatmap/ValueLabels.svelte.d.ts +3 -3
- package/dist/plots/heatmap/buildCellMarkers.d.ts +21 -0
- package/dist/plots/heatmap/buildCellMarkers.js +25 -0
- package/dist/plots/line/Plot.svelte +73 -141
- package/dist/plots/line/Plot.svelte.d.ts +4 -4
- package/dist/plots/line/ValueLabels.svelte +84 -85
- package/dist/plots/line/ValueLabels.svelte.d.ts +10 -1
- package/dist/plots/line/buildLineMarkers.d.ts +15 -0
- package/dist/plots/line/buildLineMarkers.js +57 -0
- package/dist/plots/pyramid/Plot.svelte +80 -121
- package/dist/plots/pyramid/Plot.svelte.d.ts +4 -5
- package/dist/plots/pyramid/ValueLabels.svelte +71 -25
- package/dist/plots/pyramid/ValueLabels.svelte.d.ts +11 -2
- package/dist/plots/pyramid/buildPyramidBarMarkers.d.ts +19 -0
- package/dist/plots/pyramid/buildPyramidBarMarkers.js +32 -0
- package/dist/plots/scatter/Plot.svelte +242 -0
- package/dist/{layout/plot/GridLayout.svelte.d.ts → plots/scatter/Plot.svelte.d.ts} +9 -7
- package/dist/plots/scatter/ValueLabels.svelte +149 -0
- package/dist/plots/{bar/BarSegments.svelte.d.ts → scatter/ValueLabels.svelte.d.ts} +17 -15
- package/dist/plots/scatter/buildScatterMarkers.d.ts +11 -0
- package/dist/plots/scatter/buildScatterMarkers.js +37 -0
- package/dist/plots/scatter/resolveRadiusPaddedDomain.d.ts +10 -0
- package/dist/plots/scatter/resolveRadiusPaddedDomain.js +23 -0
- package/dist/plots/utils/declutter.d.ts +55 -37
- package/dist/plots/utils/declutter.js +79 -64
- package/dist/plots/utils/geoAccessors.d.ts +1 -1
- package/dist/plots/utils/geoAccessors.js +1 -1
- package/dist/plots/utils/labelOverflow.svelte.d.ts +51 -0
- package/dist/plots/utils/labelOverflow.svelte.js +116 -0
- package/dist/plots/utils/legendDisabled.d.ts +1 -1
- package/dist/plots/utils/segments.d.ts +17 -10
- package/dist/plots/utils/segments.js +26 -16
- package/dist/types/charts/common.d.ts +3 -50
- package/dist/types/charts/props.d.ts +8 -8
- package/dist/types/configuration/styling.d.ts +26 -2
- package/dist/types/{plots/scales/geo.d.ts → layout/coordinates.d.ts} +33 -1
- package/dist/types/layout/facet.d.ts +41 -0
- package/dist/types/layout/geometry.d.ts +17 -0
- package/dist/types/layout/legend.d.ts +65 -1
- package/dist/types/{plots/axis.d.ts → layout/scales.d.ts} +11 -0
- package/dist/types/layout/styles.d.ts +115 -0
- package/dist/types/layout/timeline.d.ts +32 -0
- package/dist/types/layout/tooltip.d.ts +20 -1
- package/dist/types/markers/all.d.ts +11 -0
- package/dist/types/markers/all.js +1 -0
- package/dist/types/markers/base.d.ts +45 -0
- package/dist/types/markers/base.js +1 -0
- package/dist/types/markers/common.d.ts +64 -16
- package/dist/types/markers/geo.d.ts +75 -100
- package/dist/types/markers/line.d.ts +34 -0
- package/dist/types/markers/line.js +1 -0
- package/dist/types/markers/props.d.ts +1 -1
- package/dist/types/plots/data/common.d.ts +1 -1
- package/dist/types/plots/data/scatter.d.ts +13 -0
- package/dist/types/plots/data/scatter.js +1 -0
- package/dist/types/plots/props.d.ts +46 -74
- package/dist/types/plots/segments/common.d.ts +6 -12
- package/dist/types/plots/segments/config.d.ts +1 -1
- package/dist/types/plots/styling.d.ts +10 -0
- package/dist/{plots/utils → utils}/delta.d.ts +2 -2
- package/dist/{plots/utils → utils}/delta.js +1 -1
- package/dist/utils/edgeAwareAnchor.d.ts +37 -0
- package/dist/utils/edgeAwareAnchor.js +65 -0
- package/dist/utils/grouping.d.ts +1 -1
- package/dist/utils/interpolate.d.ts +2 -2
- package/package.json +3 -1
- package/dist/layout/geo/GeoLayout.svelte +0 -51
- package/dist/layout/geo/GeoLayout.svelte.d.ts +0 -24
- package/dist/layout/geo/resolveProjection.js +0 -48
- package/dist/layout/plot/GridLayout.svelte +0 -30
- package/dist/layout/plot/RuleLayout.svelte +0 -111
- package/dist/plots/bar/BarSegments.svelte +0 -69
- package/dist/types/charts/legend.d.ts +0 -60
- package/dist/types/plots/delta.d.ts +0 -19
- package/dist/types/plots/styles/common.d.ts +0 -52
- package/dist/types/plots/styles/geo.d.ts +0 -42
- /package/dist/{plots/geo → layout/coordinates}/projections/argentina.js +0 -0
- /package/dist/{plots/geo → layout/coordinates}/projections/fit.d.ts +0 -0
- /package/dist/{plots/geo → layout/coordinates}/projections/fit.js +0 -0
- /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.d.ts +0 -0
- /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.js +0 -0
- /package/dist/{plots/geo → layout/coordinates}/zoom.svelte.js +0 -0
- /package/dist/types/{charts/legend.js → layout/coordinates.js} +0 -0
- /package/dist/types/{plots/axis.js → layout/facet.js} +0 -0
- /package/dist/types/{plots/delta.js → layout/geometry.js} +0 -0
- /package/dist/types/{plots/scales/geo.js → layout/scales.js} +0 -0
- /package/dist/types/{plots/styles/common.js → layout/styles.js} +0 -0
- /package/dist/types/{plots/styles/geo.js → layout/timeline.js} +0 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
|
-
import { Text } from 'svelteplot';
|
|
2
|
+
import { Text, usePlot } from 'svelteplot';
|
|
3
3
|
import { resolveAccessor } from '../utils/accessors';
|
|
4
|
+
import { disabledFillOverride } from '../utils/legendDisabled';
|
|
5
|
+
import { watchLabelOverflow } from '../utils/labelOverflow.svelte';
|
|
4
6
|
import type { Series } from '../../types/plots/data/common';
|
|
5
|
-
import type { ValuesStyle } from '../../types/
|
|
7
|
+
import type { ValuesStyle } from '../../types/layout/styles';
|
|
6
8
|
import type { ValueAnchor } from '../../types/plots/constants';
|
|
7
9
|
import type { BarSegmentStyle } from '../../types/plots/segments/common';
|
|
8
10
|
import type { VisualGroup } from '../../types/plots/segments/config';
|
|
11
|
+
import type { LegendDisabledStyle } from '../../types/layout/legend';
|
|
12
|
+
import type { MarginOverflow } from '../utils/labelOverflow.svelte';
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* Renders one series' value label on a pyramid bar — anchor-driven default
|
|
@@ -16,17 +20,53 @@
|
|
|
16
20
|
let {
|
|
17
21
|
group,
|
|
18
22
|
valAnchor,
|
|
19
|
-
|
|
23
|
+
formatValue,
|
|
20
24
|
values,
|
|
21
25
|
axisColor,
|
|
26
|
+
disabled,
|
|
27
|
+
onHoverSeries,
|
|
28
|
+
onHoverEnd,
|
|
29
|
+
onOverflow,
|
|
22
30
|
}: {
|
|
23
31
|
group: VisualGroup<Series<TData> & { side: 'left' | 'right' }, BarSegmentStyle>;
|
|
24
32
|
valAnchor: ValueAnchor;
|
|
25
|
-
|
|
33
|
+
formatValue: (value: number, seriesName: string) => string;
|
|
26
34
|
values: ValuesStyle | undefined;
|
|
27
35
|
axisColor: string;
|
|
36
|
+
/** Resolved legend/hover-isolate dimming style for this group's series, if any. */
|
|
37
|
+
disabled?: LegendDisabledStyle;
|
|
38
|
+
/** Fired when the pointer enters this series' label — only wired when hover-isolate is enabled. */
|
|
39
|
+
onHoverSeries?: () => void;
|
|
40
|
+
onHoverEnd?: () => void;
|
|
41
|
+
/** Reports how far this label overflows the plot's own content box, in pixels, on each side (0 = no overflow). */
|
|
42
|
+
onOverflow?: (overflow: MarginOverflow) => void;
|
|
28
43
|
} = $props();
|
|
29
44
|
|
|
45
|
+
const fillOverride = $derived(disabledFillOverride(disabled));
|
|
46
|
+
const plot = usePlot();
|
|
47
|
+
let groupEl: SVGGElement | undefined = $state();
|
|
48
|
+
|
|
49
|
+
watchLabelOverflow({
|
|
50
|
+
el: () => groupEl,
|
|
51
|
+
bounds: () => {
|
|
52
|
+
// getBBox() inside isn't itself tracked — depend on whatever actually
|
|
53
|
+
// changes the rendered label layout so this re-measures.
|
|
54
|
+
void group;
|
|
55
|
+
void values;
|
|
56
|
+
void valAnchor;
|
|
57
|
+
const xRange = plot.scales.x?.fn?.range?.()?.map(Number);
|
|
58
|
+
const yRange = plot.scales.y?.fn?.range?.()?.map(Number);
|
|
59
|
+
if (!xRange || !yRange) return undefined;
|
|
60
|
+
return {
|
|
61
|
+
left: Math.min(xRange[0], xRange[1]),
|
|
62
|
+
right: Math.max(xRange[0], xRange[1]),
|
|
63
|
+
top: Math.min(yRange[0], yRange[1]),
|
|
64
|
+
bottom: Math.max(yRange[0], yRange[1]),
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
onOverflow: (overflow) => onOverflow?.(overflow),
|
|
68
|
+
});
|
|
69
|
+
|
|
30
70
|
const isLeft = $derived(group.series.side === 'left');
|
|
31
71
|
const getCategory = $derived(resolveAccessor(group.series.x));
|
|
32
72
|
const getMagnitude = $derived(resolveAccessor(group.series.y));
|
|
@@ -57,24 +97,30 @@
|
|
|
57
97
|
const resolvedTextAnchor = $derived(font?.textAnchor === 'outside' ? undefined : font?.textAnchor);
|
|
58
98
|
</script>
|
|
59
99
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
<!-- svelteplot's Text mark never wires pointer handlers to its DOM node,
|
|
101
|
+
despite declaring them in its types — a wrapping <g> catches the
|
|
102
|
+
boundary crossing instead (pointerenter/leave fire on every ancestor). -->
|
|
103
|
+
<g bind:this={groupEl} role="presentation" onpointerenter={onHoverSeries} onpointerleave={onHoverEnd}>
|
|
104
|
+
<Text
|
|
105
|
+
data={group.series.data}
|
|
106
|
+
x={xFn}
|
|
107
|
+
y={getCategory}
|
|
108
|
+
text={(d: TData) => formatValue(Math.abs(Number(getMagnitude(d))), group.series.name)}
|
|
109
|
+
dx={font?.dx ?? defaultDx}
|
|
110
|
+
dy={font?.dy ?? 0}
|
|
111
|
+
textAnchor={resolvedTextAnchor ?? defaultTextAnchor}
|
|
112
|
+
lineAnchor={font?.lineAnchor ?? 'middle'}
|
|
113
|
+
lineHeight={font?.lineHeight}
|
|
114
|
+
rotate={font?.rotate}
|
|
115
|
+
class={font?.class}
|
|
116
|
+
textClass={font?.textClass}
|
|
117
|
+
fill={font?.fill ?? axisColor}
|
|
118
|
+
fillOpacity={fillOverride?.fillOpacity}
|
|
119
|
+
fontSize={font?.fontSize ?? 12}
|
|
120
|
+
fontWeight={font?.fontWeight}
|
|
121
|
+
fontStyle={font?.fontStyle}
|
|
122
|
+
stroke={font?.stroke}
|
|
123
|
+
strokeWidth={font?.strokeWidth}
|
|
124
|
+
paintOrder={font?.paintOrder}
|
|
125
|
+
/>
|
|
126
|
+
</g>
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import type { Series } from '../../types/plots/data/common';
|
|
2
|
-
import type { ValuesStyle } from '../../types/
|
|
2
|
+
import type { ValuesStyle } from '../../types/layout/styles';
|
|
3
3
|
import type { ValueAnchor } from '../../types/plots/constants';
|
|
4
4
|
import type { BarSegmentStyle } from '../../types/plots/segments/common';
|
|
5
5
|
import type { VisualGroup } from '../../types/plots/segments/config';
|
|
6
|
+
import type { LegendDisabledStyle } from '../../types/layout/legend';
|
|
7
|
+
import type { MarginOverflow } from '../utils/labelOverflow.svelte';
|
|
6
8
|
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
7
9
|
props: {
|
|
8
10
|
group: VisualGroup<Series<TData> & {
|
|
9
11
|
side: "left" | "right";
|
|
10
12
|
}, BarSegmentStyle>;
|
|
11
13
|
valAnchor: ValueAnchor;
|
|
12
|
-
|
|
14
|
+
formatValue: (value: number, seriesName: string) => string;
|
|
13
15
|
values: ValuesStyle | undefined;
|
|
14
16
|
axisColor: string;
|
|
17
|
+
/** Resolved legend/hover-isolate dimming style for this group's series, if any. */
|
|
18
|
+
disabled?: LegendDisabledStyle;
|
|
19
|
+
/** Fired when the pointer enters this series' label — only wired when hover-isolate is enabled. */
|
|
20
|
+
onHoverSeries?: () => void;
|
|
21
|
+
onHoverEnd?: () => void;
|
|
22
|
+
/** Reports how far this label overflows the plot's own content box, in pixels, on each side (0 = no overflow). */
|
|
23
|
+
onOverflow?: (overflow: MarginOverflow) => void;
|
|
15
24
|
};
|
|
16
25
|
exports: {};
|
|
17
26
|
bindings: "";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Series } from '../../types/plots/data/common';
|
|
2
|
+
import type { BarSegmentStyle } from '../../types/plots/segments/common';
|
|
3
|
+
import type { VisualGroup } from '../../types/plots/segments/config';
|
|
4
|
+
import type { BarMarkerConfig } from '../../types/markers/common';
|
|
5
|
+
/**
|
|
6
|
+
* Every `'bar'` marker for one PyramidPlot render — reuses `BarMarkerConfig`
|
|
7
|
+
* directly, always horizontal (category on y, signed magnitude on x) and
|
|
8
|
+
* with no insets. Emits no `role: 'hitArea'` markers, since PyramidPlot's
|
|
9
|
+
* hover matches nearest points rather than hit-testing category bands.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildPyramidBarMarkers<TData extends Record<string, unknown>, S extends Series<TData>>(args: {
|
|
12
|
+
groupedSeries: VisualGroup<S, BarSegmentStyle>[];
|
|
13
|
+
valueFor: (series: S, d: TData) => number;
|
|
14
|
+
colorFor: (series: S) => string;
|
|
15
|
+
disabledFillFor: (seriesName: string) => {
|
|
16
|
+
fill?: string;
|
|
17
|
+
fillOpacity?: number;
|
|
18
|
+
} | undefined;
|
|
19
|
+
}): BarMarkerConfig[];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
2
|
+
/**
|
|
3
|
+
* Every `'bar'` marker for one PyramidPlot render — reuses `BarMarkerConfig`
|
|
4
|
+
* directly, always horizontal (category on y, signed magnitude on x) and
|
|
5
|
+
* with no insets. Emits no `role: 'hitArea'` markers, since PyramidPlot's
|
|
6
|
+
* hover matches nearest points rather than hit-testing category bands.
|
|
7
|
+
*/
|
|
8
|
+
export function buildPyramidBarMarkers(args) {
|
|
9
|
+
const { groupedSeries, valueFor, colorFor, disabledFillFor } = args;
|
|
10
|
+
const markers = [];
|
|
11
|
+
for (const group of groupedSeries) {
|
|
12
|
+
const getCategory = resolveAccessor(group.series.x);
|
|
13
|
+
const defaultColor = colorFor(group.series);
|
|
14
|
+
const disabledFill = disabledFillFor(group.series.name);
|
|
15
|
+
for (const seg of group.visualSegments) {
|
|
16
|
+
markers.push({
|
|
17
|
+
type: 'bar',
|
|
18
|
+
role: 'segment',
|
|
19
|
+
isHorizontal: true,
|
|
20
|
+
data: seg.data,
|
|
21
|
+
y: getCategory,
|
|
22
|
+
x1: 0,
|
|
23
|
+
x2: (d) => valueFor(group.series, d),
|
|
24
|
+
style: {
|
|
25
|
+
fill: disabledFill?.fill ?? seg.style.fill ?? defaultColor,
|
|
26
|
+
fillOpacity: disabledFill?.fillOpacity ?? seg.style.fillOpacity ?? 1,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return markers;
|
|
32
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
<script
|
|
2
|
+
lang="ts"
|
|
3
|
+
generics="TData extends Record<string, unknown>"
|
|
4
|
+
>
|
|
5
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
6
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
7
|
+
import { paletteColor } from '../../utils/color';
|
|
8
|
+
import { buildSeries } from '../../utils/grouping';
|
|
9
|
+
import { getLegendInteraction } from '../../layout/legend/interaction.svelte';
|
|
10
|
+
import { disabledDotsOverride } from '../utils/legendDisabled';
|
|
11
|
+
import { buildScatterMarkers } from './buildScatterMarkers';
|
|
12
|
+
import { resolveRadiusPaddedDomain } from './resolveRadiusPaddedDomain';
|
|
13
|
+
import { createLabelMarginTracker } from '../utils/labelOverflow.svelte';
|
|
14
|
+
import BasePlotLayout from '../../layout/plot/BasePlotLayout.svelte';
|
|
15
|
+
import ValueLabels from './ValueLabels.svelte';
|
|
16
|
+
import { hasHoverMarker, resolveHoverStrategy, resolveHoverSync } from '../../layout/tooltip/utils';
|
|
17
|
+
import { buildGroupedSeries, validateSegments, buildScopedMarkerGroups } from '../utils/segments';
|
|
18
|
+
import { numericMargin } from '../../layout/plot/margins';
|
|
19
|
+
import type { BasePlotProps } from '../../types/plots/props';
|
|
20
|
+
import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
|
|
21
|
+
import type { Series } from '../../types/plots/data/common';
|
|
22
|
+
import type { ScatterDataProps } from '../../types/plots/data/scatter';
|
|
23
|
+
import type { BasePlotStylesConfig } from '../../types/layout/styles';
|
|
24
|
+
import type { ScatterSegmentStyle } from '../../types/plots/segments/common';
|
|
25
|
+
import type { AxisBasedMarkersConfig } from '../../types/markers/common';
|
|
26
|
+
import type { LegendDisabledStyle } from '../../types/layout/legend';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A point cloud on continuous x/y axes. Accepts either a pre-built `series`
|
|
30
|
+
* array or flat `data/x/y/z` props — mirrors the line/bar plot contract so
|
|
31
|
+
* the chart-level container/timeline/facet/legend wrap it unchanged. `r`
|
|
32
|
+
* gives every dot a per-row radius (bubble sizing), independent of `z`
|
|
33
|
+
* grouping/coloring — see {@link ScatterDataProps} for its auto-scaling
|
|
34
|
+
* behavior.
|
|
35
|
+
*/
|
|
36
|
+
type Props = BasePlotProps<
|
|
37
|
+
ScatterDataProps<TData>,
|
|
38
|
+
TData,
|
|
39
|
+
BasePlotStylesConfig,
|
|
40
|
+
ScatterSegmentStyle,
|
|
41
|
+
AxisBasedMarkersConfig,
|
|
42
|
+
AxisBasedScalesConfig<TData>
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
let {
|
|
46
|
+
width,
|
|
47
|
+
height,
|
|
48
|
+
series,
|
|
49
|
+
data,
|
|
50
|
+
x,
|
|
51
|
+
y,
|
|
52
|
+
z,
|
|
53
|
+
r,
|
|
54
|
+
styles = {},
|
|
55
|
+
segments = {},
|
|
56
|
+
markers = [],
|
|
57
|
+
scales = {},
|
|
58
|
+
margins,
|
|
59
|
+
tooltip = undefined,
|
|
60
|
+
}: Props = $props();
|
|
61
|
+
|
|
62
|
+
const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, z));
|
|
63
|
+
const legendInteraction = $derived(getLegendInteraction());
|
|
64
|
+
|
|
65
|
+
$effect(() => { validateSegments(segments); });
|
|
66
|
+
|
|
67
|
+
// A scatter point cloud is discrete (no connecting line), so adjacent
|
|
68
|
+
// segments never need a bridge point at their boundary — see
|
|
69
|
+
// buildGroupedSeries's own doc.
|
|
70
|
+
const groupedSeries = $derived(
|
|
71
|
+
buildGroupedSeries<Series<TData>, ScatterSegmentStyle>(resolvedSeries, segments, false),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const cfg = $derived(getConfiguration());
|
|
75
|
+
const showVals = $derived(styles.values?.show ?? false);
|
|
76
|
+
const formatValue = $derived(styles.values?.format ?? ((v: number) => `${v}`));
|
|
77
|
+
const hoverIsolate = $derived(styles.values?.hoverIsolate ?? false);
|
|
78
|
+
|
|
79
|
+
// A value label hovered while `styles.values.hoverIsolate` is on dims
|
|
80
|
+
// every other series exactly like a legend toggle would — local to this
|
|
81
|
+
// one `<Plot>` instance (not the shared HoverStore) so the gesture can't
|
|
82
|
+
// leak into unrelated tooltip/crosshair behavior.
|
|
83
|
+
let hoverIsolatedSeries = $state<string | null>(null);
|
|
84
|
+
|
|
85
|
+
function effectiveDisabledFor(name: string): LegendDisabledStyle | undefined {
|
|
86
|
+
return (
|
|
87
|
+
legendInteraction?.disabledSeries.get(name) ??
|
|
88
|
+
(hoverIsolate && hoverIsolatedSeries && hoverIsolatedSeries !== name
|
|
89
|
+
? { opacity: cfg.legend.disabledOpacity }
|
|
90
|
+
: undefined)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const xAcc = $derived(resolveAccessor(resolvedSeries[0]?.x ?? ((d: TData) => d)));
|
|
95
|
+
const yAcc = $derived(resolveAccessor(resolvedSeries[0]?.y ?? ((d: TData) => d)));
|
|
96
|
+
const flat = $derived(resolvedSeries.flatMap((s) => s.data));
|
|
97
|
+
|
|
98
|
+
// A legend-disabled series is dimmed on the mark itself, but never surfaces
|
|
99
|
+
// in hover/tooltip — its points are simply absent from the candidate list.
|
|
100
|
+
const hoverPoints = $derived(
|
|
101
|
+
resolvedSeries.flatMap((s, idx) => {
|
|
102
|
+
if (legendInteraction?.disabledSeries.has(s.name)) return [];
|
|
103
|
+
const sx = resolveAccessor(s.x);
|
|
104
|
+
const sy = resolveAccessor(s.y);
|
|
105
|
+
return s.data.map((d) => ({
|
|
106
|
+
x: sx(d) as AxisValue,
|
|
107
|
+
y: Number(sy(d)),
|
|
108
|
+
row: d,
|
|
109
|
+
label: `${sx(d)}, ${sy(d)}`,
|
|
110
|
+
series: s.name,
|
|
111
|
+
color: paletteColor(idx, cfg.palette),
|
|
112
|
+
}));
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Points scatter freely across x/y rather than sharing discrete x values —
|
|
117
|
+
// 'punctual' matches the single nearest point, not a whole column.
|
|
118
|
+
const hover = $derived({
|
|
119
|
+
active: tooltip != null || hasHoverMarker(markers),
|
|
120
|
+
points: hoverPoints,
|
|
121
|
+
label: (row: TData) => `${xAcc(row)}, ${yAcc(row)}`,
|
|
122
|
+
strategy: resolveHoverStrategy(tooltip?.strategy, markers, 'punctual'),
|
|
123
|
+
sync: resolveHoverSync(tooltip?.sync, markers),
|
|
124
|
+
tooltip,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const scatterMarkers = $derived(
|
|
128
|
+
buildScatterMarkers({
|
|
129
|
+
groupedSeries,
|
|
130
|
+
r,
|
|
131
|
+
disabledDotsFor: (name) => disabledDotsOverride(effectiveDisabledFor(name)),
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const scopedGroups = $derived(
|
|
136
|
+
buildScopedMarkerGroups<TData, Series<TData>, ScatterSegmentStyle>({
|
|
137
|
+
groupedSeries,
|
|
138
|
+
segments,
|
|
139
|
+
colorFor: (_series, seriesIndex) => paletteColor(seriesIndex, cfg.palette),
|
|
140
|
+
segmentColorFor: (style, defaultColor) => style?.dotFill ?? defaultColor,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// svelteplot treats a per-row `r` (a field key or accessor function) as a
|
|
145
|
+
// proper channel bound to its own auto sqrt scale — the raw values it
|
|
146
|
+
// returns (e.g. a population in the millions) are data, not pixels, and
|
|
147
|
+
// get compressed into a modest, roughly data-magnitude-independent pixel
|
|
148
|
+
// range regardless of how large the raw numbers are. A literal constant
|
|
149
|
+
// `r` (or the theme/segment `dotRadius` fallback), by contrast, *is*
|
|
150
|
+
// already a literal pixel value. `AUTO_SCALED_RADIUS_ESTIMATE` stands in
|
|
151
|
+
// for whatever svelteplot's own r-scale ends up rendering at, since the
|
|
152
|
+
// real value isn't knowable until that scale is built.
|
|
153
|
+
const AUTO_SCALED_RADIUS_ESTIMATE = 20;
|
|
154
|
+
|
|
155
|
+
// The largest radius any dot actually renders at — pads the scale domain
|
|
156
|
+
// below so a point at the data extreme doesn't get its own circle clipped
|
|
157
|
+
// by the frame. svelteplot's own 'auto' margin only measures axis chrome,
|
|
158
|
+
// never mark geometry.
|
|
159
|
+
const maxDotRadius = $derived.by(() => {
|
|
160
|
+
let max = 0;
|
|
161
|
+
for (const marker of scatterMarkers) {
|
|
162
|
+
const rConst = typeof marker.r === 'number' ? marker.r : undefined;
|
|
163
|
+
const isChannel = typeof marker.r === 'function';
|
|
164
|
+
const fallbackR = marker.style?.dotRadius ?? cfg.scatter.dotRadius;
|
|
165
|
+
if (rConst !== undefined) {
|
|
166
|
+
max = Math.max(max, rConst);
|
|
167
|
+
} else if (isChannel) {
|
|
168
|
+
max = Math.max(max, AUTO_SCALED_RADIUS_ESTIMATE);
|
|
169
|
+
} else {
|
|
170
|
+
max = Math.max(max, fallbackR);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return max;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Best-effort pre-render estimate of the plot's own inner content size —
|
|
177
|
+
// the real size isn't known until svelteplot measures its own axis
|
|
178
|
+
// chrome, so this is only used to convert a pixel radius into a domain
|
|
179
|
+
// padding amount, not as the actual rendered margin.
|
|
180
|
+
const estimatedMargins = $derived({ ...cfg.margins, ...margins });
|
|
181
|
+
const estimatedInnerWidth = $derived(
|
|
182
|
+
width - numericMargin('left', estimatedMargins.left) - numericMargin('right', estimatedMargins.right),
|
|
183
|
+
);
|
|
184
|
+
const estimatedInnerHeight = $derived(
|
|
185
|
+
height - numericMargin('top', estimatedMargins.top) - numericMargin('bottom', estimatedMargins.bottom),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const resolvedScales = $derived.by((): AxisBasedScalesConfig<TData> => {
|
|
189
|
+
if (maxDotRadius <= 0) return scales;
|
|
190
|
+
const xValues = flat.map((d) => Number(xAcc(d)));
|
|
191
|
+
const yValues = flat.map((d) => Number(yAcc(d)));
|
|
192
|
+
const paddedX = scales.x?.domain
|
|
193
|
+
? undefined
|
|
194
|
+
: resolveRadiusPaddedDomain(xValues, maxDotRadius, estimatedInnerWidth);
|
|
195
|
+
const paddedY = scales.y?.domain
|
|
196
|
+
? undefined
|
|
197
|
+
: resolveRadiusPaddedDomain(yValues, maxDotRadius, estimatedInnerHeight);
|
|
198
|
+
if (!paddedX && !paddedY) return scales;
|
|
199
|
+
return {
|
|
200
|
+
...scales,
|
|
201
|
+
x: paddedX ? { ...scales.x, domain: paddedX } : scales.x,
|
|
202
|
+
y: paddedY ? { ...scales.y, domain: paddedY } : scales.y,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// A scatter point's value label can grow past the plot's own content box
|
|
207
|
+
// (e.g. a point volcado toward a corner, past what its own edge-aware flip
|
|
208
|
+
// can fit) — reserving real space there is what each label's own overflow
|
|
209
|
+
// measurement feeds into here, via a tracker local to this one `<Plot>`
|
|
210
|
+
// instance. See `labelOverflow.svelte.ts`.
|
|
211
|
+
const labelMargin = createLabelMarginTracker(() => margins);
|
|
212
|
+
</script>
|
|
213
|
+
|
|
214
|
+
<BasePlotLayout
|
|
215
|
+
{width}
|
|
216
|
+
{height}
|
|
217
|
+
{styles}
|
|
218
|
+
data={flat}
|
|
219
|
+
getX={xAcc}
|
|
220
|
+
getY={(d) => Number(yAcc(d))}
|
|
221
|
+
{hover}
|
|
222
|
+
scales={resolvedScales}
|
|
223
|
+
margins={labelMargin.resolvedMargins}
|
|
224
|
+
markers={[...scatterMarkers, ...markers]}
|
|
225
|
+
seriesData={resolvedSeries}
|
|
226
|
+
{scopedGroups}
|
|
227
|
+
>
|
|
228
|
+
{#if showVals}
|
|
229
|
+
{#each groupedSeries as group (group.series.name)}
|
|
230
|
+
<ValueLabels
|
|
231
|
+
{group}
|
|
232
|
+
{formatValue}
|
|
233
|
+
values={styles.values}
|
|
234
|
+
defaultColor={cfg.axis.color}
|
|
235
|
+
disabled={effectiveDisabledFor(group.series.name)}
|
|
236
|
+
onHoverSeries={hoverIsolate ? () => { hoverIsolatedSeries = group.series.name; } : undefined}
|
|
237
|
+
onHoverEnd={hoverIsolate ? () => { hoverIsolatedSeries = null; } : undefined}
|
|
238
|
+
onOverflow={labelMargin.report}
|
|
239
|
+
/>
|
|
240
|
+
{/each}
|
|
241
|
+
{/if}
|
|
242
|
+
</BasePlotLayout>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BasePlotProps } from '../../types/plots/props';
|
|
2
|
+
import type { AxisBasedScalesConfig } from '../../types/layout/scales';
|
|
3
|
+
import type { ScatterDataProps } from '../../types/plots/data/scatter';
|
|
4
|
+
import type { BasePlotStylesConfig } from '../../types/layout/styles';
|
|
5
|
+
import type { AxisBasedMarkersConfig } from '../../types/markers/common';
|
|
2
6
|
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
3
|
-
props:
|
|
4
|
-
scales?: AxisBasedScalesConfig<TData>;
|
|
5
|
-
};
|
|
7
|
+
props: BasePlotProps<ScatterDataProps<TData>, TData, BasePlotStylesConfig, import("../..").DotStyle, AxisBasedMarkersConfig, AxisBasedScalesConfig<TData>>;
|
|
6
8
|
exports: {};
|
|
7
9
|
bindings: "";
|
|
8
10
|
slots: {};
|
|
@@ -22,6 +24,6 @@ interface $$IsomorphicComponent {
|
|
|
22
24
|
<TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
23
25
|
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
24
26
|
}
|
|
25
|
-
declare const
|
|
26
|
-
type
|
|
27
|
-
export default
|
|
27
|
+
declare const Plot: $$IsomorphicComponent;
|
|
28
|
+
type Plot<TData extends Record<string, unknown>> = InstanceType<typeof Plot<TData>>;
|
|
29
|
+
export default Plot;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
|
+
import { Text, usePlot } from 'svelteplot';
|
|
3
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
4
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
5
|
+
import { disabledFillOverride } from '../utils/legendDisabled';
|
|
6
|
+
import { watchLabelOverflow } from '../utils/labelOverflow.svelte';
|
|
7
|
+
import { resolveEdgeAwareAnchor, estimateTextWidth } from '../../utils/edgeAwareAnchor';
|
|
8
|
+
import type { Series } from '../../types/plots/data/common';
|
|
9
|
+
import type { ValuesStyle } from '../../types/layout/styles';
|
|
10
|
+
import type { ScatterSegmentStyle } from '../../types/plots/segments/common';
|
|
11
|
+
import type { VisualGroup } from '../../types/plots/segments/config';
|
|
12
|
+
import type { LegendDisabledStyle } from '../../types/layout/legend';
|
|
13
|
+
import type { MarginOverflow } from '../utils/labelOverflow.svelte';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Renders one series' point value labels — one `<Text>` mark per series
|
|
17
|
+
* (mirrors line/bar/pyramid's own `ValueLabels`), so a legend-disabled or
|
|
18
|
+
* hover-isolated series can carry its own constant `fillOpacity`. Labels
|
|
19
|
+
* the Y value, matching the "Y is the value axis" convention every other
|
|
20
|
+
* plot kind uses. Placement is edge-aware: a label whose default
|
|
21
|
+
* anchor/offset would push it past the plot's own content box flips to
|
|
22
|
+
* the opposite side instead of clipping — `onOverflow` below is a second,
|
|
23
|
+
* coarser line of defense for whatever the flip still can't fit (e.g. a
|
|
24
|
+
* corner point with no room in any direction).
|
|
25
|
+
*/
|
|
26
|
+
let {
|
|
27
|
+
group,
|
|
28
|
+
formatValue,
|
|
29
|
+
values,
|
|
30
|
+
defaultColor,
|
|
31
|
+
disabled,
|
|
32
|
+
onHoverSeries,
|
|
33
|
+
onHoverEnd,
|
|
34
|
+
onOverflow,
|
|
35
|
+
}: {
|
|
36
|
+
group: VisualGroup<Series<TData>, ScatterSegmentStyle>;
|
|
37
|
+
formatValue: (value: number, seriesName: string) => string;
|
|
38
|
+
values: ValuesStyle | undefined;
|
|
39
|
+
defaultColor: string;
|
|
40
|
+
/** Resolved legend/hover-isolate dimming style for this group's series, if any. */
|
|
41
|
+
disabled?: LegendDisabledStyle;
|
|
42
|
+
/** Fired when the pointer enters this series' label — only wired when hover-isolate is enabled. */
|
|
43
|
+
onHoverSeries?: () => void;
|
|
44
|
+
onHoverEnd?: () => void;
|
|
45
|
+
/** Reports how far this series' labels overflow the plot's own content box, in pixels, on each side (0 = no overflow). */
|
|
46
|
+
onOverflow?: (overflow: MarginOverflow) => void;
|
|
47
|
+
} = $props();
|
|
48
|
+
|
|
49
|
+
const data = $derived(group.series.data);
|
|
50
|
+
const getX = $derived(resolveAccessor(group.series.x));
|
|
51
|
+
const getY = $derived(resolveAccessor(group.series.y));
|
|
52
|
+
const plot = usePlot();
|
|
53
|
+
let groupEl: SVGGElement | undefined = $state();
|
|
54
|
+
|
|
55
|
+
watchLabelOverflow({
|
|
56
|
+
el: () => groupEl,
|
|
57
|
+
bounds: () => {
|
|
58
|
+
// getBBox() inside isn't itself tracked — depend on whatever actually
|
|
59
|
+
// changes the rendered label layout so this re-measures.
|
|
60
|
+
void data;
|
|
61
|
+
void values;
|
|
62
|
+
const xRange = plot.scales.x?.fn?.range?.()?.map(Number);
|
|
63
|
+
const yRange = plot.scales.y?.fn?.range?.()?.map(Number);
|
|
64
|
+
if (!xRange || !yRange) return undefined;
|
|
65
|
+
return {
|
|
66
|
+
left: Math.min(xRange[0], xRange[1]),
|
|
67
|
+
right: Math.max(xRange[0], xRange[1]),
|
|
68
|
+
top: Math.min(yRange[0], yRange[1]),
|
|
69
|
+
bottom: Math.max(yRange[0], yRange[1]),
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
onOverflow: (overflow) => onOverflow?.(overflow),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const font = $derived(values?.fontStyle);
|
|
76
|
+
const preferredTextAnchor = $derived(
|
|
77
|
+
font?.textAnchor === 'outside' || font?.textAnchor === undefined ? 'start' : font.textAnchor,
|
|
78
|
+
);
|
|
79
|
+
const preferredLineAnchor = $derived(font?.lineAnchor ?? 'middle');
|
|
80
|
+
const gapX = $derived(Math.abs(font?.dx ?? 8));
|
|
81
|
+
const gapY = $derived(Math.abs(font?.dy ?? 0));
|
|
82
|
+
const fontSize = $derived(typeof font?.fontSize === 'number' ? font.fontSize : 11);
|
|
83
|
+
const fillOverride = $derived(disabledFillOverride(disabled));
|
|
84
|
+
|
|
85
|
+
function textOf(d: TData): string {
|
|
86
|
+
return formatValue(Number(getY(d)), group.series.name);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** One placement per row, keyed by row reference — avoids recomputing the same scale/edge lookup for each of the four accessor channels below. */
|
|
90
|
+
const placements = $derived.by(() => {
|
|
91
|
+
const map = new SvelteMap<TData, ReturnType<typeof resolveEdgeAwareAnchor>>();
|
|
92
|
+
const xFn = plot.scales.x?.fn;
|
|
93
|
+
const yFn = plot.scales.y?.fn;
|
|
94
|
+
if (!xFn || !yFn) return map;
|
|
95
|
+
for (const d of data) {
|
|
96
|
+
const px = Number(xFn(getX(d) as never));
|
|
97
|
+
const py = Number(yFn(getY(d) as never));
|
|
98
|
+
const text = textOf(d);
|
|
99
|
+
map.set(
|
|
100
|
+
d,
|
|
101
|
+
resolveEdgeAwareAnchor({
|
|
102
|
+
px,
|
|
103
|
+
py,
|
|
104
|
+
width: plot.plotWidth,
|
|
105
|
+
height: plot.plotHeight,
|
|
106
|
+
textAnchor: preferredTextAnchor,
|
|
107
|
+
lineAnchor: preferredLineAnchor,
|
|
108
|
+
gapX,
|
|
109
|
+
gapY,
|
|
110
|
+
// Rough average glyph width — these labels are short numeric/formatted strings, not measured DOM text.
|
|
111
|
+
extent: { width: estimateTextWidth(text, fontSize), height: fontSize * 1.2 },
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return map;
|
|
116
|
+
});
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<!-- svelteplot's Text mark never wires pointer handlers to its DOM node,
|
|
120
|
+
despite declaring them in its types — a wrapping <g> catches the
|
|
121
|
+
boundary crossing instead (pointerenter/leave fire on every ancestor). -->
|
|
122
|
+
<g bind:this={groupEl} role="presentation" onpointerenter={onHoverSeries} onpointerleave={onHoverEnd}>
|
|
123
|
+
<Text
|
|
124
|
+
{data}
|
|
125
|
+
x={getX}
|
|
126
|
+
y={getY}
|
|
127
|
+
text={(d: TData) => textOf(d)}
|
|
128
|
+
fill={font?.fill ?? defaultColor}
|
|
129
|
+
fillOpacity={fillOverride?.fillOpacity}
|
|
130
|
+
{fontSize}
|
|
131
|
+
fontWeight={font?.fontWeight ?? 500}
|
|
132
|
+
fontStyle={font?.fontStyle}
|
|
133
|
+
textAnchor={(d: TData) => placements.get(d)?.textAnchor ?? preferredTextAnchor}
|
|
134
|
+
lineAnchor={
|
|
135
|
+
// svelteplot's own `lineAnchor` accessor type isn't parameterized over the mark's row type like its siblings are.
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
|
+
(d: any) => placements.get(d)?.lineAnchor ?? preferredLineAnchor
|
|
138
|
+
}
|
|
139
|
+
dx={(d: TData) => placements.get(d)?.dx ?? gapX}
|
|
140
|
+
dy={(d: TData) => placements.get(d)?.dy ?? gapY}
|
|
141
|
+
lineHeight={font?.lineHeight}
|
|
142
|
+
rotate={font?.rotate}
|
|
143
|
+
stroke={font?.stroke}
|
|
144
|
+
strokeWidth={font?.strokeWidth}
|
|
145
|
+
paintOrder={font?.paintOrder}
|
|
146
|
+
class={font?.class}
|
|
147
|
+
textClass={font?.textClass}
|
|
148
|
+
/>
|
|
149
|
+
</g>
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import { BarX, BarY } from 'svelteplot';
|
|
2
1
|
import type { Series } from '../../types/plots/data/common';
|
|
3
|
-
import type {
|
|
2
|
+
import type { ValuesStyle } from '../../types/layout/styles';
|
|
3
|
+
import type { ScatterSegmentStyle } from '../../types/plots/segments/common';
|
|
4
4
|
import type { VisualGroup } from '../../types/plots/segments/config';
|
|
5
|
-
import type {
|
|
5
|
+
import type { LegendDisabledStyle } from '../../types/layout/legend';
|
|
6
|
+
import type { MarginOverflow } from '../utils/labelOverflow.svelte';
|
|
6
7
|
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
7
8
|
props: {
|
|
8
|
-
group: VisualGroup<Series<TData>,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
isHorizontal: boolean;
|
|
12
|
-
BarMark: typeof BarX | typeof BarY;
|
|
9
|
+
group: VisualGroup<Series<TData>, ScatterSegmentStyle>;
|
|
10
|
+
formatValue: (value: number, seriesName: string) => string;
|
|
11
|
+
values: ValuesStyle | undefined;
|
|
13
12
|
defaultColor: string;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
/** Resolved legend/hover-isolate dimming style for this group's series, if any. */
|
|
14
|
+
disabled?: LegendDisabledStyle;
|
|
15
|
+
/** Fired when the pointer enters this series' label — only wired when hover-isolate is enabled. */
|
|
16
|
+
onHoverSeries?: () => void;
|
|
17
|
+
onHoverEnd?: () => void;
|
|
18
|
+
/** Reports how far this series' labels overflow the plot's own content box, in pixels, on each side (0 = no overflow). */
|
|
19
|
+
onOverflow?: (overflow: MarginOverflow) => void;
|
|
18
20
|
};
|
|
19
21
|
exports: {};
|
|
20
22
|
bindings: "";
|
|
@@ -35,6 +37,6 @@ interface $$IsomorphicComponent {
|
|
|
35
37
|
<TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
36
38
|
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
37
39
|
}
|
|
38
|
-
declare const
|
|
39
|
-
type
|
|
40
|
-
export default
|
|
40
|
+
declare const ValueLabels: $$IsomorphicComponent;
|
|
41
|
+
type ValueLabels<TData extends Record<string, unknown>> = InstanceType<typeof ValueLabels<TData>>;
|
|
42
|
+
export default ValueLabels;
|