graphein 0.20.0 → 0.22.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/dist/animation/animate.d.ts +6 -1
- package/dist/animation/animate.d.ts.map +1 -1
- package/dist/animation/animate.js +2 -2
- package/dist/animation/animate.js.map +2 -2
- package/dist/animation/env.d.ts +5 -0
- package/dist/animation/env.d.ts.map +1 -1
- package/dist/animation/env.js +4 -0
- package/dist/animation/env.js.map +2 -2
- package/dist/animation/index.d.ts +1 -0
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/animation/index.js +1 -0
- package/dist/animation/index.js.map +2 -2
- package/dist/animation/stagger.d.ts +13 -0
- package/dist/animation/stagger.d.ts.map +1 -0
- package/dist/animation/stagger.js +13 -0
- package/dist/animation/stagger.js.map +7 -0
- package/dist/axes/draw.d.ts +5 -1
- package/dist/axes/draw.d.ts.map +1 -1
- package/dist/axes/draw.js +13 -1
- package/dist/axes/draw.js.map +2 -2
- package/dist/axes/morph.d.ts +111 -0
- package/dist/axes/morph.d.ts.map +1 -0
- package/dist/axes/morph.js +207 -0
- package/dist/axes/morph.js.map +7 -0
- package/dist/axes/overlay.d.ts.map +1 -1
- package/dist/axes/overlay.js +88 -25
- package/dist/axes/overlay.js.map +2 -2
- package/dist/axes/text.d.ts +2 -0
- package/dist/axes/text.d.ts.map +1 -1
- package/dist/axes/text.js +1 -0
- package/dist/axes/text.js.map +2 -2
- package/dist/axes/tickLabels.d.ts +2 -0
- package/dist/axes/tickLabels.d.ts.map +1 -1
- package/dist/axes/tickLabels.js +2 -2
- package/dist/axes/tickLabels.js.map +2 -2
- package/dist/charts/annotations.d.ts +9 -3
- package/dist/charts/annotations.d.ts.map +1 -1
- package/dist/charts/annotations.js +82 -27
- package/dist/charts/annotations.js.map +2 -2
- package/dist/charts/area.d.ts +37 -1
- package/dist/charts/area.d.ts.map +1 -1
- package/dist/charts/area.js +35 -17
- package/dist/charts/area.js.map +2 -2
- package/dist/charts/bar.d.ts +53 -0
- package/dist/charts/bar.d.ts.map +1 -1
- package/dist/charts/bar.js +117 -83
- package/dist/charts/bar.js.map +2 -2
- package/dist/charts/box.d.ts +31 -0
- package/dist/charts/box.d.ts.map +1 -1
- package/dist/charts/box.js +93 -70
- package/dist/charts/box.js.map +2 -2
- package/dist/charts/bubbleMap.d.ts +20 -0
- package/dist/charts/bubbleMap.d.ts.map +1 -0
- package/dist/charts/bubbleMap.js +951 -0
- package/dist/charts/bubbleMap.js.map +7 -0
- package/dist/charts/bullet.d.ts.map +1 -1
- package/dist/charts/bullet.js +69 -51
- package/dist/charts/bullet.js.map +2 -2
- package/dist/charts/calendarHeatmap.d.ts.map +1 -1
- package/dist/charts/calendarHeatmap.js +56 -19
- package/dist/charts/calendarHeatmap.js.map +2 -2
- package/dist/charts/candlestick.d.ts +40 -0
- package/dist/charts/candlestick.d.ts.map +1 -0
- package/dist/charts/candlestick.js +325 -0
- package/dist/charts/candlestick.js.map +7 -0
- package/dist/charts/choropleth.d.ts.map +1 -1
- package/dist/charts/choropleth.js +46 -67
- package/dist/charts/choropleth.js.map +2 -2
- package/dist/charts/combo.d.ts.map +1 -1
- package/dist/charts/combo.js +127 -27
- package/dist/charts/combo.js.map +3 -3
- package/dist/charts/dumbbell.d.ts.map +1 -1
- package/dist/charts/dumbbell.js +100 -31
- package/dist/charts/dumbbell.js.map +2 -2
- package/dist/charts/funnel.d.ts.map +1 -1
- package/dist/charts/funnel.js +93 -28
- package/dist/charts/funnel.js.map +3 -3
- package/dist/charts/gantt.d.ts +22 -0
- package/dist/charts/gantt.d.ts.map +1 -0
- package/dist/charts/gantt.js +564 -0
- package/dist/charts/gantt.js.map +7 -0
- package/dist/charts/gauge.d.ts.map +1 -1
- package/dist/charts/gauge.js +30 -4
- package/dist/charts/gauge.js.map +2 -2
- package/dist/charts/geo.d.ts +87 -0
- package/dist/charts/geo.d.ts.map +1 -0
- package/dist/charts/geo.js +140 -0
- package/dist/charts/geo.js.map +7 -0
- package/dist/charts/geometry.d.ts +20 -0
- package/dist/charts/geometry.d.ts.map +1 -0
- package/dist/charts/geometry.js +25 -0
- package/dist/charts/geometry.js.map +7 -0
- package/dist/charts/heatmap.d.ts.map +1 -1
- package/dist/charts/heatmap.js +58 -49
- package/dist/charts/heatmap.js.map +2 -2
- package/dist/charts/histogram.d.ts +1 -1
- package/dist/charts/histogram.d.ts.map +1 -1
- package/dist/charts/histogram.js +49 -31
- package/dist/charts/histogram.js.map +2 -2
- package/dist/charts/index.d.ts +35 -1
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +12 -0
- package/dist/charts/index.js.map +2 -2
- package/dist/charts/legibility.d.ts +38 -0
- package/dist/charts/legibility.d.ts.map +1 -0
- package/dist/charts/legibility.js +72 -0
- package/dist/charts/legibility.js.map +7 -0
- package/dist/charts/line.d.ts +39 -1
- package/dist/charts/line.d.ts.map +1 -1
- package/dist/charts/line.js +48 -16
- package/dist/charts/line.js.map +2 -2
- package/dist/charts/matrix.d.ts +2 -1
- package/dist/charts/matrix.d.ts.map +1 -1
- package/dist/charts/matrix.js +481 -319
- package/dist/charts/matrix.js.map +3 -3
- package/dist/charts/morphFrame.d.ts +83 -0
- package/dist/charts/morphFrame.d.ts.map +1 -0
- package/dist/charts/morphFrame.js +128 -0
- package/dist/charts/morphFrame.js.map +7 -0
- package/dist/charts/pie.d.ts.map +1 -1
- package/dist/charts/pie.js +89 -26
- package/dist/charts/pie.js.map +2 -2
- package/dist/charts/pieState.d.ts +27 -0
- package/dist/charts/pieState.d.ts.map +1 -0
- package/dist/charts/pieState.js +55 -0
- package/dist/charts/pieState.js.map +7 -0
- package/dist/charts/radar.d.ts +62 -0
- package/dist/charts/radar.d.ts.map +1 -0
- package/dist/charts/radar.js +912 -0
- package/dist/charts/radar.js.map +7 -0
- package/dist/charts/radialBar.d.ts +60 -0
- package/dist/charts/radialBar.d.ts.map +1 -0
- package/dist/charts/radialBar.js +708 -0
- package/dist/charts/radialBar.js.map +7 -0
- package/dist/charts/sankey.d.ts.map +1 -1
- package/dist/charts/sankey.js +223 -57
- package/dist/charts/sankey.js.map +3 -3
- package/dist/charts/scatter.d.ts +31 -0
- package/dist/charts/scatter.d.ts.map +1 -1
- package/dist/charts/scatter.js +48 -12
- package/dist/charts/scatter.js.map +2 -2
- package/dist/charts/slope.d.ts.map +1 -1
- package/dist/charts/slope.js +55 -3
- package/dist/charts/slope.js.map +2 -2
- package/dist/charts/sunburst.d.ts +14 -0
- package/dist/charts/sunburst.d.ts.map +1 -0
- package/dist/charts/sunburst.js +885 -0
- package/dist/charts/sunburst.js.map +7 -0
- package/dist/charts/table.d.ts +2 -1
- package/dist/charts/table.d.ts.map +1 -1
- package/dist/charts/table.js +214 -224
- package/dist/charts/table.js.map +3 -3
- package/dist/charts/tableView/canvas.d.ts +16 -1
- package/dist/charts/tableView/canvas.d.ts.map +1 -1
- package/dist/charts/tableView/canvas.js +361 -252
- package/dist/charts/tableView/canvas.js.map +3 -3
- package/dist/charts/tableView/conditional.d.ts +66 -40
- package/dist/charts/tableView/conditional.d.ts.map +1 -1
- package/dist/charts/tableView/conditional.js +163 -256
- package/dist/charts/tableView/conditional.js.map +3 -3
- package/dist/charts/tableView/dom.d.ts +26 -2
- package/dist/charts/tableView/dom.d.ts.map +1 -1
- package/dist/charts/tableView/dom.js +609 -269
- package/dist/charts/tableView/dom.js.map +3 -3
- package/dist/charts/tableView/layout.d.ts +22 -0
- package/dist/charts/tableView/layout.d.ts.map +1 -0
- package/dist/charts/tableView/layout.js +246 -0
- package/dist/charts/tableView/layout.js.map +7 -0
- package/dist/charts/tableView/sketch.d.ts +3 -2
- package/dist/charts/tableView/sketch.d.ts.map +1 -1
- package/dist/charts/tableView/sketch.js +2 -2
- package/dist/charts/tableView/sketch.js.map +2 -2
- package/dist/charts/tableView/sketchBar.d.ts +7 -0
- package/dist/charts/tableView/sketchBar.d.ts.map +1 -0
- package/dist/charts/tableView/sketchBar.js +32 -0
- package/dist/charts/tableView/sketchBar.js.map +7 -0
- package/dist/charts/tableView/sort.d.ts +9 -0
- package/dist/charts/tableView/sort.d.ts.map +1 -0
- package/dist/charts/tableView/sort.js +70 -0
- package/dist/charts/tableView/sort.js.map +7 -0
- package/dist/charts/tableView/sparkline.d.ts +30 -0
- package/dist/charts/tableView/sparkline.d.ts.map +1 -0
- package/dist/charts/tableView/sparkline.js +63 -0
- package/dist/charts/tableView/sparkline.js.map +7 -0
- package/dist/charts/tableView/style.d.ts +67 -0
- package/dist/charts/tableView/style.d.ts.map +1 -0
- package/dist/charts/tableView/style.js +131 -0
- package/dist/charts/tableView/style.js.map +7 -0
- package/dist/charts/tableView/types.d.ts +69 -46
- package/dist/charts/tableView/types.d.ts.map +1 -1
- package/dist/charts/tableView/types.js +22 -87
- package/dist/charts/tableView/types.js.map +2 -2
- package/dist/charts/tableView.d.ts +10 -5
- package/dist/charts/tableView.d.ts.map +1 -1
- package/dist/charts/tableView.js +12 -8
- package/dist/charts/tableView.js.map +2 -2
- package/dist/charts/treemap.d.ts.map +1 -1
- package/dist/charts/treemap.js +177 -68
- package/dist/charts/treemap.js.map +3 -3
- package/dist/charts/trendline.d.ts +13 -5
- package/dist/charts/trendline.d.ts.map +1 -1
- package/dist/charts/trendline.js +205 -56
- package/dist/charts/trendline.js.map +2 -2
- package/dist/charts/waterfall.d.ts.map +1 -1
- package/dist/charts/waterfall.js +76 -5
- package/dist/charts/waterfall.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interaction/cartesian.d.ts +4 -1
- package/dist/interaction/cartesian.d.ts.map +1 -1
- package/dist/interaction/cartesian.js +104 -34
- package/dist/interaction/cartesian.js.map +2 -2
- package/dist/interaction/controller.d.ts +29 -1
- package/dist/interaction/controller.d.ts.map +1 -1
- package/dist/interaction/controller.js +131 -8
- package/dist/interaction/controller.js.map +2 -2
- package/dist/interaction/focus.d.ts +23 -0
- package/dist/interaction/focus.d.ts.map +1 -0
- package/dist/interaction/focus.js +65 -0
- package/dist/interaction/focus.js.map +7 -0
- package/dist/interaction/tooltip.d.ts +6 -0
- package/dist/interaction/tooltip.d.ts.map +1 -1
- package/dist/interaction/tooltip.js +41 -16
- package/dist/interaction/tooltip.js.map +2 -2
- package/dist/interaction/types.d.ts +19 -0
- package/dist/interaction/types.d.ts.map +1 -1
- package/dist/interaction/veil.d.ts +25 -0
- package/dist/interaction/veil.d.ts.map +1 -0
- package/dist/interaction/veil.js +35 -0
- package/dist/interaction/veil.js.map +7 -0
- package/dist/pivot/pivot.d.ts +18 -0
- package/dist/pivot/pivot.d.ts.map +1 -1
- package/dist/pivot/pivot.js +46 -34
- package/dist/pivot/pivot.js.map +2 -2
- package/dist/render/chromeMotion.d.ts +65 -0
- package/dist/render/chromeMotion.d.ts.map +1 -0
- package/dist/render/chromeMotion.js +79 -0
- package/dist/render/chromeMotion.js.map +7 -0
- package/dist/render/pathSample.d.ts +24 -0
- package/dist/render/pathSample.d.ts.map +1 -0
- package/dist/render/pathSample.js +76 -0
- package/dist/render/pathSample.js.map +7 -0
- package/dist/render/transient.d.ts +18 -0
- package/dist/render/transient.d.ts.map +1 -0
- package/dist/render/transient.js +19 -0
- package/dist/render/transient.js.map +7 -0
- package/dist/runtime/candlestick.d.ts +112 -0
- package/dist/runtime/candlestick.d.ts.map +1 -0
- package/dist/runtime/candlestick.js +531 -0
- package/dist/runtime/candlestick.js.map +7 -0
- package/dist/runtime/cartesian.d.ts +14 -3
- package/dist/runtime/cartesian.d.ts.map +1 -1
- package/dist/runtime/cartesian.js.map +2 -2
- package/dist/runtime/chartAnimation.d.ts +54 -7
- package/dist/runtime/chartAnimation.d.ts.map +1 -1
- package/dist/runtime/chartAnimation.js +146 -28
- package/dist/runtime/chartAnimation.js.map +2 -2
- package/dist/runtime/chartRuntime.d.ts +42 -0
- package/dist/runtime/chartRuntime.d.ts.map +1 -1
- package/dist/runtime/chartRuntime.js +196 -30
- package/dist/runtime/chartRuntime.js.map +2 -2
- package/dist/runtime/dashboardDom.d.ts.map +1 -1
- package/dist/runtime/dashboardDom.js +45 -0
- package/dist/runtime/dashboardDom.js.map +2 -2
- package/dist/runtime/facet.d.ts +1 -1
- package/dist/runtime/facet.d.ts.map +1 -1
- package/dist/runtime/gantt.d.ts +137 -0
- package/dist/runtime/gantt.d.ts.map +1 -0
- package/dist/runtime/gantt.js +647 -0
- package/dist/runtime/gantt.js.map +7 -0
- package/dist/runtime/morph.d.ts +33 -0
- package/dist/runtime/morph.d.ts.map +1 -0
- package/dist/runtime/morph.js +332 -0
- package/dist/runtime/morph.js.map +7 -0
- package/dist/runtime/radar.d.ts +75 -0
- package/dist/runtime/radar.d.ts.map +1 -0
- package/dist/runtime/radar.js +201 -0
- package/dist/runtime/radar.js.map +7 -0
- package/dist/runtime/radialBar.d.ts +81 -0
- package/dist/runtime/radialBar.d.ts.map +1 -0
- package/dist/runtime/radialBar.js +125 -0
- package/dist/runtime/radialBar.js.map +7 -0
- package/dist/runtime/report/constants.d.ts +2 -2
- package/dist/runtime/report/constants.d.ts.map +1 -1
- package/dist/runtime/runtimeShared.d.ts +3 -3
- package/dist/runtime/runtimeShared.d.ts.map +1 -1
- package/dist/runtime/sunburst.d.ts +74 -0
- package/dist/runtime/sunburst.d.ts.map +1 -0
- package/dist/runtime/sunburst.js +150 -0
- package/dist/runtime/sunburst.js.map +7 -0
- package/dist/runtime/wire.d.ts.map +1 -1
- package/dist/runtime/wire.js +3 -1
- package/dist/runtime/wire.js.map +2 -2
- package/dist/shape/arc.d.ts +15 -2
- package/dist/shape/arc.d.ts.map +1 -1
- package/dist/shape/arc.js +204 -32
- package/dist/shape/arc.js.map +2 -2
- package/dist/spec/chartDefinitions.d.ts +6 -0
- package/dist/spec/chartDefinitions.d.ts.map +1 -1
- package/dist/spec/chartDefinitions.js +7 -1
- package/dist/spec/chartDefinitions.js.map +2 -2
- package/dist/spec/templates.d.ts.map +1 -1
- package/dist/spec/templates.js +177 -2
- package/dist/spec/templates.js.map +2 -2
- package/dist/spec/types.d.ts +258 -6
- package/dist/spec/types.d.ts.map +1 -1
- package/dist/spec/types.js +6 -0
- package/dist/spec/types.js.map +2 -2
- package/dist/spec/validate/charts.js +147 -15
- package/dist/spec/validate/charts.js.map +2 -2
- package/dist/spec/validate/common.d.ts +6 -0
- package/dist/spec/validate/common.d.ts.map +1 -1
- package/dist/spec/validate/overlays.js +2 -2
- package/dist/spec/validate/overlays.js.map +2 -2
- package/dist/spec/validate.data.d.ts.map +1 -1
- package/dist/spec/validate.data.js +8 -3
- package/dist/spec/validate.data.js.map +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/charts/candlestick.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Candlestick renderer.\n *\n * A custom renderer (it owns its layout): it resolves a {@link CandlestickModel},\n * paints the shared axis chrome via `drawAxesUnderlay` / `drawOverlay`, then one\n * candle per period \u2014 a crisp wick from the low to the high and a solid body from\n * the open to the close in the up / down color, or with `style: 'ohlc'` a bar\n * with an open tick on the left and a close tick on the right. Candles are\n * batched by paint (one path per color), so a thousand periods stay cheap, and\n * each one is recorded as render evidence. Hover focuses the period under the\n * pointer; updates carry every candle to its new prices.\n */\n\nimport type { Surface } from '../render/surface';\nimport type { Rect, Size } from '../types';\nimport type { CandlestickSpec, ChartSpec } from '../spec/types';\nimport type { ThemeTokens } from '../theme';\nimport type { Hover, HoverPaintState, InteractionModel, TooltipRow } from '../interaction/types';\nimport { FOCUS_VEIL, hoverFocusEnabled, paintCutoutVeil } from '../interaction/veil';\nimport { RoughPen } from '../rough';\nimport { drawAxesUnderlay, drawOverlay } from '../axes';\nimport { formatNumber, formatValue } from '../format';\nimport { staggerProgress } from '../animation/stagger';\nimport { buildCandlestickModel, type Candle, type CandlestickModel } from '../runtime/candlestick';\nimport { drawAnnotationLabels, drawAnnotations } from './annotations';\nimport { drawNoData } from './noData';\nimport { rowAlpha } from './emphasis';\nimport type { RenderContext } from './index';\nimport { lerp, mixColor, morphInput, morphKeyed, stashMorphFrame, type MorphPlan } from './morphFrame';\n\n/** A candle as drawn, keyed by period so an update can carry it to its new prices. */\nexport interface CandleMark {\n key: string;\n /** Pixel center (the wick / OHLC stem). */\n cx: number;\n /** Body width (or the OHLC tick span). */\n width: number;\n /** Wick / OHLC stroke width. */\n stroke: number;\n /** Pixel y of the high (top) and low (bottom) extremes. */\n top: number;\n bottom: number;\n openY: number;\n closeY: number;\n color: string;\n /** Highlight alpha, carried so emphasis changes fade instead of snapping. */\n emph: number;\n /** Fade for candles opening in, or leaving mid-morph. */\n alpha: number;\n}\n\n/** Fraction of the entrance spread across candles as a left-to-right wave. */\nconst ENTRANCE_STAGGER = 0.4;\n\nconst clamp01 = (value: number): number => (value < 0 ? 0 : value > 1 ? 1 : value);\nconst easeOut = (value: number): number => 1 - (1 - value) * (1 - value);\n\nfunction markOf(candle: Candle, emph: number): CandleMark {\n return {\n key: candle.key,\n cx: candle.cx,\n width: candle.width,\n stroke: candle.stroke,\n top: candle.top,\n bottom: candle.bottom,\n openY: candle.openY,\n closeY: candle.closeY,\n color: candle.color,\n emph,\n alpha: 1,\n };\n}\n\n/** `mark` at fraction `k` of its extent, opened out of (or folded into) its mid-body. */\nfunction heldMark(mark: CandleMark, k: number, alpha: number): CandleMark {\n const mid = (mark.openY + mark.closeY) / 2;\n return {\n ...mark,\n top: lerp(mid, mark.top, k),\n bottom: lerp(mid, mark.bottom, k),\n openY: lerp(mid, mark.openY, k),\n closeY: lerp(mid, mark.closeY, k),\n alpha,\n };\n}\n\nconst CANDLE_MORPH: MorphPlan<CandleMark> = {\n lerp: (a, b, t) => ({\n key: b.key,\n cx: lerp(a.cx, b.cx, t),\n width: lerp(a.width, b.width, t),\n stroke: lerp(a.stroke, b.stroke, t),\n top: lerp(a.top, b.top, t),\n bottom: lerp(a.bottom, b.bottom, t),\n openY: lerp(a.openY, b.openY, t),\n closeY: lerp(a.closeY, b.closeY, t),\n // A candle whose direction flips passes through a doji while its color turns.\n color: mixColor(a.color, b.color, t),\n emph: lerp(a.emph, b.emph, t),\n alpha: lerp(a.alpha, b.alpha, t),\n }),\n enter: (b, t) => heldMark(b, t, t),\n exit: (a, t) => heldMark(a, 1 - t, a.alpha * (1 - t)),\n};\n\n/**\n * A candle `k` of the way through its entrance: the body opens from the open\n * price toward the close, then the wick reaches out to the high and the low.\n */\nfunction openingMark(mark: CandleMark, k: number): CandleMark {\n if (k >= 1) return mark;\n const body = easeOut(clamp01(k / 0.6));\n const wick = easeOut(clamp01((k - 0.3) / 0.7));\n const closeY = lerp(mark.openY, mark.closeY, body);\n return {\n ...mark,\n closeY,\n top: lerp(Math.min(mark.openY, closeY), mark.top, wick),\n bottom: lerp(Math.max(mark.openY, closeY), mark.bottom, wick),\n alpha: mark.alpha * clamp01(k / 0.2),\n };\n}\n\n/** Every candle at entrance progress `t`, as a left-to-right wave when `wave` is set. */\nfunction enteringMarks(marks: readonly CandleMark[], t: number, wave: boolean): CandleMark[] {\n const n = marks.length;\n const rank = new Array<number>(n).fill(0);\n if (wave) {\n marks\n .map((_, i) => i)\n .sort((a, b) => marks[a].cx - marks[b].cx || a - b)\n .forEach((index, order) => {\n rank[index] = order;\n });\n }\n return marks.map((mark, i) => openingMark(mark, wave ? staggerProgress(t, rank[i], n, ENTRANCE_STAGGER) : t));\n}\n\n/** Length of an OHLC bar's open / close ticks. */\nfunction ohlcTick(width: number): number {\n return Math.max(2, Math.round(width / 2));\n}\n\n/** The body rect: open \u2192 close, at least 1px tall so a doji still shows its level. */\nfunction bodyOf(mark: CandleMark): { y: number; height: number } {\n const y0 = Math.min(mark.openY, mark.closeY);\n const y1 = Math.max(mark.openY, mark.closeY);\n return { y: y0, height: Math.max(1, y1 - y0) };\n}\n\n/** Horizontal half-extent of a drawn candle around its center. */\nfunction halfExtent(mark: CandleMark, ohlc: boolean): number {\n return ohlc ? mark.stroke / 2 + ohlcTick(mark.width) : Math.max(mark.width, mark.stroke) / 2;\n}\n\ninterface PaintGroup {\n color: string;\n opacity: number;\n stroke: number;\n marks: CandleMark[];\n}\n\n/** Marks sharing one paint, so each color draws as a single wick path and body path. */\nfunction paintGroups(marks: readonly CandleMark[]): PaintGroup[] {\n const groups = new Map<string, PaintGroup>();\n for (const mark of marks) {\n const opacity = mark.emph * mark.alpha;\n if (!(opacity > 0)) continue;\n const key = `${mark.color}\\u0001${opacity}\\u0001${mark.stroke}`;\n let group = groups.get(key);\n if (!group) groups.set(key, (group = { color: mark.color, opacity, stroke: mark.stroke, marks: [] }));\n group.marks.push(mark);\n }\n return [...groups.values()];\n}\n\nfunction paintCandleGroup(ctx: CanvasRenderingContext2D, group: PaintGroup): void {\n // Wicks run from each extreme to the body edge (never through the body), so a\n // dimmed candle shows no darker seam where the two overlap.\n let wicks = false;\n ctx.beginPath();\n for (const mark of group.marks) {\n const body = bodyOf(mark);\n if (mark.top < body.y) {\n ctx.moveTo(mark.cx, mark.top);\n ctx.lineTo(mark.cx, body.y);\n wicks = true;\n }\n if (mark.bottom > body.y + body.height) {\n ctx.moveTo(mark.cx, body.y + body.height);\n ctx.lineTo(mark.cx, mark.bottom);\n wicks = true;\n }\n }\n if (wicks) {\n ctx.lineWidth = group.stroke;\n ctx.strokeStyle = group.color;\n ctx.stroke();\n }\n ctx.beginPath();\n for (const mark of group.marks) {\n const body = bodyOf(mark);\n ctx.rect(mark.cx - mark.width / 2, body.y, mark.width, body.height);\n }\n ctx.fillStyle = group.color;\n ctx.fill();\n}\n\nfunction paintOhlcGroup(ctx: CanvasRenderingContext2D, group: PaintGroup): void {\n ctx.beginPath();\n for (const mark of group.marks) {\n const half = mark.stroke / 2;\n const tick = ohlcTick(mark.width);\n ctx.moveTo(mark.cx, Math.min(mark.top, mark.openY, mark.closeY));\n ctx.lineTo(mark.cx, Math.max(mark.bottom, mark.openY, mark.closeY));\n // Ticks start at the stem's edges, so their ends land on whole pixels.\n ctx.moveTo(mark.cx - half - tick, mark.openY);\n ctx.lineTo(mark.cx - half, mark.openY);\n ctx.moveTo(mark.cx + half, mark.closeY);\n ctx.lineTo(mark.cx + half + tick, mark.closeY);\n }\n ctx.lineWidth = group.stroke;\n ctx.strokeStyle = group.color;\n ctx.stroke();\n}\n\nfunction paintSketchCandles(ctx: CanvasRenderingContext2D, model: CandlestickModel, marks: readonly CandleMark[]): void {\n const pen = new RoughPen(ctx, model.sketch ?? {});\n for (const mark of marks) {\n const opacity = mark.emph * mark.alpha;\n if (!(opacity > 0)) continue;\n ctx.globalAlpha = opacity;\n const stroke = { stroke: mark.color };\n if (model.style === 'ohlc') {\n const half = mark.stroke / 2;\n const tick = ohlcTick(mark.width);\n pen.polyline([{ x: mark.cx, y: Math.min(mark.top, mark.openY, mark.closeY) }, { x: mark.cx, y: Math.max(mark.bottom, mark.openY, mark.closeY) }], stroke);\n pen.polyline([{ x: mark.cx - half - tick, y: mark.openY }, { x: mark.cx - half, y: mark.openY }], stroke);\n pen.polyline([{ x: mark.cx + half, y: mark.closeY }, { x: mark.cx + half + tick, y: mark.closeY }], stroke);\n continue;\n }\n const body = bodyOf(mark);\n if (mark.bottom - mark.top >= 1) pen.polyline([{ x: mark.cx, y: mark.top }, { x: mark.cx, y: mark.bottom }], stroke);\n pen.rect(mark.cx - mark.width / 2, body.y, mark.width, body.height, { stroke: mark.color, fill: mark.color });\n }\n ctx.globalAlpha = 1;\n}\n\n/** Paint candles (beneath-first order) and record each visible one as evidence. */\nfunction paintCandles(surface: Surface, model: CandlestickModel, marks: readonly CandleMark[]): void {\n if (marks.length === 0) return;\n const ctx = surface.marks.ctx;\n const { plot } = model;\n const clip: Rect = { x: plot.x - 1, y: plot.y - 1, width: plot.width + 2, height: plot.height + 2 };\n const ohlc = model.style === 'ohlc';\n ctx.save();\n ctx.beginPath();\n ctx.rect(clip.x, clip.y, clip.width, clip.height);\n ctx.clip();\n ctx.lineCap = 'butt';\n if (model.sketch) paintSketchCandles(ctx, model, marks);\n else {\n for (const group of paintGroups(marks)) {\n ctx.globalAlpha = group.opacity;\n if (ohlc) paintOhlcGroup(ctx, group);\n else paintCandleGroup(ctx, group);\n }\n ctx.globalAlpha = 1;\n }\n ctx.restore();\n\n const evidence = surface.evidence;\n if (!evidence) return;\n for (const mark of marks) {\n const opacity = mark.emph * mark.alpha;\n if (!(opacity > 0)) continue;\n const half = halfExtent(mark, ohlc);\n const top = Math.min(mark.top, mark.openY, mark.closeY);\n const bottom = Math.max(mark.bottom, mark.openY, mark.closeY, top + 1);\n evidence.mark('box', mark.cx - half, top, half * 2, bottom - top, mark.color, opacity, clip, 1, undefined, mark.emph < 1);\n }\n}\n\nfunction tooltipEnabled(spec: CandlestickSpec): boolean {\n const tooltip = spec.tooltip;\n return !(tooltip === false || (tooltip && typeof tooltip === 'object' && tooltip.show === false));\n}\n\nfunction formatPrice(value: number, format: string | undefined): string {\n return format ? formatValue(value, format) : formatNumber(value, ',');\n}\n\n/** Signed close \u2212 open change with its percentage of the open (raw values, never corrected). */\nfunction changeText(candle: Candle, format: string | undefined): string {\n const change = candle.close - candle.open;\n const sign = change > 0 ? '+' : change < 0 ? '-' : '';\n const magnitude = formatPrice(Math.abs(change), format);\n const pct = candle.open !== 0 ? (change / Math.abs(candle.open)) * 100 : NaN;\n const share = Number.isFinite(pct) ? ` (${sign}${Math.abs(pct).toFixed(2)}%)` : '';\n return `${sign}${magnitude}${share}`;\n}\n\nfunction tooltipRows(candle: Candle, spec: CandlestickSpec, format: string | undefined): TooltipRow[] {\n const enc = spec.encoding;\n return [\n { label: enc.open.title ?? 'Open', value: formatPrice(candle.open, format) },\n { label: enc.high.title ?? 'High', value: formatPrice(candle.high, format) },\n { label: enc.low.title ?? 'Low', value: formatPrice(candle.low, format) },\n { label: enc.close.title ?? 'Close', value: formatPrice(candle.close, format) },\n { swatch: candle.color, label: 'Change', value: changeText(candle, format), strong: true },\n ];\n}\n\n/** The focus cutout each hover last painted, so the next hover can glide from it. */\nconst FOCUS_FRAMES = new WeakMap<Hover, Rect>();\n\nfunction buildCandlestickInteraction(\n model: CandlestickModel,\n spec: CandlestickSpec,\n tokens: ThemeTokens,\n): InteractionModel | undefined {\n const { plot, candles } = model;\n if (candles.length === 0) return undefined;\n const xField = spec.encoding.x.field;\n const ohlc = model.style === 'ohlc';\n const order = candles.map((_, i) => i).sort((a, b) => candles[a].cx - candles[b].cx || a - b);\n const centers = order.map((i) => candles[i].cx);\n // Index-style: the candle nearest the pointer's x, anywhere up the plot.\n const nearest = (px: number): Candle => {\n let lo = 0;\n let hi = centers.length - 1;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (centers[mid] < px) lo = mid + 1;\n else hi = mid;\n }\n if (lo > 0 && Math.abs(centers[lo - 1] - px) <= Math.abs(centers[lo] - px)) lo -= 1;\n return candles[order[lo]];\n };\n const within = (px: number, py: number): boolean =>\n px >= plot.x - 6 && px <= plot.x + plot.width + 6 && py >= plot.y - 6 && py <= plot.y + plot.height + 6;\n const showTooltip = tooltipEnabled(spec);\n const focusEnabled = hoverFocusEnabled(spec);\n const veilColor = spec.background ?? tokens.color.background;\n\n // Focus recedes every other period behind a veil, leaving the hovered candle\n // (wick and body, with a little air) clear; the cutout glides between candles.\n const cutout = (candle: Candle): Rect => {\n const half = Math.max(3, halfExtent(markOf(candle, 1), ohlc) + 2);\n const top = Math.min(candle.top, candle.openY, candle.closeY) - 3;\n const bottom = Math.max(candle.bottom, candle.openY, candle.closeY) + 4;\n const x0 = Math.max(plot.x, candle.cx - half);\n const x1 = Math.min(plot.x + plot.width, candle.cx + half);\n const y0 = Math.max(plot.y, top);\n const y1 = Math.min(plot.y + plot.height, bottom);\n return { x: x0, y: y0, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };\n };\n const focus = (ictx: CanvasRenderingContext2D, hover: Hover, candle: Candle, state: HoverPaintState): void => {\n if (!focusEnabled) return;\n const target = cutout(candle);\n const previous = state.from ? FOCUS_FRAMES.get(state.from) : undefined;\n const rect = previous\n ? {\n x: lerp(previous.x, target.x, state.glide),\n y: lerp(previous.y, target.y, state.glide),\n width: lerp(previous.width, target.width, state.glide),\n height: lerp(previous.height, target.height, state.glide),\n }\n : target;\n FOCUS_FRAMES.set(hover, rect);\n paintCutoutVeil(ictx, plot, veilColor, FOCUS_VEIL * state.focus, (c) => c.rect(rect.x, rect.y, rect.width, rect.height));\n };\n\n return {\n region: { x: plot.x, y: plot.y, width: plot.width, height: plot.height },\n hitTest: (px, py) => {\n if (!showTooltip || !within(px, py)) return null;\n const candle = nearest(px);\n const hover: Hover = {\n key: `candle:${candle.sourceIndex}`,\n anchorX: candle.cx,\n anchorY: Math.max(plot.y, Math.min(candle.top, plot.y + plot.height)),\n content: { title: candle.label, rows: tooltipRows(candle, spec, model.priceFormat) },\n };\n hover.paint = (ictx, state) => focus(ictx, hover, candle, state);\n hover.draw = (ictx) => focus(ictx, hover, candle, { focus: 1, glide: 1 });\n return hover;\n },\n pick: (px, py) => {\n if (!within(px, py)) return null;\n return { kind: 'point', fields: [xField], tuples: [[nearest(px).x]] };\n },\n };\n}\n\nexport function drawCandlestick(\n surface: Surface,\n spec: ChartSpec,\n tokens: ThemeTokens,\n size: Size,\n context?: RenderContext,\n): InteractionModel | void {\n surface.evidence?.begin('rows');\n const candlestick = spec as CandlestickSpec;\n const model = buildCandlestickModel(candlestick, tokens, size);\n surface.evidence?.data(model.candles.length, 'rows');\n const emphasis = context?.emphasis ?? null;\n const finals = model.candles.map((candle) => markOf(candle, rowAlpha(emphasis, candle.row)));\n // Update morph: candles travel from the ones on screen (new periods open out of\n // their mid-body, removed ones fold away beneath) over the final axes, and a\n // changed highlight fades rather than snapping. The entrance opens each body\n // from its open price, then reaches its wick out \u2014 sweeping left to right.\n const morph = morphInput<CandleMark[]>(context);\n const moved = morph ? morphKeyed(morph.from, finals, morph.t, CANDLE_MORPH) : null;\n const progress = context?.entranceProgress;\n const frame = moved\n ? [...moved.exits, ...moved.items]\n : progress != null && progress < 1\n ? enteringMarks(finals, Math.max(0, progress), finals.length >= 3 && !model.sketch)\n : finals;\n stashMorphFrame(surface, 'candlestick', frame);\n\n drawAxesUnderlay(surface, model.base);\n paintCandles(surface, model, frame);\n if (model.candles.length === 0) {\n drawOverlay(surface, model.base);\n const rows = Array.isArray(candlestick.data) ? candlestick.data.length : 0;\n drawNoData(surface, tokens, model.plot, rows === 0 ? 'No data' : 'No usable OHLC data');\n return;\n }\n drawAnnotations(surface, model.base);\n drawOverlay(surface, model.base);\n if (model.thinnedTicks > 0 && candlestick.axes?.x?.show !== false && candlestick.axes?.x?.labels !== false) {\n // Index-axis labels skipped for room; every candle stays identifiable by its tooltip.\n surface.labels?.omit({ role: 'axis-label', reason: 'collision', required: false, count: model.thinnedTicks, axis: 'x', path: '/axes/x' });\n }\n drawAnnotationLabels(surface, model.base);\n return buildCandlestickInteraction(model, candlestick, tokens);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"choropleth.d.ts","sourceRoot":"","sources":["../../src/charts/choropleth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"choropleth.d.ts","sourceRoot":"","sources":["../../src/charts/choropleth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,KAAK,EAAE,SAAS,EAA8B,MAAM,eAAe,CAAC;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAe,IAAI,EAAE,MAAM,UAAU,CAAC;AAElD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AA+X7C,wBAAgB,cAAc,CAC5B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,aAAa,GACtB,gBAAgB,GAAG,IAAI,CA2FzB"}
|
|
@@ -8,51 +8,8 @@ import { rowAlpha } from "./emphasis.js";
|
|
|
8
8
|
import { dataBoundary } from "./boundary.js";
|
|
9
9
|
import { drawNoData } from "./noData.js";
|
|
10
10
|
import { drawColorRampLegend } from "./colorRamp.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const lons = [];
|
|
14
|
-
for (const feature of features) {
|
|
15
|
-
for (const polygon of polygonsOf(feature)) {
|
|
16
|
-
for (const ring of polygon) {
|
|
17
|
-
for (const pos of ring) {
|
|
18
|
-
if (Number.isFinite(pos[0])) lons.push(pos[0]);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
if (lons.length === 0) return 0;
|
|
24
|
-
lons.sort((a, b) => a - b);
|
|
25
|
-
let gapStart = lons[lons.length - 1];
|
|
26
|
-
let gapEnd = lons[0] + 360;
|
|
27
|
-
let maxGap = gapEnd - gapStart;
|
|
28
|
-
for (let i = 1; i < lons.length; i += 1) {
|
|
29
|
-
const g = lons[i] - lons[i - 1];
|
|
30
|
-
if (g > maxGap) {
|
|
31
|
-
maxGap = g;
|
|
32
|
-
gapStart = lons[i - 1];
|
|
33
|
-
gapEnd = lons[i];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return (gapStart + gapEnd) / 2 + 180;
|
|
37
|
-
}
|
|
38
|
-
function projector(kind, lon0) {
|
|
39
|
-
if (kind === "identity") {
|
|
40
|
-
return ([x, y]) => [x, y];
|
|
41
|
-
}
|
|
42
|
-
const wrapLon = (lon) => {
|
|
43
|
-
let d = lon - lon0;
|
|
44
|
-
while (d > 180) d -= 360;
|
|
45
|
-
while (d < -180) d += 360;
|
|
46
|
-
return lon0 + d;
|
|
47
|
-
};
|
|
48
|
-
if (kind === "equirectangular") {
|
|
49
|
-
return ([lon, lat]) => [wrapLon(lon), lat];
|
|
50
|
-
}
|
|
51
|
-
return ([lon, lat]) => {
|
|
52
|
-
const phi = clamp(lat, -85.0511, 85.0511) * Math.PI / 180;
|
|
53
|
-
return [wrapLon(lon), Math.log(Math.tan(Math.PI / 4 + phi / 2)) * 180 / Math.PI];
|
|
54
|
-
};
|
|
55
|
-
}
|
|
11
|
+
import { featureProjector, fitBounds, polygonsOf } from "./geo.js";
|
|
12
|
+
import { lerp, mixFill, morphInput, morphKeyed, rampAt, stashMorphFrame } from "./morphFrame.js";
|
|
56
13
|
function featureKey(feature, featureId) {
|
|
57
14
|
if (featureId) {
|
|
58
15
|
if (featureId === "id" && feature.id != null) return toKey(feature.id);
|
|
@@ -71,13 +28,6 @@ function featureName(feature, key) {
|
|
|
71
28
|
if (typeof props.name === "string") return props.name;
|
|
72
29
|
return key;
|
|
73
30
|
}
|
|
74
|
-
function polygonsOf(feature) {
|
|
75
|
-
const geom = feature.geometry;
|
|
76
|
-
if (!geom) return [];
|
|
77
|
-
if (geom.type === "Polygon") return [geom.coordinates];
|
|
78
|
-
if (geom.type === "MultiPolygon") return geom.coordinates;
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
31
|
function pointInRing(x, y, ring) {
|
|
82
32
|
let inside = false;
|
|
83
33
|
for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {
|
|
@@ -108,7 +58,7 @@ function joinedRows(spec) {
|
|
|
108
58
|
}
|
|
109
59
|
function projectFeatures(spec, features, joined) {
|
|
110
60
|
const projection = spec.projection ?? "mercator";
|
|
111
|
-
const project =
|
|
61
|
+
const project = featureProjector(projection, features);
|
|
112
62
|
const projected = [];
|
|
113
63
|
let minX = Infinity;
|
|
114
64
|
let maxX = -Infinity;
|
|
@@ -154,15 +104,9 @@ function projectFeatures(spec, features, joined) {
|
|
|
154
104
|
return { projected, minX, maxX, minY, maxY, vMin, vMax, flipY: projection !== "identity" };
|
|
155
105
|
}
|
|
156
106
|
function screenFeatures(projected, bounds, mapRect, colorOf) {
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
const drawW = spanX * scale;
|
|
161
|
-
const drawH = spanY * scale;
|
|
162
|
-
const offsetX = mapRect.x + (mapRect.width - drawW) / 2;
|
|
163
|
-
const offsetY = mapRect.y + (mapRect.height - drawH) / 2;
|
|
164
|
-
const sx = (x) => offsetX + (x - bounds.minX) * scale;
|
|
165
|
-
const sy = (y) => bounds.flipY ? offsetY + (bounds.maxY - y) * scale : offsetY + (y - bounds.minY) * scale;
|
|
107
|
+
const fit = fitBounds(bounds, mapRect, bounds.flipY);
|
|
108
|
+
const sx = fit.x;
|
|
109
|
+
const sy = fit.y;
|
|
166
110
|
return projected.map((pf) => {
|
|
167
111
|
let cx = 0;
|
|
168
112
|
let cy = 0;
|
|
@@ -207,14 +151,28 @@ function featureBounds(feature) {
|
|
|
207
151
|
}
|
|
208
152
|
return left === Infinity ? null : { x: left, y: top, width: right - left, height: bottom - top };
|
|
209
153
|
}
|
|
210
|
-
|
|
154
|
+
const CHOROPLETH_MORPH = {
|
|
155
|
+
lerp: (a, b, t) => {
|
|
156
|
+
const { fill, ramp } = mixFill({ fill: a.feature.color, ramp: a.ramp }, { fill: b.feature.color, ramp: b.ramp }, t);
|
|
157
|
+
return {
|
|
158
|
+
key: b.key,
|
|
159
|
+
feature: { ...b.feature, color: fill },
|
|
160
|
+
ramp,
|
|
161
|
+
emph: lerp(a.emph, b.emph, t),
|
|
162
|
+
alpha: lerp(a.alpha, b.alpha, t)
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
enter: (b, t) => ({ ...b, alpha: t }),
|
|
166
|
+
exit: (a, t) => ({ ...a, alpha: a.alpha * (1 - t) })
|
|
167
|
+
};
|
|
168
|
+
function paintChoroplethRegions(surface, tokens, regions, pen) {
|
|
211
169
|
const ctx = surface.marks.ctx;
|
|
212
170
|
const representedRows = /* @__PURE__ */ new Set();
|
|
213
171
|
ctx.save();
|
|
214
172
|
ctx.lineJoin = "round";
|
|
215
173
|
ctx.setLineDash([]);
|
|
216
|
-
for (const f of
|
|
217
|
-
const alpha =
|
|
174
|
+
for (const { feature: f, emph, alpha: fade } of regions) {
|
|
175
|
+
const alpha = emph * fade;
|
|
218
176
|
const boundary = dataBoundary(tokens, alpha);
|
|
219
177
|
ctx.strokeStyle = boundary.color;
|
|
220
178
|
ctx.lineWidth = boundary.width;
|
|
@@ -234,7 +192,7 @@ function paintChoroplethRegions(surface, spec, tokens, screen, emphasis, pen) {
|
|
|
234
192
|
}
|
|
235
193
|
}
|
|
236
194
|
if (pen) drawRoughOutlines(ctx, pen, f, boundary);
|
|
237
|
-
const bounds = f.value == null ? null : featureBounds(f);
|
|
195
|
+
const bounds = f.value == null || !surface.evidence ? null : featureBounds(f);
|
|
238
196
|
if (bounds) {
|
|
239
197
|
surface.evidence?.mark(
|
|
240
198
|
"region",
|
|
@@ -257,6 +215,12 @@ function paintChoroplethRegions(surface, spec, tokens, screen, emphasis, pen) {
|
|
|
257
215
|
ctx.restore();
|
|
258
216
|
return representedRows.size;
|
|
259
217
|
}
|
|
218
|
+
function drawLeavingRegions(surface, tokens, context) {
|
|
219
|
+
const morph = morphInput(context);
|
|
220
|
+
const leaving = morph ? morphKeyed(morph.from, [], morph.t, CHOROPLETH_MORPH).exits : [];
|
|
221
|
+
stashMorphFrame(surface, "choropleth", leaving);
|
|
222
|
+
if (leaving.length) paintChoroplethRegions(surface, tokens, leaving, null);
|
|
223
|
+
}
|
|
260
224
|
function drawRoughOutlines(ctx, pen, feature, boundary) {
|
|
261
225
|
ctx.globalAlpha = boundary.opacity;
|
|
262
226
|
for (const polygon of feature.polys) {
|
|
@@ -333,6 +297,7 @@ function drawChoropleth(surface, spec, tokens, size, context) {
|
|
|
333
297
|
const content = drawTitleBlock(surface, tokens, size, choropleth.title);
|
|
334
298
|
const features = choropleth.geo?.features ?? [];
|
|
335
299
|
if (features.length === 0 || content.width <= 0 || content.height <= 0) {
|
|
300
|
+
drawLeavingRegions(surface, tokens, context);
|
|
336
301
|
drawNoData(surface, tokens, content);
|
|
337
302
|
return;
|
|
338
303
|
}
|
|
@@ -340,6 +305,7 @@ function drawChoropleth(surface, spec, tokens, size, context) {
|
|
|
340
305
|
const spanX = projected.maxX - projected.minX;
|
|
341
306
|
const spanY = projected.maxY - projected.minY;
|
|
342
307
|
if (!(spanX > 0) || !(spanY > 0)) {
|
|
308
|
+
drawLeavingRegions(surface, tokens, context);
|
|
343
309
|
drawNoData(surface, tokens, content);
|
|
344
310
|
return;
|
|
345
311
|
}
|
|
@@ -366,7 +332,20 @@ function drawChoropleth(surface, spec, tokens, size, context) {
|
|
|
366
332
|
);
|
|
367
333
|
const sketch = resolveSketch(spec);
|
|
368
334
|
const pen = sketch ? new RoughPen(surface.marks.ctx, sketch) : null;
|
|
369
|
-
const
|
|
335
|
+
const keyField = choropleth.encoding.key.field;
|
|
336
|
+
const scheme = choropleth.scheme ?? "teal";
|
|
337
|
+
const regions = screen.map((feature) => ({
|
|
338
|
+
key: feature.key,
|
|
339
|
+
feature,
|
|
340
|
+
ramp: feature.value == null ? void 0 : { scheme, at: rampAt(domain, feature.value) },
|
|
341
|
+
emph: rowAlpha(emphasis, feature.row ?? { [keyField]: feature.key }),
|
|
342
|
+
alpha: 1
|
|
343
|
+
}));
|
|
344
|
+
const morph = morphInput(context);
|
|
345
|
+
const moved = morph && morphKeyed(morph.from, regions, morph.t, CHOROPLETH_MORPH);
|
|
346
|
+
const frame = moved ? [...moved.exits, ...moved.items] : regions;
|
|
347
|
+
stashMorphFrame(surface, "choropleth", frame);
|
|
348
|
+
const represented = paintChoroplethRegions(surface, tokens, frame, pen);
|
|
370
349
|
surface.evidence?.data(represented);
|
|
371
350
|
if (legendHeight > 0) {
|
|
372
351
|
drawColorRampLegend(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/charts/choropleth.ts"],
|
|
4
|
-
"sourcesContent": ["import { rgbaToCss, sequential, sequentialColorScale, withAlpha } from '../color';\nimport { formatValue } from '../format';\nimport type { Surface } from '../render/surface';\nimport { RoughPen } from '../rough';\nimport { resolveSketch } from '../spec/sketch';\nimport type {\n ChartSpec,\n ChoroplethSpec,\n GeoFeature,\n GeoPosition,\n MapProjection,\n} from '../spec/types';\nimport type { ThemeTokens } from '../theme';\nimport type { Datum, Rect, Size } from '../types';\nimport { accessor, toKey, toNumber } from '../util/data';\nimport type { InteractionModel } from '../interaction/types';\nimport { drawTitleBlock } from './chrome';\nimport type { RenderContext } from './index';\nimport { rowAlpha } from './emphasis';\nimport { dataBoundary } from './boundary';\nimport { drawNoData } from './noData';\nimport { drawColorRampLegend } from './colorRamp';\n\ntype Ring = GeoPosition[];\n\ninterface ProjectedFeature {\n key: string;\n rawKey: unknown;\n name: string;\n value: number | null;\n row?: Datum;\n /** Polygons in projected (pre-fit) space; each polygon is a list of rings. */\n polys: Ring[][];\n}\n\ninterface ScreenFeature {\n key: string;\n rawKey: unknown;\n name: string;\n value: number | null;\n row?: Datum;\n color: string;\n /** Polygons in screen space; each polygon is a list of rings of [x, y]. */\n polys: number[][][][];\n cx: number;\n cy: number;\n}\n\nconst clamp = (v: number, lo: number, hi: number): number => (v < lo ? lo : v > hi ? hi : v);\n\ninterface ProjectedFeatures {\n projected: ProjectedFeature[];\n minX: number;\n maxX: number;\n minY: number;\n maxY: number;\n vMin: number;\n vMax: number;\n flipY: boolean;\n}\n\n/**\n * Pick a central meridian so geometry that straddles the \u00B1180\u00B0 antimeridian\n * (e.g. Alaska's Aleutian islands) doesn't blow the longitude span out to ~360\u00B0.\n * We find the widest empty longitude gap and center the map on its antipode.\n */\nfunction centralMeridian(features: GeoFeature[]): number {\n const lons: number[] = [];\n for (const feature of features) {\n for (const polygon of polygonsOf(feature)) {\n for (const ring of polygon) {\n for (const pos of ring) {\n if (Number.isFinite(pos[0])) lons.push(pos[0]);\n }\n }\n }\n }\n if (lons.length === 0) return 0;\n lons.sort((a, b) => a - b);\n // Widest gap, including the wrap-around from the last point back to the first.\n let gapStart = lons[lons.length - 1];\n let gapEnd = lons[0] + 360;\n let maxGap = gapEnd - gapStart;\n for (let i = 1; i < lons.length; i += 1) {\n const g = lons[i] - lons[i - 1];\n if (g > maxGap) {\n maxGap = g;\n gapStart = lons[i - 1];\n gapEnd = lons[i];\n }\n }\n // Data is centered opposite the empty gap.\n return (gapStart + gapEnd) / 2 + 180;\n}\n\nfunction projector(kind: MapProjection, lon0: number): (pos: GeoPosition) => [number, number] {\n if (kind === 'identity') {\n return ([x, y]) => [x, y];\n }\n const wrapLon = (lon: number): number => {\n let d = lon - lon0;\n while (d > 180) d -= 360;\n while (d < -180) d += 360;\n return lon0 + d;\n };\n if (kind === 'equirectangular') {\n return ([lon, lat]) => [wrapLon(lon), lat];\n }\n // Web Mercator (north-up); latitude clamped to the standard cutoff.\n return ([lon, lat]) => {\n const phi = (clamp(lat, -85.0511, 85.0511) * Math.PI) / 180;\n return [wrapLon(lon), (Math.log(Math.tan(Math.PI / 4 + phi / 2)) * 180) / Math.PI];\n };\n}\n\nfunction featureKey(feature: GeoFeature, featureId: string | undefined): string {\n if (featureId) {\n if (featureId === 'id' && feature.id != null) return toKey(feature.id);\n const v = feature.properties?.[featureId];\n if (v != null) return toKey(v);\n return '';\n }\n if (feature.id != null) return toKey(feature.id);\n const props = feature.properties ?? {};\n if (props.id != null) return toKey(props.id);\n if (props.name != null) return toKey(props.name);\n return '';\n}\n\nfunction featureName(feature: GeoFeature, key: string): string {\n const props = feature.properties ?? {};\n if (typeof props.name === 'string') return props.name;\n return key;\n}\n\nfunction polygonsOf(feature: GeoFeature): Ring[][] {\n const geom = feature.geometry;\n if (!geom) return [];\n if (geom.type === 'Polygon') return [geom.coordinates];\n if (geom.type === 'MultiPolygon') return geom.coordinates;\n return [];\n}\n\nfunction pointInRing(x: number, y: number, ring: number[][]): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {\n const xi = ring[i][0];\n const yi = ring[i][1];\n const xj = ring[j][0];\n const yj = ring[j][1];\n const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;\n if (intersect) inside = !inside;\n }\n return inside;\n}\n\nfunction joinedRows(spec: ChoroplethSpec): {\n readKey: (row: Datum) => unknown;\n valueByKey: Map<string, number>;\n rowByKey: Map<string, Datum>;\n} {\n const readKey = accessor(spec.encoding.key.field);\n const readColor = accessor(spec.encoding.color.field);\n const valueByKey = new Map<string, number>();\n const rowByKey = new Map<string, Datum>();\n for (const row of spec.data ?? []) {\n const datum = row as Datum;\n const k = toKey(readKey(datum));\n const v = toNumber(readColor(datum));\n if (k !== '' && Number.isFinite(v)) {\n valueByKey.set(k, v);\n rowByKey.set(k, datum);\n }\n }\n return { readKey, valueByKey, rowByKey };\n}\n\nfunction projectFeatures(\n spec: ChoroplethSpec,\n features: GeoFeature[],\n joined: ReturnType<typeof joinedRows>,\n): ProjectedFeatures {\n const projection = spec.projection ?? 'mercator';\n const project = projector(projection, projection === 'identity' ? 0 : centralMeridian(features));\n const projected: ProjectedFeature[] = [];\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n let vMin = Infinity;\n let vMax = -Infinity;\n\n for (const feature of features) {\n const key = featureKey(feature, spec.featureId);\n const value = joined.valueByKey.has(key) ? joined.valueByKey.get(key)! : null;\n const row = joined.rowByKey.get(key);\n if (value != null) {\n if (value < vMin) vMin = value;\n if (value > vMax) vMax = value;\n }\n const polys: Ring[][] = [];\n for (const polygon of polygonsOf(feature)) {\n const rings: Ring[] = [];\n for (const ring of polygon) {\n const pr: Ring = [];\n for (const pos of ring) {\n const [x, y] = project(pos);\n if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n pr.push([x, y]);\n }\n if (pr.length >= 3) rings.push(pr);\n }\n if (rings.length) polys.push(rings);\n }\n projected.push({\n key,\n rawKey: row ? joined.readKey(row) : key,\n name: featureName(feature, key),\n value,\n row,\n polys,\n });\n }\n\n return { projected, minX, maxX, minY, maxY, vMin, vMax, flipY: projection !== 'identity' };\n}\n\nfunction screenFeatures(\n projected: readonly ProjectedFeature[],\n bounds: Pick<ProjectedFeatures, 'minX' | 'maxX' | 'minY' | 'maxY' | 'flipY'>,\n mapRect: Rect,\n colorOf: (value: number | null) => string,\n): ScreenFeature[] {\n const spanX = bounds.maxX - bounds.minX;\n const spanY = bounds.maxY - bounds.minY;\n const scale = Math.min(mapRect.width / spanX, mapRect.height / spanY);\n const drawW = spanX * scale;\n const drawH = spanY * scale;\n const offsetX = mapRect.x + (mapRect.width - drawW) / 2;\n const offsetY = mapRect.y + (mapRect.height - drawH) / 2;\n const sx = (x: number): number => offsetX + (x - bounds.minX) * scale;\n const sy = (y: number): number =>\n bounds.flipY ? offsetY + (bounds.maxY - y) * scale : offsetY + (y - bounds.minY) * scale;\n\n return projected.map((pf) => {\n let cx = 0;\n let cy = 0;\n let count = 0;\n const polys = pf.polys.map((polygon) =>\n polygon.map((ring) =>\n ring.map(([x, y]) => {\n const px = sx(x);\n const py = sy(y);\n cx += px;\n cy += py;\n count += 1;\n return [px, py];\n }),\n ),\n );\n return {\n key: pf.key,\n rawKey: pf.rawKey,\n name: pf.name,\n value: pf.value,\n row: pf.row,\n color: colorOf(pf.value),\n polys,\n cx: count ? cx / count : 0,\n cy: count ? cy / count : 0,\n };\n });\n}\n\nfunction featureBounds(feature: ScreenFeature): Rect | null {\n let left = Infinity;\n let top = Infinity;\n let right = -Infinity;\n let bottom = -Infinity;\n for (const polygon of feature.polys)\n for (const ring of polygon)\n for (const [x, y] of ring) {\n left = Math.min(left, x);\n right = Math.max(right, x);\n top = Math.min(top, y);\n bottom = Math.max(bottom, y);\n }\n return left === Infinity ? null : { x: left, y: top, width: right - left, height: bottom - top };\n}\n\nfunction paintChoroplethRegions(\n surface: Surface,\n spec: ChoroplethSpec,\n tokens: ThemeTokens,\n screen: readonly ScreenFeature[],\n emphasis: RenderContext['emphasis'] | null,\n pen: RoughPen | null,\n): number {\n const ctx = surface.marks.ctx;\n const representedRows = new Set<Datum>();\n ctx.save();\n ctx.lineJoin = 'round';\n ctx.setLineDash([]);\n for (const f of screen) {\n const alpha = rowAlpha(emphasis, f.row ?? { [spec.encoding.key.field]: f.key });\n const boundary = dataBoundary(tokens, alpha);\n ctx.strokeStyle = boundary.color;\n ctx.lineWidth = boundary.width;\n ctx.globalAlpha = alpha;\n ctx.fillStyle = f.color;\n for (const polygon of f.polys) {\n ctx.beginPath();\n for (const ring of polygon) {\n ring.forEach(([x, y], i) => (i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y)));\n ctx.closePath();\n }\n ctx.fill('evenodd');\n if (!pen) {\n ctx.globalAlpha = boundary.opacity;\n ctx.stroke();\n ctx.globalAlpha = alpha;\n }\n }\n // Hand-drawn region outlines: the rough offset auto-scales with segment\n // length, so fine coastline detail stays crisp while broad borders wobble.\n if (pen) drawRoughOutlines(ctx, pen, f, boundary);\n const bounds = f.value == null ? null : featureBounds(f);\n if (bounds) {\n surface.evidence?.mark(\n 'region',\n bounds.x,\n bounds.y,\n bounds.width,\n bounds.height,\n f.color,\n alpha,\n undefined,\n 1,\n undefined,\n alpha < 1,\n boundary,\n );\n if (f.row) representedRows.add(f.row);\n }\n ctx.globalAlpha = 1;\n }\n ctx.restore();\n return representedRows.size;\n}\n\nfunction drawRoughOutlines(\n ctx: CanvasRenderingContext2D,\n pen: RoughPen,\n feature: ScreenFeature,\n boundary: ReturnType<typeof dataBoundary>,\n): void {\n ctx.globalAlpha = boundary.opacity;\n for (const polygon of feature.polys) {\n for (const ring of polygon) {\n pen.polygon(\n ring.map(([x, y]) => ({ x, y })),\n { stroke: boundary.color, strokeWidth: boundary.width },\n );\n }\n }\n}\n\nfunction containsFeature(feature: ScreenFeature, px: number, py: number): boolean {\n for (const polygon of feature.polys) {\n let inside = false;\n if (polygon.length && pointInRing(px, py, polygon[0])) {\n inside = true;\n for (let i = 1; i < polygon.length; i += 1) {\n if (pointInRing(px, py, polygon[i])) inside = false;\n }\n if (inside) return true;\n }\n }\n return false;\n}\n\nfunction buildChoroplethInteraction(\n spec: ChoroplethSpec,\n tokens: ThemeTokens,\n mapRect: Rect,\n screen: readonly ScreenFeature[],\n): InteractionModel {\n const colorLabel = spec.encoding.color.title ?? spec.encoding.color.field;\n return {\n region: mapRect,\n hitTest: (px, py) => {\n const f = screen.find((feature) => containsFeature(feature, px, py));\n if (!f) return null;\n return {\n key: f.key,\n anchorX: px,\n anchorY: py,\n content: {\n title: f.name,\n rows: [\n {\n swatch: f.color,\n label: colorLabel,\n value: f.value == null ? 'no data' : formatValue(f.value, spec.encoding.color.format),\n },\n ],\n },\n draw: (ictx) => {\n ictx.save();\n ictx.lineJoin = 'round';\n ictx.strokeStyle = tokens.color.text;\n ictx.lineWidth = 1.5;\n for (const polygon of f.polys) {\n ictx.beginPath();\n for (const ring of polygon) {\n ring.forEach(([x, y], i) => (i === 0 ? ictx.moveTo(x, y) : ictx.lineTo(x, y)));\n ictx.closePath();\n }\n ictx.stroke();\n }\n ictx.restore();\n },\n };\n },\n pick: (px, py) => {\n const f = screen.find((feature) => containsFeature(feature, px, py));\n return f ? { kind: 'point', fields: [spec.encoding.key.field], tuples: [[f.rawKey]] } : null;\n },\n };\n}\n\nexport function drawChoropleth(\n surface: Surface,\n spec: ChartSpec,\n tokens: ThemeTokens,\n size: Size,\n context?: RenderContext,\n): InteractionModel | void {\n surface.evidence?.begin('rows');\n surface.evidence?.data(0);\n const choropleth = spec as ChoroplethSpec;\n const emphasis = context?.emphasis ?? null;\n const content = drawTitleBlock(surface, tokens, size, choropleth.title);\n const features = choropleth.geo?.features ?? [];\n if (features.length === 0 || content.width <= 0 || content.height <= 0) {\n drawNoData(surface, tokens, content);\n return;\n }\n\n const projected = projectFeatures(choropleth, features, joinedRows(choropleth));\n const spanX = projected.maxX - projected.minX;\n const spanY = projected.maxY - projected.minY;\n if (!(spanX > 0) || !(spanY > 0)) {\n drawNoData(surface, tokens, content);\n return;\n }\n\n const labelSize = tokens.font.size.small;\n const legendHeight = projected.vMax >= projected.vMin ? Math.ceil(labelSize + 26) : 0;\n const mapRect: Rect = {\n x: content.x,\n y: content.y,\n width: content.width,\n height: Math.max(10, content.height - legendHeight),\n };\n\n const hasValues = projected.vMax >= projected.vMin;\n const domain: [number, number] = hasValues ? [projected.vMin, projected.vMax] : [0, 1];\n const cscale = sequentialColorScale({\n domain,\n interpolator: sequential(choropleth.scheme ?? 'teal'),\n });\n const noDataFill = withAlpha(tokens.color.textMuted, 0.14);\n const screen = screenFeatures(\n projected.projected,\n projected,\n mapRect,\n (value) => (value == null ? noDataFill : rgbaToCss(cscale.map(value))),\n );\n const sketch = resolveSketch(spec);\n const pen = sketch ? new RoughPen(surface.marks.ctx, sketch) : null;\n const represented = paintChoroplethRegions(surface, choropleth, tokens, screen, emphasis, pen);\n surface.evidence?.data(represented);\n\n if (legendHeight > 0) {\n drawColorRampLegend(\n surface,\n tokens,\n {\n x: content.x,\n y: mapRect.y + mapRect.height + 4,\n width: content.width,\n height: legendHeight,\n },\n {\n domain,\n scheme: choropleth.scheme ?? 'teal',\n format: choropleth.encoding.color.format,\n widthRatio: 0.5,\n minWidth: 120,\n maxWidth: 240,\n path: '/encoding/color',\n },\n );\n }\n\n const tt = choropleth.tooltip;\n if (tt === false || (tt && typeof tt === 'object' && tt.show === false)) return;\n\n return buildChoroplethInteraction(choropleth, tokens, mapRect, screen);\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,WAAW,YAAY,sBAAsB,iBAAiB;AACvE,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;
|
|
4
|
+
"sourcesContent": ["import { rgbaToCss, sequential, sequentialColorScale, withAlpha } from '../color';\nimport { formatValue } from '../format';\nimport type { Surface } from '../render/surface';\nimport { RoughPen } from '../rough';\nimport { resolveSketch } from '../spec/sketch';\nimport type { ChartSpec, ChoroplethSpec, GeoFeature } from '../spec/types';\nimport type { ThemeTokens } from '../theme';\nimport type { Datum, Rect, Size } from '../types';\nimport { accessor, toKey, toNumber } from '../util/data';\nimport type { InteractionModel } from '../interaction/types';\nimport { drawTitleBlock } from './chrome';\nimport type { RenderContext } from './index';\nimport { rowAlpha } from './emphasis';\nimport { dataBoundary } from './boundary';\nimport { drawNoData } from './noData';\nimport { drawColorRampLegend } from './colorRamp';\nimport { featureProjector, fitBounds, polygonsOf, type Ring } from './geo';\nimport { lerp, mixFill, morphInput, morphKeyed, rampAt, stashMorphFrame, type MorphPlan, type RampStop } from './morphFrame';\n\ninterface ProjectedFeature {\n key: string;\n rawKey: unknown;\n name: string;\n value: number | null;\n row?: Datum;\n /** Polygons in projected (pre-fit) space; each polygon is a list of rings. */\n polys: Ring[][];\n}\n\ninterface ScreenFeature {\n key: string;\n rawKey: unknown;\n name: string;\n value: number | null;\n row?: Datum;\n color: string;\n /** Polygons in screen space; each polygon is a list of rings of [x, y]. */\n polys: number[][][][];\n cx: number;\n cy: number;\n}\n\ninterface ProjectedFeatures {\n projected: ProjectedFeature[];\n minX: number;\n maxX: number;\n minY: number;\n maxY: number;\n vMin: number;\n vMax: number;\n flipY: boolean;\n}\n\nfunction featureKey(feature: GeoFeature, featureId: string | undefined): string {\n if (featureId) {\n if (featureId === 'id' && feature.id != null) return toKey(feature.id);\n const v = feature.properties?.[featureId];\n if (v != null) return toKey(v);\n return '';\n }\n if (feature.id != null) return toKey(feature.id);\n const props = feature.properties ?? {};\n if (props.id != null) return toKey(props.id);\n if (props.name != null) return toKey(props.name);\n return '';\n}\n\nfunction featureName(feature: GeoFeature, key: string): string {\n const props = feature.properties ?? {};\n if (typeof props.name === 'string') return props.name;\n return key;\n}\n\nfunction pointInRing(x: number, y: number, ring: number[][]): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {\n const xi = ring[i][0];\n const yi = ring[i][1];\n const xj = ring[j][0];\n const yj = ring[j][1];\n const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;\n if (intersect) inside = !inside;\n }\n return inside;\n}\n\nfunction joinedRows(spec: ChoroplethSpec): {\n readKey: (row: Datum) => unknown;\n valueByKey: Map<string, number>;\n rowByKey: Map<string, Datum>;\n} {\n const readKey = accessor(spec.encoding.key.field);\n const readColor = accessor(spec.encoding.color.field);\n const valueByKey = new Map<string, number>();\n const rowByKey = new Map<string, Datum>();\n for (const row of spec.data ?? []) {\n const datum = row as Datum;\n const k = toKey(readKey(datum));\n const v = toNumber(readColor(datum));\n if (k !== '' && Number.isFinite(v)) {\n valueByKey.set(k, v);\n rowByKey.set(k, datum);\n }\n }\n return { readKey, valueByKey, rowByKey };\n}\n\nfunction projectFeatures(\n spec: ChoroplethSpec,\n features: GeoFeature[],\n joined: ReturnType<typeof joinedRows>,\n): ProjectedFeatures {\n const projection = spec.projection ?? 'mercator';\n const project = featureProjector(projection, features);\n const projected: ProjectedFeature[] = [];\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n let vMin = Infinity;\n let vMax = -Infinity;\n\n for (const feature of features) {\n const key = featureKey(feature, spec.featureId);\n const value = joined.valueByKey.has(key) ? joined.valueByKey.get(key)! : null;\n const row = joined.rowByKey.get(key);\n if (value != null) {\n if (value < vMin) vMin = value;\n if (value > vMax) vMax = value;\n }\n const polys: Ring[][] = [];\n for (const polygon of polygonsOf(feature)) {\n const rings: Ring[] = [];\n for (const ring of polygon) {\n const pr: Ring = [];\n for (const pos of ring) {\n const [x, y] = project(pos);\n if (!Number.isFinite(x) || !Number.isFinite(y)) continue;\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n pr.push([x, y]);\n }\n if (pr.length >= 3) rings.push(pr);\n }\n if (rings.length) polys.push(rings);\n }\n projected.push({\n key,\n rawKey: row ? joined.readKey(row) : key,\n name: featureName(feature, key),\n value,\n row,\n polys,\n });\n }\n\n return { projected, minX, maxX, minY, maxY, vMin, vMax, flipY: projection !== 'identity' };\n}\n\nfunction screenFeatures(\n projected: readonly ProjectedFeature[],\n bounds: Pick<ProjectedFeatures, 'minX' | 'maxX' | 'minY' | 'maxY' | 'flipY'>,\n mapRect: Rect,\n colorOf: (value: number | null) => string,\n): ScreenFeature[] {\n const fit = fitBounds(bounds, mapRect, bounds.flipY);\n const sx = fit.x;\n const sy = fit.y;\n\n return projected.map((pf) => {\n let cx = 0;\n let cy = 0;\n let count = 0;\n const polys = pf.polys.map((polygon) =>\n polygon.map((ring) =>\n ring.map(([x, y]) => {\n const px = sx(x);\n const py = sy(y);\n cx += px;\n cy += py;\n count += 1;\n return [px, py];\n }),\n ),\n );\n return {\n key: pf.key,\n rawKey: pf.rawKey,\n name: pf.name,\n value: pf.value,\n row: pf.row,\n color: colorOf(pf.value),\n polys,\n cx: count ? cx / count : 0,\n cy: count ? cy / count : 0,\n };\n });\n}\n\nfunction featureBounds(feature: ScreenFeature): Rect | null {\n let left = Infinity;\n let top = Infinity;\n let right = -Infinity;\n let bottom = -Infinity;\n for (const polygon of feature.polys)\n for (const ring of polygon)\n for (const [x, y] of ring) {\n left = Math.min(left, x);\n right = Math.max(right, x);\n top = Math.min(top, y);\n bottom = Math.max(bottom, y);\n }\n return left === Infinity ? null : { x: left, y: top, width: right - left, height: bottom - top };\n}\n\n/** A region as drawn, keyed by its feature so an update can recolor it from the one on screen. */\ninterface ChoroplethMorphRegion {\n key: string;\n feature: ScreenFeature;\n /** Where a region's value sits on the color ramp (no-data regions have none). */\n ramp?: RampStop;\n /** Highlight alpha, carried so emphasis changes fade instead of snapping. */\n emph: number;\n /** Fade for regions arriving or leaving mid-morph. */\n alpha: number;\n}\n\n// Regions keep their final geometry (data updates don't move the map); only\n// their fills travel between the two color scales.\nconst CHOROPLETH_MORPH: MorphPlan<ChoroplethMorphRegion> = {\n lerp: (a, b, t) => {\n const { fill, ramp } = mixFill({ fill: a.feature.color, ramp: a.ramp }, { fill: b.feature.color, ramp: b.ramp }, t);\n return {\n key: b.key,\n feature: { ...b.feature, color: fill },\n ramp,\n emph: lerp(a.emph, b.emph, t),\n alpha: lerp(a.alpha, b.alpha, t),\n };\n },\n enter: (b, t) => ({ ...b, alpha: t }),\n exit: (a, t) => ({ ...a, alpha: a.alpha * (1 - t) }),\n};\n\nfunction paintChoroplethRegions(\n surface: Surface,\n tokens: ThemeTokens,\n regions: readonly ChoroplethMorphRegion[],\n pen: RoughPen | null,\n): number {\n const ctx = surface.marks.ctx;\n const representedRows = new Set<Datum>();\n ctx.save();\n ctx.lineJoin = 'round';\n ctx.setLineDash([]);\n for (const { feature: f, emph, alpha: fade } of regions) {\n const alpha = emph * fade;\n const boundary = dataBoundary(tokens, alpha);\n ctx.strokeStyle = boundary.color;\n ctx.lineWidth = boundary.width;\n ctx.globalAlpha = alpha;\n ctx.fillStyle = f.color;\n for (const polygon of f.polys) {\n ctx.beginPath();\n for (const ring of polygon) {\n ring.forEach(([x, y], i) => (i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y)));\n ctx.closePath();\n }\n ctx.fill('evenodd');\n if (!pen) {\n ctx.globalAlpha = boundary.opacity;\n ctx.stroke();\n ctx.globalAlpha = alpha;\n }\n }\n // Hand-drawn region outlines: the rough offset auto-scales with segment\n // length, so fine coastline detail stays crisp while broad borders wobble.\n if (pen) drawRoughOutlines(ctx, pen, f, boundary);\n // Tween frames have no ledger, so they skip the bounds pass.\n const bounds = f.value == null || !surface.evidence ? null : featureBounds(f);\n if (bounds) {\n surface.evidence?.mark(\n 'region',\n bounds.x,\n bounds.y,\n bounds.width,\n bounds.height,\n f.color,\n alpha,\n undefined,\n 1,\n undefined,\n alpha < 1,\n boundary,\n );\n if (f.row) representedRows.add(f.row);\n }\n ctx.globalAlpha = 1;\n }\n ctx.restore();\n return representedRows.size;\n}\n\n/** No map to draw: regions on screen fade out, and a later update fades its regions in. */\nfunction drawLeavingRegions(surface: Surface, tokens: ThemeTokens, context?: RenderContext): void {\n const morph = morphInput<ChoroplethMorphRegion[]>(context);\n const leaving = morph ? morphKeyed(morph.from, [], morph.t, CHOROPLETH_MORPH).exits : [];\n stashMorphFrame(surface, 'choropleth', leaving);\n if (leaving.length) paintChoroplethRegions(surface, tokens, leaving, null);\n}\n\nfunction drawRoughOutlines(\n ctx: CanvasRenderingContext2D,\n pen: RoughPen,\n feature: ScreenFeature,\n boundary: ReturnType<typeof dataBoundary>,\n): void {\n ctx.globalAlpha = boundary.opacity;\n for (const polygon of feature.polys) {\n for (const ring of polygon) {\n pen.polygon(\n ring.map(([x, y]) => ({ x, y })),\n { stroke: boundary.color, strokeWidth: boundary.width },\n );\n }\n }\n}\n\nfunction containsFeature(feature: ScreenFeature, px: number, py: number): boolean {\n for (const polygon of feature.polys) {\n let inside = false;\n if (polygon.length && pointInRing(px, py, polygon[0])) {\n inside = true;\n for (let i = 1; i < polygon.length; i += 1) {\n if (pointInRing(px, py, polygon[i])) inside = false;\n }\n if (inside) return true;\n }\n }\n return false;\n}\n\nfunction buildChoroplethInteraction(\n spec: ChoroplethSpec,\n tokens: ThemeTokens,\n mapRect: Rect,\n screen: readonly ScreenFeature[],\n): InteractionModel {\n const colorLabel = spec.encoding.color.title ?? spec.encoding.color.field;\n return {\n region: mapRect,\n hitTest: (px, py) => {\n const f = screen.find((feature) => containsFeature(feature, px, py));\n if (!f) return null;\n return {\n key: f.key,\n anchorX: px,\n anchorY: py,\n content: {\n title: f.name,\n rows: [\n {\n swatch: f.color,\n label: colorLabel,\n value: f.value == null ? 'no data' : formatValue(f.value, spec.encoding.color.format),\n },\n ],\n },\n draw: (ictx) => {\n ictx.save();\n ictx.lineJoin = 'round';\n ictx.strokeStyle = tokens.color.text;\n ictx.lineWidth = 1.5;\n for (const polygon of f.polys) {\n ictx.beginPath();\n for (const ring of polygon) {\n ring.forEach(([x, y], i) => (i === 0 ? ictx.moveTo(x, y) : ictx.lineTo(x, y)));\n ictx.closePath();\n }\n ictx.stroke();\n }\n ictx.restore();\n },\n };\n },\n pick: (px, py) => {\n const f = screen.find((feature) => containsFeature(feature, px, py));\n return f ? { kind: 'point', fields: [spec.encoding.key.field], tuples: [[f.rawKey]] } : null;\n },\n };\n}\n\nexport function drawChoropleth(\n surface: Surface,\n spec: ChartSpec,\n tokens: ThemeTokens,\n size: Size,\n context?: RenderContext,\n): InteractionModel | void {\n surface.evidence?.begin('rows');\n surface.evidence?.data(0);\n const choropleth = spec as ChoroplethSpec;\n const emphasis = context?.emphasis ?? null;\n const content = drawTitleBlock(surface, tokens, size, choropleth.title);\n const features = choropleth.geo?.features ?? [];\n if (features.length === 0 || content.width <= 0 || content.height <= 0) {\n drawLeavingRegions(surface, tokens, context);\n drawNoData(surface, tokens, content);\n return;\n }\n\n const projected = projectFeatures(choropleth, features, joinedRows(choropleth));\n const spanX = projected.maxX - projected.minX;\n const spanY = projected.maxY - projected.minY;\n if (!(spanX > 0) || !(spanY > 0)) {\n drawLeavingRegions(surface, tokens, context);\n drawNoData(surface, tokens, content);\n return;\n }\n\n const labelSize = tokens.font.size.small;\n const legendHeight = projected.vMax >= projected.vMin ? Math.ceil(labelSize + 26) : 0;\n const mapRect: Rect = {\n x: content.x,\n y: content.y,\n width: content.width,\n height: Math.max(10, content.height - legendHeight),\n };\n\n const hasValues = projected.vMax >= projected.vMin;\n const domain: [number, number] = hasValues ? [projected.vMin, projected.vMax] : [0, 1];\n const cscale = sequentialColorScale({\n domain,\n interpolator: sequential(choropleth.scheme ?? 'teal'),\n });\n const noDataFill = withAlpha(tokens.color.textMuted, 0.14);\n const screen = screenFeatures(\n projected.projected,\n projected,\n mapRect,\n (value) => (value == null ? noDataFill : rgbaToCss(cscale.map(value))),\n );\n const sketch = resolveSketch(spec);\n const pen = sketch ? new RoughPen(surface.marks.ctx, sketch) : null;\n const keyField = choropleth.encoding.key.field;\n const scheme = choropleth.scheme ?? 'teal';\n const regions = screen.map((feature): ChoroplethMorphRegion => ({\n key: feature.key,\n feature,\n ramp: feature.value == null ? undefined : { scheme, at: rampAt(domain, feature.value) },\n emph: rowAlpha(emphasis, feature.row ?? { [keyField]: feature.key }),\n alpha: 1,\n }));\n // Update morph: regions recolor from the ones on screen, regions entering or\n // leaving the map fade, and a changed highlight fades rather than snapping.\n // The legend is the final frame's.\n const morph = morphInput<ChoroplethMorphRegion[]>(context);\n const moved = morph && morphKeyed(morph.from, regions, morph.t, CHOROPLETH_MORPH);\n const frame = moved ? [...moved.exits, ...moved.items] : regions;\n stashMorphFrame(surface, 'choropleth', frame);\n const represented = paintChoroplethRegions(surface, tokens, frame, pen);\n surface.evidence?.data(represented);\n\n if (legendHeight > 0) {\n drawColorRampLegend(\n surface,\n tokens,\n {\n x: content.x,\n y: mapRect.y + mapRect.height + 4,\n width: content.width,\n height: legendHeight,\n },\n {\n domain,\n scheme: choropleth.scheme ?? 'teal',\n format: choropleth.encoding.color.format,\n widthRatio: 0.5,\n minWidth: 120,\n maxWidth: 240,\n path: '/encoding/color',\n },\n );\n }\n\n const tt = choropleth.tooltip;\n if (tt === false || (tt && typeof tt === 'object' && tt.show === false)) return;\n\n return buildChoroplethInteraction(choropleth, tokens, mapRect, screen);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combo.d.ts","sourceRoot":"","sources":["../../src/charts/combo.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"combo.d.ts","sourceRoot":"","sources":["../../src/charts/combo.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAQ,IAAI,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAW7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAgP7C,wBAAgB,SAAS,CACvB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,aAAa,GACtB,gBAAgB,GAAG,IAAI,CAkFzB"}
|