@fundar/data-chart-telling 0.0.1
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/README.md +250 -0
- package/dist/charts/Chart.svelte +125 -0
- package/dist/charts/Chart.svelte.d.ts +42 -0
- package/dist/charts/bar/Chart.svelte +104 -0
- package/dist/charts/bar/Chart.svelte.d.ts +27 -0
- package/dist/charts/container/ChartContainer.svelte +127 -0
- package/dist/charts/container/ChartContainer.svelte.d.ts +14 -0
- package/dist/charts/heatmap/Chart.svelte +107 -0
- package/dist/charts/heatmap/Chart.svelte.d.ts +30 -0
- package/dist/charts/line/Chart.svelte +106 -0
- package/dist/charts/line/Chart.svelte.d.ts +27 -0
- package/dist/charts/pyramid/Chart.svelte +127 -0
- package/dist/charts/pyramid/Chart.svelte.d.ts +31 -0
- package/dist/configuration/config.svelte.d.ts +12 -0
- package/dist/configuration/config.svelte.js +159 -0
- package/dist/configuration/themes/compact.d.ts +2 -0
- package/dist/configuration/themes/compact.js +9 -0
- package/dist/configuration/themes/dark.d.ts +2 -0
- package/dist/configuration/themes/dark.js +17 -0
- package/dist/configuration/themes/default.d.ts +3 -0
- package/dist/configuration/themes/default.js +2 -0
- package/dist/configuration/themes/editorial.d.ts +2 -0
- package/dist/configuration/themes/editorial.js +14 -0
- package/dist/configuration/themes/index.d.ts +481 -0
- package/dist/configuration/themes/index.js +16 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +27 -0
- package/dist/layout/facet/FacetLayout.svelte +97 -0
- package/dist/layout/facet/FacetLayout.svelte.d.ts +35 -0
- package/dist/layout/legend/ContinuousSection.svelte +95 -0
- package/dist/layout/legend/ContinuousSection.svelte.d.ts +7 -0
- package/dist/layout/legend/DiscreteSection.svelte +87 -0
- package/dist/layout/legend/DiscreteSection.svelte.d.ts +7 -0
- package/dist/layout/legend/LegendLayout.svelte +47 -0
- package/dist/layout/legend/LegendLayout.svelte.d.ts +7 -0
- package/dist/layout/plot/PlotLayout.svelte +236 -0
- package/dist/layout/plot/PlotLayout.svelte.d.ts +70 -0
- package/dist/layout/timeline/Timeline.svelte +86 -0
- package/dist/layout/timeline/Timeline.svelte.d.ts +11 -0
- package/dist/layout/timeline/TimelineLayout.svelte +181 -0
- package/dist/layout/timeline/TimelineLayout.svelte.d.ts +38 -0
- package/dist/layout/tooltip/Tooltip.svelte +55 -0
- package/dist/layout/tooltip/Tooltip.svelte.d.ts +12 -0
- package/dist/layout/tooltip/controller.svelte.d.ts +52 -0
- package/dist/layout/tooltip/controller.svelte.js +128 -0
- package/dist/layout/tooltip/hover.svelte.d.ts +7 -0
- package/dist/layout/tooltip/hover.svelte.js +28 -0
- package/dist/layout/tooltip/utils.d.ts +41 -0
- package/dist/layout/tooltip/utils.js +78 -0
- package/dist/plots/bar/Plot.svelte +176 -0
- package/dist/plots/bar/Plot.svelte.d.ts +26 -0
- package/dist/plots/heatmap/Plot.svelte +175 -0
- package/dist/plots/heatmap/Plot.svelte.d.ts +26 -0
- package/dist/plots/line/Plot.svelte +232 -0
- package/dist/plots/line/Plot.svelte.d.ts +26 -0
- package/dist/plots/markers/DeltaMarker.svelte +104 -0
- package/dist/plots/markers/DeltaMarker.svelte.d.ts +30 -0
- package/dist/plots/markers/DotMarker.svelte +41 -0
- package/dist/plots/markers/DotMarker.svelte.d.ts +31 -0
- package/dist/plots/markers/HoverMarker.svelte +78 -0
- package/dist/plots/markers/HoverMarker.svelte.d.ts +22 -0
- package/dist/plots/markers/PeakMarker.svelte +80 -0
- package/dist/plots/markers/PeakMarker.svelte.d.ts +13 -0
- package/dist/plots/markers/TextMarker.svelte +55 -0
- package/dist/plots/markers/TextMarker.svelte.d.ts +32 -0
- package/dist/plots/pyramid/Plot.svelte +282 -0
- package/dist/plots/pyramid/Plot.svelte.d.ts +26 -0
- package/dist/plots/utils/accessors.d.ts +6 -0
- package/dist/plots/utils/accessors.js +10 -0
- package/dist/plots/utils/delta.d.ts +12 -0
- package/dist/plots/utils/delta.js +69 -0
- package/dist/plots/utils/segments.d.ts +33 -0
- package/dist/plots/utils/segments.js +153 -0
- package/dist/stores/device.d.ts +7 -0
- package/dist/stores/device.js +39 -0
- package/dist/types/charts/common.d.ts +64 -0
- package/dist/types/charts/common.js +1 -0
- package/dist/types/charts/interpolate.d.ts +2 -0
- package/dist/types/charts/interpolate.js +1 -0
- package/dist/types/charts/legend.d.ts +29 -0
- package/dist/types/charts/legend.js +1 -0
- package/dist/types/charts/props.d.ts +35 -0
- package/dist/types/charts/props.js +1 -0
- package/dist/types/configuration/styling.d.ts +97 -0
- package/dist/types/configuration/styling.js +1 -0
- package/dist/types/layout/tooltip.d.ts +88 -0
- package/dist/types/layout/tooltip.js +1 -0
- package/dist/types/plots/common.d.ts +100 -0
- package/dist/types/plots/common.js +1 -0
- package/dist/types/plots/delta.d.ts +19 -0
- package/dist/types/plots/delta.js +1 -0
- package/dist/types/plots/markers.d.ts +56 -0
- package/dist/types/plots/markers.js +1 -0
- package/dist/types/plots/props.d.ts +23 -0
- package/dist/types/plots/props.js +1 -0
- package/dist/types/plots/styles.d.ts +176 -0
- package/dist/types/plots/styles.js +1 -0
- package/dist/types/viewport.d.ts +4 -0
- package/dist/types/viewport.js +1 -0
- package/dist/utils/color.d.ts +5 -0
- package/dist/utils/color.js +42 -0
- package/dist/utils/grouping.d.ts +14 -0
- package/dist/utils/grouping.js +36 -0
- package/dist/utils/interpolate.d.ts +30 -0
- package/dist/utils/interpolate.js +71 -0
- package/package.json +76 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script
|
|
2
|
+
lang="ts"
|
|
3
|
+
generics="TData extends Record<string, unknown>"
|
|
4
|
+
>
|
|
5
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
6
|
+
import { paletteColor } from '../../utils/color';
|
|
7
|
+
import { buildSeries } from '../../utils/grouping';
|
|
8
|
+
import { BarY, Text } from 'svelteplot';
|
|
9
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
10
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
11
|
+
import { buildGroupedSeries, validateSegments, resolveSegmentsForSeries, matchesSegmentX } from '../utils/segments';
|
|
12
|
+
import PlotLayout from '../../layout/plot/PlotLayout.svelte';
|
|
13
|
+
import HoverMarker from '../markers/HoverMarker.svelte';
|
|
14
|
+
import type { Marker } from '../../types/plots/markers';
|
|
15
|
+
import type { PlotProps } from '../../types/plots/props';
|
|
16
|
+
import type { Series, AxisValue } from '../../types/plots/common';
|
|
17
|
+
import type { BarSegmentStyle, ValueAnchor } from '../../types/plots/styles';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A vertical bar chart. Accepts either a pre-built `series` array or flat
|
|
21
|
+
* `data/x/y/z` props — mirrors the line/heatmap plot contract so the
|
|
22
|
+
* chart-level container/timeline/facet/legend wrap it unchanged.
|
|
23
|
+
*/
|
|
24
|
+
let {
|
|
25
|
+
width,
|
|
26
|
+
height,
|
|
27
|
+
series,
|
|
28
|
+
data,
|
|
29
|
+
x,
|
|
30
|
+
y,
|
|
31
|
+
z,
|
|
32
|
+
styles = {},
|
|
33
|
+
segments = {},
|
|
34
|
+
markers = [],
|
|
35
|
+
scales = {},
|
|
36
|
+
margins,
|
|
37
|
+
tooltip = undefined,
|
|
38
|
+
}: PlotProps<TData, BarSegmentStyle> = $props();
|
|
39
|
+
|
|
40
|
+
$effect(() => { validateSegments(segments); });
|
|
41
|
+
|
|
42
|
+
const cfg = $derived(getConfiguration());
|
|
43
|
+
const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, z));
|
|
44
|
+
const groupedSeries = $derived(
|
|
45
|
+
buildGroupedSeries<Series<TData>, BarSegmentStyle>(resolvedSeries, segments),
|
|
46
|
+
);
|
|
47
|
+
const showVals = $derived(styles.values?.show ?? false);
|
|
48
|
+
const fmtVal = $derived(styles.values?.format ?? ((v: number) => `${v}`));
|
|
49
|
+
const valAnchor = $derived<ValueAnchor>(styles.values?.anchor ?? 'outside');
|
|
50
|
+
|
|
51
|
+
const seriesMarkers = $derived.by(() => {
|
|
52
|
+
const map = new SvelteMap<string, Marker[]>();
|
|
53
|
+
for (const marker of markers) {
|
|
54
|
+
if (marker.type === 'hover' && marker.series) {
|
|
55
|
+
if (!map.has(marker.series)) map.set(marker.series, []);
|
|
56
|
+
map.get(marker.series)!.push(marker);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return map;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const xAcc = $derived(resolveAccessor(resolvedSeries[0]?.x ?? ((d: TData) => d)));
|
|
63
|
+
const yAcc = $derived(resolveAccessor(resolvedSeries[0]?.y ?? ((d: TData) => d)));
|
|
64
|
+
const flat = $derived(resolvedSeries.flatMap((s) => s.data));
|
|
65
|
+
const hoverPoints = $derived(
|
|
66
|
+
resolvedSeries.flatMap((s) => {
|
|
67
|
+
const sx = resolveAccessor(s.x);
|
|
68
|
+
const sy = resolveAccessor(s.y);
|
|
69
|
+
return s.data.map((d) => ({
|
|
70
|
+
x: sx(d) as AxisValue,
|
|
71
|
+
y: Number(sy(d)),
|
|
72
|
+
row: d,
|
|
73
|
+
label: String(sy(d)),
|
|
74
|
+
series: s.name,
|
|
75
|
+
}));
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<PlotLayout
|
|
81
|
+
{width}
|
|
82
|
+
{height}
|
|
83
|
+
x={scales.x}
|
|
84
|
+
y={scales.y}
|
|
85
|
+
data={flat}
|
|
86
|
+
getX={xAcc}
|
|
87
|
+
getY={(d) => Number(yAcc(d))}
|
|
88
|
+
{hoverPoints}
|
|
89
|
+
label={(r) => String(yAcc(r))}
|
|
90
|
+
{tooltip}
|
|
91
|
+
{markers}
|
|
92
|
+
seriesData={resolvedSeries}
|
|
93
|
+
{scales}
|
|
94
|
+
{margins}
|
|
95
|
+
defaultStrategy="x"
|
|
96
|
+
>
|
|
97
|
+
{#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY })}
|
|
98
|
+
{#each groupedSeries as group, seriesIndex (group.series.name)}
|
|
99
|
+
{@const defaultColor = paletteColor(seriesIndex, cfg.palette)}
|
|
100
|
+
{@const xFn = resolveAccessor(group.series.x)}
|
|
101
|
+
{@const yFn = resolveAccessor(group.series.y)}
|
|
102
|
+
{#each group.visualSegments as seg, i (`${group.series.name}-${i}`)}
|
|
103
|
+
<BarY
|
|
104
|
+
data={seg.data}
|
|
105
|
+
x={xFn}
|
|
106
|
+
y={yFn}
|
|
107
|
+
fill={seg.style.fill ?? defaultColor}
|
|
108
|
+
fillOpacity={seg.style.fillOpacity ?? 1}
|
|
109
|
+
/>
|
|
110
|
+
{/each}
|
|
111
|
+
|
|
112
|
+
{#if showVals}
|
|
113
|
+
{@const yTextFn = valAnchor === 'middle'
|
|
114
|
+
? (d: TData) => Number(yFn(d)) / 2
|
|
115
|
+
: valAnchor === 'end'
|
|
116
|
+
? () => 0
|
|
117
|
+
: (d: TData) => Number(yFn(d))}
|
|
118
|
+
{@const defaultDy = valAnchor === 'outside' ? -6 : valAnchor === 'start' ? 4 : valAnchor === 'end' ? -4 : 0}
|
|
119
|
+
{@const defaultLA = valAnchor === 'outside' || valAnchor === 'end'
|
|
120
|
+
? ('bottom' as const)
|
|
121
|
+
: valAnchor === 'start'
|
|
122
|
+
? ('top' as const)
|
|
123
|
+
: ('middle' as const)}
|
|
124
|
+
<Text
|
|
125
|
+
data={group.series.data}
|
|
126
|
+
x={xFn}
|
|
127
|
+
y={yTextFn}
|
|
128
|
+
text={(d: TData) => fmtVal(Number(yFn(d)), group.series.name)}
|
|
129
|
+
dx={styles.values?.dx ?? 0}
|
|
130
|
+
dy={styles.values?.dy ?? defaultDy}
|
|
131
|
+
textAnchor={styles.values?.textAnchor ?? 'middle'}
|
|
132
|
+
lineAnchor={styles.values?.lineAnchor ?? defaultLA}
|
|
133
|
+
lineHeight={styles.values?.lineHeight}
|
|
134
|
+
rotate={styles.values?.rotate}
|
|
135
|
+
class={styles.values?.class}
|
|
136
|
+
textClass={styles.values?.textClass}
|
|
137
|
+
fill={cfg.axis.color}
|
|
138
|
+
fontSize={12}
|
|
139
|
+
/>
|
|
140
|
+
{/if}
|
|
141
|
+
|
|
142
|
+
{#each seriesMarkers.get(group.series.name) ?? [] as marker, i (`series-${group.series.name}-hover-${i}`)}
|
|
143
|
+
{#if marker.type === 'hover'}
|
|
144
|
+
{@const seriesPoints = matchedPoints.filter((p) => p.series === group.series.name)}
|
|
145
|
+
{#if marker.scope === 'segment'}
|
|
146
|
+
{@const segs = resolveSegmentsForSeries(segments, group.series.name)}
|
|
147
|
+
{#each segs as seg, segIdx (`${group.series.name}-hover-segment-${segIdx}`)}
|
|
148
|
+
{@const segPoints = seriesPoints.filter((p) => matchesSegmentX(p.x, seg))}
|
|
149
|
+
{#if segPoints.length > 0}
|
|
150
|
+
<HoverMarker
|
|
151
|
+
data={segPoints}
|
|
152
|
+
ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
|
|
153
|
+
ruleY={marker.ruleY !== false && (impliesRuleY || marker.ruleY === true) ? ruleY : null}
|
|
154
|
+
showLabels={marker.showLabels}
|
|
155
|
+
ruleStyle={marker.ruleStyle}
|
|
156
|
+
dotStyle={marker.dotStyle}
|
|
157
|
+
fontStyle={marker.fontStyle}
|
|
158
|
+
/>
|
|
159
|
+
{/if}
|
|
160
|
+
{/each}
|
|
161
|
+
{:else}
|
|
162
|
+
<HoverMarker
|
|
163
|
+
data={seriesPoints}
|
|
164
|
+
ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
|
|
165
|
+
ruleY={marker.ruleY !== false && (impliesRuleY || marker.ruleY === true) ? ruleY : null}
|
|
166
|
+
showLabels={marker.showLabels}
|
|
167
|
+
ruleStyle={marker.ruleStyle}
|
|
168
|
+
dotStyle={marker.dotStyle}
|
|
169
|
+
fontStyle={marker.fontStyle}
|
|
170
|
+
/>
|
|
171
|
+
{/if}
|
|
172
|
+
{/if}
|
|
173
|
+
{/each}
|
|
174
|
+
{/each}
|
|
175
|
+
{/snippet}
|
|
176
|
+
</PlotLayout>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PlotProps } from '../../types/plots/props';
|
|
2
|
+
import type { BarSegmentStyle } from '../../types/plots/styles';
|
|
3
|
+
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
4
|
+
props: PlotProps<TData, BarSegmentStyle>;
|
|
5
|
+
exports: {};
|
|
6
|
+
bindings: "";
|
|
7
|
+
slots: {};
|
|
8
|
+
events: {};
|
|
9
|
+
};
|
|
10
|
+
declare class __sveltets_Render<TData extends Record<string, unknown>> {
|
|
11
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
12
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
13
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
14
|
+
bindings(): "";
|
|
15
|
+
exports(): {};
|
|
16
|
+
}
|
|
17
|
+
interface $$IsomorphicComponent {
|
|
18
|
+
new <TData extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
19
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
20
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
21
|
+
<TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
22
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
23
|
+
}
|
|
24
|
+
declare const Plot: $$IsomorphicComponent;
|
|
25
|
+
type Plot<TData extends Record<string, unknown>> = InstanceType<typeof Plot<TData>>;
|
|
26
|
+
export default Plot;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
|
+
import { Cell, Text } from 'svelteplot';
|
|
3
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
4
|
+
import { buildSeries } from '../../utils/grouping';
|
|
5
|
+
import { makeColorScale } from '../../utils/color';
|
|
6
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
7
|
+
import PlotLayout from '../../layout/plot/PlotLayout.svelte';
|
|
8
|
+
import type { PlotProps } from '../../types/plots/props';
|
|
9
|
+
import type { AxisValue } from '../../types/plots/common';
|
|
10
|
+
import type { CellSegmentStyle } from '../../types/plots/styles';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A categorical grid of value-coloured cells. Accepts either a pre-built
|
|
14
|
+
* `series` array (each {@link Series} may carry `z` as the colour accessor) or
|
|
15
|
+
* flat `data/x/y/z` props — mirrors the line/bar plot contract so the
|
|
16
|
+
* chart-level container/timeline/facet/legend wrap it unchanged.
|
|
17
|
+
*
|
|
18
|
+
* The colour domain is read from `scales.z.domain`; when absent it is derived
|
|
19
|
+
* from the visible slice of data.
|
|
20
|
+
*/
|
|
21
|
+
let {
|
|
22
|
+
width,
|
|
23
|
+
height,
|
|
24
|
+
series,
|
|
25
|
+
data,
|
|
26
|
+
x,
|
|
27
|
+
y,
|
|
28
|
+
z,
|
|
29
|
+
styles = {},
|
|
30
|
+
segments = [],
|
|
31
|
+
scales = {},
|
|
32
|
+
margins,
|
|
33
|
+
markers = [],
|
|
34
|
+
tooltip = undefined,
|
|
35
|
+
}: PlotProps<TData, CellSegmentStyle> = $props();
|
|
36
|
+
|
|
37
|
+
$effect(() => {
|
|
38
|
+
const scoped = markers.filter(
|
|
39
|
+
(m) => (m.type === 'hover' || m.type === 'component') && m.series,
|
|
40
|
+
);
|
|
41
|
+
if (scoped.length > 0) {
|
|
42
|
+
console.warn(
|
|
43
|
+
'HeatmapPlot: markers scoped to a `series` are ignored — heatmaps have no series concept.',
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (!Array.isArray(segments) && Object.keys(segments).length > 0) {
|
|
47
|
+
console.warn(
|
|
48
|
+
'HeatmapPlot: `segments` was passed as a record (SeriesAwareSegmentsMap) but heatmaps have no named series — use the array form (Segment[]) instead.',
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const cfg = $derived(getConfiguration());
|
|
54
|
+
const minColor = $derived(styles.colors?.min ?? cfg.continuous.min);
|
|
55
|
+
const maxColor = $derived(styles.colors?.max ?? cfg.continuous.max);
|
|
56
|
+
const showVals = $derived(styles.values?.show ?? true);
|
|
57
|
+
const fmtVal = $derived(styles.values?.format ?? ((v: number) => `${v}`));
|
|
58
|
+
|
|
59
|
+
const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, undefined));
|
|
60
|
+
const flat = $derived(resolvedSeries.flatMap((s) => s.data));
|
|
61
|
+
|
|
62
|
+
const getX = $derived(resolveAccessor<TData>(resolvedSeries[0]?.x ?? x!));
|
|
63
|
+
const getY = $derived(resolveAccessor<TData>(resolvedSeries[0]?.y ?? y!));
|
|
64
|
+
const getZ = $derived(resolveAccessor<TData>(resolvedSeries[0]?.z ?? z!));
|
|
65
|
+
|
|
66
|
+
const hoverPoints = $derived(
|
|
67
|
+
flat.map((d) => ({
|
|
68
|
+
x: getX(d) as AxisValue,
|
|
69
|
+
y: getY(d) as AxisValue,
|
|
70
|
+
row: d,
|
|
71
|
+
label: fmtVal(Number(getZ(d)), ''),
|
|
72
|
+
})),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const xDomain = $derived([...new Set(flat.map((d) => getX(d) as AxisValue))]);
|
|
76
|
+
const yDomain = $derived([...new Set(flat.map((d) => getY(d) as AxisValue))]);
|
|
77
|
+
|
|
78
|
+
const valueDomain = $derived.by((): [number, number] => {
|
|
79
|
+
if (scales.z?.domain && scales.z.domain.length >= 2) {
|
|
80
|
+
return [Number(scales.z.domain[0]), Number(scales.z.domain[1])];
|
|
81
|
+
}
|
|
82
|
+
const vals = flat.map((d) => Number(getZ(d)));
|
|
83
|
+
return vals.length ? [Math.min(...vals, 0), Math.max(...vals)] : [0, 1];
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const colorScale = $derived(
|
|
87
|
+
makeColorScale(valueDomain[0], valueDomain[1], minColor, maxColor),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const segList = $derived(Array.isArray(segments) ? segments : []);
|
|
91
|
+
|
|
92
|
+
const segStyleMap = $derived.by(() => {
|
|
93
|
+
const map = new Map<string, CellSegmentStyle>();
|
|
94
|
+
if (!segList.length) return map;
|
|
95
|
+
for (const d of flat) {
|
|
96
|
+
const cx = getX(d) as AxisValue;
|
|
97
|
+
const cy = getY(d) as AxisValue;
|
|
98
|
+
const key = `${String(cx)}::${String(cy)}`;
|
|
99
|
+
if (!map.has(key)) {
|
|
100
|
+
let style: CellSegmentStyle = {};
|
|
101
|
+
for (const seg of segList) {
|
|
102
|
+
const areas = seg.areas;
|
|
103
|
+
const matches =
|
|
104
|
+
!areas || areas.length === 0
|
|
105
|
+
? true
|
|
106
|
+
: areas.some((area) => {
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
+
const xOk = !area.x || ((area.x.from === undefined || (cx as any) >= area.x.from) && (area.x.to === undefined || (cx as any) <= area.x.to));
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
110
|
+
const yOk = !area.y || ((area.y.from === undefined || (cy as any) >= area.y.from) && (area.y.to === undefined || (cy as any) <= area.y.to));
|
|
111
|
+
return xOk && yOk;
|
|
112
|
+
});
|
|
113
|
+
if (matches) style = { ...style, ...seg.style };
|
|
114
|
+
}
|
|
115
|
+
map.set(key, style);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return map;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const getCellStyle = $derived(
|
|
122
|
+
(d: TData) => segStyleMap.get(`${String(getX(d))}::${String(getY(d))}`) ?? {},
|
|
123
|
+
);
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<PlotLayout
|
|
127
|
+
{width}
|
|
128
|
+
{height}
|
|
129
|
+
x={{ type: 'band', labelAnchor: 'right', domain: xDomain, ...(scales.x ?? {}) }}
|
|
130
|
+
y={{ type: 'band', domain: yDomain, ...(scales.y ?? {}) }}
|
|
131
|
+
data={flat}
|
|
132
|
+
{getX}
|
|
133
|
+
{getY}
|
|
134
|
+
matchY={getY}
|
|
135
|
+
{hoverPoints}
|
|
136
|
+
label={(d) => fmtVal(Number(getZ(d)), '')}
|
|
137
|
+
{tooltip}
|
|
138
|
+
{markers}
|
|
139
|
+
seriesData={resolvedSeries}
|
|
140
|
+
scales={scales}
|
|
141
|
+
{margins}
|
|
142
|
+
showHoverLabels={false}
|
|
143
|
+
defaultStrategy="punctual"
|
|
144
|
+
>
|
|
145
|
+
<Cell
|
|
146
|
+
data={flat as unknown as Record<string | symbol, never>[]}
|
|
147
|
+
x={getX}
|
|
148
|
+
y={getY}
|
|
149
|
+
fill={(d) => getCellStyle(d as TData).fill ?? colorScale(Number(getZ(d as TData)))}
|
|
150
|
+
fillOpacity={(d) => getCellStyle(d as TData).fillOpacity ?? 1}
|
|
151
|
+
stroke={(d) => getCellStyle(d as TData).stroke}
|
|
152
|
+
strokeWidth={(d) => getCellStyle(d as TData).strokeWidth ?? 0}
|
|
153
|
+
inset={0.5}
|
|
154
|
+
/>
|
|
155
|
+
|
|
156
|
+
{#if showVals}
|
|
157
|
+
<Text
|
|
158
|
+
data={flat}
|
|
159
|
+
x={getX}
|
|
160
|
+
y={getY}
|
|
161
|
+
text={(d) => fmtVal(Number(getZ(d)), '')}
|
|
162
|
+
fill={cfg.continuous.labelColor}
|
|
163
|
+
fontSize={12}
|
|
164
|
+
fontWeight={600}
|
|
165
|
+
textAnchor={styles.values?.textAnchor ?? 'middle'}
|
|
166
|
+
lineAnchor={styles.values?.lineAnchor ?? 'middle'}
|
|
167
|
+
dx={styles.values?.dx ?? 0}
|
|
168
|
+
dy={styles.values?.dy ?? 0}
|
|
169
|
+
lineHeight={styles.values?.lineHeight}
|
|
170
|
+
rotate={styles.values?.rotate}
|
|
171
|
+
class={styles.values?.class}
|
|
172
|
+
textClass={styles.values?.textClass}
|
|
173
|
+
/>
|
|
174
|
+
{/if}
|
|
175
|
+
</PlotLayout>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PlotProps } from '../../types/plots/props';
|
|
2
|
+
import type { CellSegmentStyle } from '../../types/plots/styles';
|
|
3
|
+
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
4
|
+
props: PlotProps<TData, CellSegmentStyle>;
|
|
5
|
+
exports: {};
|
|
6
|
+
bindings: "";
|
|
7
|
+
slots: {};
|
|
8
|
+
events: {};
|
|
9
|
+
};
|
|
10
|
+
declare class __sveltets_Render<TData extends Record<string, unknown>> {
|
|
11
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
12
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
13
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
14
|
+
bindings(): "";
|
|
15
|
+
exports(): {};
|
|
16
|
+
}
|
|
17
|
+
interface $$IsomorphicComponent {
|
|
18
|
+
new <TData extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
19
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
20
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
21
|
+
<TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
22
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
23
|
+
}
|
|
24
|
+
declare const Plot: $$IsomorphicComponent;
|
|
25
|
+
type Plot<TData extends Record<string, unknown>> = InstanceType<typeof Plot<TData>>;
|
|
26
|
+
export default Plot;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
<script
|
|
2
|
+
lang="ts"
|
|
3
|
+
generics="TData extends Record<string, unknown>"
|
|
4
|
+
>
|
|
5
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
6
|
+
import { Line, Dot, Text } from 'svelteplot';
|
|
7
|
+
import { resolveAccessor } from '../utils/accessors';
|
|
8
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
9
|
+
import { paletteColor } from '../../utils/color';
|
|
10
|
+
import { buildSeries } from '../../utils/grouping';
|
|
11
|
+
import PlotLayout from '../../layout/plot/PlotLayout.svelte';
|
|
12
|
+
import HoverMarker from '../markers/HoverMarker.svelte';
|
|
13
|
+
import { buildGroupedSeries, validateSegments, resolveSegmentsForSeries, matchesSegmentX, getDataForSegments } from '../utils/segments';
|
|
14
|
+
import type { PlotProps } from '../../types/plots/props';
|
|
15
|
+
import type { Series, AxisValue } from '../../types/plots/common';
|
|
16
|
+
import type { LineSegmentStyle, ValueAnchor } from '../../types/plots/styles';
|
|
17
|
+
import type { Marker } from '../../types/plots/markers';
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
width,
|
|
21
|
+
height,
|
|
22
|
+
series,
|
|
23
|
+
data,
|
|
24
|
+
x,
|
|
25
|
+
y,
|
|
26
|
+
z,
|
|
27
|
+
styles = {},
|
|
28
|
+
segments = {},
|
|
29
|
+
markers = [],
|
|
30
|
+
scales = {},
|
|
31
|
+
margins,
|
|
32
|
+
tooltip = undefined,
|
|
33
|
+
}: PlotProps<TData, LineSegmentStyle> = $props();
|
|
34
|
+
|
|
35
|
+
const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, z));
|
|
36
|
+
|
|
37
|
+
$effect(() => { validateSegments(segments); });
|
|
38
|
+
|
|
39
|
+
const groupedSeries = $derived(
|
|
40
|
+
buildGroupedSeries<Series<TData>, LineSegmentStyle>(resolvedSeries, segments),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const seriesMarkers = $derived.by(() => {
|
|
44
|
+
const map = new SvelteMap<string, Marker[]>();
|
|
45
|
+
for (const marker of markers) {
|
|
46
|
+
if ((marker.type === 'component' || marker.type === 'hover') && marker.series) {
|
|
47
|
+
if (!map.has(marker.series)) map.set(marker.series, []);
|
|
48
|
+
map.get(marker.series)!.push(marker);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return map;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const cfg = $derived(getConfiguration());
|
|
55
|
+
const showVals = $derived(styles.values?.show ?? false);
|
|
56
|
+
const fmtVal = $derived(
|
|
57
|
+
styles.values?.format ?? ((v: number, name: string) => `${name} - ${v}`),
|
|
58
|
+
);
|
|
59
|
+
const valAnchor = $derived<ValueAnchor>(styles.values?.anchor ?? 'start');
|
|
60
|
+
|
|
61
|
+
const xAcc = $derived(resolveAccessor(resolvedSeries[0]?.x ?? ((d: TData) => d)));
|
|
62
|
+
const yAcc = $derived(resolveAccessor(resolvedSeries[0]?.y ?? ((d: TData) => d)));
|
|
63
|
+
const flat = $derived(resolvedSeries.flatMap((s) => s.data));
|
|
64
|
+
const hoverPoints = $derived(
|
|
65
|
+
resolvedSeries.flatMap((s) => {
|
|
66
|
+
const sx = resolveAccessor(s.x);
|
|
67
|
+
const sy = resolveAccessor(s.y);
|
|
68
|
+
return s.data.map((d) => ({
|
|
69
|
+
x: sx(d) as AxisValue,
|
|
70
|
+
y: Number(sy(d)),
|
|
71
|
+
row: d,
|
|
72
|
+
label: String(sy(d)),
|
|
73
|
+
series: s.name,
|
|
74
|
+
}));
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Last data point per series, with its palette index for colour.
|
|
79
|
+
const lastPoints = $derived(
|
|
80
|
+
resolvedSeries
|
|
81
|
+
.map((s, idx) => ({ series: s, lastRow: s.data[s.data.length - 1], seriesIndex: idx }))
|
|
82
|
+
.filter(({ lastRow }) => lastRow !== undefined),
|
|
83
|
+
);
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<PlotLayout
|
|
87
|
+
{width}
|
|
88
|
+
{height}
|
|
89
|
+
x={scales.x}
|
|
90
|
+
y={scales.y}
|
|
91
|
+
data={flat}
|
|
92
|
+
getX={xAcc}
|
|
93
|
+
getY={(d) => Number(yAcc(d))}
|
|
94
|
+
{hoverPoints}
|
|
95
|
+
label={(r) => String(yAcc(r))}
|
|
96
|
+
{tooltip}
|
|
97
|
+
{markers}
|
|
98
|
+
seriesData={resolvedSeries}
|
|
99
|
+
{scales}
|
|
100
|
+
{margins}
|
|
101
|
+
defaultStrategy="x"
|
|
102
|
+
>
|
|
103
|
+
{#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY })}
|
|
104
|
+
{#each groupedSeries as group, seriesIndex (group.series.name)}
|
|
105
|
+
{@const defaultColor = paletteColor(seriesIndex, cfg.palette)}
|
|
106
|
+
{#each group.visualSegments as seg, i (`${group.series.name}-${i}`)}
|
|
107
|
+
{@const strokeStyle = seg.style.stroke}
|
|
108
|
+
{@const lineData = seg.data as unknown as Record<string | symbol, never>[]}
|
|
109
|
+
<Line
|
|
110
|
+
data={lineData}
|
|
111
|
+
x={resolveAccessor(group.series.x)}
|
|
112
|
+
y={resolveAccessor(group.series.y)}
|
|
113
|
+
stroke={strokeStyle?.stroke ?? defaultColor}
|
|
114
|
+
strokeWidth={strokeStyle?.strokeWidth ?? cfg.line.strokeWidth}
|
|
115
|
+
strokeOpacity={strokeStyle?.strokeOpacity ?? 1}
|
|
116
|
+
strokeDasharray={strokeStyle?.strokeDasharray}
|
|
117
|
+
strokeLinejoin={strokeStyle?.strokeLinejoin}
|
|
118
|
+
strokeLinecap={strokeStyle?.strokeLinecap}
|
|
119
|
+
/>
|
|
120
|
+
{#if seg.style.dots}
|
|
121
|
+
{@const dotStyle = seg.style.dots}
|
|
122
|
+
<Dot
|
|
123
|
+
data={seg.data}
|
|
124
|
+
x={resolveAccessor(group.series.x)}
|
|
125
|
+
y={resolveAccessor(group.series.y)}
|
|
126
|
+
r={dotStyle.dotRadius ?? cfg.line.dotRadius}
|
|
127
|
+
fill={dotStyle.dotFill ?? strokeStyle?.stroke ?? defaultColor}
|
|
128
|
+
symbol={dotStyle.dotSymbol ?? 'circle'}
|
|
129
|
+
stroke={dotStyle.dotStroke ?? strokeStyle?.stroke ?? defaultColor}
|
|
130
|
+
strokeWidth={dotStyle.dotStrokeWidth ?? 1}
|
|
131
|
+
fillOpacity={dotStyle.dotFillOpacity ?? strokeStyle?.strokeOpacity ?? 1}
|
|
132
|
+
strokeOpacity={dotStyle.dotStrokeOpacity ?? strokeStyle?.strokeOpacity ?? 1}
|
|
133
|
+
strokeDasharray={dotStyle.dotStrokeDasharray}
|
|
134
|
+
/>
|
|
135
|
+
{/if}
|
|
136
|
+
{/each}
|
|
137
|
+
|
|
138
|
+
{#each seriesMarkers.get(group.series.name) ?? [] as marker, i (`series-${group.series.name}-${marker.type}-${i}`)}
|
|
139
|
+
{#if marker.type === 'component'}
|
|
140
|
+
{#if marker.scope === 'segment'}
|
|
141
|
+
{@const segs = resolveSegmentsForSeries(segments, group.series.name)}
|
|
142
|
+
{@const xAccessor = resolveAccessor(group.series.x)}
|
|
143
|
+
{@const segmentDataArrays = getDataForSegments(group.series.data, xAccessor, segs)}
|
|
144
|
+
{#each segmentDataArrays as segmentData, segIdx (`${group.series.name}-marker-segment-${segIdx}`)}
|
|
145
|
+
{#if segmentData.length > 0}
|
|
146
|
+
{@const segmentColor = segs[segIdx]?.style?.stroke?.stroke ?? defaultColor}
|
|
147
|
+
<marker.component
|
|
148
|
+
{...marker.props}
|
|
149
|
+
series={group.series.name}
|
|
150
|
+
data={segmentData}
|
|
151
|
+
seriesColor={segmentColor}
|
|
152
|
+
/>
|
|
153
|
+
{/if}
|
|
154
|
+
{/each}
|
|
155
|
+
{:else}
|
|
156
|
+
{@const segs = resolveSegmentsForSeries(segments, group.series.name)}
|
|
157
|
+
{@const seriesColor = segs[0]?.style?.stroke?.stroke ?? defaultColor}
|
|
158
|
+
<marker.component
|
|
159
|
+
{...marker.props}
|
|
160
|
+
series={group.series.name}
|
|
161
|
+
data={group.series.data}
|
|
162
|
+
seriesColor={seriesColor}
|
|
163
|
+
/>
|
|
164
|
+
{/if}
|
|
165
|
+
{:else if marker.type === 'hover'}
|
|
166
|
+
{@const seriesPoints = matchedPoints.filter((p) => p.series === group.series.name)}
|
|
167
|
+
{#if marker.scope === 'segment'}
|
|
168
|
+
{@const segs = resolveSegmentsForSeries(segments, group.series.name)}
|
|
169
|
+
{#each segs as seg, segIdx (`${group.series.name}-hover-segment-${segIdx}`)}
|
|
170
|
+
{@const segPoints = seriesPoints.filter((p) => matchesSegmentX(p.x, seg))}
|
|
171
|
+
{#if segPoints.length > 0}
|
|
172
|
+
<HoverMarker
|
|
173
|
+
data={segPoints}
|
|
174
|
+
ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
|
|
175
|
+
ruleY={marker.ruleY !== false && (impliesRuleY || marker.ruleY === true) ? ruleY : null}
|
|
176
|
+
showLabels={marker.showLabels}
|
|
177
|
+
ruleStyle={marker.ruleStyle}
|
|
178
|
+
dotStyle={marker.dotStyle}
|
|
179
|
+
fontStyle={marker.fontStyle}
|
|
180
|
+
/>
|
|
181
|
+
{/if}
|
|
182
|
+
{/each}
|
|
183
|
+
{:else}
|
|
184
|
+
<HoverMarker
|
|
185
|
+
data={seriesPoints}
|
|
186
|
+
ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
|
|
187
|
+
ruleY={marker.ruleY !== false && (impliesRuleY || marker.ruleY === true) ? ruleY : null}
|
|
188
|
+
showLabels={marker.showLabels}
|
|
189
|
+
ruleStyle={marker.ruleStyle}
|
|
190
|
+
dotStyle={marker.dotStyle}
|
|
191
|
+
fontStyle={marker.fontStyle}
|
|
192
|
+
/>
|
|
193
|
+
{/if}
|
|
194
|
+
{/if}
|
|
195
|
+
{/each}
|
|
196
|
+
{/each}
|
|
197
|
+
|
|
198
|
+
{#if showVals}
|
|
199
|
+
{@const defaultDx = valAnchor === 'start' ? 6 : valAnchor === 'end' ? -6 : 0}
|
|
200
|
+
{@const defaultDy = valAnchor === 'outside' ? -8 : 0}
|
|
201
|
+
{@const defaultTA = valAnchor === 'start'
|
|
202
|
+
? ('start' as const)
|
|
203
|
+
: valAnchor === 'end'
|
|
204
|
+
? ('end' as const)
|
|
205
|
+
: ('middle' as const)}
|
|
206
|
+
{@const defaultLA = valAnchor === 'outside'
|
|
207
|
+
? ('bottom' as const)
|
|
208
|
+
: ('middle' as const)}
|
|
209
|
+
{#each lastPoints as { series: s, lastRow, seriesIndex } (s.name)}
|
|
210
|
+
{@const xFn = resolveAccessor(s.x)}
|
|
211
|
+
{@const yFn = resolveAccessor(s.y)}
|
|
212
|
+
{@const seriesColor = paletteColor(seriesIndex, cfg.palette)}
|
|
213
|
+
<Text
|
|
214
|
+
data={[lastRow]}
|
|
215
|
+
x={xFn}
|
|
216
|
+
y={yFn}
|
|
217
|
+
text={() => fmtVal(Number(yFn(lastRow)), s.name)}
|
|
218
|
+
dx={styles.values?.dx ?? defaultDx}
|
|
219
|
+
dy={styles.values?.dy ?? defaultDy}
|
|
220
|
+
textAnchor={styles.values?.textAnchor ?? defaultTA}
|
|
221
|
+
lineAnchor={styles.values?.lineAnchor ?? defaultLA}
|
|
222
|
+
lineHeight={styles.values?.lineHeight}
|
|
223
|
+
rotate={styles.values?.rotate}
|
|
224
|
+
class={styles.values?.class}
|
|
225
|
+
textClass={styles.values?.textClass}
|
|
226
|
+
fill={seriesColor}
|
|
227
|
+
fontSize={12}
|
|
228
|
+
/>
|
|
229
|
+
{/each}
|
|
230
|
+
{/if}
|
|
231
|
+
{/snippet}
|
|
232
|
+
</PlotLayout>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PlotProps } from '../../types/plots/props';
|
|
2
|
+
import type { LineSegmentStyle } from '../../types/plots/styles';
|
|
3
|
+
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
4
|
+
props: PlotProps<TData, LineSegmentStyle>;
|
|
5
|
+
exports: {};
|
|
6
|
+
bindings: "";
|
|
7
|
+
slots: {};
|
|
8
|
+
events: {};
|
|
9
|
+
};
|
|
10
|
+
declare class __sveltets_Render<TData extends Record<string, unknown>> {
|
|
11
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
12
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
13
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
14
|
+
bindings(): "";
|
|
15
|
+
exports(): {};
|
|
16
|
+
}
|
|
17
|
+
interface $$IsomorphicComponent {
|
|
18
|
+
new <TData extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
19
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
20
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
21
|
+
<TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
22
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
23
|
+
}
|
|
24
|
+
declare const Plot: $$IsomorphicComponent;
|
|
25
|
+
type Plot<TData extends Record<string, unknown>> = InstanceType<typeof Plot<TData>>;
|
|
26
|
+
export default Plot;
|