@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,49 @@
|
|
|
1
|
+
/** The `text` marker that writes a `'box'` label, anchored in the box of each shape. */
|
|
2
|
+
export class BoxLayout {
|
|
3
|
+
static build(label, geometry, plot = {}) {
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
5
|
+
const { anchor, gap, layout, ...text } = label;
|
|
6
|
+
const placed = BoxLayout.#anchored({ anchor, gap: gap ?? plot.gap }, text, geometry.size, plot.scales);
|
|
7
|
+
return {
|
|
8
|
+
box: geometry.box,
|
|
9
|
+
marker: {
|
|
10
|
+
fit: true,
|
|
11
|
+
...text,
|
|
12
|
+
...placed,
|
|
13
|
+
type: 'text',
|
|
14
|
+
data: plot.data,
|
|
15
|
+
x: geometry.x,
|
|
16
|
+
y: geometry.y
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** The offset (per row, as each box has its own size) and text anchors that put a label at `anchor`. */
|
|
21
|
+
static #anchored(where, label, size, scales) {
|
|
22
|
+
const { anchor = 'middle' } = where;
|
|
23
|
+
if (anchor === 'middle' || !size)
|
|
24
|
+
return {};
|
|
25
|
+
const gap = where.gap ?? 4;
|
|
26
|
+
const horizontal = anchor.endsWith('left') ? -1 : anchor.endsWith('right') ? 1 : 0;
|
|
27
|
+
const vertical = anchor.startsWith('top') ? -1 : anchor.startsWith('bottom') ? 1 : 0;
|
|
28
|
+
const move = (axis, side, row) => {
|
|
29
|
+
// Read at draw time: the scales move as the labels register.
|
|
30
|
+
const current = scales?.();
|
|
31
|
+
const box = current ? size(row, current) : undefined;
|
|
32
|
+
return box ? side * (box[axis] / 2 - gap) : 0;
|
|
33
|
+
};
|
|
34
|
+
const own = (value, fallback) => Number(typeof value === 'number' ? value : (fallback ?? 0));
|
|
35
|
+
const baseX = own(label.dx, label.styles?.dx);
|
|
36
|
+
const baseY = own(label.dy, label.styles?.dy);
|
|
37
|
+
return {
|
|
38
|
+
dx: horizontal === 0 ? label.dx : (row) => baseX + move('width', horizontal, row),
|
|
39
|
+
dy: vertical === 0 ? label.dy : (row) => baseY + move('height', vertical, row),
|
|
40
|
+
styles: {
|
|
41
|
+
...label.styles,
|
|
42
|
+
textAnchor: label.styles?.textAnchor ??
|
|
43
|
+
(horizontal === 0 ? 'middle' : horizontal === 1 ? 'end' : 'start'),
|
|
44
|
+
lineAnchor: label.styles?.lineAnchor ??
|
|
45
|
+
(vertical === 0 ? 'middle' : vertical === 1 ? 'bottom' : 'top')
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BoxGeometryOf, BoxLabelMarker, BoxScales } from '../../../types/markers/labels/box';
|
|
2
|
+
import type { PathLabelConfig } from '../../../types/markers/labels/path';
|
|
3
|
+
/** The `text` marker that writes a `'path'` label along an edge of each shape's box. */
|
|
4
|
+
export declare class PathLayout {
|
|
5
|
+
#private;
|
|
6
|
+
static build(label: PathLabelConfig, geometry: BoxGeometryOf, plot?: {
|
|
7
|
+
data?: unknown[];
|
|
8
|
+
scales?: () => BoxScales | undefined;
|
|
9
|
+
gap?: number;
|
|
10
|
+
}): BoxLabelMarker | undefined;
|
|
11
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Each page edge's outward direction. */
|
|
2
|
+
const NORMALS = {
|
|
3
|
+
top: [0, -1],
|
|
4
|
+
bottom: [0, 1],
|
|
5
|
+
left: [-1, 0],
|
|
6
|
+
right: [1, 0]
|
|
7
|
+
};
|
|
8
|
+
const OPPOSITE = {
|
|
9
|
+
top: 'bottom',
|
|
10
|
+
bottom: 'top',
|
|
11
|
+
left: 'right',
|
|
12
|
+
right: 'left'
|
|
13
|
+
};
|
|
14
|
+
/** The `text` marker that writes a `'path'` label along an edge of each shape's box. */
|
|
15
|
+
export class PathLayout {
|
|
16
|
+
static build(label, geometry, plot = {}) {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
18
|
+
const { layout, edge, side = 'outside', offset = 'middle', gap, ...text } = label;
|
|
19
|
+
const { size, ends } = geometry;
|
|
20
|
+
if (!size)
|
|
21
|
+
return undefined;
|
|
22
|
+
const sign = side === 'outside' ? 1 : side === 'inside' ? -1 : 0;
|
|
23
|
+
const space = sign * (gap ?? plot.gap ?? 4);
|
|
24
|
+
const along = PathLayout.#fraction(offset);
|
|
25
|
+
const own = (value, fallback) => Number(typeof value === 'number' ? value : (fallback ?? 0));
|
|
26
|
+
const [baseX, baseY] = [own(text.dx, text.styles?.dx), own(text.dy, text.styles?.dy)];
|
|
27
|
+
// Read at draw time: the scales move as the labels register.
|
|
28
|
+
const placed = (row) => {
|
|
29
|
+
const scales = plot.scales?.();
|
|
30
|
+
const box = scales ? size(row, scales) : undefined;
|
|
31
|
+
const page = PathLayout.#pageEdge(edge ?? (ends ? 'tip' : 'top'), ends, row, scales);
|
|
32
|
+
const [h, v] = NORMALS[page];
|
|
33
|
+
const [width, height] = [box?.width ?? 0, box?.height ?? 0];
|
|
34
|
+
const across = (extent) => extent / 2 + space;
|
|
35
|
+
const end = (from, to) => along === 0 ? from : along === 1 ? to : 'middle';
|
|
36
|
+
const out = (towards, before, after) => sign === 0 ? 'middle' : towards * sign > 0 ? after : before;
|
|
37
|
+
return v !== 0
|
|
38
|
+
? {
|
|
39
|
+
dx: (along - 0.5) * width,
|
|
40
|
+
dy: v * across(height),
|
|
41
|
+
textAnchor: end('start', 'end'),
|
|
42
|
+
lineAnchor: out(v, 'bottom', 'top')
|
|
43
|
+
}
|
|
44
|
+
: {
|
|
45
|
+
dx: h * across(width),
|
|
46
|
+
dy: (along - 0.5) * height,
|
|
47
|
+
textAnchor: out(h, 'end', 'start'),
|
|
48
|
+
lineAnchor: end('top', 'bottom')
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
const styles = text.styles ?? {};
|
|
52
|
+
return {
|
|
53
|
+
box: geometry.box,
|
|
54
|
+
marker: {
|
|
55
|
+
fit: false,
|
|
56
|
+
...text,
|
|
57
|
+
dx: (row) => baseX + placed(row).dx,
|
|
58
|
+
dy: (row) => baseY + placed(row).dy,
|
|
59
|
+
...(styles.textAnchor ? {} : { textAnchor: (row) => placed(row).textAnchor }),
|
|
60
|
+
...(styles.lineAnchor ? {} : { lineAnchor: (row) => placed(row).lineAnchor }),
|
|
61
|
+
type: 'text',
|
|
62
|
+
data: plot.data,
|
|
63
|
+
x: geometry.x,
|
|
64
|
+
y: geometry.y
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** How far along the edge, from its left or top end. */
|
|
69
|
+
static #fraction(offset) {
|
|
70
|
+
if (offset === 'start')
|
|
71
|
+
return 0;
|
|
72
|
+
if (offset === 'end')
|
|
73
|
+
return 1;
|
|
74
|
+
if (offset === 'middle')
|
|
75
|
+
return 0.5;
|
|
76
|
+
const percent = Number.parseFloat(offset);
|
|
77
|
+
return Number.isFinite(percent) ? Math.min(Math.max(percent / 100, 0), 1) : 0.5;
|
|
78
|
+
}
|
|
79
|
+
/** The page edge a row's `edge` is: its tip is the end of the shape its value runs to. */
|
|
80
|
+
static #pageEdge(edge, ends, row, scales) {
|
|
81
|
+
if (edge !== 'tip' && edge !== 'base')
|
|
82
|
+
return edge;
|
|
83
|
+
const scale = ends && scales?.[ends.axis];
|
|
84
|
+
const [from, to] = scale
|
|
85
|
+
? [Number(scale.fn(ends.from(row))), Number(scale.fn(ends.to(row)))]
|
|
86
|
+
: [0, 0];
|
|
87
|
+
// Pixels grow rightward and downward: a value up the page runs to a smaller `y`.
|
|
88
|
+
const tip = ends?.axis === 'x' ? (to >= from ? 'right' : 'left') : to <= from ? 'top' : 'bottom';
|
|
89
|
+
return edge === 'tip' ? tip : OPPOSITE[tip];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TextMarkerConfig } from '../../../types/markers/basic/text';
|
|
2
|
+
import type { PointGeometryOf, PointLabelConfig, PointSide } from '../../../types/markers/labels/point';
|
|
3
|
+
import type { LineAnchor, TextAnchor } from '../../../types/plots/constants';
|
|
4
|
+
/** The `text` marker that writes a `'point'` label beside the points it selects. */
|
|
5
|
+
export declare class PointLayout {
|
|
6
|
+
#private;
|
|
7
|
+
static build(label: PointLabelConfig, point: PointGeometryOf, rows: unknown[], plot?: {
|
|
8
|
+
grouping?: unknown;
|
|
9
|
+
valueAxis?: 'x' | 'y';
|
|
10
|
+
gap?: number;
|
|
11
|
+
}): TextMarkerConfig | undefined;
|
|
12
|
+
/** The offset and anchors that write a label on `side` of its point, `gap` from it. */
|
|
13
|
+
static sideOf(side: PointSide, gap?: number): {
|
|
14
|
+
dx: number;
|
|
15
|
+
dy: number;
|
|
16
|
+
textAnchor: TextAnchor;
|
|
17
|
+
lineAnchor: LineAnchor;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ValueSelection } from '../../../phases/transforms/ValueSelection';
|
|
2
|
+
const read = (channel) => (row) => typeof channel === 'function'
|
|
3
|
+
? channel(row)
|
|
4
|
+
: typeof channel === 'string' || typeof channel === 'symbol'
|
|
5
|
+
? row?.[channel]
|
|
6
|
+
: channel;
|
|
7
|
+
const placed = (value) => value !== null && value !== undefined && !(typeof value === 'number' && Number.isNaN(value));
|
|
8
|
+
/** The `text` marker that writes a `'point'` label beside the points it selects. */
|
|
9
|
+
export class PointLayout {
|
|
10
|
+
static build(label, point, rows, plot = {}) {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
12
|
+
const { layout, select, anchor, side, gap, ...text } = label;
|
|
13
|
+
const [x, y] = [read(point.x), read(point.y)];
|
|
14
|
+
const placeable = rows.filter((row) => placed(x(row)) && placed(y(row)));
|
|
15
|
+
const data = select === undefined
|
|
16
|
+
? placeable
|
|
17
|
+
: typeof select === 'function'
|
|
18
|
+
? placeable.filter(select)
|
|
19
|
+
: ValueSelection.pick(placeable, ValueSelection.transforms(select, undefined, plot.valueAxis ?? 'y'), { x, y, group: plot.grouping ?? (() => 0) });
|
|
20
|
+
if (data.length === 0)
|
|
21
|
+
return undefined;
|
|
22
|
+
const onBox = point.size && anchor && anchor !== 'middle'
|
|
23
|
+
? PointLayout.#onBox(text, point.size, anchor, gap ?? plot.gap ?? 4)
|
|
24
|
+
: side
|
|
25
|
+
? { styles: { ...PointLayout.sideOf(side, gap), ...PointLayout.#defined(text.styles) } }
|
|
26
|
+
: {};
|
|
27
|
+
// A label on its own point is read in its shape: one drawn over it hides it.
|
|
28
|
+
const { size, round } = point;
|
|
29
|
+
const onPoint = !(size && anchor && anchor !== 'middle') && (side === undefined || side === 'on');
|
|
30
|
+
const covers = onPoint && size
|
|
31
|
+
? (row, scales) => {
|
|
32
|
+
const shape = size(row, scales);
|
|
33
|
+
return shape && { ...shape, round };
|
|
34
|
+
}
|
|
35
|
+
: undefined;
|
|
36
|
+
// A point shifted off its `x`/`y` (a series' slot of its band) carries its label, and its anchor's offset, along.
|
|
37
|
+
const anchored = onBox.origin;
|
|
38
|
+
const origin = point.shift &&
|
|
39
|
+
((row, scales) => {
|
|
40
|
+
const shift = point.shift(row, scales) ?? { dx: 0, dy: 0 };
|
|
41
|
+
const own = anchored?.(row, scales) ?? { dx: 0, dy: 0 };
|
|
42
|
+
return { dx: shift.dx + own.dx, dy: shift.dy + own.dy };
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
...text,
|
|
46
|
+
...onBox,
|
|
47
|
+
...(origin ? { origin } : {}),
|
|
48
|
+
...(covers ? { covers } : {}),
|
|
49
|
+
type: 'text',
|
|
50
|
+
data,
|
|
51
|
+
x: point.x,
|
|
52
|
+
y: point.y
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
static #defined(styles) {
|
|
56
|
+
return Object.fromEntries(Object.entries(styles ?? {}).filter(([, value]) => value !== undefined));
|
|
57
|
+
}
|
|
58
|
+
/** The offset and anchors that write a label on `side` of its point, `gap` from it. */
|
|
59
|
+
static sideOf(side, gap) {
|
|
60
|
+
const across = gap ?? 6;
|
|
61
|
+
const along = gap ?? 8;
|
|
62
|
+
switch (side) {
|
|
63
|
+
case 'after':
|
|
64
|
+
return { dx: across, dy: 0, textAnchor: 'start', lineAnchor: 'middle' };
|
|
65
|
+
case 'before':
|
|
66
|
+
return { dx: -across, dy: 0, textAnchor: 'end', lineAnchor: 'middle' };
|
|
67
|
+
case 'above':
|
|
68
|
+
return { dx: 0, dy: -along, textAnchor: 'middle', lineAnchor: 'bottom' };
|
|
69
|
+
case 'below':
|
|
70
|
+
return { dx: 0, dy: along, textAnchor: 'middle', lineAnchor: 'top' };
|
|
71
|
+
default:
|
|
72
|
+
return { dx: 0, dy: 0, textAnchor: 'middle', lineAnchor: 'middle' };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Starts each label at `anchor` on its box, written away from it. */
|
|
76
|
+
static #onBox(text, size, anchor, gap) {
|
|
77
|
+
const horizontal = anchor.endsWith('left') ? -1 : anchor.endsWith('right') ? 1 : 0;
|
|
78
|
+
const vertical = anchor.startsWith('top') ? -1 : anchor.startsWith('bottom') ? 1 : 0;
|
|
79
|
+
const styles = text.styles ?? {};
|
|
80
|
+
return {
|
|
81
|
+
origin: (row, scales) => {
|
|
82
|
+
const box = size(row, scales);
|
|
83
|
+
return box && { dx: (horizontal * box.width) / 2, dy: (vertical * box.height) / 2 };
|
|
84
|
+
},
|
|
85
|
+
styles: {
|
|
86
|
+
...styles,
|
|
87
|
+
dx: styles.dx ?? horizontal * gap,
|
|
88
|
+
dy: styles.dy ?? vertical * gap,
|
|
89
|
+
textAnchor: styles.textAnchor ?? ['end', 'middle', 'start'][horizontal + 1],
|
|
90
|
+
lineAnchor: styles.lineAnchor ?? ['bottom', 'middle', 'top'][vertical + 1]
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
const events = MarkerEvents.use(() => marker);
|
|
23
23
|
|
|
24
24
|
const plot = usePlot();
|
|
25
|
-
const isHorizontal = $derived(plot.axes?.isHorizontal ?? false);
|
|
25
|
+
const isHorizontal = $derived(marker.isHorizontal ?? plot.axes?.isHorizontal ?? false);
|
|
26
26
|
const RegressionMark = $derived(isHorizontal ? RegressionX : RegressionY);
|
|
27
27
|
|
|
28
28
|
const data = $derived(marker.data ?? []);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Box } from '../../layout/margins/Box';
|
|
2
|
+
/** A row's shape in pixels: its box, or the circle in it. */
|
|
3
|
+
type Shape = {
|
|
4
|
+
box: Box;
|
|
5
|
+
round?: boolean;
|
|
6
|
+
};
|
|
7
|
+
/** Which labels the shapes drawn after their own row cover, as a dot drawn over another hides that one's label. */
|
|
8
|
+
export declare class Covering {
|
|
9
|
+
#private;
|
|
10
|
+
/** The indices of `labels` some shape of a later row meets; shapes and labels are in draw order, one per row. */
|
|
11
|
+
static hidden(labels: Box[], shapes: (Shape | undefined)[]): Set<number>;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Which labels the shapes drawn after their own row cover, as a dot drawn over another hides that one's label. */
|
|
2
|
+
export class Covering {
|
|
3
|
+
/** The indices of `labels` some shape of a later row meets; shapes and labels are in draw order, one per row. */
|
|
4
|
+
static hidden(labels, shapes) {
|
|
5
|
+
const hidden = new Set();
|
|
6
|
+
labels.forEach((label, i) => {
|
|
7
|
+
for (let j = i + 1; j < shapes.length; j++) {
|
|
8
|
+
const shape = shapes[j];
|
|
9
|
+
if (shape && Covering.#meets(shape, label)) {
|
|
10
|
+
hidden.add(i);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return hidden;
|
|
16
|
+
}
|
|
17
|
+
static #meets({ box, round }, label) {
|
|
18
|
+
if (!label.intersects(box))
|
|
19
|
+
return false;
|
|
20
|
+
if (!round)
|
|
21
|
+
return true;
|
|
22
|
+
// The circle meets the label where the label's nearest point to its center is within its radius.
|
|
23
|
+
const r = Math.min(box.width, box.height) / 2;
|
|
24
|
+
const [cx, cy] = [box.x + box.width / 2, box.y + box.height / 2];
|
|
25
|
+
const nx = Math.min(Math.max(cx, label.left), label.right);
|
|
26
|
+
const ny = Math.min(Math.max(cy, label.top), label.bottom);
|
|
27
|
+
return (nx - cx) ** 2 + (ny - cy) ** 2 < r * r;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,11 +1,32 @@
|
|
|
1
|
+
import { Box } from '../../layout/margins/Box';
|
|
1
2
|
import type { LabelLane } from '../../types/markers/basic/text';
|
|
3
|
+
import type { BoxAnchors, BoxFlip } from '../../types/layout/box';
|
|
2
4
|
/**
|
|
3
|
-
* Lays out point labels that would overlap: spreads their pixel `y`
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* Lays out point labels that would overlap: either spreads their pixel `y`
|
|
6
|
+
* apart into lanes ({@link Declutter.buildLanes}), or puts each one on a free
|
|
7
|
+
* spot around its point ({@link Declutter.around}).
|
|
6
8
|
*/
|
|
7
9
|
export declare class Declutter {
|
|
8
10
|
#private;
|
|
11
|
+
/**
|
|
12
|
+
* Each label on the first spot around its point - its `preferred` one, then on clockwise -
|
|
13
|
+
* that overlaps no label placed before it nor any point, inside `bounds`.
|
|
14
|
+
* A label with no free spot is left out of the map: it is not drawn.
|
|
15
|
+
*/
|
|
16
|
+
static around<K>(args: {
|
|
17
|
+
items: {
|
|
18
|
+
key: K;
|
|
19
|
+
px: number;
|
|
20
|
+
py: number;
|
|
21
|
+
box: Box;
|
|
22
|
+
}[];
|
|
23
|
+
preferred: BoxAnchors;
|
|
24
|
+
/** Distance (px) from the point to the label. */
|
|
25
|
+
gap: number;
|
|
26
|
+
bounds: Box;
|
|
27
|
+
/** Room (px) kept clear around every point. */
|
|
28
|
+
clearance?: number;
|
|
29
|
+
}): Map<K, BoxFlip>;
|
|
9
30
|
/**
|
|
10
31
|
* Lays out labels keyed by each item's `key`; fewer than two need none. A
|
|
11
32
|
* leftward fan with no room before the content box's left edge (the Y
|
|
@@ -1,10 +1,53 @@
|
|
|
1
|
+
import { Box } from '../../layout/margins/Box';
|
|
1
2
|
const clamp = (value, lo, hi) => Math.min(Math.max(value, lo), hi);
|
|
3
|
+
/** The spots around a point, clockwise from above it. */
|
|
4
|
+
const AROUND = [
|
|
5
|
+
{ x: 'middle', y: 'before' },
|
|
6
|
+
{ x: 'after', y: 'before' },
|
|
7
|
+
{ x: 'after', y: 'middle' },
|
|
8
|
+
{ x: 'after', y: 'after' },
|
|
9
|
+
{ x: 'middle', y: 'after' },
|
|
10
|
+
{ x: 'before', y: 'after' },
|
|
11
|
+
{ x: 'before', y: 'middle' },
|
|
12
|
+
{ x: 'before', y: 'before' }
|
|
13
|
+
];
|
|
2
14
|
/**
|
|
3
|
-
* Lays out point labels that would overlap: spreads their pixel `y`
|
|
4
|
-
*
|
|
5
|
-
*
|
|
15
|
+
* Lays out point labels that would overlap: either spreads their pixel `y`
|
|
16
|
+
* apart into lanes ({@link Declutter.buildLanes}), or puts each one on a free
|
|
17
|
+
* spot around its point ({@link Declutter.around}).
|
|
6
18
|
*/
|
|
7
19
|
export class Declutter {
|
|
20
|
+
/**
|
|
21
|
+
* Each label on the first spot around its point - its `preferred` one, then on clockwise -
|
|
22
|
+
* that overlaps no label placed before it nor any point, inside `bounds`.
|
|
23
|
+
* A label with no free spot is left out of the map: it is not drawn.
|
|
24
|
+
*/
|
|
25
|
+
static around(args) {
|
|
26
|
+
const { items, preferred, gap, bounds, clearance = 3 } = args;
|
|
27
|
+
// Its own spot first, then on clockwise; a label on the point itself tries it, then goes round from above.
|
|
28
|
+
const first = AROUND.findIndex((spot) => spot.x === preferred.x && spot.y === preferred.y);
|
|
29
|
+
const ring = [...AROUND.slice(Math.max(first, 0)), ...AROUND.slice(0, Math.max(first, 0))];
|
|
30
|
+
const spots = first < 0 ? [preferred, ...ring] : ring;
|
|
31
|
+
const taken = items.map(({ px, py }) => new Box(px - clearance, py - clearance, 2 * clearance, 2 * clearance));
|
|
32
|
+
const placed = [];
|
|
33
|
+
const out = new Map();
|
|
34
|
+
items.forEach(({ key, px, py, box }, index) => {
|
|
35
|
+
const clashes = (at) => placed.some((other) => at.intersects(other)) ||
|
|
36
|
+
taken.some((other, i) => i !== index && at.intersects(other));
|
|
37
|
+
for (const spot of spots) {
|
|
38
|
+
const corner = spot.x !== 'middle' && spot.y !== 'middle';
|
|
39
|
+
const space = corner ? gap * Math.SQRT1_2 : gap;
|
|
40
|
+
const at = box.beside({ x: px, y: py }, spot, { x: space, y: space });
|
|
41
|
+
if (!at.inside(bounds) || clashes(at))
|
|
42
|
+
continue;
|
|
43
|
+
placed.push(at);
|
|
44
|
+
const offset = (side) => side === 'after' ? space : side === 'before' ? -space : 0;
|
|
45
|
+
out.set(key, { anchor: spot, dx: offset(spot.x), dy: offset(spot.y) });
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
8
51
|
/**
|
|
9
52
|
* Lays out labels keyed by each item's `key`; fewer than two need none. A
|
|
10
53
|
* leftward fan with no room before the content box's left edge (the Y
|
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
import { RowOffset } from '../utils/RowOffset';
|
|
9
9
|
import RowOffsetMarks from '../utils/RowOffsetMarks.svelte';
|
|
10
10
|
import { Declutter } from './Declutter';
|
|
11
|
+
import { Covering } from './Covering';
|
|
11
12
|
import { SvelteMap } from 'svelte/reactivity';
|
|
12
13
|
import { TextFit, type FittedText } from './TextFit';
|
|
13
14
|
import { NumberFormat } from '../../utils/NumberFormat';
|
|
14
15
|
import { PlotMargins } from '../../layout/margins/PlotMargins.svelte';
|
|
15
16
|
import { Box } from '../../layout/margins/Box';
|
|
16
17
|
import type { TextMarkerConfig } from '../../types/markers/common';
|
|
17
|
-
import type { BoxChannels, BoxSizer } from '
|
|
18
|
+
import type { BoxChannels, BoxSizer } from '../../types/markers/labels/box';
|
|
18
19
|
import type { TextAnchor } from '../../types/plots/constants';
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -77,7 +78,13 @@
|
|
|
77
78
|
: (marker.textAnchor ?? styles?.textAnchor)
|
|
78
79
|
)
|
|
79
80
|
);
|
|
80
|
-
const lineAnchor = $derived(
|
|
81
|
+
const lineAnchor = $derived(
|
|
82
|
+
(typeof marker.lineAnchor === 'function' ? undefined : marker.lineAnchor) ??
|
|
83
|
+
styles?.lineAnchor ??
|
|
84
|
+
'middle'
|
|
85
|
+
);
|
|
86
|
+
const lineAnchorAt = (d: TData) =>
|
|
87
|
+
typeof marker.lineAnchor === 'function' ? marker.lineAnchor(d) : lineAnchor;
|
|
81
88
|
// A label in a slice of its band sits off the band's middle, at its drawn width.
|
|
82
89
|
const nudge = $derived(BandNudge.of(plot.scales, marker.bandSlice));
|
|
83
90
|
// The fixed offset, which `declutter` and `edgeAware` place labels by; a function of the row moves each label after that, through `RowOffsetMarks`.
|
|
@@ -92,9 +99,15 @@
|
|
|
92
99
|
|
|
93
100
|
const declutter = $derived.by(() => {
|
|
94
101
|
if (!marker.declutter) return undefined;
|
|
95
|
-
const own =
|
|
102
|
+
const own =
|
|
103
|
+
marker.declutter === true
|
|
104
|
+
? {}
|
|
105
|
+
: typeof marker.declutter === 'string'
|
|
106
|
+
? { mode: marker.declutter }
|
|
107
|
+
: marker.declutter;
|
|
96
108
|
const defaults = cfg.text.declutter;
|
|
97
109
|
return {
|
|
110
|
+
mode: own.mode ?? 'lanes',
|
|
98
111
|
elbowGap: own.elbowGap ?? defaults.elbowGap,
|
|
99
112
|
laneStep: own.laneStep ?? defaults.laneStep,
|
|
100
113
|
labelGap: own.labelGap ?? defaults.labelGap,
|
|
@@ -104,8 +117,16 @@
|
|
|
104
117
|
};
|
|
105
118
|
});
|
|
106
119
|
const perRow = $derived(
|
|
107
|
-
!!declutter ||
|
|
120
|
+
!!declutter ||
|
|
121
|
+
!!marker.edgeAware ||
|
|
122
|
+
!!marker.origin ||
|
|
123
|
+
events.any ||
|
|
124
|
+
!!marker.covers ||
|
|
125
|
+
typeof marker.textAnchor === 'function' ||
|
|
126
|
+
typeof marker.lineAnchor === 'function'
|
|
108
127
|
);
|
|
128
|
+
/** Where a row's point sits off its `x`/`y`. */
|
|
129
|
+
const originOf = (d: TData) => marker.origin?.(d, plot.scales) ?? { dx: 0, dy: 0 };
|
|
109
130
|
const dir = $derived(Math.sign(dx) || 1);
|
|
110
131
|
|
|
111
132
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -232,21 +253,24 @@
|
|
|
232
253
|
const points = $derived.by(() => {
|
|
233
254
|
const xScale = plot.scales.x?.fn;
|
|
234
255
|
const yScale = plot.scales.y?.fn;
|
|
235
|
-
if ((!declutter && !marker.edgeAware) || !xScale || !yScale) return undefined;
|
|
256
|
+
if ((!declutter && !marker.edgeAware && !marker.covers) || !xScale || !yScale) return undefined;
|
|
236
257
|
const xOf = channel(x ?? 0);
|
|
237
258
|
const yOf = channel(y ?? 0);
|
|
238
259
|
// A band's position is its start; a mark sits at its middle.
|
|
239
260
|
const middle = (scale: typeof plot.scales.x) =>
|
|
240
261
|
scale?.type === 'band' ? scale.fn.bandwidth() / 2 : 0;
|
|
241
262
|
const [dx0, dy0] = [middle(plot.scales.x), middle(plot.scales.y)];
|
|
242
|
-
return displayData.map((d) =>
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
263
|
+
return displayData.map((d) => {
|
|
264
|
+
const origin = originOf(d);
|
|
265
|
+
return {
|
|
266
|
+
px: Number(xScale(xOf(d) as never)) + dx0 + origin.dx,
|
|
267
|
+
py: Number(yScale(yOf(d) as never)) + dy0 + origin.dy
|
|
268
|
+
};
|
|
269
|
+
});
|
|
246
270
|
});
|
|
247
271
|
|
|
248
272
|
const lanes = $derived(
|
|
249
|
-
declutter && points && bounds
|
|
273
|
+
declutter?.mode === 'lanes' && points && bounds
|
|
250
274
|
? Declutter.buildLanes({
|
|
251
275
|
items: points.map((p, key) => ({ key, ...p })),
|
|
252
276
|
dx,
|
|
@@ -259,8 +283,56 @@
|
|
|
259
283
|
: undefined
|
|
260
284
|
);
|
|
261
285
|
|
|
262
|
-
/**
|
|
286
|
+
/** The rows whose label a shape drawn after its own row covers: their labels are left out. */
|
|
287
|
+
const covered = $derived.by(() => {
|
|
288
|
+
const covers = marker.covers;
|
|
289
|
+
if (!covers || !points) return undefined;
|
|
290
|
+
const shapes = displayData.map((d, i) => {
|
|
291
|
+
const shape = covers(d, plot.scales);
|
|
292
|
+
if (!shape) return undefined;
|
|
293
|
+
const { px, py } = points[i];
|
|
294
|
+
const { width, height, round } = shape;
|
|
295
|
+
return { box: new Box(px - width / 2, py - height / 2, width, height), round };
|
|
296
|
+
});
|
|
297
|
+
const labels = displayData.map((d, i) =>
|
|
298
|
+
Box.ofText(textOf(d), fontSize, cfg).beside(
|
|
299
|
+
{ x: points[i].px + dx, y: points[i].py + dy },
|
|
300
|
+
{ x: TEXT_SIDES[anchorAt(d)], y: LINE_SIDES[lineAnchorAt(d)] }
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
return Covering.hidden(labels, shapes);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/** Each row's spot around its point, `null` for a row with none free. */
|
|
307
|
+
const around = $derived.by(() => {
|
|
308
|
+
if (declutter?.mode !== 'around' || !points) return undefined;
|
|
309
|
+
const within = bounds ?? Box.size(plot.plotWidth, plot.plotHeight);
|
|
310
|
+
const spots = Declutter.around({
|
|
311
|
+
items: points.map((p, key) => ({
|
|
312
|
+
key,
|
|
313
|
+
...p,
|
|
314
|
+
box: Box.ofText(textOf(displayData[key]), fontSize, cfg)
|
|
315
|
+
})),
|
|
316
|
+
preferred: { x: TEXT_SIDES[textAnchor], y: LINE_SIDES[lineAnchor] },
|
|
317
|
+
gap: Math.max(Math.abs(dx), Math.abs(dy)) || declutter.labelGap,
|
|
318
|
+
bounds: within
|
|
319
|
+
});
|
|
320
|
+
return displayData.map((_, i) => {
|
|
321
|
+
const spot = spots.get(i);
|
|
322
|
+
return spot
|
|
323
|
+
? {
|
|
324
|
+
dx: spot.dx,
|
|
325
|
+
dy: spot.dy,
|
|
326
|
+
textAnchor: TEXT_ANCHORS[spot.anchor.x],
|
|
327
|
+
lineAnchor: LINE_ANCHORS[spot.anchor.y]
|
|
328
|
+
}
|
|
329
|
+
: null;
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
/** Each row's anchor and offset: on its spot around its point, else flipped where its text would cross the content box's edge. */
|
|
263
334
|
const placements = $derived.by(() => {
|
|
335
|
+
if (around) return around;
|
|
264
336
|
if (!marker.edgeAware || !points) return undefined;
|
|
265
337
|
const within = bounds ?? Box.size(plot.plotWidth, plot.plotHeight);
|
|
266
338
|
const anchor = { x: TEXT_SIDES[textAnchor], y: LINE_SIDES[lineAnchor] };
|
|
@@ -342,12 +414,13 @@
|
|
|
342
414
|
{:else if (!declutter && !marker.edgeAware) || points}
|
|
343
415
|
<!-- A decluttered or edge-aware label waits for both scales. -->
|
|
344
416
|
{#each displayData as d, i (i)}
|
|
345
|
-
{#if !fitted || fitted.get(d)}
|
|
417
|
+
{#if (!fitted || fitted.get(d)) && placements?.[i] !== null && !covered?.has(i)}
|
|
346
418
|
{@const lane = lanes?.get(i)}
|
|
347
419
|
{@const placement = placements?.[i]}
|
|
348
420
|
{@const extraDy = lane?.extraDy ?? 0}
|
|
349
421
|
{@const laneDir = lane?.aligned ? lane.dir : dir}
|
|
350
422
|
{@const labelDx = lane?.aligned ? laneDir * lane.reach : dx}
|
|
423
|
+
{@const origin = originOf(d)}
|
|
351
424
|
{#if declutter && points && (lane?.aligned || extraDy !== 0)}
|
|
352
425
|
{@const { px, py } = points[i]}
|
|
353
426
|
{@const laneX = lane?.aligned ? px + laneDir * lane.laneReach : px + labelDx}
|
|
@@ -370,18 +443,18 @@
|
|
|
370
443
|
{#if placement}
|
|
371
444
|
{@render label(
|
|
372
445
|
rows,
|
|
373
|
-
placement.dx,
|
|
374
|
-
placement.dy + extraDy,
|
|
446
|
+
placement.dx + origin.dx,
|
|
447
|
+
placement.dy + extraDy + origin.dy,
|
|
375
448
|
placement.textAnchor,
|
|
376
449
|
placement.lineAnchor
|
|
377
450
|
)}
|
|
378
451
|
{:else}
|
|
379
452
|
{@render label(
|
|
380
453
|
rows,
|
|
381
|
-
labelDx,
|
|
382
|
-
dy + extraDy,
|
|
454
|
+
labelDx + origin.dx,
|
|
455
|
+
dy + extraDy + origin.dy,
|
|
383
456
|
lane?.aligned && laneDir !== dir ? flipAnchor(anchorAt(d)) : anchorAt(d),
|
|
384
|
-
|
|
457
|
+
lineAnchorAt(d)
|
|
385
458
|
)}
|
|
386
459
|
{/if}
|
|
387
460
|
{/snippet}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TextMarkerConfig } from '../../types/markers/common';
|
|
2
|
-
import type { BoxChannels, BoxSizer } from '
|
|
2
|
+
import type { BoxChannels, BoxSizer } from '../../types/markers/labels/box';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
marker: TextMarkerConfig;
|
|
5
5
|
box?: BoxChannels | BoxSizer;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const events = MarkerEvents.use(() => marker);
|
|
13
13
|
|
|
14
14
|
const plot = usePlot();
|
|
15
|
-
const isHorizontal = $derived(plot.axes?.isHorizontal ?? false);
|
|
15
|
+
const isHorizontal = $derived(marker.isHorizontal ?? plot.axes?.isHorizontal ?? false);
|
|
16
16
|
const TickMark = $derived(isHorizontal ? TickX : TickY);
|
|
17
17
|
|
|
18
18
|
const cfg = $derived(getConfiguration());
|
|
@@ -19,7 +19,7 @@ export declare class MarkerChannels {
|
|
|
19
19
|
* pivot) carry over; the domain is read from the marker's own rows.
|
|
20
20
|
*/
|
|
21
21
|
static inheritScales(own: ChannelScalesConfig | undefined, plot: ChannelScalesConfig | undefined): ChannelScalesConfig | undefined;
|
|
22
|
-
/** `markers`, each
|
|
22
|
+
/** `markers`, each of a kind that inherits them with no scales of its own carrying the plot's ({@link inheritScales}). */
|
|
23
23
|
static inheritAll<TMarker extends {
|
|
24
24
|
type: string;
|
|
25
25
|
scales?: ChannelScalesConfig;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MarkerKinds } from '../MarkerKinds';
|
|
1
2
|
import { resolveAccessor } from '../../utils/accessors';
|
|
2
3
|
import { ColorRamp } from '../../utils/color';
|
|
3
4
|
import { OpacityScale } from '../../utils/OpacityScale';
|
|
@@ -20,9 +21,9 @@ export class MarkerChannels {
|
|
|
20
21
|
const numeric = Array.isArray(domain) && domain.every((v) => typeof v === 'number');
|
|
21
22
|
return { ...plot, color: numeric ? color : plot.color };
|
|
22
23
|
}
|
|
23
|
-
/** `markers`, each
|
|
24
|
+
/** `markers`, each of a kind that inherits them with no scales of its own carrying the plot's ({@link inheritScales}). */
|
|
24
25
|
static inheritAll(markers, plot) {
|
|
25
|
-
return markers.map((marker) => marker.type
|
|
26
|
+
return markers.map((marker) => MarkerKinds.inheritsScales(marker.type) && !marker.scales
|
|
26
27
|
? { ...marker, scales: MarkerChannels.inheritScales(undefined, plot) }
|
|
27
28
|
: marker);
|
|
28
29
|
}
|