@fundar/data-chart-telling 1.17.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +2 -1
- package/dist/configuration/config.svelte.js +4 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +4 -1
- package/dist/layout/coordinates/CoordinatesLayout.svelte +3 -1
- package/dist/layout/geometry/GeometryLayout.svelte +222 -53
- package/dist/layout/geometry/ScaleFreeContext.d.ts +31 -0
- package/dist/layout/geometry/ScaleFreeContext.js +65 -0
- package/dist/layout/geometry/ScaleFreeMarks.svelte +14 -0
- package/dist/layout/geometry/ScaleFreeMarks.svelte.d.ts +7 -0
- package/dist/layout/geometry/ScaleGate.svelte.d.ts +8 -0
- package/dist/layout/geometry/ScaleGate.svelte.js +95 -0
- package/dist/layout/geometry/ScaleGateMarks.svelte +15 -0
- package/dist/layout/geometry/ScaleGateMarks.svelte.d.ts +7 -0
- package/dist/layout/hover/HoverDefaults.d.ts +52 -0
- package/dist/layout/hover/HoverDefaults.js +96 -0
- package/dist/layout/hover/HoverLayers.d.ts +35 -0
- package/dist/layout/hover/HoverLayers.js +122 -0
- package/dist/layout/hover/HoverSettings.d.ts +1 -3
- package/dist/layout/hover/HoverSettings.js +3 -2
- package/dist/layout/hover/HoverShapes.d.ts +56 -0
- package/dist/layout/hover/HoverShapes.js +163 -0
- package/dist/layout/hover/PlotHover.svelte.d.ts +18 -0
- package/dist/layout/hover/PlotHover.svelte.js +54 -1
- package/dist/layout/legend/LegendState.svelte.d.ts +2 -0
- package/dist/layout/legend/LegendState.svelte.js +5 -0
- package/dist/layout/margins/Box.d.ts +4 -0
- package/dist/layout/margins/Box.js +14 -0
- package/dist/layout/margins/PlotMargins.svelte.js +26 -1
- package/dist/layout/plot/{Plot.svelte.d.ts → AbstractPlot.svelte.d.ts} +3 -3
- package/dist/layout/plot/Core.svelte +2 -2
- package/dist/layout/plot/PublicPlotContext.d.ts +6 -0
- package/dist/layout/plot/PublicPlotContext.js +8 -0
- package/dist/layout/styles/ValueLabels.d.ts +118 -59
- package/dist/layout/styles/ValueLabels.js +313 -146
- package/dist/layout/tooltip/TooltipState.svelte.d.ts +8 -9
- package/dist/layout/tooltip/TooltipState.svelte.js +28 -29
- package/dist/markers/MarkerKinds.d.ts +84 -1
- package/dist/markers/MarkerKinds.js +150 -23
- package/dist/markers/area/AreaMarker.svelte +1 -1
- package/dist/markers/band/BandMarker.svelte +74 -0
- package/dist/markers/band/BandMarker.svelte.d.ts +7 -0
- package/dist/markers/band/Bands.d.ts +16 -0
- package/dist/markers/band/Bands.js +48 -0
- package/dist/markers/bar/BarMarker.svelte +29 -70
- package/dist/markers/bollinger/BollingerMarker.svelte +1 -1
- package/dist/markers/box/BoxMarker.svelte +1 -1
- package/dist/markers/dot/DotRadius.d.ts +11 -0
- package/dist/markers/dot/DotRadius.js +21 -0
- package/dist/markers/hover/HoverMarker.svelte +7 -265
- package/dist/markers/hover/HoverMarker.svelte.d.ts +4 -27
- package/dist/markers/labels/CurveLabel.svelte +56 -0
- package/dist/markers/labels/CurveLabel.svelte.d.ts +9 -0
- package/dist/markers/labels/LabelMerge.d.ts +11 -0
- package/dist/markers/labels/LabelMerge.js +87 -0
- package/dist/markers/labels/MarkerLabel.svelte +36 -0
- package/dist/markers/labels/MarkerLabel.svelte.d.ts +10 -0
- package/dist/markers/labels/MarkerLabels.d.ts +52 -0
- package/dist/markers/labels/MarkerLabels.js +97 -0
- package/dist/markers/labels/geometry/BoxGeometry.d.ts +8 -0
- package/dist/markers/labels/geometry/BoxGeometry.js +101 -0
- package/dist/markers/labels/geometry/CurveGeometry.d.ts +34 -0
- package/dist/markers/labels/geometry/CurveGeometry.js +193 -0
- package/dist/markers/labels/geometry/PointGeometry.d.ts +9 -0
- package/dist/markers/labels/geometry/PointGeometry.js +22 -0
- package/dist/markers/labels/layouts/BoxLayout.d.ts +10 -0
- package/dist/markers/labels/layouts/BoxLayout.js +49 -0
- package/dist/markers/labels/layouts/PathLayout.d.ts +11 -0
- package/dist/markers/labels/layouts/PathLayout.js +91 -0
- package/dist/markers/labels/layouts/PointLayout.d.ts +19 -0
- package/dist/markers/labels/layouts/PointLayout.js +94 -0
- package/dist/markers/regression/RegressionMarker.svelte +1 -1
- package/dist/markers/text/Covering.d.ts +13 -0
- package/dist/markers/text/Covering.js +29 -0
- package/dist/markers/text/Declutter.d.ts +24 -3
- package/dist/markers/text/Declutter.js +46 -3
- package/dist/markers/text/TextMarker.svelte +90 -17
- package/dist/markers/text/TextMarker.svelte.d.ts +1 -1
- package/dist/markers/tick/TickMarker.svelte +1 -1
- package/dist/markers/utils/MarkerChannels.d.ts +1 -1
- package/dist/markers/utils/MarkerChannels.js +3 -2
- package/dist/markers/utils/PointPixel.d.ts +37 -0
- package/dist/markers/utils/PointPixel.js +44 -0
- package/dist/markers/violin/ViolinMarker.svelte +1 -1
- package/dist/markers/waffle/WaffleMarker.svelte +1 -1
- package/dist/phases/Pipeline.d.ts +7 -7
- package/dist/phases/Pipeline.js +12 -9
- package/dist/phases/context/ContextPhase.d.ts +22 -0
- package/dist/phases/context/ContextPhase.js +23 -0
- package/dist/phases/context/RowContext.d.ts +37 -0
- package/dist/phases/context/RowContext.js +97 -0
- package/dist/phases/data/DataPhase.d.ts +4 -4
- package/dist/phases/data/DataPhase.js +7 -7
- package/dist/phases/hover/HoverPhase.d.ts +6 -7
- package/dist/phases/hover/HoverPhase.js +28 -23
- package/dist/phases/mapping/MappingPhase.d.ts +4 -4
- package/dist/phases/mapping/MappingPhase.js +2 -2
- package/dist/phases/markers/MarkerStyles.d.ts +8 -0
- package/dist/phases/markers/MarkerStyles.js +24 -6
- package/dist/phases/markers/MarkersPhase.d.ts +6 -7
- package/dist/phases/markers/MarkersPhase.js +18 -12
- package/dist/phases/scales/ScalesPhase.d.ts +4 -4
- package/dist/phases/scales/ScalesPhase.js +4 -4
- package/dist/phases/sharedScales/SharedScalesPhase.d.ts +2 -2
- package/dist/phases/sharedScales/SharedScalesPhase.js +2 -2
- package/dist/phases/statistics/MarkerGrouping.d.ts +0 -9
- package/dist/phases/statistics/MarkerGrouping.js +5 -48
- package/dist/phases/statistics/MarkerInheritance.d.ts +11 -0
- package/dist/phases/statistics/MarkerInheritance.js +25 -0
- package/dist/phases/statistics/MarkerOrientation.d.ts +26 -0
- package/dist/phases/statistics/MarkerOrientation.js +29 -0
- package/dist/phases/statistics/StatisticsPhase.js +15 -15
- package/dist/phases/styles/StylesPhase.d.ts +3 -3
- package/dist/phases/styles/StylesPhase.js +2 -2
- package/dist/phases/tooltip/TooltipPhase.d.ts +4 -4
- package/dist/phases/tooltip/TooltipPhase.js +10 -7
- package/dist/phases/transforms/TransformsPhase.d.ts +3 -3
- package/dist/phases/transforms/TransformsPhase.js +7 -5
- package/dist/plots/correlation/heatmap/Plot.svelte +2 -2
- package/dist/plots/correlation/heatmap/Plot.svelte.d.ts +0 -1
- package/dist/plots/correlation/heatmap/defaults.d.ts +2 -2
- package/dist/plots/correlation/heatmap/defaults.js +35 -7
- package/dist/plots/correlation/heatmap/markers.d.ts +2 -8
- package/dist/plots/correlation/heatmap/markers.js +8 -11
- package/dist/plots/correlation/scatter/Plot.svelte +2 -2
- package/dist/plots/correlation/scatter/Plot.svelte.d.ts +0 -1
- package/dist/plots/correlation/scatter/defaults.d.ts +2 -2
- package/dist/plots/correlation/scatter/defaults.js +18 -6
- package/dist/plots/correlation/scatter/markers.d.ts +2 -11
- package/dist/plots/correlation/scatter/markers.js +24 -30
- package/dist/plots/distribution/box/BoxSummaries.d.ts +22 -8
- package/dist/plots/distribution/box/BoxSummaries.js +99 -40
- package/dist/plots/distribution/box/Plot.svelte +2 -2
- package/dist/plots/distribution/box/Plot.svelte.d.ts +0 -1
- package/dist/plots/distribution/box/defaults.d.ts +2 -2
- package/dist/plots/distribution/box/defaults.js +20 -6
- package/dist/plots/distribution/box/markers.d.ts +2 -2
- package/dist/plots/distribution/box/markers.js +13 -4
- package/dist/plots/distribution/density/Plot.svelte +2 -2
- package/dist/plots/distribution/density/Plot.svelte.d.ts +0 -1
- package/dist/plots/distribution/density/defaults.d.ts +2 -2
- package/dist/plots/distribution/density/defaults.js +33 -1
- package/dist/plots/distribution/density/markers.d.ts +2 -2
- package/dist/plots/distribution/density/markers.js +9 -5
- package/dist/plots/distribution/histogram/Plot.svelte +2 -2
- package/dist/plots/distribution/histogram/Plot.svelte.d.ts +0 -1
- package/dist/plots/distribution/histogram/defaults.d.ts +2 -2
- package/dist/plots/distribution/histogram/defaults.js +36 -36
- package/dist/plots/distribution/histogram/markers.d.ts +2 -2
- package/dist/plots/distribution/histogram/markers.js +7 -6
- package/dist/plots/distribution/pyramid/Plot.svelte +2 -2
- package/dist/plots/distribution/pyramid/Plot.svelte.d.ts +0 -1
- package/dist/plots/distribution/pyramid/defaults.d.ts +2 -2
- package/dist/plots/distribution/pyramid/defaults.js +13 -3
- package/dist/plots/distribution/pyramid/markers.d.ts +2 -10
- package/dist/plots/distribution/pyramid/markers.js +9 -18
- package/dist/plots/distribution/violin/Plot.svelte +2 -2
- package/dist/plots/distribution/violin/Plot.svelte.d.ts +0 -1
- package/dist/plots/distribution/violin/defaults.d.ts +2 -2
- package/dist/plots/distribution/violin/defaults.js +20 -7
- package/dist/plots/distribution/violin/markers.d.ts +2 -2
- package/dist/plots/distribution/violin/markers.js +30 -6
- package/dist/plots/evolution/area/Plot.svelte +2 -2
- package/dist/plots/evolution/area/Plot.svelte.d.ts +0 -1
- package/dist/plots/evolution/area/defaults.d.ts +2 -2
- package/dist/plots/evolution/area/defaults.js +5 -8
- package/dist/plots/evolution/area/markers.d.ts +2 -2
- package/dist/plots/evolution/area/markers.js +7 -5
- package/dist/plots/evolution/line/Plot.svelte +2 -2
- package/dist/plots/evolution/line/Plot.svelte.d.ts +0 -1
- package/dist/plots/evolution/line/defaults.d.ts +2 -2
- package/dist/plots/evolution/line/defaults.js +5 -7
- package/dist/plots/evolution/line/markers.d.ts +2 -2
- package/dist/plots/evolution/line/markers.js +6 -4
- package/dist/plots/evolution/slope/Plot.svelte +2 -2
- package/dist/plots/evolution/slope/Plot.svelte.d.ts +0 -1
- package/dist/plots/evolution/slope/defaults.d.ts +2 -2
- package/dist/plots/evolution/slope/defaults.js +11 -6
- package/dist/plots/evolution/slope/markers.d.ts +6 -5
- package/dist/plots/evolution/slope/markers.js +19 -12
- package/dist/plots/evolution/sparkline/Plot.svelte +2 -2
- package/dist/plots/evolution/sparkline/Plot.svelte.d.ts +0 -1
- package/dist/plots/evolution/sparkline/defaults.d.ts +2 -2
- package/dist/plots/evolution/sparkline/defaults.js +5 -2
- package/dist/plots/evolution/sparkline/markers.d.ts +2 -2
- package/dist/plots/evolution/sparkline/markers.js +6 -4
- package/dist/plots/generic/Plot.svelte +42 -0
- package/dist/plots/generic/Plot.svelte.d.ts +25 -0
- package/dist/plots/generic/defaults.d.ts +4 -0
- package/dist/plots/generic/defaults.js +7 -0
- package/dist/plots/map/geo/Core.svelte +3 -3
- package/dist/plots/map/geo/Plot.svelte +2 -2
- package/dist/plots/map/geo/Plot.svelte.d.ts +0 -1
- package/dist/plots/map/geo/defaults.d.ts +2 -2
- package/dist/plots/map/geo/phases/data.d.ts +1 -1
- package/dist/plots/map/geo/phases/data.js +1 -1
- package/dist/plots/map/geo/phases/hover.d.ts +2 -2
- package/dist/plots/map/geo/phases/hover.js +3 -3
- package/dist/plots/map/geo/phases/markers.d.ts +2 -2
- package/dist/plots/map/geo/phases/markers.js +3 -3
- package/dist/plots/map/geo/phases/scales.d.ts +2 -2
- package/dist/plots/map/geo/phases/scales.js +2 -2
- package/dist/plots/part-of-a-whole/treemap/Plot.svelte +2 -2
- package/dist/plots/part-of-a-whole/treemap/Plot.svelte.d.ts +0 -1
- package/dist/plots/part-of-a-whole/treemap/defaults.d.ts +2 -2
- package/dist/plots/part-of-a-whole/treemap/defaults.js +38 -22
- package/dist/plots/ranking/bar/Plot.svelte +2 -2
- package/dist/plots/ranking/bar/Plot.svelte.d.ts +0 -1
- package/dist/plots/ranking/bar/defaults.d.ts +2 -2
- package/dist/plots/ranking/bar/defaults.js +26 -10
- package/dist/plots/ranking/bar/markers.d.ts +3 -3
- package/dist/plots/ranking/bar/markers.js +17 -33
- package/dist/plots/ranking/bullet/Plot.svelte +2 -2
- package/dist/plots/ranking/bullet/Plot.svelte.d.ts +0 -1
- package/dist/plots/ranking/bullet/defaults.d.ts +2 -2
- package/dist/plots/ranking/bullet/defaults.js +24 -4
- package/dist/plots/ranking/bullet/markers.d.ts +3 -3
- package/dist/plots/ranking/bullet/markers.js +16 -10
- package/dist/plots/ranking/cleveland/ClevelandMarkers.d.ts +4 -11
- package/dist/plots/ranking/cleveland/ClevelandMarkers.js +73 -54
- package/dist/plots/ranking/cleveland/Plot.svelte +2 -2
- package/dist/plots/ranking/cleveland/Plot.svelte.d.ts +0 -1
- package/dist/plots/ranking/cleveland/defaults.d.ts +2 -2
- package/dist/plots/ranking/cleveland/defaults.js +39 -5
- package/dist/plots/ranking/dumbbell/Plot.svelte +2 -2
- package/dist/plots/ranking/dumbbell/Plot.svelte.d.ts +0 -1
- package/dist/plots/ranking/dumbbell/defaults.d.ts +2 -2
- package/dist/plots/ranking/forest/Plot.svelte +2 -2
- package/dist/plots/ranking/forest/Plot.svelte.d.ts +0 -1
- package/dist/plots/ranking/forest/defaults.d.ts +2 -2
- package/dist/plots/ranking/lollipop/Plot.svelte +2 -2
- package/dist/plots/ranking/lollipop/Plot.svelte.d.ts +0 -1
- package/dist/plots/ranking/lollipop/defaults.d.ts +2 -2
- package/dist/types/configuration/styling.d.ts +4 -0
- package/dist/types/layout/context.d.ts +23 -0
- package/dist/types/layout/context.js +1 -0
- package/dist/types/layout/hover.d.ts +3 -0
- package/dist/types/layout/styles/bullet.d.ts +4 -1
- package/dist/types/layout/styles/values.d.ts +59 -13
- package/dist/types/layout/tooltip.d.ts +5 -10
- package/dist/types/markers/axis/area.d.ts +5 -1
- package/dist/types/markers/axis/axisBasedMarkersConfig.d.ts +2 -1
- package/dist/types/markers/axis/band.d.ts +24 -0
- package/dist/types/markers/axis/band.js +1 -0
- package/dist/types/markers/axis/bar.d.ts +7 -4
- package/dist/types/markers/axis/bollinger.d.ts +2 -1
- package/dist/types/markers/axis/box.d.ts +2 -1
- package/dist/types/markers/axis/cell.d.ts +4 -1
- package/dist/types/markers/axis/hover.d.ts +60 -15
- package/dist/types/markers/axis/line.d.ts +4 -1
- package/dist/types/markers/axis/rect.d.ts +4 -1
- package/dist/types/markers/axis/regression.d.ts +2 -1
- package/dist/types/markers/axis/tick.d.ts +2 -1
- package/dist/types/markers/axis/trail.d.ts +4 -1
- package/dist/types/markers/axis/violin.d.ts +2 -1
- package/dist/types/markers/axis/waffle.d.ts +5 -1
- package/dist/types/markers/basic/dot.d.ts +4 -1
- package/dist/types/markers/basic/text.d.ts +25 -21
- package/dist/types/markers/channels/commonMarkerConfig.d.ts +6 -2
- package/dist/types/markers/channels/commonMarkerConfigWithLabel.d.ts +5 -9
- package/dist/types/markers/channels/commonMarkerConfigWithOrientation.d.ts +5 -0
- package/dist/types/markers/channels/commonMarkerConfigWithOrientation.js +1 -0
- package/dist/types/markers/common.d.ts +1 -0
- package/dist/types/markers/common.js +1 -0
- package/dist/types/markers/labels/box.d.ts +57 -0
- package/dist/types/markers/labels/box.js +1 -0
- package/dist/types/markers/labels/common.d.ts +15 -0
- package/dist/types/markers/labels/common.js +1 -0
- package/dist/types/markers/labels/path.d.ts +52 -0
- package/dist/types/markers/labels/path.js +1 -0
- package/dist/types/markers/labels/point.d.ts +40 -0
- package/dist/types/markers/labels/point.js +1 -0
- package/dist/types/markers/traits.d.ts +10 -0
- package/dist/types/phases/context/phase.d.ts +7 -0
- package/dist/types/phases/context/phase.js +1 -0
- package/dist/types/phases/data/phase.d.ts +2 -2
- package/dist/types/phases/hover/phase.d.ts +10 -7
- package/dist/types/phases/index.d.ts +2 -1
- package/dist/types/phases/kind.d.ts +11 -8
- package/dist/types/phases/mapping/phase.d.ts +2 -2
- package/dist/types/phases/markers/phase.d.ts +3 -10
- package/dist/types/phases/scales/phase.d.ts +4 -4
- package/dist/types/phases/stage.d.ts +3 -3
- package/dist/types/phases/state.d.ts +1 -1
- package/dist/types/phases/statistics/phase.d.ts +8 -3
- package/dist/types/phases/styles/phase.d.ts +18 -5
- package/dist/types/phases/transforms/phase.d.ts +4 -2
- package/dist/types/plots/constants.d.ts +3 -17
- package/dist/types/plots/correlation/heatmap/props.d.ts +3 -2
- package/dist/types/plots/correlation/heatmap/state.d.ts +2 -2
- package/dist/types/plots/correlation/scatter/props.d.ts +3 -2
- package/dist/types/plots/correlation/scatter/state.d.ts +2 -2
- package/dist/types/plots/distribution/box/props.d.ts +28 -2
- package/dist/types/plots/distribution/box/state.d.ts +6 -17
- package/dist/types/plots/distribution/density/props.d.ts +2 -1
- package/dist/types/plots/distribution/density/state.d.ts +2 -2
- package/dist/types/plots/distribution/histogram/props.d.ts +16 -2
- package/dist/types/plots/distribution/histogram/state.d.ts +2 -2
- package/dist/types/plots/distribution/pyramid/props.d.ts +3 -2
- package/dist/types/plots/distribution/pyramid/state.d.ts +2 -2
- package/dist/types/plots/distribution/violin/props.d.ts +6 -2
- package/dist/types/plots/distribution/violin/state.d.ts +2 -2
- package/dist/types/plots/evolution/area/props.d.ts +3 -2
- package/dist/types/plots/evolution/area/state.d.ts +2 -2
- package/dist/types/plots/evolution/line/props.d.ts +3 -2
- package/dist/types/plots/evolution/line/state.d.ts +2 -2
- package/dist/types/plots/evolution/slope/state.d.ts +2 -2
- package/dist/types/plots/evolution/sparkline/state.d.ts +2 -2
- package/dist/types/plots/generic/props.d.ts +16 -0
- package/dist/types/plots/generic/props.js +1 -0
- package/dist/types/plots/generic/state.d.ts +4 -0
- package/dist/types/plots/generic/state.js +1 -0
- package/dist/types/plots/map/geo/state.d.ts +1 -1
- package/dist/types/plots/part-of-a-whole/treemap/props.d.ts +18 -6
- package/dist/types/plots/part-of-a-whole/treemap/state.d.ts +2 -2
- package/dist/types/plots/props.d.ts +3 -0
- package/dist/types/plots/ranking/bar/props.d.ts +3 -2
- package/dist/types/plots/ranking/bar/state.d.ts +2 -2
- package/dist/types/plots/ranking/bullet/state.d.ts +2 -2
- package/dist/types/plots/ranking/cleveland/props.d.ts +3 -2
- package/dist/types/plots/ranking/cleveland/state.d.ts +2 -2
- package/dist/types/plots/ranking/dumbbell/props.d.ts +3 -2
- package/dist/types/plots/ranking/dumbbell/state.d.ts +2 -2
- package/dist/types/plots/ranking/forest/props.d.ts +5 -2
- package/dist/types/plots/ranking/forest/state.d.ts +2 -2
- package/dist/types/plots/ranking/lollipop/props.d.ts +3 -2
- package/dist/types/plots/ranking/lollipop/state.d.ts +2 -2
- package/docs/architecture.md +9 -9
- package/docs/markers.md +36 -6
- package/docs/plots/evolution/line.md +1 -1
- package/docs/plots/evolution/slope.md +1 -1
- package/docs/plots/part-of-a-whole/treemap.md +36 -2
- package/docs/plots/ranking/bar.md +1 -0
- package/docs/plots/ranking/cleveland.md +1 -1
- package/docs/state.md +33 -0
- package/docs/styles-and-segments.md +2 -15
- package/docs/theming.md +2 -1
- package/docs/tooltip-and-hover.md +81 -3
- package/docs/value-labels.md +159 -0
- package/examples/BarValues.svelte +1 -1
- package/examples/LineDates.svelte +1 -1
- package/examples/ResponsiveLine.svelte +3 -1
- package/examples/TreemapHover.svelte +25 -0
- package/examples/ValueLabels.svelte +1 -1
- package/llms-full.txt +9 -7
- package/llms.txt +3 -1
- package/package.json +3 -1
- package/dist/markers/utils/BoxLabel.d.ts +0 -58
- package/dist/markers/utils/BoxLabel.js +0 -175
- package/dist/markers/utils/MarkerLabel.svelte +0 -33
- package/dist/markers/utils/MarkerLabel.svelte.d.ts +0 -10
- /package/dist/layout/plot/{Plot.svelte → AbstractPlot.svelte} +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** The label a merged row was written by. */
|
|
2
|
+
export const LABEL_SOURCE = Symbol('labelSource');
|
|
3
|
+
/** What may differ from one merged label to another, each row reading its own: a field key is a field, a text either. */
|
|
4
|
+
const ROW_KEYS = {
|
|
5
|
+
x: 'field',
|
|
6
|
+
y: 'field',
|
|
7
|
+
dx: 'field',
|
|
8
|
+
dy: 'field',
|
|
9
|
+
text: 'either',
|
|
10
|
+
textAnchor: 'literal',
|
|
11
|
+
lineAnchor: 'literal'
|
|
12
|
+
};
|
|
13
|
+
const ROW_STYLE_KEYS = ['fill', 'fillOpacity', 'stroke', 'strokeWidth', 'fontWeight', 'fontStyle'];
|
|
14
|
+
/** Functions of the row each row calls its own label's: handlers, and where its point sits. */
|
|
15
|
+
const ROW_FUNCTIONS = ['origin', 'covers'];
|
|
16
|
+
const dispatched = (key) => ROW_FUNCTIONS.includes(key) || (key.startsWith('on') && key !== 'onoverflow');
|
|
17
|
+
const read = (value, row, as) => {
|
|
18
|
+
if (typeof value === 'function')
|
|
19
|
+
return value(row);
|
|
20
|
+
const key = typeof value === 'string' || typeof value === 'symbol';
|
|
21
|
+
if (!key || as === 'literal')
|
|
22
|
+
return value;
|
|
23
|
+
return as === 'field' || (row && value in row) ? row?.[value] : value;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Labels drawn as one `text` marker: those whose other options are alike merge, each row tagged with the label
|
|
27
|
+
* it comes from, so labels from several markers declutter together and draw as one.
|
|
28
|
+
*/
|
|
29
|
+
export class LabelMerge {
|
|
30
|
+
static merge(markers) {
|
|
31
|
+
const groups = new Map();
|
|
32
|
+
const ids = new Map();
|
|
33
|
+
for (const marker of markers) {
|
|
34
|
+
const key = LabelMerge.#signature(marker, ids);
|
|
35
|
+
groups.set(key, [...(groups.get(key) ?? []), marker]);
|
|
36
|
+
}
|
|
37
|
+
return [...groups.values()].map((group) => group.length === 1 ? group[0] : LabelMerge.#merged(group));
|
|
38
|
+
}
|
|
39
|
+
static #merged(sources) {
|
|
40
|
+
const data = sources.flatMap((source, i) => (source.data ?? []).map((row) => ({ ...row, [LABEL_SOURCE]: i })));
|
|
41
|
+
const sourceOf = (row) => sources[row[LABEL_SOURCE]];
|
|
42
|
+
const pick = (values, at, as) => values.every((value) => value === values[0])
|
|
43
|
+
? values[0]
|
|
44
|
+
: (row) => read(at(sourceOf(row)), row, as);
|
|
45
|
+
const merged = { ...sources[0], data };
|
|
46
|
+
for (const [key, as] of Object.entries(ROW_KEYS)) {
|
|
47
|
+
merged[key] = pick(sources.map((source) => source[key]), (source) => source[key], as);
|
|
48
|
+
}
|
|
49
|
+
const stylesOf = (source) => (source.styles ?? {});
|
|
50
|
+
const styles = { ...stylesOf(sources[0]) };
|
|
51
|
+
for (const key of ROW_STYLE_KEYS) {
|
|
52
|
+
styles[key] = pick(sources.map((source) => stylesOf(source)[key]), (source) => stylesOf(source)[key], 'either');
|
|
53
|
+
}
|
|
54
|
+
merged.styles = styles;
|
|
55
|
+
for (const key of new Set(sources.flatMap((source) => Object.keys(source)))) {
|
|
56
|
+
if (!dispatched(key))
|
|
57
|
+
continue;
|
|
58
|
+
const own = sources.map((source) => source[key]);
|
|
59
|
+
if (own.every((fn) => fn === own[0]))
|
|
60
|
+
continue;
|
|
61
|
+
const call = (row, args) => sourceOf(row)[key]?.(...args);
|
|
62
|
+
merged[key] = ROW_FUNCTIONS.includes(key)
|
|
63
|
+
? (row, scales) => call(row, [row, scales])
|
|
64
|
+
: (event, row, index) => call(row, [event, row, index]);
|
|
65
|
+
}
|
|
66
|
+
return merged;
|
|
67
|
+
}
|
|
68
|
+
/** What labels share to merge: every option but their rows and what each row reads on its own. */
|
|
69
|
+
static #signature(marker, ids) {
|
|
70
|
+
const shared = {};
|
|
71
|
+
for (const [key, value] of Object.entries(marker)) {
|
|
72
|
+
if (key === 'data' || key in ROW_KEYS || dispatched(key))
|
|
73
|
+
continue;
|
|
74
|
+
shared[key] = value;
|
|
75
|
+
}
|
|
76
|
+
const styles = (marker.styles ?? {});
|
|
77
|
+
shared.styles = Object.fromEntries(Object.entries(styles).filter(([key]) => !ROW_STYLE_KEYS.includes(key)));
|
|
78
|
+
// A function or a symbol is told apart by its identity.
|
|
79
|
+
return JSON.stringify(shared, (_, value) => {
|
|
80
|
+
if (typeof value !== 'function' && typeof value !== 'symbol')
|
|
81
|
+
return value;
|
|
82
|
+
if (!ids.has(value))
|
|
83
|
+
ids.set(value, ids.size);
|
|
84
|
+
return `#${ids.get(value)}`;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import TextMarker from '../text/TextMarker.svelte';
|
|
3
|
+
import CurveLabel from './CurveLabel.svelte';
|
|
4
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
5
|
+
import { usePlot } from '../../layout/plot/usePlot';
|
|
6
|
+
import { MarkerLabels } from './MarkerLabels';
|
|
7
|
+
import { MARK_EVENTS } from '../../types/markers/channels/commonMarkerEvents';
|
|
8
|
+
import type { BoxedMarker } from '../../types/markers/labels/box';
|
|
9
|
+
import type { MarkerLabelConfig } from '../../types/markers/labels/common';
|
|
10
|
+
|
|
11
|
+
const { marker }: { marker: BoxedMarker & { label?: MarkerLabelConfig | MarkerLabelConfig[] } } =
|
|
12
|
+
$props();
|
|
13
|
+
const plot = usePlot();
|
|
14
|
+
const cfg = $derived(getConfiguration());
|
|
15
|
+
|
|
16
|
+
const labels = $derived(
|
|
17
|
+
MarkerLabels.build(marker, {
|
|
18
|
+
isHorizontal: plot.axes?.isHorizontal,
|
|
19
|
+
gap: cfg.text.label.gap,
|
|
20
|
+
scales: () => plot.svelteplot.scales as never
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
const curves = $derived(MarkerLabels.curves(marker));
|
|
24
|
+
// Without a handler of its own, a label leaves the pointer to its shape.
|
|
25
|
+
const listens = (label: (typeof labels)[number]) =>
|
|
26
|
+
MARK_EVENTS.some((name) => !!(label.marker as Record<string, unknown>)[name]);
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
{#each labels as label, i (i)}
|
|
30
|
+
<g pointer-events={listens(label) ? undefined : 'none'}>
|
|
31
|
+
<TextMarker marker={label.marker} box={label.box} />
|
|
32
|
+
</g>
|
|
33
|
+
{/each}
|
|
34
|
+
{#each curves as curve, i (i)}
|
|
35
|
+
<CurveLabel marker={marker as Record<string, unknown>} label={curve.label} shape={curve.shape} />
|
|
36
|
+
{/each}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BoxedMarker } from '../../types/markers/labels/box';
|
|
2
|
+
import type { MarkerLabelConfig } from '../../types/markers/labels/common';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
marker: BoxedMarker & {
|
|
5
|
+
label?: MarkerLabelConfig | MarkerLabelConfig[];
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
declare const MarkerLabel: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type MarkerLabel = ReturnType<typeof MarkerLabel>;
|
|
10
|
+
export default MarkerLabel;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { BoxLabelMarker, BoxScales, BoxedMarker } from '../../types/markers/labels/box';
|
|
2
|
+
import type { LabelLayout, MarkerLabelConfig } from '../../types/markers/labels/common';
|
|
3
|
+
import type { PathLabelConfig, PathShape } from '../../types/markers/labels/path';
|
|
4
|
+
import type { TextMarkerConfig } from '../../types/markers/basic/text';
|
|
5
|
+
type Labelled = BoxedMarker & {
|
|
6
|
+
label?: MarkerLabelConfig | MarkerLabelConfig[];
|
|
7
|
+
};
|
|
8
|
+
type LabelPlot = {
|
|
9
|
+
isHorizontal?: boolean;
|
|
10
|
+
scales?: () => BoxScales | undefined;
|
|
11
|
+
gap?: number;
|
|
12
|
+
};
|
|
13
|
+
/** A marker's labels, each laid out in its `layout` (else its kind's) on the geometry its kind gives it. */
|
|
14
|
+
export declare class MarkerLabels {
|
|
15
|
+
static list(marker: {
|
|
16
|
+
label?: MarkerLabelConfig | MarkerLabelConfig[];
|
|
17
|
+
}): MarkerLabelConfig[];
|
|
18
|
+
/** Maps each label, keeping a single one single. */
|
|
19
|
+
static map<T>(marker: T, fn: (label: MarkerLabelConfig) => MarkerLabelConfig): T;
|
|
20
|
+
/** `undefined` when the kind does not support it. */
|
|
21
|
+
static layoutOf(marker: {
|
|
22
|
+
type?: string;
|
|
23
|
+
}, label: MarkerLabelConfig): LabelLayout | undefined;
|
|
24
|
+
/** Whether it is read on its shape's color. */
|
|
25
|
+
static isOnShape(marker: {
|
|
26
|
+
type?: string;
|
|
27
|
+
}, label: MarkerLabelConfig): boolean;
|
|
28
|
+
/** The `'box'` and `'path'` labels of a marker, drawn with its shapes. */
|
|
29
|
+
static build(marker: Labelled, plot?: LabelPlot): BoxLabelMarker[];
|
|
30
|
+
/** The `'path'` labels a marker writes along its curves - its lines, an area's edge, each dot's rim - with their shape. */
|
|
31
|
+
static curves(marker: {
|
|
32
|
+
type?: string;
|
|
33
|
+
label?: MarkerLabelConfig | MarkerLabelConfig[];
|
|
34
|
+
}): {
|
|
35
|
+
label: PathLabelConfig<string, string>;
|
|
36
|
+
shape: Exclude<PathShape, 'box'>;
|
|
37
|
+
}[];
|
|
38
|
+
/**
|
|
39
|
+
* `markers` without their `'point'` labels, and those labels as `text` markers, merged where alike so they
|
|
40
|
+
* declutter together. `rows` are what a marker with no `data` of its own draws.
|
|
41
|
+
*/
|
|
42
|
+
static extractPoints<T extends object>(markers: T[], plot?: {
|
|
43
|
+
rows?: unknown[];
|
|
44
|
+
valueAxis?: 'x' | 'y';
|
|
45
|
+
isHorizontal?: boolean;
|
|
46
|
+
gap?: number;
|
|
47
|
+
}): {
|
|
48
|
+
markers: T[];
|
|
49
|
+
labels: TextMarkerConfig[];
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { MarkerKinds } from '../MarkerKinds';
|
|
2
|
+
import { BoxGeometry } from './geometry/BoxGeometry';
|
|
3
|
+
import { PointGeometry } from './geometry/PointGeometry';
|
|
4
|
+
import { BoxLayout } from './layouts/BoxLayout';
|
|
5
|
+
import { PathLayout } from './layouts/PathLayout';
|
|
6
|
+
import { PointLayout } from './layouts/PointLayout';
|
|
7
|
+
import { LabelMerge } from './LabelMerge';
|
|
8
|
+
import { SERIES_KEY } from '../../phases/transforms/TransformsPhase';
|
|
9
|
+
/** A marker's labels, each laid out in its `layout` (else its kind's) on the geometry its kind gives it. */
|
|
10
|
+
export class MarkerLabels {
|
|
11
|
+
static list(marker) {
|
|
12
|
+
const { label } = marker;
|
|
13
|
+
return label === undefined ? [] : Array.isArray(label) ? label : [label];
|
|
14
|
+
}
|
|
15
|
+
/** Maps each label, keeping a single one single. */
|
|
16
|
+
static map(marker, fn) {
|
|
17
|
+
const { label } = marker;
|
|
18
|
+
if (label === undefined)
|
|
19
|
+
return marker;
|
|
20
|
+
return { ...marker, label: Array.isArray(label) ? label.map(fn) : fn(label) };
|
|
21
|
+
}
|
|
22
|
+
/** `undefined` when the kind does not support it. */
|
|
23
|
+
static layoutOf(marker, label) {
|
|
24
|
+
const traits = MarkerKinds.of(marker.type)?.labels;
|
|
25
|
+
if (!traits)
|
|
26
|
+
return undefined;
|
|
27
|
+
const layout = label.layout ?? traits.default;
|
|
28
|
+
return traits[layout] === undefined ? undefined : layout;
|
|
29
|
+
}
|
|
30
|
+
/** Whether it is read on its shape's color. */
|
|
31
|
+
static isOnShape(marker, label) {
|
|
32
|
+
const layout = MarkerLabels.layoutOf(marker, label);
|
|
33
|
+
return layout === 'box' || (layout === 'path' && label.side === 'inside');
|
|
34
|
+
}
|
|
35
|
+
/** The `'box'` and `'path'` labels of a marker, drawn with its shapes. */
|
|
36
|
+
static build(marker, plot = {}) {
|
|
37
|
+
const shape = MarkerKinds.of(marker.type)?.labels?.box;
|
|
38
|
+
if (!shape)
|
|
39
|
+
return [];
|
|
40
|
+
const options = { data: marker.data, scales: plot.scales, gap: plot.gap };
|
|
41
|
+
return MarkerLabels.list(marker).flatMap((label) => {
|
|
42
|
+
const layout = MarkerLabels.layoutOf(marker, label);
|
|
43
|
+
if (layout !== 'box' && layout !== 'path')
|
|
44
|
+
return [];
|
|
45
|
+
const geometry = BoxGeometry.of(shape, marker, plot);
|
|
46
|
+
if (!geometry)
|
|
47
|
+
return [];
|
|
48
|
+
const built = layout === 'box'
|
|
49
|
+
? BoxLayout.build(label, geometry, options)
|
|
50
|
+
: PathLayout.build(label, geometry, options);
|
|
51
|
+
return built ? [built] : [];
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** The `'path'` labels a marker writes along its curves - its lines, an area's edge, each dot's rim - with their shape. */
|
|
55
|
+
static curves(marker) {
|
|
56
|
+
const shape = MarkerKinds.of(marker.type)?.labels?.path;
|
|
57
|
+
if (!shape || shape === 'box')
|
|
58
|
+
return [];
|
|
59
|
+
return MarkerLabels.list(marker)
|
|
60
|
+
.filter((label) => MarkerLabels.layoutOf(marker, label) === 'path')
|
|
61
|
+
.map((label) => ({ label: label, shape }));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* `markers` without their `'point'` labels, and those labels as `text` markers, merged where alike so they
|
|
65
|
+
* declutter together. `rows` are what a marker with no `data` of its own draws.
|
|
66
|
+
*/
|
|
67
|
+
static extractPoints(markers, plot = {}) {
|
|
68
|
+
const labels = [];
|
|
69
|
+
const stripped = markers.map((marker) => {
|
|
70
|
+
const own = marker;
|
|
71
|
+
const traits = MarkerKinds.of(own.type)?.labels;
|
|
72
|
+
const shape = traits?.point;
|
|
73
|
+
const all = MarkerLabels.list(own);
|
|
74
|
+
const points = all.filter((label) => MarkerLabels.layoutOf(own, label) === 'point');
|
|
75
|
+
if (points.length === 0)
|
|
76
|
+
return marker;
|
|
77
|
+
const point = shape && PointGeometry.of(shape, own, { box: traits.box, isHorizontal: plot.isHorizontal });
|
|
78
|
+
if (point) {
|
|
79
|
+
const rows = own.data ??
|
|
80
|
+
(plot.rows ?? []).filter((row) => own.series === undefined ||
|
|
81
|
+
row[SERIES_KEY] === own.series);
|
|
82
|
+
for (const label of points) {
|
|
83
|
+
const text = PointLayout.build(label, point, rows, {
|
|
84
|
+
grouping: own.grouping,
|
|
85
|
+
valueAxis: plot.valueAxis,
|
|
86
|
+
gap: plot.gap
|
|
87
|
+
});
|
|
88
|
+
if (text)
|
|
89
|
+
labels.push(text);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const rest = all.filter((label) => !points.includes(label));
|
|
93
|
+
return { ...marker, label: rest.length > 0 ? rest : undefined };
|
|
94
|
+
});
|
|
95
|
+
return { markers: stripped, labels: LabelMerge.merge(labels) };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BoxGeometryOf, BoxShape, BoxedMarker } from '../../../types/markers/labels/box';
|
|
2
|
+
/** The box each row of a marker spans, as its kind's {@link BoxShape} tells it. */
|
|
3
|
+
export declare class BoxGeometry {
|
|
4
|
+
#private;
|
|
5
|
+
static of(shape: BoxShape, marker: BoxedMarker, plot?: {
|
|
6
|
+
isHorizontal?: boolean;
|
|
7
|
+
}): BoxGeometryOf | undefined;
|
|
8
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const read = (edge) => (row) => typeof edge === 'function'
|
|
2
|
+
? edge(row)
|
|
3
|
+
: typeof edge === 'string' || typeof edge === 'symbol'
|
|
4
|
+
? row?.[edge]
|
|
5
|
+
: edge;
|
|
6
|
+
const middle = (from, to) => from instanceof Date || to instanceof Date
|
|
7
|
+
? new Date((Number(from) + Number(to)) / 2)
|
|
8
|
+
: (Number(from) + Number(to)) / 2;
|
|
9
|
+
const bandwidth = (scale) => scale?.type === 'band' ? Number(scale.fn.bandwidth()) : undefined;
|
|
10
|
+
const distance = (scale, from, to) => scale ? Math.abs(Number(scale.fn(to)) - Number(scale.fn(from))) : undefined;
|
|
11
|
+
/** The box each row of a marker spans, as its kind's {@link BoxShape} tells it. */
|
|
12
|
+
export class BoxGeometry {
|
|
13
|
+
static of(shape, marker, plot = {}) {
|
|
14
|
+
if (shape === 'cell')
|
|
15
|
+
return BoxGeometry.#cell(marker);
|
|
16
|
+
if (shape === 'band') {
|
|
17
|
+
return marker.role === 'hitArea'
|
|
18
|
+
? undefined
|
|
19
|
+
: BoxGeometry.#band(marker, marker.isHorizontal ?? plot.isHorizontal ?? false);
|
|
20
|
+
}
|
|
21
|
+
return BoxGeometry.#edges(marker);
|
|
22
|
+
}
|
|
23
|
+
/** A style of the marker, only when it is a number. */
|
|
24
|
+
static #style(marker, name) {
|
|
25
|
+
const value = marker.styles?.[name];
|
|
26
|
+
return typeof value === 'number' ? value : undefined;
|
|
27
|
+
}
|
|
28
|
+
static #edges({ x, y, x1, x2, y1, y2 }) {
|
|
29
|
+
// A side with only a value runs from zero to it, as a bar does.
|
|
30
|
+
if (y1 === undefined &&
|
|
31
|
+
y2 === undefined &&
|
|
32
|
+
y !== undefined &&
|
|
33
|
+
x1 !== undefined &&
|
|
34
|
+
x2 !== undefined) {
|
|
35
|
+
[y1, y2] = [0, y];
|
|
36
|
+
}
|
|
37
|
+
if (x1 === undefined &&
|
|
38
|
+
x2 === undefined &&
|
|
39
|
+
x !== undefined &&
|
|
40
|
+
y1 !== undefined &&
|
|
41
|
+
y2 !== undefined) {
|
|
42
|
+
[x1, x2] = [0, x];
|
|
43
|
+
}
|
|
44
|
+
if ([x1, x2, y1, y2].some((edge) => edge === undefined))
|
|
45
|
+
return undefined;
|
|
46
|
+
const [left, right, top, bottom] = [x1, x2, y1, y2].map(read);
|
|
47
|
+
const size = (row, scales) => {
|
|
48
|
+
const [width, height] = [
|
|
49
|
+
distance(scales.x, left(row), right(row)),
|
|
50
|
+
distance(scales.y, top(row), bottom(row))
|
|
51
|
+
];
|
|
52
|
+
return width === undefined || height === undefined ? undefined : { width, height };
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
box: { x1, x2, y1, y2 },
|
|
56
|
+
size,
|
|
57
|
+
x: (row) => middle(left(row), right(row)),
|
|
58
|
+
y: (row) => middle(top(row), bottom(row))
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
static #cell(marker) {
|
|
62
|
+
if (marker.x === undefined || marker.y === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
const inset = Number(BoxGeometry.#style(marker, 'inset') ?? 0.5);
|
|
65
|
+
const box = (_, scales) => {
|
|
66
|
+
const [width, height] = [bandwidth(scales.x), bandwidth(scales.y)];
|
|
67
|
+
return width === undefined || height === undefined
|
|
68
|
+
? undefined
|
|
69
|
+
: { width: width - 2 * inset, height: height - 2 * inset };
|
|
70
|
+
};
|
|
71
|
+
return { box, size: box, x: marker.x, y: marker.y };
|
|
72
|
+
}
|
|
73
|
+
static #band(marker, isHorizontal) {
|
|
74
|
+
const [category, from, to] = isHorizontal
|
|
75
|
+
? [marker.y, marker.x1, marker.x2]
|
|
76
|
+
: [marker.x, marker.y1, marker.y2];
|
|
77
|
+
const value = isHorizontal ? marker.x : marker.y;
|
|
78
|
+
const [start, end] = from !== undefined && to !== undefined ? [from, to] : [0, value];
|
|
79
|
+
if (category === undefined || end === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
const [first, last] = [start, end].map(read);
|
|
82
|
+
const [before, after] = isHorizontal
|
|
83
|
+
? [BoxGeometry.#style(marker, 'insetTop'), BoxGeometry.#style(marker, 'insetBottom')]
|
|
84
|
+
: [BoxGeometry.#style(marker, 'insetLeft'), BoxGeometry.#style(marker, 'insetRight')];
|
|
85
|
+
const middleOf = (row) => middle(first(row), last(row));
|
|
86
|
+
const box = (row, scales) => {
|
|
87
|
+
const band = bandwidth(isHorizontal ? scales.y : scales.x);
|
|
88
|
+
const length = distance(isHorizontal ? scales.x : scales.y, first(row), last(row));
|
|
89
|
+
if (band === undefined || length === undefined)
|
|
90
|
+
return undefined;
|
|
91
|
+
const thickness = marker.thickness ?? band * (1 - Number(before ?? 0) - Number(after ?? 0));
|
|
92
|
+
return isHorizontal
|
|
93
|
+
? { width: length, height: thickness }
|
|
94
|
+
: { width: thickness, height: length };
|
|
95
|
+
};
|
|
96
|
+
const ends = { axis: isHorizontal ? 'x' : 'y', from: first, to: last };
|
|
97
|
+
return isHorizontal
|
|
98
|
+
? { box, size: box, ends, x: middleOf, y: category }
|
|
99
|
+
: { box, size: box, ends, x: category, y: middleOf };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { BoxScales } from '../../../types/markers/labels/box';
|
|
2
|
+
import type { CurveName } from '../../../types/layout/styles/curve';
|
|
3
|
+
import type { CurvedLabel, PathLabelConfig, PathShape } from '../../../types/markers/labels/path';
|
|
4
|
+
type CurvedMarker = {
|
|
5
|
+
data?: unknown[];
|
|
6
|
+
x?: unknown;
|
|
7
|
+
y?: unknown;
|
|
8
|
+
x1?: unknown;
|
|
9
|
+
x2?: unknown;
|
|
10
|
+
y1?: unknown;
|
|
11
|
+
y2?: unknown;
|
|
12
|
+
r?: unknown;
|
|
13
|
+
grouping?: unknown;
|
|
14
|
+
color?: unknown;
|
|
15
|
+
curve?: CurveName;
|
|
16
|
+
tension?: number;
|
|
17
|
+
isHorizontal?: boolean;
|
|
18
|
+
styles?: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The curves a marker writes its `'path'` labels along, in pixels: each line (one per `grouping`), the top or
|
|
22
|
+
* bottom edge of each area, or the rim of each dot, as its kind's {@link PathShape} tells it.
|
|
23
|
+
*/
|
|
24
|
+
export declare class CurveGeometry {
|
|
25
|
+
#private;
|
|
26
|
+
static build(shape: Exclude<PathShape, 'box'>, marker: CurvedMarker, label: PathLabelConfig<string, string>, plot: {
|
|
27
|
+
scales: BoxScales;
|
|
28
|
+
gap: number;
|
|
29
|
+
dotRadius?: number;
|
|
30
|
+
/** How wide a text is written, in pixels: a label centred near an end of its line is moved in to fit. */
|
|
31
|
+
measure?: (text: string) => number;
|
|
32
|
+
}): CurvedLabel[];
|
|
33
|
+
}
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { line } from 'd3-shape';
|
|
2
|
+
import { DotRadius } from '../../dot/DotRadius';
|
|
3
|
+
import { maybeCurve } from 'svelteplot/helpers/curves.js';
|
|
4
|
+
const read = (channel) => (row) => typeof channel === 'function'
|
|
5
|
+
? channel(row)
|
|
6
|
+
: typeof channel === 'string' || typeof channel === 'symbol'
|
|
7
|
+
? row?.[channel]
|
|
8
|
+
: channel;
|
|
9
|
+
const placed = (value) => value !== null && value !== undefined && !(typeof value === 'number' && Number.isNaN(value));
|
|
10
|
+
/**
|
|
11
|
+
* The curves a marker writes its `'path'` labels along, in pixels: each line (one per `grouping`), the top or
|
|
12
|
+
* bottom edge of each area, or the rim of each dot, as its kind's {@link PathShape} tells it.
|
|
13
|
+
*/
|
|
14
|
+
export class CurveGeometry {
|
|
15
|
+
static build(shape, marker, label, plot) {
|
|
16
|
+
const rows = (marker.data ?? []).filter((row) => row !== null && row !== undefined);
|
|
17
|
+
const { scales } = plot;
|
|
18
|
+
const gap = label.gap ?? plot.gap;
|
|
19
|
+
const at = (axis, value) => {
|
|
20
|
+
const scale = scales[axis];
|
|
21
|
+
if (!scale || !placed(value))
|
|
22
|
+
return undefined;
|
|
23
|
+
const band = scale.type === 'band' ? Number(scale.fn.bandwidth()) / 2 : 0;
|
|
24
|
+
const px = Number(scale.fn(value)) + band;
|
|
25
|
+
return Number.isFinite(px) ? px : undefined;
|
|
26
|
+
};
|
|
27
|
+
const textOf = CurveGeometry.#text(label.text);
|
|
28
|
+
const colorOf = CurveGeometry.#color(marker, scales);
|
|
29
|
+
if (shape === 'rim') {
|
|
30
|
+
const side = label.side ?? 'outside';
|
|
31
|
+
const [x, y] = [read(marker.x), read(marker.y)];
|
|
32
|
+
return rows.flatMap((row) => {
|
|
33
|
+
const [cx, cy] = [at('x', x(row)), at('y', y(row))];
|
|
34
|
+
const r = DotRadius.of(marker, row, scales, plot.dotRadius ?? 5);
|
|
35
|
+
if (cx === undefined || cy === undefined)
|
|
36
|
+
return [];
|
|
37
|
+
// From its bottom, clockwise: up its left, over its top - the side text reads upright on - and down its
|
|
38
|
+
// right, so a text centred on the top has half the circle free on either side.
|
|
39
|
+
const d = `M${cx},${cy + r}A${r},${r} 0 1 1 ${cx},${cy - r}A${r},${r} 0 1 1 ${cx},${cy + r}`;
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
d,
|
|
43
|
+
text: textOf(row),
|
|
44
|
+
row,
|
|
45
|
+
...CurveGeometry.#along(label.offset, 0.5, 0.25),
|
|
46
|
+
...CurveGeometry.#across(side === 'outside' ? 'above' : side === 'inside' ? 'below' : 'on', gap),
|
|
47
|
+
fill: colorOf(row)
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const [x, y] = CurveGeometry.#channels(shape, marker, label);
|
|
53
|
+
const groupOf = read(marker.grouping);
|
|
54
|
+
const groups = new Map();
|
|
55
|
+
for (const row of rows) {
|
|
56
|
+
const key = marker.grouping === undefined ? undefined : groupOf(row);
|
|
57
|
+
groups.set(key, [...(groups.get(key) ?? []), row]);
|
|
58
|
+
}
|
|
59
|
+
// Text along a straight stretch's corners spreads its letters apart: a linear line is read along a smooth one
|
|
60
|
+
// through the same points, which never overshoots them.
|
|
61
|
+
const curve = !marker.curve || marker.curve === 'linear' ? 'monotone-x' : marker.curve;
|
|
62
|
+
const path = line().curve(maybeCurve(curve, marker.tension ?? 0));
|
|
63
|
+
return [...groups.values()].flatMap((members) => {
|
|
64
|
+
const points = members.flatMap((row) => {
|
|
65
|
+
const [px, py] = [at('x', x(row)), at('y', y(row))];
|
|
66
|
+
return px === undefined || py === undefined ? [] : [[px, py]];
|
|
67
|
+
});
|
|
68
|
+
if (points.length < 2)
|
|
69
|
+
return [];
|
|
70
|
+
// Text reads left to right: a line drawn the other way is written along its reverse.
|
|
71
|
+
if (points[0][0] > points[points.length - 1][0])
|
|
72
|
+
points.reverse();
|
|
73
|
+
const d = path(points);
|
|
74
|
+
if (!d)
|
|
75
|
+
return [];
|
|
76
|
+
const offset = label.offset === 'max' || label.offset === 'min'
|
|
77
|
+
? CurveGeometry.#extreme(points, label.offset, marker.isHorizontal)
|
|
78
|
+
: label.offset;
|
|
79
|
+
const text = textOf(members[0]);
|
|
80
|
+
const along = CurveGeometry.#along(offset, 1);
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
d,
|
|
84
|
+
text,
|
|
85
|
+
row: members[0],
|
|
86
|
+
...along,
|
|
87
|
+
...CurveGeometry.#inside(along, plot.measure?.(text), CurveGeometry.#length(points)),
|
|
88
|
+
...CurveGeometry.#across(label.side ?? 'above', gap),
|
|
89
|
+
fill: colorOf(members[0])
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** A centred text moved in along its line just enough to fit whole on it: what falls past an end is not drawn. */
|
|
95
|
+
static #inside(along, width, length) {
|
|
96
|
+
if (!width || along.textAnchor !== 'middle' || length <= 0)
|
|
97
|
+
return {};
|
|
98
|
+
const half = Math.min(50, (width / 2 / length) * 100);
|
|
99
|
+
const at = Number.parseFloat(along.startOffset);
|
|
100
|
+
const fitted = Math.min(Math.max(at, half), 100 - half);
|
|
101
|
+
return fitted === at ? {} : { startOffset: `${Number(fitted.toFixed(3))}%` };
|
|
102
|
+
}
|
|
103
|
+
/** The length of the line through `points`, in pixels. */
|
|
104
|
+
static #length(points) {
|
|
105
|
+
let total = 0;
|
|
106
|
+
for (let i = 1; i < points.length; i++) {
|
|
107
|
+
total += Math.hypot(points[i][0] - points[i - 1][0], points[i][1] - points[i - 1][1]);
|
|
108
|
+
}
|
|
109
|
+
return total;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* How far along `points` - as a share of their length - the one with the highest (`'max'`) or lowest value is:
|
|
113
|
+
* the one highest up the page, or furthest right on a horizontal area.
|
|
114
|
+
*/
|
|
115
|
+
static #extreme(points, which, isHorizontal) {
|
|
116
|
+
const valueOf = ([px, py]) => (isHorizontal ? px : -py);
|
|
117
|
+
let at = 0;
|
|
118
|
+
points.forEach((point, i) => {
|
|
119
|
+
const better = which === 'max'
|
|
120
|
+
? valueOf(point) > valueOf(points[at])
|
|
121
|
+
: valueOf(point) < valueOf(points[at]);
|
|
122
|
+
if (better)
|
|
123
|
+
at = i;
|
|
124
|
+
});
|
|
125
|
+
let total = 0;
|
|
126
|
+
let upTo = 0;
|
|
127
|
+
for (let i = 1; i < points.length; i++) {
|
|
128
|
+
total += Math.hypot(points[i][0] - points[i - 1][0], points[i][1] - points[i - 1][1]);
|
|
129
|
+
if (i === at)
|
|
130
|
+
upTo = total;
|
|
131
|
+
}
|
|
132
|
+
return `${total > 0 ? (upTo / total) * 100 : 50}%`;
|
|
133
|
+
}
|
|
134
|
+
/** The channels the curve runs through: a line's own, or an area's top or bottom edge. */
|
|
135
|
+
static #channels(shape, marker, label) {
|
|
136
|
+
if (shape === 'stroke')
|
|
137
|
+
return [read(marker.x), read(marker.y)];
|
|
138
|
+
const top = (label.edge ?? 'top') === 'top';
|
|
139
|
+
if (marker.isHorizontal) {
|
|
140
|
+
const edge = top ? (marker.x2 ?? marker.x) : (marker.x1 ?? 0);
|
|
141
|
+
return [read(edge), read(marker.y)];
|
|
142
|
+
}
|
|
143
|
+
const edge = top ? (marker.y2 ?? marker.y) : (marker.y1 ?? 0);
|
|
144
|
+
return [read(marker.x), read(edge)];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Where along the curve, as `startOffset` and `text-anchor`: `offset` runs over the stretch of it that starts at
|
|
148
|
+
* `from` and spans `extent` - a rim's upper half, from its left side to its right.
|
|
149
|
+
*/
|
|
150
|
+
static #along(offset = 'middle', extent, from = 0) {
|
|
151
|
+
const share = (fraction) => `${Number(((from + fraction * extent) * 100).toFixed(3))}%`;
|
|
152
|
+
if (offset === 'start')
|
|
153
|
+
return { startOffset: share(0), textAnchor: 'start' };
|
|
154
|
+
if (offset === 'end')
|
|
155
|
+
return { startOffset: share(1), textAnchor: 'end' };
|
|
156
|
+
const percent = offset === 'middle' ? 50 : Number.parseFloat(offset);
|
|
157
|
+
const fraction = Number.isFinite(percent) ? Math.min(Math.max(percent / 100, 0), 1) : 0.5;
|
|
158
|
+
return { startOffset: share(fraction), textAnchor: 'middle' };
|
|
159
|
+
}
|
|
160
|
+
/** Which side of the curve, as the text reads it. */
|
|
161
|
+
static #across(side, gap) {
|
|
162
|
+
if (side === 'below')
|
|
163
|
+
return { dy: gap, baseline: 'hanging' };
|
|
164
|
+
if (side === 'on')
|
|
165
|
+
return { dy: 0, baseline: 'central' };
|
|
166
|
+
return { dy: -gap, baseline: 'auto' };
|
|
167
|
+
}
|
|
168
|
+
/** A label's text: a function of the row, a field of it, else the text itself. */
|
|
169
|
+
static #text(text) {
|
|
170
|
+
return (row) => {
|
|
171
|
+
if (typeof text === 'function')
|
|
172
|
+
return String(text(row) ?? '');
|
|
173
|
+
if (typeof text === 'string' && row && typeof row === 'object' && text in row) {
|
|
174
|
+
return String(row[text] ?? '');
|
|
175
|
+
}
|
|
176
|
+
return text === undefined || text === null ? '' : String(text);
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** The marker's own color for a row: its `color` channel's, else its stroke or fill when that is one color. */
|
|
180
|
+
static #color(marker, scales) {
|
|
181
|
+
const scale = scales.color;
|
|
182
|
+
if (marker.color !== undefined && scale) {
|
|
183
|
+
const color = read(marker.color);
|
|
184
|
+
return (row) => {
|
|
185
|
+
const value = scale.fn(color(row));
|
|
186
|
+
return typeof value === 'string' ? value : undefined;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const { stroke, fill, dotFill } = marker.styles ?? {};
|
|
190
|
+
const own = [stroke, fill, dotFill].find((value) => typeof value === 'string');
|
|
191
|
+
return () => own;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BoxShape, BoxedMarker } from '../../../types/markers/labels/box';
|
|
2
|
+
import type { PointGeometryOf, PointShape } from '../../../types/markers/labels/point';
|
|
3
|
+
/** The point each row of a marker is labelled at, as its kind's {@link PointShape} tells it. */
|
|
4
|
+
export declare class PointGeometry {
|
|
5
|
+
static of(shape: PointShape, marker: BoxedMarker, plot?: {
|
|
6
|
+
box?: BoxShape;
|
|
7
|
+
isHorizontal?: boolean;
|
|
8
|
+
}): PointGeometryOf | undefined;
|
|
9
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BoxGeometry } from './BoxGeometry';
|
|
2
|
+
import { DotRadius } from '../../dot/DotRadius';
|
|
3
|
+
/** The point each row of a marker is labelled at, as its kind's {@link PointShape} tells it. */
|
|
4
|
+
export class PointGeometry {
|
|
5
|
+
static of(shape, marker, plot = {}) {
|
|
6
|
+
if (shape === 'box') {
|
|
7
|
+
const geometry = plot.box && BoxGeometry.of(plot.box, marker, plot);
|
|
8
|
+
return geometry && { x: geometry.x, y: geometry.y, size: geometry.size };
|
|
9
|
+
}
|
|
10
|
+
if (shape === 'dot') {
|
|
11
|
+
if (marker.x === undefined || marker.y === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
const size = (row, scales) => {
|
|
14
|
+
const r = DotRadius.of(marker, row, scales);
|
|
15
|
+
return { width: 2 * r, height: 2 * r };
|
|
16
|
+
};
|
|
17
|
+
return { x: marker.x, y: marker.y, size, round: true };
|
|
18
|
+
}
|
|
19
|
+
const y = shape === 'top' ? (marker.y2 ?? marker.y) : marker.y;
|
|
20
|
+
return marker.x === undefined || y === undefined ? undefined : { x: marker.x, y };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BoxGeometryOf, BoxLabelConfig, BoxLabelMarker, BoxScales } from '../../../types/markers/labels/box';
|
|
2
|
+
/** The `text` marker that writes a `'box'` label, anchored in the box of each shape. */
|
|
3
|
+
export declare class BoxLayout {
|
|
4
|
+
#private;
|
|
5
|
+
static build(label: BoxLabelConfig, geometry: BoxGeometryOf, plot?: {
|
|
6
|
+
data?: unknown[];
|
|
7
|
+
scales?: () => BoxScales | undefined;
|
|
8
|
+
gap?: number;
|
|
9
|
+
}): BoxLabelMarker;
|
|
10
|
+
}
|