@fundar/data-chart-telling 1.16.0 → 1.18.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 +56 -0
- package/dist/configuration/ConfigVars.svelte +14 -0
- package/dist/configuration/ConfigVars.svelte.d.ts +3 -0
- package/dist/configuration/config.svelte.d.ts +5 -0
- package/dist/configuration/config.svelte.js +20 -6
- package/dist/configuration/themes/dark.js +1 -1
- package/dist/configuration/themes/editorial.js +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +4 -1
- package/dist/layout/base/BasePlotLayout.svelte +1 -1
- package/dist/layout/figure/Figure.svelte +5 -12
- package/dist/layout/figure/FigureChrome.svelte +2 -10
- package/dist/layout/geometry/GeometryLayout.svelte +219 -44
- 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.js +4 -1
- package/dist/layout/hover/HoverShapes.d.ts +44 -0
- package/dist/layout/hover/HoverShapes.js +103 -0
- package/dist/layout/hover/PlotHover.svelte.d.ts +18 -0
- package/dist/layout/hover/PlotHover.svelte.js +54 -1
- package/dist/layout/legend/DisabledStyle.d.ts +1 -1
- package/dist/layout/legend/DisabledStyle.js +1 -1
- package/dist/layout/legend/DiscreteBadgeItem.svelte +1 -1
- package/dist/layout/legend/DiscreteSection.svelte +1 -1
- package/dist/layout/legend/LegendState.svelte.d.ts +14 -2
- package/dist/layout/legend/LegendState.svelte.js +23 -4
- package/dist/layout/legend/LegendText.d.ts +4 -1
- package/dist/layout/legend/LegendText.js +7 -1
- package/dist/layout/margins/PlotMargins.svelte.d.ts +2 -2
- package/dist/layout/margins/PlotMargins.svelte.js +2 -2
- package/dist/layout/plot/Core.svelte +2 -2
- package/dist/layout/plot/Plot.svelte +4 -2
- package/dist/layout/plot/Plot.svelte.d.ts +2 -1
- package/dist/layout/plot/PublicPlotContext.d.ts +6 -0
- package/dist/layout/plot/PublicPlotContext.js +8 -0
- package/dist/layout/scales/ScalesLayout.svelte +1 -1
- package/dist/layout/styles/ValueLabels.d.ts +43 -4
- package/dist/layout/styles/ValueLabels.js +87 -19
- package/dist/layout/timeline/Timeline.svelte +5 -1
- 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 +5 -0
- package/dist/markers/MarkerKinds.js +1 -0
- package/dist/markers/area/AreaMarker.svelte +3 -0
- package/dist/markers/arrow/ArrowMarker.svelte +9 -5
- 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 +50 -96
- package/dist/markers/bollinger/BollingerMarker.svelte +3 -0
- package/dist/markers/box/BoxMarker.svelte +3 -1
- package/dist/markers/cell/CellMarker.svelte +11 -7
- package/dist/markers/contour/ContourMarker.svelte +3 -0
- package/dist/markers/delaunay/DelaunayLinkMarker.svelte +9 -5
- package/dist/markers/delaunay/DelaunayMeshMarker.svelte +3 -0
- package/dist/markers/density/DensityMarker.svelte +3 -0
- package/dist/markers/difference/DifferenceMarker.svelte +3 -0
- package/dist/markers/dot/DotMarker.svelte +11 -6
- package/dist/markers/geo/GraticuleMarker.svelte +3 -0
- package/dist/markers/geo/SphereMarker.svelte +3 -0
- package/dist/markers/hover/HoverMarker.svelte +7 -249
- package/dist/markers/hover/HoverMarker.svelte.d.ts +4 -27
- package/dist/markers/hull/HullMarker.svelte +3 -0
- package/dist/markers/image/ImageMarker.svelte +3 -0
- package/dist/markers/line/LineMarker.svelte +3 -0
- package/dist/markers/link/LinkMarker.svelte +3 -0
- package/dist/markers/raster/RasterMarker.svelte +3 -0
- package/dist/markers/rect/RectMarker.svelte +25 -17
- package/dist/markers/regression/RegressionMarker.svelte +3 -0
- package/dist/markers/rule/RuleMarker.svelte +15 -5
- package/dist/markers/text/TextFit.d.ts +44 -0
- package/dist/markers/text/TextFit.js +86 -0
- package/dist/markers/text/TextMarker.svelte +186 -90
- package/dist/markers/text/TextMarker.svelte.d.ts +2 -0
- package/dist/markers/tick/TickMarker.svelte +9 -3
- package/dist/markers/trail/TrailMarker.svelte +3 -0
- package/dist/markers/utils/BoxLabel.d.ts +58 -0
- package/dist/markers/utils/BoxLabel.js +175 -0
- package/dist/markers/utils/MarkerEvents.d.ts +23 -0
- package/dist/markers/utils/MarkerEvents.js +44 -0
- package/dist/markers/utils/MarkerLabel.svelte +33 -0
- package/dist/markers/utils/MarkerLabel.svelte.d.ts +10 -0
- package/dist/markers/utils/PointPixel.d.ts +37 -0
- package/dist/markers/utils/PointPixel.js +44 -0
- package/dist/markers/utils/RowStroke.d.ts +9 -21
- package/dist/markers/utils/RowStroke.js +10 -21
- package/dist/markers/utils/RowStyle.d.ts +26 -0
- package/dist/markers/utils/RowStyle.js +55 -0
- package/dist/markers/vector/VectorMarker.svelte +3 -0
- package/dist/markers/voronoi/VoronoiMarker.svelte +9 -5
- package/dist/markers/voronoi/VoronoiMeshMarker.svelte +3 -0
- package/dist/markers/waffle/WaffleMarker.svelte +9 -5
- package/dist/phases/Pipeline.d.ts +10 -8
- package/dist/phases/Pipeline.js +13 -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 +35 -0
- package/dist/phases/context/RowContext.js +95 -0
- package/dist/phases/data/DataPhase.d.ts +4 -4
- package/dist/phases/data/DataPhase.js +53 -12
- package/dist/phases/hover/HoverPhase.d.ts +6 -7
- package/dist/phases/hover/HoverPhase.js +28 -15
- package/dist/phases/mapping/MappingPhase.d.ts +4 -4
- package/dist/phases/mapping/MappingPhase.js +4 -3
- package/dist/phases/markers/MarkerStyles.d.ts +26 -0
- package/dist/phases/markers/MarkerStyles.js +126 -0
- package/dist/phases/markers/MarkersPhase.d.ts +5 -5
- package/dist/phases/markers/MarkersPhase.js +8 -6
- 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 +0 -16
- package/dist/phases/statistics/StatisticsPhase.js +1 -1
- 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/Transforms.js +3 -1
- package/dist/phases/transforms/TransformsPhase.d.ts +3 -3
- package/dist/phases/transforms/TransformsPhase.js +60 -6
- package/dist/phases/transforms/methods/TreemapTransform.d.ts +28 -0
- package/dist/phases/transforms/methods/TreemapTransform.js +119 -0
- package/dist/plots/correlation/heatmap/defaults.d.ts +2 -2
- package/dist/plots/correlation/heatmap/defaults.js +23 -8
- package/dist/plots/correlation/heatmap/markers.d.ts +2 -10
- package/dist/plots/correlation/heatmap/markers.js +11 -37
- package/dist/plots/correlation/scatter/defaults.d.ts +2 -2
- package/dist/plots/correlation/scatter/defaults.js +9 -3
- package/dist/plots/correlation/scatter/markers.d.ts +3 -3
- package/dist/plots/correlation/scatter/markers.js +6 -5
- package/dist/plots/distribution/box/BoxSummaries.d.ts +17 -8
- package/dist/plots/distribution/box/BoxSummaries.js +90 -40
- package/dist/plots/distribution/box/defaults.d.ts +2 -2
- package/dist/plots/distribution/box/defaults.js +9 -6
- package/dist/plots/distribution/box/markers.d.ts +2 -2
- package/dist/plots/distribution/box/markers.js +3 -3
- package/dist/plots/distribution/density/defaults.d.ts +2 -2
- package/dist/plots/distribution/density/defaults.js +17 -1
- package/dist/plots/distribution/density/markers.d.ts +2 -2
- package/dist/plots/distribution/density/markers.js +4 -4
- package/dist/plots/distribution/histogram/defaults.d.ts +2 -2
- package/dist/plots/distribution/histogram/defaults.js +22 -27
- package/dist/plots/distribution/histogram/markers.d.ts +3 -12
- package/dist/plots/distribution/histogram/markers.js +21 -62
- package/dist/plots/distribution/pyramid/defaults.d.ts +2 -2
- package/dist/plots/distribution/pyramid/defaults.js +7 -4
- package/dist/plots/distribution/pyramid/markers.d.ts +2 -12
- package/dist/plots/distribution/pyramid/markers.js +16 -66
- package/dist/plots/distribution/violin/defaults.d.ts +2 -2
- package/dist/plots/distribution/violin/defaults.js +9 -7
- package/dist/plots/distribution/violin/markers.d.ts +2 -2
- package/dist/plots/distribution/violin/markers.js +4 -4
- package/dist/plots/evolution/area/defaults.d.ts +2 -2
- package/dist/plots/evolution/area/defaults.js +1 -1
- package/dist/plots/evolution/area/markers.d.ts +2 -2
- package/dist/plots/evolution/area/markers.js +4 -4
- package/dist/plots/evolution/line/defaults.d.ts +2 -2
- package/dist/plots/evolution/line/defaults.js +1 -1
- package/dist/plots/evolution/line/markers.d.ts +2 -2
- package/dist/plots/evolution/line/markers.js +3 -3
- package/dist/plots/evolution/slope/defaults.d.ts +2 -2
- package/dist/plots/evolution/slope/defaults.js +8 -4
- package/dist/plots/evolution/slope/markers.d.ts +2 -2
- package/dist/plots/evolution/slope/markers.js +3 -3
- 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 +3 -3
- package/dist/plots/map/geo/Core.svelte +3 -3
- 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 +5 -4
- 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 +47 -0
- package/dist/plots/part-of-a-whole/treemap/Plot.svelte.d.ts +26 -0
- package/dist/plots/part-of-a-whole/treemap/defaults.d.ts +10 -0
- package/dist/plots/part-of-a-whole/treemap/defaults.js +183 -0
- package/dist/plots/ranking/bar/defaults.d.ts +2 -2
- package/dist/plots/ranking/bar/defaults.js +15 -9
- package/dist/plots/ranking/bar/markers.d.ts +4 -14
- package/dist/plots/ranking/bar/markers.js +45 -86
- package/dist/plots/ranking/bullet/defaults.d.ts +2 -2
- package/dist/plots/ranking/bullet/defaults.js +4 -4
- package/dist/plots/ranking/bullet/markers.d.ts +3 -3
- package/dist/plots/ranking/bullet/markers.js +11 -12
- package/dist/plots/ranking/cleveland/ClevelandMarkers.d.ts +3 -3
- package/dist/plots/ranking/cleveland/ClevelandMarkers.js +28 -23
- package/dist/plots/ranking/cleveland/defaults.d.ts +2 -2
- package/dist/plots/ranking/cleveland/defaults.js +7 -4
- package/dist/plots/ranking/dumbbell/defaults.d.ts +2 -2
- package/dist/plots/ranking/forest/defaults.d.ts +2 -2
- package/dist/plots/ranking/lollipop/defaults.d.ts +2 -2
- package/dist/types/configuration/styling.d.ts +31 -2
- package/dist/types/layout/context.d.ts +21 -0
- package/dist/types/layout/context.js +1 -0
- package/dist/types/layout/hover.d.ts +3 -0
- package/dist/types/layout/legend.d.ts +8 -1
- package/dist/types/layout/mapping.d.ts +12 -3
- package/dist/types/layout/scales.d.ts +6 -0
- package/dist/types/layout/styles/values.d.ts +8 -2
- package/dist/types/layout/tooltip.d.ts +11 -4
- package/dist/types/markers/axis/arrow.d.ts +2 -8
- 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 +6 -10
- package/dist/types/markers/axis/cell.d.ts +5 -5
- package/dist/types/markers/axis/delaunayLink.d.ts +2 -2
- package/dist/types/markers/axis/hover.d.ts +60 -15
- package/dist/types/markers/axis/rect.d.ts +4 -8
- package/dist/types/markers/axis/rule.d.ts +2 -16
- package/dist/types/markers/axis/tick.d.ts +2 -2
- package/dist/types/markers/axis/voronoi.d.ts +2 -2
- package/dist/types/markers/axis/waffle.d.ts +4 -3
- package/dist/types/markers/basic/dot.d.ts +2 -2
- package/dist/types/markers/basic/text.d.ts +56 -14
- package/dist/types/markers/channels/commonMarkerConfig.d.ts +8 -1
- package/dist/types/markers/channels/commonMarkerConfigWithArea.d.ts +1 -2
- package/dist/types/markers/channels/commonMarkerConfigWithLabel.d.ts +10 -0
- package/dist/types/markers/channels/commonMarkerConfigWithLabel.js +1 -0
- package/dist/types/markers/channels/commonMarkerEvents.d.ts +17 -0
- package/dist/types/markers/channels/commonMarkerEvents.js +13 -0
- package/dist/types/markers/common.d.ts +1 -0
- package/dist/types/markers/common.js +1 -0
- package/dist/types/markers/geo/contour.d.ts +2 -1
- package/dist/types/markers/geo/graticule.d.ts +2 -1
- package/dist/types/markers/geo/link.d.ts +2 -1
- package/dist/types/markers/geo/sphere.d.ts +2 -1
- package/dist/types/markers/geo/vector.d.ts +2 -1
- 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 +7 -2
- package/dist/types/phases/hover/phase.d.ts +10 -5
- 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 -3
- 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 +8 -1
- package/dist/types/phases/statistics/phase.d.ts +2 -1
- package/dist/types/phases/styles/phase.d.ts +3 -2
- package/dist/types/phases/transforms/phase.d.ts +13 -2
- package/dist/types/plots/constants.d.ts +17 -19
- package/dist/types/plots/correlation/heatmap/props.d.ts +3 -3
- package/dist/types/plots/correlation/heatmap/state.d.ts +2 -2
- package/dist/types/plots/correlation/scatter/props.d.ts +3 -3
- package/dist/types/plots/correlation/scatter/state.d.ts +2 -2
- package/dist/types/plots/data/common.d.ts +19 -6
- package/dist/types/plots/data/geo.d.ts +1 -1
- package/dist/types/plots/distribution/box/props.d.ts +25 -3
- package/dist/types/plots/distribution/box/state.d.ts +6 -17
- package/dist/types/plots/distribution/density/props.d.ts +3 -11
- package/dist/types/plots/distribution/density/state.d.ts +2 -2
- package/dist/types/plots/distribution/histogram/props.d.ts +16 -11
- package/dist/types/plots/distribution/histogram/state.d.ts +2 -2
- package/dist/types/plots/distribution/pyramid/props.d.ts +3 -3
- package/dist/types/plots/distribution/pyramid/state.d.ts +2 -2
- package/dist/types/plots/distribution/violin/props.d.ts +4 -3
- package/dist/types/plots/distribution/violin/state.d.ts +2 -2
- package/dist/types/plots/evolution/area/props.d.ts +3 -3
- package/dist/types/plots/evolution/area/state.d.ts +2 -2
- package/dist/types/plots/evolution/line/props.d.ts +3 -3
- 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/map/geo/state.d.ts +1 -1
- package/dist/types/plots/part-of-a-whole/treemap/props.d.ts +29 -0
- package/dist/types/plots/part-of-a-whole/treemap/props.js +1 -0
- package/dist/types/plots/part-of-a-whole/treemap/state.d.ts +6 -0
- package/dist/types/plots/part-of-a-whole/treemap/state.js +1 -0
- package/dist/types/plots/props.d.ts +10 -4
- package/dist/types/plots/ranking/bar/props.d.ts +3 -3
- 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 -3
- package/dist/types/plots/ranking/cleveland/state.d.ts +2 -2
- package/dist/types/plots/ranking/dumbbell/props.d.ts +3 -3
- package/dist/types/plots/ranking/dumbbell/state.d.ts +2 -2
- package/dist/types/plots/ranking/forest/props.d.ts +3 -3
- package/dist/types/plots/ranking/forest/state.d.ts +2 -2
- package/dist/types/plots/ranking/lollipop/props.d.ts +3 -3
- package/dist/types/plots/ranking/lollipop/state.d.ts +2 -2
- package/dist/types/plots/styling.d.ts +24 -0
- package/dist/types/transforms/index.d.ts +3 -1
- package/dist/types/transforms/treemap.d.ts +32 -0
- package/dist/types/transforms/treemap.js +5 -0
- package/dist/utils/color/ColorTones.d.ts +19 -0
- package/dist/utils/color/ColorTones.js +64 -0
- package/dist/utils/color/CssColor.d.ts +14 -5
- package/dist/utils/color/CssColor.js +69 -27
- package/dist/utils/color/index.d.ts +4 -0
- package/dist/utils/color/index.js +4 -0
- package/dist/utils/grouping.d.ts +18 -1
- package/dist/utils/grouping.js +31 -0
- package/docs/concepts.md +3 -2
- package/docs/legend.md +8 -5
- package/docs/markers.md +110 -28
- 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 +103 -0
- package/docs/plots/ranking/bar.md +2 -1
- package/docs/plots/ranking/cleveland.md +1 -1
- package/docs/plots.md +21 -20
- package/docs/scales.md +3 -0
- package/docs/state.md +34 -1
- package/docs/styles-and-segments.md +20 -19
- package/docs/theming.md +6 -12
- package/docs/tooltip-and-hover.md +81 -3
- package/docs/transforms-and-statistics.md +17 -16
- package/examples/LineDates.svelte +1 -1
- package/examples/ResponsiveLine.svelte +3 -1
- package/examples/Treemap.svelte +13 -0
- package/examples/TreemapHover.svelte +25 -0
- package/examples/ValueLabels.svelte +1 -1
- package/examples/datasets/economic-activity.ts +40 -0
- package/examples/datasets/index.ts +1 -0
- package/llms-full.txt +11 -9
- package/llms.txt +4 -1
- package/package.json +3 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { hierarchy, treemap, treemapBinary, treemapDice, treemapSlice, treemapSliceDice, treemapSquarify } from 'd3-hierarchy';
|
|
2
|
+
import { resolveAccessor } from '../../../utils/accessors';
|
|
3
|
+
import { TREEMAP_CHANNEL_KEYS } from '../../../types/transforms/treemap';
|
|
4
|
+
const TILES = {
|
|
5
|
+
squarify: treemapSquarify,
|
|
6
|
+
binary: treemapBinary,
|
|
7
|
+
slice: treemapSlice,
|
|
8
|
+
dice: treemapDice,
|
|
9
|
+
sliceDice: treemapSliceDice
|
|
10
|
+
};
|
|
11
|
+
/** What a treemap leaves on each row it produces, and the channels it reads them back by. */
|
|
12
|
+
export const TREEMAP_ROW = {
|
|
13
|
+
/** The tile's name: the value at its last level. */
|
|
14
|
+
name: Symbol('treemap.name'),
|
|
15
|
+
/** The value at every level of the tile, outermost first. */
|
|
16
|
+
path: Symbol('treemap.path'),
|
|
17
|
+
/** The tile's summed `value`. */
|
|
18
|
+
value: Symbol('treemap.value'),
|
|
19
|
+
/** The tile's part of the whole, from `0` to `1`. */
|
|
20
|
+
share: Symbol('treemap.share'),
|
|
21
|
+
x1: Symbol('treemap.x1'),
|
|
22
|
+
x2: Symbol('treemap.x2'),
|
|
23
|
+
y1: Symbol('treemap.y1'),
|
|
24
|
+
y2: Symbol('treemap.y2'),
|
|
25
|
+
x: Symbol('treemap.x'),
|
|
26
|
+
y: Symbol('treemap.y')
|
|
27
|
+
};
|
|
28
|
+
const read = (channel) => typeof channel === 'function'
|
|
29
|
+
? channel
|
|
30
|
+
: (row) => row[channel];
|
|
31
|
+
/** A treemap as a transform: the layout of d3-hierarchy, in the shape every other transform returns. */
|
|
32
|
+
export class TreemapTransform {
|
|
33
|
+
static adapter = {
|
|
34
|
+
channelKeys: TREEMAP_CHANNEL_KEYS,
|
|
35
|
+
run: (channels, options) => TreemapTransform.layout(channels, options)
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* A row per tile of `channels.data`, biggest first within its group, placed in the unit square. A row whose
|
|
39
|
+
* value is not a positive number has no area, so it makes no tile. The layout is `aspect` wide while it is
|
|
40
|
+
* computed and scaled back after, so its tiles are shaped after the box they fill.
|
|
41
|
+
*/
|
|
42
|
+
static layout(channels, options) {
|
|
43
|
+
const { data, value, ...rest } = channels;
|
|
44
|
+
if (!data)
|
|
45
|
+
throw new Error('treemap: missing data');
|
|
46
|
+
if (value === undefined)
|
|
47
|
+
throw new Error('treemap: missing value channel');
|
|
48
|
+
const size = read(value);
|
|
49
|
+
const levels = (options.levels ?? []).map((level) => resolveAccessor(level));
|
|
50
|
+
const tile = options.tile ?? 'squarify';
|
|
51
|
+
const aspect = Number(options.aspect);
|
|
52
|
+
const width = aspect > 0 && Number.isFinite(aspect) ? aspect : 1;
|
|
53
|
+
const root = { name: '', path: [], rows: [], children: [] };
|
|
54
|
+
const nodes = new Map();
|
|
55
|
+
const nodeOf = (path) => {
|
|
56
|
+
let parent = root;
|
|
57
|
+
for (let depth = 1; depth <= path.length; depth++) {
|
|
58
|
+
const key = JSON.stringify(path.slice(0, depth));
|
|
59
|
+
let node = nodes.get(key);
|
|
60
|
+
if (!node) {
|
|
61
|
+
node = { name: path[depth - 1], path: path.slice(0, depth), rows: [], children: [] };
|
|
62
|
+
nodes.set(key, node);
|
|
63
|
+
parent.children.push(node);
|
|
64
|
+
}
|
|
65
|
+
parent = node;
|
|
66
|
+
}
|
|
67
|
+
return parent;
|
|
68
|
+
};
|
|
69
|
+
for (const row of data) {
|
|
70
|
+
const area = Number(size(row));
|
|
71
|
+
if (!(Number.isFinite(area) && area > 0))
|
|
72
|
+
continue;
|
|
73
|
+
const leaf = nodeOf(levels.map((level) => String(level(row))));
|
|
74
|
+
leaf.rows.push(row);
|
|
75
|
+
leaf.value = (leaf.value ?? 0) + area;
|
|
76
|
+
}
|
|
77
|
+
const tiles = [];
|
|
78
|
+
if (root.children.length > 0 || root.value !== undefined) {
|
|
79
|
+
const tree = hierarchy(root, (node) => (node.children?.length ? node.children : null))
|
|
80
|
+
.sum((node) => node.value ?? 0)
|
|
81
|
+
.sort((a, b) => (b.value ?? 0) - (a.value ?? 0));
|
|
82
|
+
const total = tree.value ?? 0;
|
|
83
|
+
treemap().size([width, 1]).tile(TILES[tile]).round(false)(tree);
|
|
84
|
+
for (const leaf of tree.leaves()) {
|
|
85
|
+
const at = leaf;
|
|
86
|
+
const { rows, path, name } = leaf.data;
|
|
87
|
+
const sum = leaf.value ?? 0;
|
|
88
|
+
const first = rows[0];
|
|
89
|
+
const x1 = at.x0 / width;
|
|
90
|
+
const x2 = at.x1 / width;
|
|
91
|
+
tiles.push({
|
|
92
|
+
...first,
|
|
93
|
+
// A string `value` names the field the tile's sum reads back from.
|
|
94
|
+
...(typeof value === 'string' ? { [value]: sum } : {}),
|
|
95
|
+
[TREEMAP_ROW.name]: name,
|
|
96
|
+
[TREEMAP_ROW.path]: path,
|
|
97
|
+
[TREEMAP_ROW.value]: sum,
|
|
98
|
+
[TREEMAP_ROW.share]: total > 0 ? sum / total : 0,
|
|
99
|
+
[TREEMAP_ROW.x1]: x1,
|
|
100
|
+
[TREEMAP_ROW.x2]: x2,
|
|
101
|
+
[TREEMAP_ROW.y1]: at.y0,
|
|
102
|
+
[TREEMAP_ROW.y2]: at.y1,
|
|
103
|
+
[TREEMAP_ROW.x]: (x1 + x2) / 2,
|
|
104
|
+
[TREEMAP_ROW.y]: (at.y0 + at.y1) / 2
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...rest,
|
|
110
|
+
data: tiles,
|
|
111
|
+
x: TREEMAP_ROW.x,
|
|
112
|
+
x1: TREEMAP_ROW.x1,
|
|
113
|
+
x2: TREEMAP_ROW.x2,
|
|
114
|
+
y: TREEMAP_ROW.y,
|
|
115
|
+
y1: TREEMAP_ROW.y1,
|
|
116
|
+
y2: TREEMAP_ROW.y2
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AxisPlotDefaults } from '../../../types/phases';
|
|
2
|
-
import type { HeatmapPlotState,
|
|
2
|
+
import type { HeatmapPlotState, HeatmapPlotEnv } from '../../../types/plots/correlation/heatmap/state';
|
|
3
3
|
/**
|
|
4
4
|
* HeatmapPlot on the shared pipeline: both axes are categories. A heatmap's
|
|
5
5
|
* `color` is each cell's numeric value, which the "data" phase reads as a
|
|
@@ -8,4 +8,4 @@ import type { HeatmapPlotState, HeatmapPlotCtx } from '../../../types/plots/corr
|
|
|
8
8
|
* cell's tooltip names its coordinates and its value.
|
|
9
9
|
* Its plot defaults to: hover one cell.
|
|
10
10
|
*/
|
|
11
|
-
export declare function buildHeatmapDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<HeatmapPlotState<TData>,
|
|
11
|
+
export declare function buildHeatmapDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<HeatmapPlotState<TData>, HeatmapPlotEnv>;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { ColorRamp } from '../../../utils/color';
|
|
2
|
+
import { HoverDefaults } from '../../../layout/hover/HoverDefaults';
|
|
2
3
|
import { OpacityScale } from '../../../utils/OpacityScale';
|
|
3
4
|
import { ContinuousDomain } from '../../../utils/ContinuousDomain';
|
|
4
|
-
import { buildMarks
|
|
5
|
+
import { buildMarks } from './markers';
|
|
6
|
+
import { RowContext } from '../../../phases/context/RowContext';
|
|
5
7
|
/**
|
|
6
8
|
* HeatmapPlot's own "scales" output, over the band axes the shared phase laid
|
|
7
9
|
* out: the color domain over every cell's value (when `color` is set), and each
|
|
8
10
|
* row's own resolved color and opacity.
|
|
9
11
|
*/
|
|
10
|
-
function extendWithColorScales(state,
|
|
12
|
+
function extendWithColorScales(state, env) {
|
|
11
13
|
const { scales, channels } = state;
|
|
12
|
-
const { range, cfg } =
|
|
14
|
+
const { range, cfg } = env;
|
|
13
15
|
const { color: colorAcc } = channels;
|
|
14
16
|
const rows = (state.processedData?.data ?? []);
|
|
15
17
|
const valueDomain = ContinuousDomain.build(scales.color?.domain, rows.map((d) => Number(colorAcc.accessor(d))), true) ?? [0, 1];
|
|
@@ -68,6 +70,14 @@ export function buildHeatmapDefaults() {
|
|
|
68
70
|
}
|
|
69
71
|
})
|
|
70
72
|
},
|
|
73
|
+
// A cell's value is its color's, not a position's.
|
|
74
|
+
context: (state) => {
|
|
75
|
+
const color = state.channels.color.accessor;
|
|
76
|
+
return ((row) => {
|
|
77
|
+
const value = Number(color(row));
|
|
78
|
+
return { value: Number.isFinite(value) ? value : undefined };
|
|
79
|
+
});
|
|
80
|
+
},
|
|
71
81
|
statistics: { inheritGrouping: false },
|
|
72
82
|
sharedScales: { fromScalesPhase: ['color'] },
|
|
73
83
|
scales: {
|
|
@@ -76,13 +86,18 @@ export function buildHeatmapDefaults() {
|
|
|
76
86
|
extend: extendWithColorScales
|
|
77
87
|
},
|
|
78
88
|
styles: { values: { show: true } },
|
|
79
|
-
markers: { marks: buildMarks
|
|
89
|
+
markers: { marks: buildMarks },
|
|
80
90
|
hover: {
|
|
81
|
-
// A
|
|
91
|
+
// A cell writes its own value: the hover only restyles the cells - the hovered one outlined.
|
|
92
|
+
marker: (_state, env) => HoverDefaults.shapes(env.cfg),
|
|
93
|
+
// A hovered cell reads as its value, as its value labels write it.
|
|
82
94
|
label: (state) => {
|
|
83
|
-
const format = state.values?.format
|
|
84
|
-
const
|
|
85
|
-
return ((row) =>
|
|
95
|
+
const format = state.values?.format;
|
|
96
|
+
const number = (v) => `${v}`;
|
|
97
|
+
return ((row) => {
|
|
98
|
+
const context = RowContext.of(row);
|
|
99
|
+
return format ? format(Number(context.value), number, context) : `${context.value}`;
|
|
100
|
+
});
|
|
86
101
|
},
|
|
87
102
|
// A cell's color is its value, not a series'.
|
|
88
103
|
decorate: () => () => ({ series: undefined, color: undefined })
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { HeatmapPlotState, HeatmapPlotCtx } from '../../../types/plots/correlation/heatmap/state';
|
|
3
|
-
import type { ValueLabels } from '../../../layout/styles/ValueLabels';
|
|
1
|
+
import type { HeatmapPlotState, HeatmapPlotEnv } from '../../../types/plots/correlation/heatmap/state';
|
|
4
2
|
import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
|
|
5
|
-
/**
|
|
6
|
-
* HeatmapPlot's `styles.values` as one `'text'` marker over the same cells
|
|
7
|
-
* the plot draws, each labelled with its own value and centred on it. Shown
|
|
8
|
-
* by default.
|
|
9
|
-
*/
|
|
10
|
-
export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: HeatmapPlotState<TData>, ctx: HeatmapPlotCtx): TextMarkerConfig[];
|
|
11
3
|
/**
|
|
12
4
|
* HeatmapPlot's "markers" phase: one `'cell'` marker per resolved-style
|
|
13
5
|
* bucket - svelteplot's opacity/numeric scale machinery requires
|
|
14
6
|
* `fillOpacity`/`strokeWidth` to be constant per mark instance, unlike
|
|
15
7
|
* `fill`, which stays a per-row accessor.
|
|
16
8
|
*/
|
|
17
|
-
export declare function buildMarks<TData extends Record<string, unknown>>(state: HeatmapPlotState<TData>,
|
|
9
|
+
export declare function buildMarks<TData extends Record<string, unknown>>(state: HeatmapPlotState<TData>, env: HeatmapPlotEnv): AxisBasedMarkersConfig[];
|
|
@@ -8,47 +8,16 @@ function readChannel(channel) {
|
|
|
8
8
|
return resolveAccessor(channel);
|
|
9
9
|
}
|
|
10
10
|
const OPACITY_STEPS = 32;
|
|
11
|
-
/**
|
|
12
|
-
* HeatmapPlot's `styles.values` as one `'text'` marker over the same cells
|
|
13
|
-
* the plot draws, each labelled with its own value and centred on it. Shown
|
|
14
|
-
* by default.
|
|
15
|
-
*/
|
|
16
|
-
export function buildValueLabels(labels, state, ctx) {
|
|
17
|
-
const { processedData } = state;
|
|
18
|
-
if (!processedData?.data.length)
|
|
19
|
-
return [];
|
|
20
|
-
const { font, format } = labels;
|
|
21
|
-
const getColor = state.channels.color.accessor;
|
|
22
|
-
return [
|
|
23
|
-
{
|
|
24
|
-
type: 'text',
|
|
25
|
-
data: processedData.data,
|
|
26
|
-
// Read like the cells read them, so the labels add no axis title the cells don't.
|
|
27
|
-
x: readChannel(processedData.channels.x),
|
|
28
|
-
y: readChannel(processedData.channels.y),
|
|
29
|
-
text: (d) => format(Number(getColor(d)), ''),
|
|
30
|
-
styles: {
|
|
31
|
-
...font,
|
|
32
|
-
fill: font?.fill ?? ctx.cfg.continuous.labelColor,
|
|
33
|
-
fontSize: font?.fontSize ?? 12,
|
|
34
|
-
fontWeight: font?.fontWeight ?? 600,
|
|
35
|
-
dx: font?.dx ?? 0,
|
|
36
|
-
dy: font?.dy ?? 0,
|
|
37
|
-
textAnchor: labels.textAnchor ?? 'middle',
|
|
38
|
-
lineAnchor: font?.lineAnchor ?? 'middle'
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
];
|
|
42
|
-
}
|
|
43
11
|
/**
|
|
44
12
|
* HeatmapPlot's "markers" phase: one `'cell'` marker per resolved-style
|
|
45
13
|
* bucket - svelteplot's opacity/numeric scale machinery requires
|
|
46
14
|
* `fillOpacity`/`strokeWidth` to be constant per mark instance, unlike
|
|
47
15
|
* `fill`, which stays a per-row accessor.
|
|
48
16
|
*/
|
|
49
|
-
export function buildMarks(state,
|
|
17
|
+
export function buildMarks(state, env) {
|
|
50
18
|
const { segments, channels, resolveColor, resolveOpacity } = state;
|
|
51
|
-
const { range } =
|
|
19
|
+
const { range } = env;
|
|
20
|
+
const labels = state.valueLabels?.show ? state.valueLabels : undefined;
|
|
52
21
|
const getColor = channels.color.accessor;
|
|
53
22
|
// The cells are the rows the "transforms" phase left, placed by its channels.
|
|
54
23
|
const processed = state.processedData;
|
|
@@ -131,16 +100,21 @@ export function buildMarks(state, ctx) {
|
|
|
131
100
|
data: group.data,
|
|
132
101
|
x: getX,
|
|
133
102
|
y: getY,
|
|
134
|
-
fill: (d) => group.styles.fill ?? resolveColor(d),
|
|
135
103
|
styles: {
|
|
104
|
+
fill: (d) => group.styles.fill ?? resolveColor(d),
|
|
136
105
|
fillOpacity: group.styles.fillOpacity ?? group.opacity ?? 1,
|
|
137
106
|
stroke: group.styles.stroke,
|
|
138
107
|
strokeWidth: group.styles.strokeWidth ?? 0,
|
|
139
108
|
strokeOpacity: group.styles.strokeOpacity,
|
|
140
109
|
strokeDasharray: group.styles.strokeDasharray,
|
|
141
|
-
borderRadius: group.styles.borderRadius
|
|
110
|
+
borderRadius: group.styles.borderRadius,
|
|
111
|
+
inset: 0.5
|
|
142
112
|
},
|
|
143
|
-
|
|
113
|
+
hover: 'style',
|
|
114
|
+
// Each cell writes its own value, in the middle of it.
|
|
115
|
+
label: labels?.shapeLabel(env, (d) => labels.format(Number(getColor(d)), d), {
|
|
116
|
+
fontWeight: 600
|
|
117
|
+
})
|
|
144
118
|
}));
|
|
145
119
|
return markers;
|
|
146
120
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { AxisPlotDefaults } from '../../../types/phases';
|
|
2
|
-
import type { ScatterPlotState,
|
|
2
|
+
import type { ScatterPlotState, ScatterPlotEnv } from '../../../types/plots/correlation/scatter/state';
|
|
3
3
|
/** ScatterPlot on the shared pipeline: discrete dots, its own radius-padded scales, and its own markers. Its plot defaults to: hover the exact point. */
|
|
4
|
-
export declare function buildScatterDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<ScatterPlotState<TData>,
|
|
4
|
+
export declare function buildScatterDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<ScatterPlotState<TData>, ScatterPlotEnv>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ProcessedPositions } from '../../../phases/transforms/ProcessedPositions';
|
|
2
|
+
import { HoverDefaults } from '../../../layout/hover/HoverDefaults';
|
|
2
3
|
import { buildMarks, buildValueLabels } from './markers';
|
|
3
4
|
import { Margins } from '../../../layout/margins/Margins';
|
|
4
5
|
import { MarkerAxisValues } from '../../../phases/scales/MarkerAxisValues';
|
|
@@ -25,7 +26,7 @@ function paddedDomain(values, radiusPx, sizePx) {
|
|
|
25
26
|
* the largest dot's radius, and grown to cover what derived markers draw
|
|
26
27
|
* (a density curve's peak, a regression band).
|
|
27
28
|
*/
|
|
28
|
-
function fitDomains(state,
|
|
29
|
+
function fitDomains(state, env) {
|
|
29
30
|
const { scales, channels, groupedSeries } = state;
|
|
30
31
|
const processed = state.processedData;
|
|
31
32
|
const position = ProcessedPositions.of(processed);
|
|
@@ -39,10 +40,10 @@ function fitDomains(state, ctx) {
|
|
|
39
40
|
? r
|
|
40
41
|
: r !== undefined
|
|
41
42
|
? AUTO_SCALED_RADIUS_ESTIMATE
|
|
42
|
-
: Math.max(0, ...groupedSeries.flatMap((group) => group.visualSegments.map((seg) => seg.styles.dotRadius ??
|
|
43
|
+
: Math.max(0, ...groupedSeries.flatMap((group) => group.visualSegments.map((seg) => seg.styles.dotRadius ?? env.cfg.scatter.dotRadius)));
|
|
43
44
|
if (radius <= 0 && !extra.x.length && !extra.y.length)
|
|
44
45
|
return {};
|
|
45
|
-
const box = Margins.resolve({ ...
|
|
46
|
+
const box = Margins.resolve({ ...env.cfg.margins, ...state.margins }).contentBox(state.width, state.height);
|
|
46
47
|
const fit = {};
|
|
47
48
|
for (const [axis, size] of [
|
|
48
49
|
['x', box.width],
|
|
@@ -72,6 +73,11 @@ export function buildScatterDefaults() {
|
|
|
72
73
|
markers: { marks: buildMarks, valueLabels: buildValueLabels },
|
|
73
74
|
hover: {
|
|
74
75
|
strategy: 'punctual',
|
|
76
|
+
// A crosshair through the hovered point, and its label - the point is a dot already.
|
|
77
|
+
marker: () => ({
|
|
78
|
+
back: [HoverDefaults.rule('x'), HoverDefaults.rule('y')],
|
|
79
|
+
front: [HoverDefaults.text()]
|
|
80
|
+
}),
|
|
75
81
|
label: (state) => {
|
|
76
82
|
const { x, y } = state.channels;
|
|
77
83
|
return (row) => `${x.accessor(row)}, ${y.accessor(row)}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScatterPlotState,
|
|
1
|
+
import type { ScatterPlotState, ScatterPlotEnv } from '../../../types/plots/correlation/scatter/state';
|
|
2
2
|
import type { TextMarkerConfig } from '../../../types/markers/common';
|
|
3
3
|
import { ValueLabels } from '../../../layout/styles/ValueLabels';
|
|
4
4
|
import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
|
|
@@ -8,7 +8,7 @@ import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
|
|
|
8
8
|
* sits beside its point and flips to the other side where its text would
|
|
9
9
|
* cross the plot's edge (`edgeAware`).
|
|
10
10
|
*/
|
|
11
|
-
export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: ScatterPlotState<TData
|
|
11
|
+
export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: ScatterPlotState<TData>): TextMarkerConfig[];
|
|
12
12
|
/**
|
|
13
13
|
* ScatterPlot's "markers" phase: one `'dot'` marker per visual segment of the
|
|
14
14
|
* rows the "transforms" phase left, in their own order, so a reordering
|
|
@@ -16,4 +16,4 @@ export declare function buildValueLabels<TData extends Record<string, unknown>>(
|
|
|
16
16
|
* "statistics" phase already put in `state.markers` - and its value labels on
|
|
17
17
|
* top of everything.
|
|
18
18
|
*/
|
|
19
|
-
export declare function buildMarks<TData extends Record<string, unknown>>(state: ScatterPlotState<TData>,
|
|
19
|
+
export declare function buildMarks<TData extends Record<string, unknown>>(state: ScatterPlotState<TData>, env: ScatterPlotEnv): AxisBasedMarkersConfig[];
|
|
@@ -7,7 +7,7 @@ import { ValueLabels } from '../../../layout/styles/ValueLabels';
|
|
|
7
7
|
* sits beside its point and flips to the other side where its text would
|
|
8
8
|
* cross the plot's edge (`edgeAware`).
|
|
9
9
|
*/
|
|
10
|
-
export function buildValueLabels(labels, state
|
|
10
|
+
export function buildValueLabels(labels, state) {
|
|
11
11
|
const { processedData } = state;
|
|
12
12
|
if (!processedData?.data.length)
|
|
13
13
|
return [];
|
|
@@ -21,10 +21,10 @@ export function buildValueLabels(labels, state, ctx) {
|
|
|
21
21
|
data: processedData.data,
|
|
22
22
|
x: position.x,
|
|
23
23
|
y: position.y,
|
|
24
|
-
text: (d) => format(Number(valueOf(d)),
|
|
24
|
+
text: (d) => format(Number(valueOf(d)), d, nameOf(d)),
|
|
25
25
|
styles: {
|
|
26
26
|
...font,
|
|
27
|
-
fill: font?.fill ??
|
|
27
|
+
fill: font?.fill ?? 'currentColor',
|
|
28
28
|
fontSize: font?.fontSize ?? 11,
|
|
29
29
|
fontWeight: font?.fontWeight ?? 500,
|
|
30
30
|
dx: font?.dx ?? 8,
|
|
@@ -43,9 +43,9 @@ export function buildValueLabels(labels, state, ctx) {
|
|
|
43
43
|
* "statistics" phase already put in `state.markers` - and its value labels on
|
|
44
44
|
* top of everything.
|
|
45
45
|
*/
|
|
46
|
-
export function buildMarks(state,
|
|
46
|
+
export function buildMarks(state, env) {
|
|
47
47
|
const { channels, colorForSeries, scales, flat } = state;
|
|
48
|
-
const { cfg, interaction } =
|
|
48
|
+
const { cfg, interaction } = env;
|
|
49
49
|
const r = channels.r;
|
|
50
50
|
const rFn = r === undefined || typeof r === 'number' ? r : r.accessor;
|
|
51
51
|
const opacityFn = channels.opacity?.accessor;
|
|
@@ -98,6 +98,7 @@ export function buildMarks(state, ctx) {
|
|
|
98
98
|
markers.push({
|
|
99
99
|
type: 'dot',
|
|
100
100
|
data: seg.data,
|
|
101
|
+
hover: 'style',
|
|
101
102
|
// Forwarded raw (a field key or function, whatever `series.x`/`.y`
|
|
102
103
|
// is) rather than pre-resolved into a closure - `DotMarker.svelte`
|
|
103
104
|
// and svelteplot's own `resolve()` already handle a field-key
|
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import type { HoverPosition } from '../../../types/phases';
|
|
1
|
+
import type { HoverPosition, ProcessedRow } from '../../../types/phases';
|
|
2
2
|
import type { BoxPlotState, BoxSummaryRow } from '../../../types/plots/distribution/box/state';
|
|
3
|
+
import type { BoxSummary } from '../../../types/plots/distribution/box/props';
|
|
3
4
|
/**
|
|
4
|
-
* The boxes a box plot draws, one
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* The boxes a box plot draws, one per series, category and facet panel - the same quartiles and whiskers
|
|
6
|
+
* svelteplot's `BoxX`/`BoxY` compute, worked out once as each value's context ({@link context}), so its hover and
|
|
7
|
+
* tooltip read a box rather than every value inside it ({@link of}).
|
|
7
8
|
*/
|
|
8
9
|
export declare class BoxSummaries {
|
|
9
|
-
|
|
10
|
+
#private;
|
|
11
|
+
/**
|
|
12
|
+
* Each value's context: the summary of the box it is in - its category, quartiles, whisker ends (the farthest
|
|
13
|
+
* values within 1.5 IQR of the box), how many values it has and the ones past the whiskers.
|
|
14
|
+
*/
|
|
15
|
+
static context<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): (row: ProcessedRow) => BoxSummary | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* One summary row per box, in the order the processed rows first reach it, read off its first value's context.
|
|
18
|
+
* Its own context is the box's, with the median for its value, and where it sits for `x` and `y`.
|
|
19
|
+
*/
|
|
10
20
|
static of<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): BoxSummaryRow[];
|
|
11
21
|
/** Where a box's summary sits for hover: at its category, level with its median. */
|
|
12
22
|
static position<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): HoverPosition;
|
|
13
23
|
/**
|
|
14
|
-
* How a hovered box reads by default: its series, when the plot groups its
|
|
15
|
-
*
|
|
16
|
-
* values fall past the whiskers.
|
|
24
|
+
* How a hovered box reads by default, from its context: its series, when the plot groups its rows; its
|
|
25
|
+
* category; then its five numbers, highest first, and how many values fall past the whiskers.
|
|
17
26
|
*/
|
|
18
27
|
static format<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): (row: BoxSummaryRow) => string;
|
|
19
28
|
}
|
|
@@ -4,28 +4,37 @@ import { SERIES_ID_KEY, SERIES_KEY } from '../../../phases/transforms/Transforms
|
|
|
4
4
|
import { PANEL_KEY } from '../../../phases/facet/FacetPanels';
|
|
5
5
|
import { PlotAxes } from '../../../phases/data/PlotAxes';
|
|
6
6
|
import { TooltipState } from '../../../layout/tooltip/TooltipState.svelte';
|
|
7
|
+
import { RowContext } from '../../../phases/context/RowContext';
|
|
7
8
|
import { isGapValue } from '../../utils/gaps';
|
|
8
9
|
/** The whiskers reach this many interquartile ranges past the box, as svelteplot draws them. */
|
|
9
10
|
const WHISKER_IQR = 1.5;
|
|
10
11
|
/** A summary value as a tooltip shows it: at most two decimals. */
|
|
11
12
|
const round = (value) => String(Math.round(value * 100) / 100);
|
|
12
13
|
/**
|
|
13
|
-
* The boxes a box plot draws, one
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* The boxes a box plot draws, one per series, category and facet panel - the same quartiles and whiskers
|
|
15
|
+
* svelteplot's `BoxX`/`BoxY` compute, worked out once as each value's context ({@link context}), so its hover and
|
|
16
|
+
* tooltip read a box rather than every value inside it ({@link of}).
|
|
16
17
|
*/
|
|
17
18
|
export class BoxSummaries {
|
|
18
|
-
/**
|
|
19
|
-
static
|
|
19
|
+
/** Which box a processed row is in: its series, panel and category. */
|
|
20
|
+
static #keyOf(state) {
|
|
21
|
+
const categoryAxis = PlotAxes.categoryOf(state);
|
|
22
|
+
const categoryOf = ProcessedPositions.of(state.processedData, PlotAxes.valueOf(state))[categoryAxis];
|
|
23
|
+
return (row) => JSON.stringify([row[SERIES_ID_KEY], row[PANEL_KEY] ?? null, String(categoryOf(row))]);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Each value's context: the summary of the box it is in - its category, quartiles, whisker ends (the farthest
|
|
27
|
+
* values within 1.5 IQR of the box), how many values it has and the ones past the whiskers.
|
|
28
|
+
*/
|
|
29
|
+
static context(state) {
|
|
20
30
|
const processed = state.processedData;
|
|
21
31
|
if (!processed?.data.length)
|
|
22
|
-
return
|
|
32
|
+
return () => undefined;
|
|
23
33
|
const valueAxis = PlotAxes.valueOf(state);
|
|
24
|
-
const categoryAxis = PlotAxes.categoryOf(state);
|
|
25
34
|
const position = ProcessedPositions.of(processed, valueAxis);
|
|
26
35
|
const valueOf = position[valueAxis];
|
|
27
|
-
const categoryOf = position[
|
|
28
|
-
const
|
|
36
|
+
const categoryOf = position[PlotAxes.categoryOf(state)];
|
|
37
|
+
const keyOf = BoxSummaries.#keyOf(state);
|
|
29
38
|
const boxes = new Map();
|
|
30
39
|
for (const row of processed.data) {
|
|
31
40
|
const value = valueOf(row);
|
|
@@ -34,39 +43,80 @@ export class BoxSummaries {
|
|
|
34
43
|
const n = Number(value);
|
|
35
44
|
if (!Number.isFinite(n))
|
|
36
45
|
continue;
|
|
37
|
-
const key =
|
|
38
|
-
row[SERIES_ID_KEY],
|
|
39
|
-
row[PANEL_KEY] ?? null,
|
|
40
|
-
String(categoryOf(row))
|
|
41
|
-
]);
|
|
46
|
+
const key = keyOf(row);
|
|
42
47
|
const box = boxes.get(key);
|
|
43
48
|
if (box)
|
|
44
49
|
box.values.push(n);
|
|
45
50
|
else
|
|
46
|
-
boxes.set(key, {
|
|
51
|
+
boxes.set(key, { category: categoryOf(row), values: [n] });
|
|
52
|
+
}
|
|
53
|
+
const summaries = new Map([...boxes].map(([key, { category, values }]) => [
|
|
54
|
+
key,
|
|
55
|
+
BoxSummaries.#summarize(category, values)
|
|
56
|
+
]));
|
|
57
|
+
return (row) => summaries.get(keyOf(row));
|
|
58
|
+
}
|
|
59
|
+
static #summarize(category, values) {
|
|
60
|
+
const sorted = values.sort((a, b) => a - b);
|
|
61
|
+
const p25 = quantile(sorted, 0.25);
|
|
62
|
+
const median = quantile(sorted, 0.5);
|
|
63
|
+
const p75 = quantile(sorted, 0.75);
|
|
64
|
+
const reach = (p75 - p25) * WHISKER_IQR;
|
|
65
|
+
const inside = sorted.filter((v) => v >= p25 - reach && v <= p75 + reach);
|
|
66
|
+
return {
|
|
67
|
+
category,
|
|
68
|
+
min: inside[0] ?? p25,
|
|
69
|
+
p25,
|
|
70
|
+
median,
|
|
71
|
+
p75,
|
|
72
|
+
max: inside.at(-1) ?? p75,
|
|
73
|
+
count: sorted.length,
|
|
74
|
+
outliers: sorted.filter((v) => v < p25 - reach || v > p75 + reach)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* One summary row per box, in the order the processed rows first reach it, read off its first value's context.
|
|
79
|
+
* Its own context is the box's, with the median for its value, and where it sits for `x` and `y`.
|
|
80
|
+
*/
|
|
81
|
+
static of(state) {
|
|
82
|
+
const processed = state.processedData;
|
|
83
|
+
if (!processed?.data.length)
|
|
84
|
+
return [];
|
|
85
|
+
const valueAxis = PlotAxes.valueOf(state);
|
|
86
|
+
const categoryAxis = PlotAxes.categoryOf(state);
|
|
87
|
+
const categoryField = processed.channels[categoryAxis];
|
|
88
|
+
const keyOf = BoxSummaries.#keyOf(state);
|
|
89
|
+
const firsts = new Map();
|
|
90
|
+
for (const row of processed.data) {
|
|
91
|
+
const key = keyOf(row);
|
|
92
|
+
if (!firsts.has(key) && RowContext.of(row).median !== undefined)
|
|
93
|
+
firsts.set(key, row);
|
|
47
94
|
}
|
|
48
|
-
return [...
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const median
|
|
52
|
-
const
|
|
53
|
-
const reach = (p75 - p25) * WHISKER_IQR;
|
|
54
|
-
const inside = sorted.filter((v) => v >= p25 - reach && v <= p75 + reach);
|
|
55
|
-
const category = categoryOf(first);
|
|
56
|
-
return {
|
|
95
|
+
return [...firsts.values()].map((first) => {
|
|
96
|
+
// The box's context, not its first value's row.
|
|
97
|
+
const context = { ...RowContext.of(first), row: undefined };
|
|
98
|
+
const { category, min, p25, median, p75, max, count, outliers } = context;
|
|
99
|
+
const summary = {
|
|
57
100
|
...(typeof categoryField === 'string' ? { [categoryField]: category } : {}),
|
|
58
101
|
[SERIES_KEY]: first[SERIES_KEY],
|
|
59
102
|
[SERIES_ID_KEY]: first[SERIES_ID_KEY],
|
|
60
103
|
...(first[PANEL_KEY] !== undefined ? { [PANEL_KEY]: first[PANEL_KEY] } : {}),
|
|
61
104
|
category,
|
|
62
|
-
min
|
|
105
|
+
min,
|
|
63
106
|
p25,
|
|
64
107
|
median,
|
|
65
108
|
p75,
|
|
66
|
-
max
|
|
67
|
-
count
|
|
68
|
-
outliers
|
|
109
|
+
max,
|
|
110
|
+
count,
|
|
111
|
+
outliers
|
|
69
112
|
};
|
|
113
|
+
RowContext.tag(summary, {
|
|
114
|
+
...context,
|
|
115
|
+
[valueAxis]: median,
|
|
116
|
+
[categoryAxis]: category,
|
|
117
|
+
value: median
|
|
118
|
+
});
|
|
119
|
+
return summary;
|
|
70
120
|
});
|
|
71
121
|
}
|
|
72
122
|
/** Where a box's summary sits for hover: at its category, level with its median. */
|
|
@@ -79,22 +129,22 @@ export class BoxSummaries {
|
|
|
79
129
|
return at;
|
|
80
130
|
}
|
|
81
131
|
/**
|
|
82
|
-
* How a hovered box reads by default: its series, when the plot groups its
|
|
83
|
-
*
|
|
84
|
-
* values fall past the whiskers.
|
|
132
|
+
* How a hovered box reads by default, from its context: its series, when the plot groups its rows; its
|
|
133
|
+
* category; then its five numbers, highest first, and how many values fall past the whiskers.
|
|
85
134
|
*/
|
|
86
135
|
static format(state) {
|
|
87
136
|
const grouped = state.channels?.grouping !== undefined;
|
|
88
137
|
return (row) => {
|
|
89
|
-
const
|
|
90
|
-
lines
|
|
91
|
-
lines.push(
|
|
92
|
-
lines.push(`
|
|
93
|
-
lines.push(`
|
|
94
|
-
lines.push(`
|
|
95
|
-
lines.push(`
|
|
96
|
-
|
|
97
|
-
|
|
138
|
+
const box = RowContext.of(row);
|
|
139
|
+
const lines = grouped ? [String(box.series)] : [];
|
|
140
|
+
lines.push(TooltipState.value(box.category));
|
|
141
|
+
lines.push(`max: ${round(box.max)}`);
|
|
142
|
+
lines.push(`p75: ${round(box.p75)}`);
|
|
143
|
+
lines.push(`median: ${round(box.median)}`);
|
|
144
|
+
lines.push(`p25: ${round(box.p25)}`);
|
|
145
|
+
lines.push(`min: ${round(box.min)}`);
|
|
146
|
+
if (box.outliers.length)
|
|
147
|
+
lines.push(`outliers: ${box.outliers.length}`);
|
|
98
148
|
return lines.join('\n');
|
|
99
149
|
};
|
|
100
150
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AxisPlotDefaults } from '../../../types/phases';
|
|
2
|
-
import type { BoxPlotState,
|
|
2
|
+
import type { BoxPlotState, BoxPlotEnv } from '../../../types/plots/distribution/box/state';
|
|
3
3
|
/**
|
|
4
4
|
* BoxPlot on the shared pipeline: one box per category of each series along
|
|
5
5
|
* one category axis - `x`, or `y` for horizontal boxes, as the "data" phase
|
|
@@ -9,4 +9,4 @@ import type { BoxPlotState, BoxPlotCtx } from '../../../types/plots/distribution
|
|
|
9
9
|
* hover along the category axis, over one summary per box rather than every
|
|
10
10
|
* value inside it.
|
|
11
11
|
*/
|
|
12
|
-
export declare function buildBoxDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<BoxPlotState<TData>,
|
|
12
|
+
export declare function buildBoxDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<BoxPlotState<TData>, BoxPlotEnv>;
|