@dou-ui/chart 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/chart.js +6 -0
- package/dist/chart.js.map +1 -0
- package/dist/chart.vue_vue_type_script_setup_true_lang.js +4245 -0
- package/dist/chart.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/area.js +6 -0
- package/dist/components/area.js.map +1 -0
- package/dist/components/area.vue_vue_type_script_setup_true_lang.js +55 -0
- package/dist/components/area.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/axis.js +6 -0
- package/dist/components/axis.js.map +1 -0
- package/dist/components/axis.vue_vue_type_script_setup_true_lang.js +46 -0
- package/dist/components/axis.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/bar.js +6 -0
- package/dist/components/bar.js.map +1 -0
- package/dist/components/bar.vue_vue_type_script_setup_true_lang.js +46 -0
- package/dist/components/bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/boxplot.js +6 -0
- package/dist/components/boxplot.js.map +1 -0
- package/dist/components/boxplot.vue_vue_type_script_setup_true_lang.js +42 -0
- package/dist/components/boxplot.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/brush.js +6 -0
- package/dist/components/brush.js.map +1 -0
- package/dist/components/brush.vue_vue_type_script_setup_true_lang.js +42 -0
- package/dist/components/brush.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/funnel.js +6 -0
- package/dist/components/funnel.js.map +1 -0
- package/dist/components/funnel.vue_vue_type_script_setup_true_lang.js +41 -0
- package/dist/components/funnel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/gauge.js +6 -0
- package/dist/components/gauge.js.map +1 -0
- package/dist/components/gauge.vue_vue_type_script_setup_true_lang.js +91 -0
- package/dist/components/gauge.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/heatmap.js +6 -0
- package/dist/components/heatmap.js.map +1 -0
- package/dist/components/heatmap.vue_vue_type_script_setup_true_lang.js +45 -0
- package/dist/components/heatmap.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/histogram.js +6 -0
- package/dist/components/histogram.js.map +1 -0
- package/dist/components/histogram.vue_vue_type_script_setup_true_lang.js +61 -0
- package/dist/components/histogram.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/line.js +6 -0
- package/dist/components/line.js.map +1 -0
- package/dist/components/line.vue_vue_type_script_setup_true_lang.js +52 -0
- package/dist/components/line.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/marker.js +6 -0
- package/dist/components/marker.js.map +1 -0
- package/dist/components/marker.vue_vue_type_script_setup_true_lang.js +31 -0
- package/dist/components/marker.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/pie.js +6 -0
- package/dist/components/pie.js.map +1 -0
- package/dist/components/pie.vue_vue_type_script_setup_true_lang.js +42 -0
- package/dist/components/pie.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/radar.js +6 -0
- package/dist/components/radar.js.map +1 -0
- package/dist/components/radar.vue_vue_type_script_setup_true_lang.js +45 -0
- package/dist/components/radar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/range.js +6 -0
- package/dist/components/range.js.map +1 -0
- package/dist/components/range.vue_vue_type_script_setup_true_lang.js +27 -0
- package/dist/components/range.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/scatter.js +6 -0
- package/dist/components/scatter.js.map +1 -0
- package/dist/components/scatter.vue_vue_type_script_setup_true_lang.js +45 -0
- package/dist/components/scatter.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sunburst.js +6 -0
- package/dist/components/sunburst.js.map +1 -0
- package/dist/components/sunburst.vue_vue_type_script_setup_true_lang.js +36 -0
- package/dist/components/sunburst.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/tooltip.js +6 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/components/tooltip.vue_vue_type_script_setup_true_lang.js +31 -0
- package/dist/components/tooltip.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/treemap.js +6 -0
- package/dist/components/treemap.js.map +1 -0
- package/dist/components/treemap.vue_vue_type_script_setup_true_lang.js +33 -0
- package/dist/components/treemap.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/waterfall.js +6 -0
- package/dist/components/waterfall.js.map +1 -0
- package/dist/components/waterfall.vue_vue_type_script_setup_true_lang.js +49 -0
- package/dist/components/waterfall.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/zoom.js +6 -0
- package/dist/components/zoom.js.map +1 -0
- package/dist/components/zoom.vue_vue_type_script_setup_true_lang.js +37 -0
- package/dist/components/zoom.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/context.js +30 -0
- package/dist/context.js.map +1 -0
- package/dist/core/bar.js +223 -0
- package/dist/core/bar.js.map +1 -0
- package/dist/core/box.js +150 -0
- package/dist/core/box.js.map +1 -0
- package/dist/core/colors.js +58 -0
- package/dist/core/colors.js.map +1 -0
- package/dist/core/data.js +257 -0
- package/dist/core/data.js.map +1 -0
- package/dist/core/decimate.js +211 -0
- package/dist/core/decimate.js.map +1 -0
- package/dist/core/format.js +151 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/funnel.js +68 -0
- package/dist/core/funnel.js.map +1 -0
- package/dist/core/grid.js +87 -0
- package/dist/core/grid.js.map +1 -0
- package/dist/core/heatmap.js +187 -0
- package/dist/core/heatmap.js.map +1 -0
- package/dist/core/histogram.js +81 -0
- package/dist/core/histogram.js.map +1 -0
- package/dist/core/hit.js +115 -0
- package/dist/core/hit.js.map +1 -0
- package/dist/core/labels.js +141 -0
- package/dist/core/labels.js.map +1 -0
- package/dist/core/layout.js +1002 -0
- package/dist/core/layout.js.map +1 -0
- package/dist/core/line.js +230 -0
- package/dist/core/line.js.map +1 -0
- package/dist/core/marker.js +205 -0
- package/dist/core/marker.js.map +1 -0
- package/dist/core/model.js +391 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/painter.js +79 -0
- package/dist/core/painter.js.map +1 -0
- package/dist/core/pattern.js +102 -0
- package/dist/core/pattern.js.map +1 -0
- package/dist/core/pie.js +119 -0
- package/dist/core/pie.js.map +1 -0
- package/dist/core/radar.js +83 -0
- package/dist/core/radar.js.map +1 -0
- package/dist/core/scale.js +149 -0
- package/dist/core/scale.js.map +1 -0
- package/dist/core/scatter.js +263 -0
- package/dist/core/scatter.js.map +1 -0
- package/dist/core/svg.js +209 -0
- package/dist/core/svg.js.map +1 -0
- package/dist/core/sync.js +21 -0
- package/dist/core/sync.js.map +1 -0
- package/dist/core/time.js +217 -0
- package/dist/core/time.js.map +1 -0
- package/dist/core/tree.js +348 -0
- package/dist/core/tree.js.map +1 -0
- package/dist/core/warn.js +11 -0
- package/dist/core/warn.js.map +1 -0
- package/dist/core/waterfall.js +134 -0
- package/dist/core/waterfall.js.map +1 -0
- package/dist/core/window.js +94 -0
- package/dist/core/window.js.map +1 -0
- package/dist/define.js +62 -0
- package/dist/define.js.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/locale.js +170 -0
- package/dist/locale.js.map +1 -0
- package/dist/overlay.js +6 -0
- package/dist/overlay.js.map +1 -0
- package/dist/overlay.vue_vue_type_script_setup_true_lang.js +207 -0
- package/dist/overlay.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/pointer.js +6 -0
- package/dist/pointer.js.map +1 -0
- package/dist/pointer.vue_vue_type_script_setup_true_lang.js +136 -0
- package/dist/pointer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/static.js +6 -0
- package/dist/static.js.map +1 -0
- package/dist/static.vue_vue_type_script_setup_true_lang.js +34 -0
- package/dist/static.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +3 -0
- package/dist/table.js +6 -0
- package/dist/table.js.map +1 -0
- package/dist/table.vue_vue_type_script_setup_true_lang.js +52 -0
- package/dist/table.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/types/chart.vue.d.ts +24 -0
- package/dist/types/components/area.vue.d.ts +16 -0
- package/dist/types/components/axis.vue.d.ts +8 -0
- package/dist/types/components/bar.vue.d.ts +14 -0
- package/dist/types/components/boxplot.vue.d.ts +14 -0
- package/dist/types/components/brush.vue.d.ts +22 -0
- package/dist/types/components/funnel.vue.d.ts +14 -0
- package/dist/types/components/gauge.vue.d.ts +17 -0
- package/dist/types/components/heatmap.vue.d.ts +14 -0
- package/dist/types/components/histogram.vue.d.ts +14 -0
- package/dist/types/components/line.vue.d.ts +16 -0
- package/dist/types/components/marker.vue.d.ts +6 -0
- package/dist/types/components/pie.vue.d.ts +23 -0
- package/dist/types/components/radar.vue.d.ts +16 -0
- package/dist/types/components/range.vue.d.ts +12 -0
- package/dist/types/components/scatter.vue.d.ts +14 -0
- package/dist/types/components/sunburst.vue.d.ts +17 -0
- package/dist/types/components/tooltip.vue.d.ts +13 -0
- package/dist/types/components/treemap.vue.d.ts +8 -0
- package/dist/types/components/waterfall.vue.d.ts +16 -0
- package/dist/types/components/zoom.vue.d.ts +18 -0
- package/dist/types/context.d.ts +87 -0
- package/dist/types/core/bar.d.ts +38 -0
- package/dist/types/core/box.d.ts +25 -0
- package/dist/types/core/colors.d.ts +16 -0
- package/dist/types/core/data.d.ts +72 -0
- package/dist/types/core/decimate.d.ts +39 -0
- package/dist/types/core/format.d.ts +33 -0
- package/dist/types/core/funnel.d.ts +15 -0
- package/dist/types/core/grid.d.ts +18 -0
- package/dist/types/core/heatmap.d.ts +17 -0
- package/dist/types/core/histogram.d.ts +23 -0
- package/dist/types/core/hit.d.ts +31 -0
- package/dist/types/core/labels.d.ts +45 -0
- package/dist/types/core/layout.d.ts +259 -0
- package/dist/types/core/line.d.ts +36 -0
- package/dist/types/core/marker.d.ts +67 -0
- package/dist/types/core/model.d.ts +86 -0
- package/dist/types/core/painter.d.ts +34 -0
- package/dist/types/core/pattern.d.ts +14 -0
- package/dist/types/core/pie.d.ts +66 -0
- package/dist/types/core/radar.d.ts +12 -0
- package/dist/types/core/scale.d.ts +60 -0
- package/dist/types/core/scatter.d.ts +16 -0
- package/dist/types/core/series.d.ts +228 -0
- package/dist/types/core/svg.d.ts +63 -0
- package/dist/types/core/sync.d.ts +8 -0
- package/dist/types/core/time.d.ts +20 -0
- package/dist/types/core/tree.d.ts +84 -0
- package/dist/types/core/warn.d.ts +4 -0
- package/dist/types/core/waterfall.d.ts +24 -0
- package/dist/types/core/window.d.ts +36 -0
- package/dist/types/define.d.ts +115 -0
- package/dist/types/index.d.ts +363 -0
- package/dist/types/locale.d.ts +100 -0
- package/dist/types/overlay.vue.d.ts +95 -0
- package/dist/types/pointer.vue.d.ts +53 -0
- package/dist/types/static.vue.d.ts +24 -0
- package/dist/types/table.vue.d.ts +19 -0
- package/dist/types/types.d.ts +555 -0
- package/dist/types/zoom-layer.vue.d.ts +41 -0
- package/dist/zoom-layer.js +6 -0
- package/dist/zoom-layer.js.map +1 -0
- package/dist/zoom-layer.vue_vue_type_script_setup_true_lang.js +210 -0
- package/dist/zoom-layer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/package.json +53 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zoom.vue_vue_type_script_setup_true_lang.js","names":["_useModel"],"sources":["../../src/components/zoom.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useChartPart, useOwnNode, renderNothing } from '../context'\nimport type { ChartInterval, ChartZoomProps } from '../types'\n\ndefineOptions({ name: 'DouChartZoom', render: renderNothing })\n\nconst props = defineProps<ChartZoomProps>()\n/** The stretch of x shown; `null` for all of it. */\nconst x = defineModel<ChartInterval | null>('x', { default: null })\n/** The stretch of values shown; `null` for all of them. */\nconst y = defineModel<ChartInterval | null>('y', { default: null })\n\nconst el = useOwnNode()\nuseChartPart('DouChartZoom', (chart) => chart.addZoom({ props, el, x, y }))\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAMA,MAAM,QAAQ;;EAEd,MAAM,IAAIA,SAAiC,SAAC,GAAsB;;EAElE,MAAM,IAAIA,SAAiC,SAAC,GAAsB;EAElE,MAAM,KAAK,WAAW;EACtB,aAAa,iBAAiB,UAAU,MAAM,QAAQ;GAAE;GAAO;GAAI;GAAG;EAAE,CAAC,CAAC"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getCurrentInstance, inject, onBeforeUnmount, onMounted } from "vue";
|
|
2
|
+
// src/context.ts
|
|
3
|
+
var chartKey = Symbol("DouChart");
|
|
4
|
+
/**
|
|
5
|
+
* The render of a part: it draws nothing itself. A comment in the page on
|
|
6
|
+
* the server and in the browser alike, so taking over a server-rendered
|
|
7
|
+
* page finds each part where it expects it (an empty template renders
|
|
8
|
+
* nothing at all on the server).
|
|
9
|
+
*/
|
|
10
|
+
var renderNothing = () => null;
|
|
11
|
+
/** Registers a child (a series, an axis) with the surrounding chart while it is mounted. */
|
|
12
|
+
function useChartPart(name, add) {
|
|
13
|
+
const registry = inject(chartKey, null);
|
|
14
|
+
if (!registry) {
|
|
15
|
+
if (process.env.NODE_ENV !== "production") console.warn(`[@dou-ui/chart] <${name}> must be placed inside <DouChart>.`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let remove;
|
|
19
|
+
onBeforeUnmount(() => remove?.());
|
|
20
|
+
if (typeof window === "undefined" || getCurrentInstance()?.vnode.el) remove = add(registry);
|
|
21
|
+
else onMounted(() => remove = add(registry));
|
|
22
|
+
}
|
|
23
|
+
/** The node a renderless child leaves in the page. */
|
|
24
|
+
function useOwnNode() {
|
|
25
|
+
const instance = getCurrentInstance();
|
|
26
|
+
return () => instance?.vnode.el ?? null;
|
|
27
|
+
}
|
|
28
|
+
export { chartKey, renderNothing, useChartPart, useOwnNode };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import {\n getCurrentInstance,\n inject,\n onBeforeUnmount,\n onMounted,\n type InjectionKey,\n type Ref,\n type VNodeChild,\n} from 'vue'\nimport type { ChartData } from './core/data'\nimport type { ChartMessages } from './locale'\nimport type { ChartSeriesKind, ChartSeriesProps } from './core/series'\nimport type {\n ChartAxisProps,\n ChartBrushEvent,\n ChartBrushProps,\n ChartInterval,\n ChartMarkerProps,\n ChartPieProps,\n ChartSunburstProps,\n ChartTooltipProps,\n ChartTooltipSlots,\n ChartZoomProps,\n} from './types'\n\n/** A child component as the chart keeps it. */\nexport interface ChartPart<P> {\n props: P\n /** The child's place in the page, which orders the children as written. */\n el: () => Node | null\n}\n\nexport interface SeriesPart extends ChartPart<ChartSeriesProps & Record<string, unknown>> {\n kind: ChartSeriesKind<any>\n /** Turned off in the legend (`v-model:hidden`). */\n hidden: Ref<boolean>\n}\n\nexport interface PiePart extends ChartPart<ChartPieProps> {\n /** What goes in the hole of a donut. */\n center: () => VNodeChild\n /** The labels of the slices turned off in the legend (`v-model:hidden`). */\n hidden: Ref<string[]>\n /**\n * For a gauge, drawn as a pie of its value and the rest of its track:\n * the value, its range and its text. It has no legend nor tooltip.\n */\n gauge?: () => { value: number; min: number; max: number; text: string }\n}\n\nexport interface TreePart extends ChartPart<ChartSunburstProps> {\n /** A treemap fills a rectangle; a sunburst draws rings round a center. */\n shape: 'treemap' | 'sunburst'\n /** What goes in a sunburst's hole. */\n center?: () => VNodeChild\n}\n\nexport interface TooltipPart extends ChartPart<ChartTooltipProps> {\n /** The tooltip's slot, if one is given. */\n content: () => ChartTooltipSlots['default']\n}\n\nexport type AxisPart = ChartPart<ChartAxisProps>\n\n/** A stretch of x and of the values; `null` (or left out) for none. */\nexport interface IntervalModels {\n x: Ref<ChartInterval | null | undefined>\n y: Ref<ChartInterval | null | undefined>\n}\n\nexport interface ZoomPart extends ChartPart<ChartZoomProps>, IntervalModels {}\n\nexport interface BrushPart extends ChartPart<ChartBrushProps>, IntervalModels {\n /** A stretch was selected: tells the page. */\n select: (event: ChartBrushEvent) => void\n}\n\nexport interface RangePart extends ChartPart<Record<string, never>> {\n x: Ref<ChartInterval | null | undefined>\n}\n\nexport type MarkerPart = ChartPart<ChartMarkerProps>\n\nexport interface ChartRegistry {\n addSeries(part: SeriesPart): () => void\n addAxis(part: AxisPart): () => void\n addPie(part: PiePart): () => void\n addTooltip(part: TooltipPart): () => void\n addZoom(part: ZoomPart): () => void\n addBrush(part: BrushPart): () => void\n addRange(part: RangePart): () => void\n addMarker(part: MarkerPart): () => void\n addTree(part: TreePart): () => void\n /** The chart's `data`, for a part that works its rows out from it (a histogram's bins). */\n data(): ChartData | undefined\n /** The chart's locale, for numbers a part writes itself (a gauge's value). */\n locale(): string\n /** The chart's messages, for a part that names something itself (a histogram's count). */\n messages(): ChartMessages\n}\n\nexport const chartKey: InjectionKey<ChartRegistry> = Symbol('DouChart')\n\n/**\n * The render of a part: it draws nothing itself. A comment in the page on\n * the server and in the browser alike, so taking over a server-rendered\n * page finds each part where it expects it (an empty template renders\n * nothing at all on the server).\n */\nexport const renderNothing = () => null\n\n/** Registers a child (a series, an axis) with the surrounding chart while it is mounted. */\nexport function useChartPart(name: string, add: (registry: ChartRegistry) => () => void) {\n const registry = inject(chartKey, null)\n if (!registry) {\n if (process.env.NODE_ENV !== 'production')\n console.warn(`[@dou-ui/chart] <${name}> must be placed inside <DouChart>.`)\n return\n }\n let remove: (() => void) | undefined\n onBeforeUnmount(() => remove?.())\n // on the server, and while taking over the page it rendered, the chart\n // draws before anything mounts: it needs its parts now\n if (typeof window === 'undefined' || getCurrentInstance()?.vnode.el) remove = add(registry)\n else onMounted(() => (remove = add(registry)))\n}\n\n/** The node a renderless child leaves in the page. */\nexport function useOwnNode(): () => Node | null {\n const instance = getCurrentInstance()\n return () => (instance?.vnode.el as Node | null) ?? null\n}\n"],"mappings":";;AAqGA,IAAa,WAAwC,OAAO,UAAU;;;;;;;AAQtE,IAAa,sBAAsB;;AAGnC,SAAgB,aAAa,MAAc,KAA8C;CACvF,MAAM,WAAW,OAAO,UAAU,IAAI;CACtC,IAAI,CAAC,UAAU;EACb,IAAA,QAAA,IAAA,aAA6B,cAC3B,QAAQ,KAAK,oBAAoB,KAAK,oCAAoC;EAC5E;CACF;CACA,IAAI;CACJ,sBAAsB,SAAS,CAAC;CAGhC,IAAI,OAAO,WAAW,eAAe,mBAAmB,CAAC,EAAE,MAAM,IAAI,SAAS,IAAI,QAAQ;MACrF,gBAAiB,SAAS,IAAI,QAAQ,CAAE;AAC/C;;AAGA,SAAgB,aAAgC;CAC9C,MAAM,WAAW,mBAAmB;CACpC,aAAc,UAAU,MAAM,MAAsB;AACtD"}
|
package/dist/core/bar.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { visibleRange } from "./decimate.js";
|
|
2
|
+
import { fillPattern } from "./pattern.js";
|
|
3
|
+
import { baseline } from "./scale.js";
|
|
4
|
+
// src/core/bar.ts
|
|
5
|
+
/** The part of each x band that a group of bars takes, alone and side by side. */
|
|
6
|
+
var SINGLE = .6;
|
|
7
|
+
var GROUPED = .76;
|
|
8
|
+
/** The widest a bar gets, in px. */
|
|
9
|
+
var MAX_WIDTH = 56;
|
|
10
|
+
/** The room between bars of a group, in px. */
|
|
11
|
+
var GAP = 2;
|
|
12
|
+
/** Below this width (px), bars are drawn one column of pixels at a time. */
|
|
13
|
+
var DENSE = 1.5;
|
|
14
|
+
/**
|
|
15
|
+
* Where a series' bars go in each x band: the offset of their near edge
|
|
16
|
+
* from the band's center, and their width, in px.
|
|
17
|
+
*/
|
|
18
|
+
function barPlacement(band, slot) {
|
|
19
|
+
const { index, count } = slot ?? {
|
|
20
|
+
index: 0,
|
|
21
|
+
count: 1
|
|
22
|
+
};
|
|
23
|
+
let group = band * (count > 1 ? GROUPED : SINGLE);
|
|
24
|
+
let gap = count > 1 && group / count >= 8 ? GAP : 0;
|
|
25
|
+
let width = (group - gap * (count - 1)) / count;
|
|
26
|
+
if (width > MAX_WIDTH) {
|
|
27
|
+
width = MAX_WIDTH;
|
|
28
|
+
gap = count > 1 ? 4 : 0;
|
|
29
|
+
group = width * count + gap * (count - 1);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
offset: -group / 2 + index * (width + gap),
|
|
33
|
+
width
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Draws bars from 0 (or from the slice of the stack below) to each value.
|
|
38
|
+
* The end away from 0 is rounded; in a stack, only the outermost slice's.
|
|
39
|
+
* Bars narrower than a pixel and a half are merged per pixel column into
|
|
40
|
+
* one bar from the lowest to the highest value there, which looks the
|
|
41
|
+
* same and keeps a million bars as cheap as a few hundred.
|
|
42
|
+
*/
|
|
43
|
+
function drawBars(painter, series, view, placement = barPlacement(view.band, series.slot)) {
|
|
44
|
+
const { x: sx, y: sy, dpr, horizontal } = view;
|
|
45
|
+
const xs = series.x.values;
|
|
46
|
+
const top = series.top;
|
|
47
|
+
const base = series.base;
|
|
48
|
+
const ends = series.stackEnds;
|
|
49
|
+
const [from, to] = visibleRange(xs, series.x.sorted, sx.domain[0], sx.domain[1]);
|
|
50
|
+
const { offset, width } = placement;
|
|
51
|
+
const zero = baseline(sy);
|
|
52
|
+
const log = sy.kind === "log";
|
|
53
|
+
const snap = (v) => Math.round(v * dpr) / dpr;
|
|
54
|
+
const rect = (u0, u1, v0, v1) => horizontal ? painter.rect(Math.min(v0, v1), u0, Math.abs(v1 - v0), u1 - u0) : painter.rect(u0, Math.min(v0, v1), u1 - u0, Math.abs(v1 - v0));
|
|
55
|
+
painter.save();
|
|
56
|
+
painter.fillStyle = series.color;
|
|
57
|
+
painter.beginPath();
|
|
58
|
+
if (width < DENSE) {
|
|
59
|
+
let column = NaN;
|
|
60
|
+
let lo = 0;
|
|
61
|
+
let hi = 0;
|
|
62
|
+
const flush = () => {
|
|
63
|
+
if (column === column && hi > lo) rect(column, column + 1, lo, hi);
|
|
64
|
+
};
|
|
65
|
+
for (let i = from; i < to; i++) {
|
|
66
|
+
const value = top[i];
|
|
67
|
+
const x = xs[i];
|
|
68
|
+
if (value !== value || x !== x || log && !(value > 0)) continue;
|
|
69
|
+
const v1 = sy.map(value);
|
|
70
|
+
const v0 = base ? sy.map(base[i]) : zero;
|
|
71
|
+
const c = Math.floor(sx.map(x) + offset + width / 2);
|
|
72
|
+
if (c !== column) {
|
|
73
|
+
flush();
|
|
74
|
+
column = c;
|
|
75
|
+
lo = Math.min(v0, v1);
|
|
76
|
+
hi = Math.max(v0, v1);
|
|
77
|
+
} else {
|
|
78
|
+
lo = Math.min(lo, v0, v1);
|
|
79
|
+
hi = Math.max(hi, v0, v1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
flush();
|
|
83
|
+
painter.fill();
|
|
84
|
+
painter.restore();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const radius = Math.max(0, series.props.radius ?? 2);
|
|
88
|
+
const bar = (i) => {
|
|
89
|
+
const value = top[i];
|
|
90
|
+
const x = xs[i];
|
|
91
|
+
if (value !== value || x !== x) return;
|
|
92
|
+
if (log && !(value > 0)) {
|
|
93
|
+
warnLog();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const from0 = base ? base[i] : 0;
|
|
97
|
+
if (value === from0) return;
|
|
98
|
+
const u0 = snap(sx.map(x) + offset);
|
|
99
|
+
const u1 = Math.max(u0 + 1 / dpr, snap(sx.map(x) + offset + width));
|
|
100
|
+
let v0 = base ? sy.map(from0) : zero;
|
|
101
|
+
const v1 = sy.map(value);
|
|
102
|
+
const length = Math.abs(v1 - v0);
|
|
103
|
+
if (base && from0 !== 0 && length > 2) v0 += v1 > v0 ? 1 : -1;
|
|
104
|
+
const r = !ends || (value > from0 ? value === ends.up[i] : value === ends.down[i]) ? Math.min(radius, (u1 - u0) / 2, Math.abs(v1 - v0)) : 0;
|
|
105
|
+
if (r < .5) rect(u0, u1, v0, v1);
|
|
106
|
+
else roundedEnd(painter, u0, u1, v0, v1, r, horizontal);
|
|
107
|
+
};
|
|
108
|
+
const own = series.colors;
|
|
109
|
+
if (own) {
|
|
110
|
+
const groups = /* @__PURE__ */ new Map();
|
|
111
|
+
for (let i = from; i < to; i++) {
|
|
112
|
+
const color = own[i] ?? series.color;
|
|
113
|
+
let group = groups.get(color);
|
|
114
|
+
if (!group) groups.set(color, group = []);
|
|
115
|
+
group.push(i);
|
|
116
|
+
}
|
|
117
|
+
for (const [color, rows] of groups) {
|
|
118
|
+
painter.fillStyle = color;
|
|
119
|
+
painter.beginPath();
|
|
120
|
+
for (const i of rows) bar(i);
|
|
121
|
+
painter.fill();
|
|
122
|
+
if (series.pattern) fillPattern(painter, series.pattern, view.plot, view.colors.surface);
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
for (let i = from; i < to; i++) bar(i);
|
|
126
|
+
painter.fill();
|
|
127
|
+
if (series.pattern) fillPattern(painter, series.pattern, view.plot, view.colors.surface);
|
|
128
|
+
}
|
|
129
|
+
painter.restore();
|
|
130
|
+
}
|
|
131
|
+
/** A bar from `v0` to `v1` with the corners at `v1` rounded. */
|
|
132
|
+
function roundedEnd(painter, u0, u1, v0, v1, r, swap) {
|
|
133
|
+
const p = (u, v) => swap ? [v, u] : [u, v];
|
|
134
|
+
const back = v1 < v0 ? r : -r;
|
|
135
|
+
painter.moveTo(...p(u0, v0));
|
|
136
|
+
painter.lineTo(...p(u0, v1 + back));
|
|
137
|
+
painter.arcTo(...p(u0, v1), ...p(u0 + r, v1), r);
|
|
138
|
+
painter.lineTo(...p(u1 - r, v1));
|
|
139
|
+
painter.arcTo(...p(u1, v1), ...p(u1, v1 + back), r);
|
|
140
|
+
painter.lineTo(...p(u1, v0));
|
|
141
|
+
painter.closePath();
|
|
142
|
+
}
|
|
143
|
+
var logWarned = false;
|
|
144
|
+
function warnLog() {
|
|
145
|
+
if (process.env.NODE_ENV === "production" || logWarned) return;
|
|
146
|
+
logWarned = true;
|
|
147
|
+
console.warn("[@dou-ui/chart] bars on a log axis grow from its start; values at or below 0 are left out.");
|
|
148
|
+
}
|
|
149
|
+
/** Bars narrower than this (px) get no labels: they couldn't be told apart. */
|
|
150
|
+
var LABEL_WIDTH = 8;
|
|
151
|
+
/**
|
|
152
|
+
* Where bars' labels go: past each bar's end, or in its middle (stacked
|
|
153
|
+
* bars, by default), with the bar to fit in.
|
|
154
|
+
*/
|
|
155
|
+
function barLabels(series, view, placement = barPlacement(view.band, series.slot)) {
|
|
156
|
+
const { x: sx, y: sy, horizontal } = view;
|
|
157
|
+
const { offset, width } = placement;
|
|
158
|
+
if (width < LABEL_WIDTH) return [];
|
|
159
|
+
const xs = series.x.values;
|
|
160
|
+
const top = series.top;
|
|
161
|
+
const base = series.base;
|
|
162
|
+
const [from, to] = visibleRange(xs, series.x.sorted, sx.domain[0], sx.domain[1]);
|
|
163
|
+
const inside = (series.props.labelPosition ?? (series.stackEnds ? "inside" : "outside")) === "inside";
|
|
164
|
+
const zero = baseline(sy);
|
|
165
|
+
const room = view.band / (series.slot?.count ?? 1);
|
|
166
|
+
const out = [];
|
|
167
|
+
for (let i = from; i < to; i++) {
|
|
168
|
+
const value = top[i];
|
|
169
|
+
const x = xs[i];
|
|
170
|
+
if (value !== value || x !== x) continue;
|
|
171
|
+
const from0 = base ? base[i] : 0;
|
|
172
|
+
if (value === from0 || sy.kind === "log" && !(value > 0)) continue;
|
|
173
|
+
const u = sx.map(x) + offset + width / 2;
|
|
174
|
+
const v1 = sy.map(value);
|
|
175
|
+
const v0 = base ? sy.map(from0) : zero;
|
|
176
|
+
const up = horizontal ? v1 > v0 : v1 < v0;
|
|
177
|
+
if (inside) {
|
|
178
|
+
const lo = Math.min(v0, v1);
|
|
179
|
+
const length = Math.abs(v1 - v0);
|
|
180
|
+
const box = horizontal ? {
|
|
181
|
+
x: lo,
|
|
182
|
+
y: u - width / 2,
|
|
183
|
+
width: length,
|
|
184
|
+
height: width
|
|
185
|
+
} : {
|
|
186
|
+
x: u - width / 2,
|
|
187
|
+
y: lo,
|
|
188
|
+
width,
|
|
189
|
+
height: length
|
|
190
|
+
};
|
|
191
|
+
const middle = (v0 + v1) / 2;
|
|
192
|
+
out.push({
|
|
193
|
+
index: i,
|
|
194
|
+
at: horizontal ? [middle, u] : [u, middle],
|
|
195
|
+
side: "center",
|
|
196
|
+
box: {
|
|
197
|
+
...box,
|
|
198
|
+
color: series.colors?.[i] ?? series.color
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
const side = horizontal ? up ? "right" : "left" : up ? "top" : "bottom";
|
|
203
|
+
out.push({
|
|
204
|
+
index: i,
|
|
205
|
+
at: horizontal ? [v1, u] : [u, v1],
|
|
206
|
+
side,
|
|
207
|
+
room
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
var barKind = {
|
|
214
|
+
type: "bar",
|
|
215
|
+
zero: true,
|
|
216
|
+
stackable: true,
|
|
217
|
+
band: true,
|
|
218
|
+
draw: drawBars,
|
|
219
|
+
labels: barLabels
|
|
220
|
+
};
|
|
221
|
+
export { barKind, barLabels, barPlacement, drawBars };
|
|
222
|
+
|
|
223
|
+
//# sourceMappingURL=bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bar.js","names":[],"sources":["../../src/core/bar.ts"],"sourcesContent":["import { visibleRange } from './decimate'\nimport type { Painter } from './painter'\nimport { fillPattern } from './pattern'\nimport { baseline } from './scale'\nimport type { ChartLabelAnchor, ChartSeries, ChartSeriesKind, ChartView } from './series'\n\nexport interface BarStyle {\n /** Corner radius of the bar's end, in px. */\n radius?: number\n labelPosition?: 'outside' | 'inside'\n}\n\n/** The part of each x band that a group of bars takes, alone and side by side. */\nconst SINGLE = 0.6\nconst GROUPED = 0.76\n/** The widest a bar gets, in px. */\nconst MAX_WIDTH = 56\n/** The room between bars of a group, in px. */\nconst GAP = 2\n/** Below this width (px), bars are drawn one column of pixels at a time. */\nconst DENSE = 1.5\n\n/**\n * Where a series' bars go in each x band: the offset of their near edge\n * from the band's center, and their width, in px.\n */\nexport function barPlacement(\n band: number,\n slot: { index: number; count: number } | null,\n): { offset: number; width: number } {\n const { index, count } = slot ?? { index: 0, count: 1 }\n let group = band * (count > 1 ? GROUPED : SINGLE)\n let gap = count > 1 && group / count >= GAP * 4 ? GAP : 0\n let width = (group - gap * (count - 1)) / count\n if (width > MAX_WIDTH) {\n width = MAX_WIDTH\n gap = count > 1 ? GAP * 2 : 0\n group = width * count + gap * (count - 1)\n }\n return { offset: -group / 2 + index * (width + gap), width }\n}\n\n/**\n * Draws bars from 0 (or from the slice of the stack below) to each value.\n * The end away from 0 is rounded; in a stack, only the outermost slice's.\n * Bars narrower than a pixel and a half are merged per pixel column into\n * one bar from the lowest to the highest value there, which looks the\n * same and keeps a million bars as cheap as a few hundred.\n */\nexport function drawBars(\n painter: Painter,\n series: ChartSeries<BarStyle>,\n view: ChartView,\n placement = barPlacement(view.band, series.slot),\n) {\n const { x: sx, y: sy, dpr, horizontal } = view\n const xs = series.x.values\n const top = series.top\n const base = series.base\n const ends = series.stackEnds\n const [from, to] = visibleRange(xs, series.x.sorted, sx.domain[0], sx.domain[1])\n const { offset, width } = placement\n const zero = baseline(sy)\n const log = sy.kind === 'log'\n const snap = (v: number) => Math.round(v * dpr) / dpr\n\n // a bar is drawn along `u` (the x axis) and `v` (the value axis); swapped when horizontal\n const rect = (u0: number, u1: number, v0: number, v1: number) =>\n horizontal\n ? painter.rect(Math.min(v0, v1), u0, Math.abs(v1 - v0), u1 - u0)\n : painter.rect(u0, Math.min(v0, v1), u1 - u0, Math.abs(v1 - v0))\n\n painter.save()\n painter.fillStyle = series.color\n painter.beginPath()\n\n if (width < DENSE) {\n // one column per pixel: from the lowest to the highest end of the bars in it\n let column = Number.NaN\n let lo = 0\n let hi = 0\n const flush = () => {\n if (column === column && hi > lo) rect(column, column + 1, lo, hi)\n }\n for (let i = from; i < to; i++) {\n const value = top[i]!\n const x = xs[i]!\n if (value !== value || x !== x || (log && !(value > 0))) continue\n const v1 = sy.map(value)\n const v0 = base ? sy.map(base[i]!) : zero\n const c = Math.floor(sx.map(x) + offset + width / 2)\n if (c !== column) {\n flush()\n column = c\n lo = Math.min(v0, v1)\n hi = Math.max(v0, v1)\n } else {\n lo = Math.min(lo, v0, v1)\n hi = Math.max(hi, v0, v1)\n }\n }\n flush()\n painter.fill()\n painter.restore()\n return\n }\n\n const radius = Math.max(0, series.props.radius ?? 2)\n const bar = (i: number) => {\n const value = top[i]!\n const x = xs[i]!\n if (value !== value || x !== x) return\n // a log axis has no 0 to grow from, nor room for values at or below it\n if (log && !(value > 0)) {\n warnLog()\n return\n }\n const from0 = base ? base[i]! : 0\n if (value === from0) return\n const u0 = snap(sx.map(x) + offset)\n const u1 = Math.max(u0 + 1 / dpr, snap(sx.map(x) + offset + width))\n let v0 = base ? sy.map(from0) : zero\n const v1 = sy.map(value)\n const length = Math.abs(v1 - v0)\n // a slice on another one leaves a hairline of room between them\n if (base && from0 !== 0 && length > 2) v0 += v1 > v0 ? 1 : -1\n const outermost = !ends || (value > from0 ? value === ends.up[i] : value === ends.down[i])\n const r = outermost ? Math.min(radius, (u1 - u0) / 2, Math.abs(v1 - v0)) : 0\n if (r < 0.5) rect(u0, u1, v0, v1)\n else roundedEnd(painter, u0, u1, v0, v1, r, horizontal)\n }\n const own = series.colors\n if (own) {\n // bars of their own color: one shape per color\n const groups = new Map<string, number[]>()\n for (let i = from; i < to; i++) {\n const color = own[i] ?? series.color\n let group = groups.get(color)\n if (!group) groups.set(color, (group = []))\n group.push(i)\n }\n for (const [color, rows] of groups) {\n painter.fillStyle = color\n painter.beginPath()\n for (const i of rows) bar(i)\n painter.fill()\n if (series.pattern) fillPattern(painter, series.pattern, view.plot, view.colors.surface)\n }\n } else {\n for (let i = from; i < to; i++) bar(i)\n painter.fill()\n if (series.pattern) fillPattern(painter, series.pattern, view.plot, view.colors.surface)\n }\n painter.restore()\n}\n\n/** A bar from `v0` to `v1` with the corners at `v1` rounded. */\nfunction roundedEnd(\n painter: Painter,\n u0: number,\n u1: number,\n v0: number,\n v1: number,\n r: number,\n swap: boolean,\n) {\n const p = (u: number, v: number): [number, number] => (swap ? [v, u] : [u, v])\n // the side the corners curve away from: back towards v0\n const back = v1 < v0 ? r : -r\n painter.moveTo(...p(u0, v0))\n painter.lineTo(...p(u0, v1 + back))\n painter.arcTo(...p(u0, v1), ...p(u0 + r, v1), r)\n painter.lineTo(...p(u1 - r, v1))\n painter.arcTo(...p(u1, v1), ...p(u1, v1 + back), r)\n painter.lineTo(...p(u1, v0))\n painter.closePath()\n}\n\nlet logWarned = false\nfunction warnLog() {\n if (process.env.NODE_ENV === 'production' || logWarned) return\n logWarned = true\n console.warn(\n '[@dou-ui/chart] bars on a log axis grow from its start; values at or below 0 are left out.',\n )\n}\n\n/** Bars narrower than this (px) get no labels: they couldn't be told apart. */\nconst LABEL_WIDTH = 8\n\n/**\n * Where bars' labels go: past each bar's end, or in its middle (stacked\n * bars, by default), with the bar to fit in.\n */\nexport function barLabels(\n series: ChartSeries<BarStyle>,\n view: ChartView,\n placement = barPlacement(view.band, series.slot),\n): ChartLabelAnchor[] {\n const { x: sx, y: sy, horizontal } = view\n const { offset, width } = placement\n if (width < LABEL_WIDTH) return []\n const xs = series.x.values\n const top = series.top\n const base = series.base\n const [from, to] = visibleRange(xs, series.x.sorted, sx.domain[0], sx.domain[1])\n const inside =\n (series.props.labelPosition ?? (series.stackEnds ? 'inside' : 'outside')) === 'inside'\n const zero = baseline(sy)\n // past its end, a label may take its bar's share of the x band, no more\n const room = view.band / (series.slot?.count ?? 1)\n const out: ChartLabelAnchor[] = []\n for (let i = from; i < to; i++) {\n const value = top[i]!\n const x = xs[i]!\n if (value !== value || x !== x) continue\n const from0 = base ? base[i]! : 0\n if (value === from0 || (sy.kind === 'log' && !(value > 0))) continue\n const u = sx.map(x) + offset + width / 2\n const v1 = sy.map(value)\n const v0 = base ? sy.map(from0) : zero\n // which way the bar runs on screen: a reversed axis turns it round\n const up = horizontal ? v1 > v0 : v1 < v0\n if (inside) {\n const lo = Math.min(v0, v1)\n const length = Math.abs(v1 - v0)\n const box = horizontal\n ? { x: lo, y: u - width / 2, width: length, height: width }\n : { x: u - width / 2, y: lo, width, height: length }\n const middle = (v0 + v1) / 2\n out.push({\n index: i,\n at: horizontal ? [middle, u] : [u, middle],\n side: 'center',\n box: { ...box, color: series.colors?.[i] ?? series.color },\n })\n } else {\n // past the end away from 0: above a bar going up, below one going down\n const side = horizontal ? (up ? 'right' : 'left') : up ? 'top' : 'bottom'\n out.push({ index: i, at: horizontal ? [v1, u] : [u, v1], side, room })\n }\n }\n return out\n}\n\nexport const barKind: ChartSeriesKind<BarStyle> = {\n type: 'bar',\n zero: true,\n stackable: true,\n band: true,\n draw: drawBars,\n labels: barLabels,\n}\n"],"mappings":"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"}
|
package/dist/core/box.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { visibleRange } from "./decimate.js";
|
|
2
|
+
import { crisp } from "./painter.js";
|
|
3
|
+
import { barPlacement } from "./bar.js";
|
|
4
|
+
import { read, rowCount, rowOf } from "./data.js";
|
|
5
|
+
import { warnOnce } from "./warn.js";
|
|
6
|
+
// src/core/box.ts
|
|
7
|
+
/** The `p`th quantile (0 to 1) of sorted values, between the two nearest. */
|
|
8
|
+
function quantile(sorted, p) {
|
|
9
|
+
const at = (sorted.length - 1) * p;
|
|
10
|
+
const i = Math.floor(at);
|
|
11
|
+
const next = sorted[Math.min(i + 1, sorted.length - 1)];
|
|
12
|
+
return sorted[i] + (next - sorted[i]) * (at - i);
|
|
13
|
+
}
|
|
14
|
+
/** Sums up a group of values; `null` when none is a number. */
|
|
15
|
+
function boxStats(values, whisker = 1.5) {
|
|
16
|
+
if (!values || typeof values.length !== "number") return null;
|
|
17
|
+
const list = [];
|
|
18
|
+
for (const v of Array.from(values)) {
|
|
19
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
20
|
+
if (v != null && v !== "" && Number.isFinite(n)) list.push(n);
|
|
21
|
+
}
|
|
22
|
+
if (!list.length) return null;
|
|
23
|
+
list.sort((a, b) => a - b);
|
|
24
|
+
const q1 = quantile(list, .25);
|
|
25
|
+
const q3 = quantile(list, .75);
|
|
26
|
+
const reach = (q3 - q1) * whisker;
|
|
27
|
+
let low = q1;
|
|
28
|
+
let high = q3;
|
|
29
|
+
const outliers = [];
|
|
30
|
+
for (const v of list) if (v < q1 - reach || v > q3 + reach) outliers.push(v);
|
|
31
|
+
else {
|
|
32
|
+
if (v < low) low = v;
|
|
33
|
+
if (v > high) high = v;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
low,
|
|
37
|
+
high,
|
|
38
|
+
q1,
|
|
39
|
+
median: quantile(list, .5),
|
|
40
|
+
q3,
|
|
41
|
+
outliers,
|
|
42
|
+
count: list.length
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
46
|
+
/** Each row's numbers, worked out once for the same rows, accessor and whisker. */
|
|
47
|
+
function boxesOf(rows, values, whisker = 1.5) {
|
|
48
|
+
let byAccessor = cache.get(rows);
|
|
49
|
+
if (!byAccessor) cache.set(rows, byAccessor = /* @__PURE__ */ new Map());
|
|
50
|
+
const key = `${whisker}`;
|
|
51
|
+
let byWhisker = byAccessor.get(values);
|
|
52
|
+
if (!byWhisker) byAccessor.set(values, byWhisker = /* @__PURE__ */ new Map());
|
|
53
|
+
let out = byWhisker.get(key);
|
|
54
|
+
if (!out) {
|
|
55
|
+
out = [];
|
|
56
|
+
const n = rowCount(rows);
|
|
57
|
+
for (let i = 0; i < n; i++) {
|
|
58
|
+
const group = read(rowOf(rows, i), values, i);
|
|
59
|
+
if (group != null && typeof group.length !== "number") warnOnce("box-values", "a box plot's `y` gives each row an array of values; other values are left out.");
|
|
60
|
+
out.push(boxStats(group, whisker));
|
|
61
|
+
}
|
|
62
|
+
byWhisker.set(key, out);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/** A box plot series' numbers by row. */
|
|
67
|
+
var boxes = (series) => boxesOf(series.rows, series.props.y, series.props.whisker);
|
|
68
|
+
/** The radius of an outlier's dot, in px. */
|
|
69
|
+
var DOT = 2.5;
|
|
70
|
+
function draw(painter, series, view) {
|
|
71
|
+
const { x: sx, y: sy, dpr, horizontal } = view;
|
|
72
|
+
const xs = series.x.values;
|
|
73
|
+
const list = boxes(series);
|
|
74
|
+
const [from, to] = visibleRange(xs, series.x.sorted, sx.domain[0], sx.domain[1]);
|
|
75
|
+
const { offset, width } = barPlacement(view.band, series.slot);
|
|
76
|
+
const snap = (v) => Math.round(v * dpr) / dpr;
|
|
77
|
+
const line = (v) => crisp(v, dpr);
|
|
78
|
+
const p = (u, v) => horizontal ? [v, u] : [u, v];
|
|
79
|
+
const color = series.color;
|
|
80
|
+
painter.save();
|
|
81
|
+
painter.lineWidth = 1;
|
|
82
|
+
for (let i = from; i < to; i++) {
|
|
83
|
+
const box = list[i];
|
|
84
|
+
const x = xs[i];
|
|
85
|
+
if (!box || x !== x) continue;
|
|
86
|
+
const u0 = snap(sx.map(x) + offset);
|
|
87
|
+
const u1 = Math.max(u0 + 1, snap(sx.map(x) + offset + width));
|
|
88
|
+
const mid = line((u0 + u1) / 2);
|
|
89
|
+
const cap = Math.max(4, (u1 - u0) / 2);
|
|
90
|
+
const [q1, q3] = [snap(sy.map(box.q1)), snap(sy.map(box.q3))];
|
|
91
|
+
painter.strokeStyle = color;
|
|
92
|
+
painter.beginPath();
|
|
93
|
+
for (const [end, edge] of [[box.low, box.q1], [box.high, box.q3]]) {
|
|
94
|
+
const v = line(sy.map(end));
|
|
95
|
+
painter.moveTo(...p(mid, snap(sy.map(edge))));
|
|
96
|
+
painter.lineTo(...p(mid, v));
|
|
97
|
+
painter.moveTo(...p(mid - cap / 2, v));
|
|
98
|
+
painter.lineTo(...p(mid + cap / 2, v));
|
|
99
|
+
}
|
|
100
|
+
painter.stroke();
|
|
101
|
+
const [x0, y0] = p(u0, Math.min(q1, q3));
|
|
102
|
+
const [x1, y1] = p(u1, Math.max(q1, q3));
|
|
103
|
+
const rect = [
|
|
104
|
+
Math.min(x0, x1),
|
|
105
|
+
Math.min(y0, y1),
|
|
106
|
+
Math.abs(x1 - x0),
|
|
107
|
+
Math.abs(y1 - y0)
|
|
108
|
+
];
|
|
109
|
+
painter.beginPath();
|
|
110
|
+
painter.rect(...rect);
|
|
111
|
+
painter.fillStyle = view.colors.surface;
|
|
112
|
+
painter.fill();
|
|
113
|
+
painter.save();
|
|
114
|
+
painter.globalAlpha *= .16;
|
|
115
|
+
painter.fillStyle = color;
|
|
116
|
+
painter.fill();
|
|
117
|
+
painter.restore();
|
|
118
|
+
painter.beginPath();
|
|
119
|
+
painter.rect(rect[0] + .5, rect[1] + .5, Math.max(0, rect[2] - 1), Math.max(0, rect[3] - 1));
|
|
120
|
+
painter.stroke();
|
|
121
|
+
painter.lineWidth = 2;
|
|
122
|
+
painter.beginPath();
|
|
123
|
+
const m = snap(sy.map(box.median));
|
|
124
|
+
painter.moveTo(...p(u0, m));
|
|
125
|
+
painter.lineTo(...p(u1, m));
|
|
126
|
+
painter.stroke();
|
|
127
|
+
painter.lineWidth = 1;
|
|
128
|
+
if (box.outliers.length) {
|
|
129
|
+
painter.beginPath();
|
|
130
|
+
for (const v of box.outliers) {
|
|
131
|
+
const [cx, cy] = p(mid, sy.map(v));
|
|
132
|
+
painter.moveTo(cx + DOT, cy);
|
|
133
|
+
painter.arc(cx, cy, DOT, 0, Math.PI * 2);
|
|
134
|
+
}
|
|
135
|
+
painter.fillStyle = view.colors.surface;
|
|
136
|
+
painter.fill();
|
|
137
|
+
painter.stroke();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
painter.restore();
|
|
141
|
+
}
|
|
142
|
+
var boxKind = {
|
|
143
|
+
type: "box",
|
|
144
|
+
box: true,
|
|
145
|
+
band: true,
|
|
146
|
+
draw
|
|
147
|
+
};
|
|
148
|
+
export { boxKind, boxStats, boxes, boxesOf };
|
|
149
|
+
|
|
150
|
+
//# sourceMappingURL=box.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"box.js","names":[],"sources":["../../src/core/box.ts"],"sourcesContent":["import { barPlacement } from './bar'\nimport { read, rowCount, rowOf, type ChartAccessor, type ChartData } from './data'\nimport { visibleRange } from './decimate'\nimport { crisp, type Painter } from './painter'\nimport { warnOnce } from './warn'\nimport type { ChartSeries, ChartSeriesKind, ChartView } from './series'\n\n/*\n * A box plot: for each x, a group of values summed up in five numbers. The\n * box runs from the lower quartile to the upper one with a line at the\n * median; the whiskers reach the farthest values within 1.5 times the box's\n * length of it, and values past them are dots of their own.\n */\n\nexport interface BoxStyle {\n /** How far whiskers reach, in box lengths past the box: `1.5` by default, `Infinity` for all the values. */\n whisker?: number\n}\n\nexport interface BoxStats {\n /** The ends of the whiskers. */\n low: number\n high: number\n q1: number\n median: number\n q3: number\n /** The values past the whiskers. */\n outliers: number[]\n /** How many values there are. */\n count: number\n}\n\n/** The `p`th quantile (0 to 1) of sorted values, between the two nearest. */\nfunction quantile(sorted: number[], p: number): number {\n const at = (sorted.length - 1) * p\n const i = Math.floor(at)\n const next = sorted[Math.min(i + 1, sorted.length - 1)]!\n return sorted[i]! + (next - sorted[i]!) * (at - i)\n}\n\n/** Sums up a group of values; `null` when none is a number. */\nexport function boxStats(values: unknown, whisker = 1.5): BoxStats | null {\n if (!values || typeof (values as ArrayLike<unknown>).length !== 'number') return null\n const list: number[] = []\n for (const v of Array.from(values as ArrayLike<unknown>)) {\n const n = typeof v === 'number' ? v : Number(v)\n if (v != null && v !== '' && Number.isFinite(n)) list.push(n)\n }\n if (!list.length) return null\n list.sort((a, b) => a - b)\n const q1 = quantile(list, 0.25)\n const q3 = quantile(list, 0.75)\n const reach = (q3 - q1) * whisker\n let low = q1\n let high = q3\n const outliers: number[] = []\n for (const v of list) {\n if (v < q1 - reach || v > q3 + reach) outliers.push(v)\n else {\n if (v < low) low = v\n if (v > high) high = v\n }\n }\n return { low, high, q1, median: quantile(list, 0.5), q3, outliers, count: list.length }\n}\n\nconst cache = new WeakMap<object, Map<unknown, Array<BoxStats | null>>>()\n\n/** Each row's numbers, worked out once for the same rows, accessor and whisker. */\nexport function boxesOf(\n rows: ChartData,\n values: ChartAccessor,\n whisker = 1.5,\n): Array<BoxStats | null> {\n let byAccessor = cache.get(rows)\n if (!byAccessor) cache.set(rows, (byAccessor = new Map()))\n const key = `${whisker}`\n let byWhisker = byAccessor.get(values) as unknown as Map<string, Array<BoxStats | null>>\n if (!byWhisker) byAccessor.set(values, (byWhisker = new Map()) as never)\n let out = byWhisker.get(key)\n if (!out) {\n out = []\n const n = rowCount(rows)\n for (let i = 0; i < n; i++) {\n const group = read(rowOf(rows, i), values, i)\n if (group != null && typeof (group as ArrayLike<unknown>).length !== 'number')\n warnOnce(\n 'box-values',\n \"a box plot's `y` gives each row an array of values; other values are left out.\",\n )\n out.push(boxStats(group, whisker))\n }\n byWhisker.set(key, out)\n }\n return out\n}\n\n/** A box plot series' numbers by row. */\nexport const boxes = (series: Pick<ChartSeries<BoxStyle>, 'rows' | 'props'>) =>\n boxesOf(series.rows, series.props.y, series.props.whisker)\n\n/** The radius of an outlier's dot, in px. */\nconst DOT = 2.5\n\nfunction draw(painter: Painter, series: ChartSeries<BoxStyle>, view: ChartView) {\n const { x: sx, y: sy, dpr, horizontal } = view\n const xs = series.x.values\n const list = boxes(series)\n const [from, to] = visibleRange(xs, series.x.sorted, sx.domain[0], sx.domain[1])\n const { offset, width } = barPlacement(view.band, series.slot)\n const snap = (v: number) => Math.round(v * dpr) / dpr\n // a line 1 px wide on whole device pixels\n const line = (v: number) => crisp(v, dpr)\n const p = (u: number, v: number): [number, number] => (horizontal ? [v, u] : [u, v])\n const color = series.color\n\n painter.save()\n painter.lineWidth = 1\n for (let i = from; i < to; i++) {\n const box = list[i]\n const x = xs[i]!\n if (!box || x !== x) continue\n const u0 = snap(sx.map(x) + offset)\n const u1 = Math.max(u0 + 1, snap(sx.map(x) + offset + width))\n const mid = line((u0 + u1) / 2)\n const cap = Math.max(4, (u1 - u0) / 2)\n const [q1, q3] = [snap(sy.map(box.q1)), snap(sy.map(box.q3))]\n // whiskers and their caps\n painter.strokeStyle = color\n painter.beginPath()\n for (const [end, edge] of [\n [box.low, box.q1],\n [box.high, box.q3],\n ] as const) {\n const v = line(sy.map(end))\n painter.moveTo(...p(mid, snap(sy.map(edge))))\n painter.lineTo(...p(mid, v))\n painter.moveTo(...p(mid - cap / 2, v))\n painter.lineTo(...p(mid + cap / 2, v))\n }\n painter.stroke()\n // the box: a light fill, its outline, and the median across it\n const [x0, y0] = p(u0, Math.min(q1, q3))\n const [x1, y1] = p(u1, Math.max(q1, q3))\n const rect = [Math.min(x0, x1), Math.min(y0, y1), Math.abs(x1 - x0), Math.abs(y1 - y0)] as const\n painter.beginPath()\n painter.rect(...rect)\n painter.fillStyle = view.colors.surface\n painter.fill()\n painter.save()\n painter.globalAlpha *= 0.16\n painter.fillStyle = color\n painter.fill()\n painter.restore()\n painter.beginPath()\n painter.rect(rect[0] + 0.5, rect[1] + 0.5, Math.max(0, rect[2] - 1), Math.max(0, rect[3] - 1))\n painter.stroke()\n painter.lineWidth = 2\n painter.beginPath()\n const m = snap(sy.map(box.median))\n painter.moveTo(...p(u0, m))\n painter.lineTo(...p(u1, m))\n painter.stroke()\n painter.lineWidth = 1\n // the values past the whiskers: rings, so many on one spot still show\n if (box.outliers.length) {\n painter.beginPath()\n for (const v of box.outliers) {\n const [cx, cy] = p(mid, sy.map(v))\n painter.moveTo(cx + DOT, cy)\n painter.arc(cx, cy, DOT, 0, Math.PI * 2)\n }\n painter.fillStyle = view.colors.surface\n painter.fill()\n painter.stroke()\n }\n }\n painter.restore()\n}\n\nexport const boxKind: ChartSeriesKind<BoxStyle> = {\n type: 'box',\n box: true,\n band: true,\n draw,\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/core/colors.ts
|
|
2
|
+
function createColorResolver(host) {
|
|
3
|
+
const cache = /* @__PURE__ */ new Map();
|
|
4
|
+
let probe = null;
|
|
5
|
+
const resolve = ((css) => {
|
|
6
|
+
const known = cache.get(css);
|
|
7
|
+
if (known) return known;
|
|
8
|
+
const root = host();
|
|
9
|
+
if (!root || typeof getComputedStyle === "undefined") return css;
|
|
10
|
+
if (!probe || probe.parentNode !== root) {
|
|
11
|
+
probe = document.createElement("span");
|
|
12
|
+
probe.setAttribute("aria-hidden", "true");
|
|
13
|
+
probe.style.cssText = "position:absolute;width:0;height:0;overflow:hidden;visibility:hidden";
|
|
14
|
+
root.appendChild(probe);
|
|
15
|
+
}
|
|
16
|
+
probe.style.color = "";
|
|
17
|
+
probe.style.color = css;
|
|
18
|
+
const color = getComputedStyle(probe).color || css;
|
|
19
|
+
cache.set(css, color);
|
|
20
|
+
return color;
|
|
21
|
+
});
|
|
22
|
+
resolve.clear = () => cache.clear();
|
|
23
|
+
return resolve;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Calls `onChange` when the page's theme may have changed: the theme
|
|
27
|
+
* attribute or class on `<html>` / `<body>`, or the system color scheme.
|
|
28
|
+
*/
|
|
29
|
+
function observeTheme(onChange) {
|
|
30
|
+
if (typeof window === "undefined") return () => {};
|
|
31
|
+
let pending = 0;
|
|
32
|
+
const notify = () => {
|
|
33
|
+
if (pending) return;
|
|
34
|
+
pending = requestAnimationFrame(() => {
|
|
35
|
+
pending = 0;
|
|
36
|
+
onChange();
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const observer = typeof MutationObserver === "undefined" ? null : new MutationObserver(notify);
|
|
40
|
+
for (const el of [document.documentElement, document.body]) observer?.observe(el, {
|
|
41
|
+
attributes: true,
|
|
42
|
+
attributeFilter: [
|
|
43
|
+
"class",
|
|
44
|
+
"style",
|
|
45
|
+
"data-dou-theme"
|
|
46
|
+
]
|
|
47
|
+
});
|
|
48
|
+
const scheme = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
49
|
+
scheme?.addEventListener?.("change", notify);
|
|
50
|
+
return () => {
|
|
51
|
+
observer?.disconnect();
|
|
52
|
+
scheme?.removeEventListener?.("change", notify);
|
|
53
|
+
if (pending) cancelAnimationFrame(pending);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export { createColorResolver, observeTheme };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=colors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colors.js","names":[],"sources":["../../src/core/colors.ts"],"sourcesContent":["/**\n * Canvas drawing needs real colors, while the theme speaks in CSS\n * variables (`var(--dou-color-chart-2)`, `color-mix(…)`). A hidden probe\n * inside the chart lets the browser work them out, as they apply there.\n */\nexport interface ColorResolver {\n (css: string): string\n /** Forget what was worked out (the theme changed). */\n clear(): void\n}\n\nexport function createColorResolver(host: () => HTMLElement | null | undefined): ColorResolver {\n const cache = new Map<string, string>()\n let probe: HTMLElement | null = null\n const resolve = ((css: string) => {\n const known = cache.get(css)\n if (known) return known\n const root = host()\n if (!root || typeof getComputedStyle === 'undefined') return css\n if (!probe || probe.parentNode !== root) {\n probe = document.createElement('span')\n probe.setAttribute('aria-hidden', 'true')\n probe.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden;visibility:hidden'\n root.appendChild(probe)\n }\n probe.style.color = ''\n probe.style.color = css\n const color = getComputedStyle(probe).color || css\n cache.set(css, color)\n return color\n }) as ColorResolver\n resolve.clear = () => cache.clear()\n return resolve\n}\n\n/**\n * Calls `onChange` when the page's theme may have changed: the theme\n * attribute or class on `<html>` / `<body>`, or the system color scheme.\n */\nexport function observeTheme(onChange: () => void): () => void {\n if (typeof window === 'undefined') return () => {}\n let pending = 0\n const notify = () => {\n if (pending) return\n // after the new styles apply\n pending = requestAnimationFrame(() => {\n pending = 0\n onChange()\n })\n }\n const observer = typeof MutationObserver === 'undefined' ? null : new MutationObserver(notify)\n for (const el of [document.documentElement, document.body])\n observer?.observe(el, {\n attributes: true,\n attributeFilter: ['class', 'style', 'data-dou-theme'],\n })\n const scheme = window.matchMedia?.('(prefers-color-scheme: dark)')\n scheme?.addEventListener?.('change', notify)\n return () => {\n observer?.disconnect()\n scheme?.removeEventListener?.('change', notify)\n if (pending) cancelAnimationFrame(pending)\n }\n}\n"],"mappings":";AAWA,SAAgB,oBAAoB,MAA2D;CAC7F,MAAM,wBAAQ,IAAI,IAAoB;CACtC,IAAI,QAA4B;CAChC,MAAM,YAAY,QAAgB;EAChC,MAAM,QAAQ,MAAM,IAAI,GAAG;EAC3B,IAAI,OAAO,OAAO;EAClB,MAAM,OAAO,KAAK;EAClB,IAAI,CAAC,QAAQ,OAAO,qBAAqB,aAAa,OAAO;EAC7D,IAAI,CAAC,SAAS,MAAM,eAAe,MAAM;GACvC,QAAQ,SAAS,cAAc,MAAM;GACrC,MAAM,aAAa,eAAe,MAAM;GACxC,MAAM,MAAM,UAAU;GACtB,KAAK,YAAY,KAAK;EACxB;EACA,MAAM,MAAM,QAAQ;EACpB,MAAM,MAAM,QAAQ;EACpB,MAAM,QAAQ,iBAAiB,KAAK,CAAC,CAAC,SAAS;EAC/C,MAAM,IAAI,KAAK,KAAK;EACpB,OAAO;CACT;CACA,QAAQ,cAAc,MAAM,MAAM;CAClC,OAAO;AACT;;;;;AAMA,SAAgB,aAAa,UAAkC;CAC7D,IAAI,OAAO,WAAW,aAAa,aAAa,CAAC;CACjD,IAAI,UAAU;CACd,MAAM,eAAe;EACnB,IAAI,SAAS;EAEb,UAAU,4BAA4B;GACpC,UAAU;GACV,SAAS;EACX,CAAC;CACH;CACA,MAAM,WAAW,OAAO,qBAAqB,cAAc,OAAO,IAAI,iBAAiB,MAAM;CAC7F,KAAK,MAAM,MAAM,CAAC,SAAS,iBAAiB,SAAS,IAAI,GACvD,UAAU,QAAQ,IAAI;EACpB,YAAY;EACZ,iBAAiB;GAAC;GAAS;GAAS;EAAgB;CACtD,CAAC;CACH,MAAM,SAAS,OAAO,aAAa,8BAA8B;CACjE,QAAQ,mBAAmB,UAAU,MAAM;CAC3C,aAAa;EACX,UAAU,WAAW;EACrB,QAAQ,sBAAsB,UAAU,MAAM;EAC9C,IAAI,SAAS,qBAAqB,OAAO;CAC3C;AACF"}
|