decantr 0.9.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/AGENTS.md +868 -0
- package/CHANGELOG.md +255 -0
- package/CLAUDE.md +178 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/cli/art.js +127 -0
- package/cli/commands/a11y.js +61 -0
- package/cli/commands/audit.js +225 -0
- package/cli/commands/build.js +38 -0
- package/cli/commands/dev.js +18 -0
- package/cli/commands/doctor.js +197 -0
- package/cli/commands/figma-sync.js +48 -0
- package/cli/commands/figma-tokens.js +55 -0
- package/cli/commands/generate.js +26 -0
- package/cli/commands/init.js +116 -0
- package/cli/commands/lint.js +209 -0
- package/cli/commands/mcp.js +530 -0
- package/cli/commands/migrate.js +175 -0
- package/cli/commands/test.js +38 -0
- package/cli/commands/validate.js +354 -0
- package/cli/index.js +113 -0
- package/package.json +95 -0
- package/reference/atoms.md +517 -0
- package/reference/behaviors.md +384 -0
- package/reference/build-tooling.md +275 -0
- package/reference/color-guidelines.md +965 -0
- package/reference/component-lifecycle.md +137 -0
- package/reference/compound-spacing.md +95 -0
- package/reference/decantation-process.md +499 -0
- package/reference/dev-server-routes.md +93 -0
- package/reference/form-system.md +253 -0
- package/reference/i18n.md +336 -0
- package/reference/icons.md +576 -0
- package/reference/llm-primer.md +953 -0
- package/reference/plugins.md +252 -0
- package/reference/registry-consumption.md +76 -0
- package/reference/router.md +217 -0
- package/reference/shells.md +116 -0
- package/reference/spatial-guidelines.md +541 -0
- package/reference/ssr.md +234 -0
- package/reference/state-data.md +215 -0
- package/reference/state-patterns.md +166 -0
- package/reference/state.md +194 -0
- package/reference/style-system.md +110 -0
- package/reference/tokens.md +460 -0
- package/src/app.js +19 -0
- package/src/chart/_animate.js +266 -0
- package/src/chart/_base.js +109 -0
- package/src/chart/_data.js +209 -0
- package/src/chart/_format.js +106 -0
- package/src/chart/_interact.js +364 -0
- package/src/chart/_palette.js +105 -0
- package/src/chart/_renderer.js +52 -0
- package/src/chart/_scene.js +262 -0
- package/src/chart/_shared.js +371 -0
- package/src/chart/index.js +637 -0
- package/src/chart/layouts/_layout-base.js +328 -0
- package/src/chart/layouts/cartesian.js +148 -0
- package/src/chart/layouts/hierarchy.js +562 -0
- package/src/chart/layouts/polar.js +101 -0
- package/src/chart/renderers/canvas.js +179 -0
- package/src/chart/renderers/svg.js +256 -0
- package/src/chart/renderers/webgpu.js +715 -0
- package/src/chart/types/_type-base.js +26 -0
- package/src/chart/types/area.js +134 -0
- package/src/chart/types/bar.js +173 -0
- package/src/chart/types/box-plot.js +125 -0
- package/src/chart/types/bubble.js +63 -0
- package/src/chart/types/candlestick.js +115 -0
- package/src/chart/types/chord.js +85 -0
- package/src/chart/types/combination.js +108 -0
- package/src/chart/types/funnel.js +68 -0
- package/src/chart/types/gauge.js +163 -0
- package/src/chart/types/heatmap.js +98 -0
- package/src/chart/types/histogram.js +71 -0
- package/src/chart/types/line.js +111 -0
- package/src/chart/types/org-chart.js +93 -0
- package/src/chart/types/pie.js +81 -0
- package/src/chart/types/radar.js +96 -0
- package/src/chart/types/radial.js +68 -0
- package/src/chart/types/range-area.js +55 -0
- package/src/chart/types/range-bar.js +61 -0
- package/src/chart/types/sankey.js +73 -0
- package/src/chart/types/scatter.js +66 -0
- package/src/chart/types/sparkline.js +81 -0
- package/src/chart/types/sunburst.js +69 -0
- package/src/chart/types/swimlane.js +88 -0
- package/src/chart/types/treemap.js +62 -0
- package/src/chart/types/waterfall.js +100 -0
- package/src/components/_base.js +1658 -0
- package/src/components/_behaviors.js +1140 -0
- package/src/components/_primitives.js +534 -0
- package/src/components/_qr-encoder.js +539 -0
- package/src/components/accordion.js +207 -0
- package/src/components/affix.js +62 -0
- package/src/components/alert-dialog.js +75 -0
- package/src/components/alert.js +47 -0
- package/src/components/aspect-ratio.js +24 -0
- package/src/components/avatar-group.js +55 -0
- package/src/components/avatar.js +38 -0
- package/src/components/back-top.js +75 -0
- package/src/components/badge.js +74 -0
- package/src/components/banner.js +68 -0
- package/src/components/breadcrumb.js +162 -0
- package/src/components/button.js +115 -0
- package/src/components/calendar.js +131 -0
- package/src/components/card.js +192 -0
- package/src/components/carousel.js +98 -0
- package/src/components/cascader.js +261 -0
- package/src/components/checkbox.js +80 -0
- package/src/components/chip.js +81 -0
- package/src/components/code-block.js +82 -0
- package/src/components/collapsible.js +50 -0
- package/src/components/color-palette.js +438 -0
- package/src/components/color-picker.js +314 -0
- package/src/components/combobox.js +181 -0
- package/src/components/command.js +174 -0
- package/src/components/comment.js +206 -0
- package/src/components/context-menu.js +76 -0
- package/src/components/data-table.js +724 -0
- package/src/components/date-picker.js +217 -0
- package/src/components/date-range-picker.js +244 -0
- package/src/components/datetime-picker.js +271 -0
- package/src/components/descriptions.js +68 -0
- package/src/components/drawer.js +179 -0
- package/src/components/dropdown.js +88 -0
- package/src/components/empty.js +41 -0
- package/src/components/float-button.js +90 -0
- package/src/components/form.js +106 -0
- package/src/components/hover-card.js +49 -0
- package/src/components/icon.js +87 -0
- package/src/components/image.js +97 -0
- package/src/components/index.js +117 -0
- package/src/components/input-group.js +75 -0
- package/src/components/input-number.js +155 -0
- package/src/components/input-otp.js +178 -0
- package/src/components/input.js +91 -0
- package/src/components/kbd.js +36 -0
- package/src/components/label.js +25 -0
- package/src/components/list.js +118 -0
- package/src/components/masked-input.js +236 -0
- package/src/components/mentions.js +165 -0
- package/src/components/menu.js +259 -0
- package/src/components/message.js +80 -0
- package/src/components/modal.js +147 -0
- package/src/components/navigation-menu.js +166 -0
- package/src/components/notification.js +84 -0
- package/src/components/pagination.js +104 -0
- package/src/components/placeholder.js +132 -0
- package/src/components/popconfirm.js +70 -0
- package/src/components/popover.js +58 -0
- package/src/components/progress.js +61 -0
- package/src/components/qrcode.js +251 -0
- package/src/components/radiogroup.js +120 -0
- package/src/components/range-slider.js +176 -0
- package/src/components/rate.js +186 -0
- package/src/components/resizable.js +83 -0
- package/src/components/result.js +57 -0
- package/src/components/scroll-area.js +43 -0
- package/src/components/segmented.js +97 -0
- package/src/components/select.js +165 -0
- package/src/components/separator.js +31 -0
- package/src/components/shell.js +407 -0
- package/src/components/skeleton.js +39 -0
- package/src/components/slider.js +141 -0
- package/src/components/sortable-list.js +176 -0
- package/src/components/space.js +42 -0
- package/src/components/spinner.js +112 -0
- package/src/components/splitter.js +147 -0
- package/src/components/statistic.js +136 -0
- package/src/components/steps.js +99 -0
- package/src/components/switch.js +95 -0
- package/src/components/table.js +44 -0
- package/src/components/tabs.js +216 -0
- package/src/components/tag.js +115 -0
- package/src/components/textarea.js +82 -0
- package/src/components/time-picker.js +153 -0
- package/src/components/time-range-picker.js +170 -0
- package/src/components/timeline.js +226 -0
- package/src/components/toast.js +71 -0
- package/src/components/toggle.js +213 -0
- package/src/components/tooltip.js +57 -0
- package/src/components/tour.js +159 -0
- package/src/components/transfer.js +163 -0
- package/src/components/tree-select.js +274 -0
- package/src/components/tree.js +141 -0
- package/src/components/typography.js +136 -0
- package/src/components/upload.js +118 -0
- package/src/components/visually-hidden.js +20 -0
- package/src/components/watermark.js +124 -0
- package/src/core/index.js +539 -0
- package/src/core/lifecycle.js +69 -0
- package/src/css/atoms.js +651 -0
- package/src/css/components.js +940 -0
- package/src/css/derive.js +1296 -0
- package/src/css/index.js +265 -0
- package/src/css/runtime.js +268 -0
- package/src/css/styles/addons/bioluminescent.js +93 -0
- package/src/css/styles/addons/clay.js +70 -0
- package/src/css/styles/addons/clean.js +57 -0
- package/src/css/styles/addons/command-center.js +143 -0
- package/src/css/styles/addons/dopamine.js +83 -0
- package/src/css/styles/addons/editorial.js +80 -0
- package/src/css/styles/addons/glassmorphism.js +99 -0
- package/src/css/styles/addons/liquid-glass.js +105 -0
- package/src/css/styles/addons/prismatic.js +100 -0
- package/src/css/styles/addons/retro.js +63 -0
- package/src/css/styles/auradecantism.js +96 -0
- package/src/css/theme-registry.js +444 -0
- package/src/data/entity.js +281 -0
- package/src/data/index.js +13 -0
- package/src/data/persist.js +225 -0
- package/src/data/query.js +839 -0
- package/src/data/realtime.js +299 -0
- package/src/data/url.js +177 -0
- package/src/data/worker.js +134 -0
- package/src/explorer/archetypes.js +243 -0
- package/src/explorer/atoms.js +228 -0
- package/src/explorer/charts.js +497 -0
- package/src/explorer/components.js +129 -0
- package/src/explorer/foundations.js +949 -0
- package/src/explorer/icons.js +178 -0
- package/src/explorer/patterns.js +247 -0
- package/src/explorer/recipes.js +194 -0
- package/src/explorer/shared/pattern-examples.js +1337 -0
- package/src/explorer/shared/showcase-renderer.js +958 -0
- package/src/explorer/shared/spec-table.js +41 -0
- package/src/explorer/shared/usage-links.js +87 -0
- package/src/explorer/shell-config.js +10 -0
- package/src/explorer/shells.js +551 -0
- package/src/explorer/styles.js +161 -0
- package/src/explorer/tokens.js +262 -0
- package/src/explorer/tools.js +525 -0
- package/src/form/index.js +804 -0
- package/src/i18n/index.js +251 -0
- package/src/icons/essential.js +479 -0
- package/src/icons/index.js +53 -0
- package/src/plugins/index.js +282 -0
- package/src/registry/archetypes/content-site.json +71 -0
- package/src/registry/archetypes/docs-explorer.json +23 -0
- package/src/registry/archetypes/ecommerce.json +104 -0
- package/src/registry/archetypes/financial-dashboard.json +77 -0
- package/src/registry/archetypes/index.json +41 -0
- package/src/registry/archetypes/portfolio.json +82 -0
- package/src/registry/archetypes/recipe-community.json +159 -0
- package/src/registry/archetypes/saas-dashboard.json +86 -0
- package/src/registry/architect/cross-cutting.json +45 -0
- package/src/registry/architect/domains/ecommerce.json +294 -0
- package/src/registry/architect/domains/financial-services.json +302 -0
- package/src/registry/architect/index.json +26 -0
- package/src/registry/architect/traits.json +379 -0
- package/src/registry/atoms.json +16 -0
- package/src/registry/chart-showcase.json +160 -0
- package/src/registry/chart.json +136 -0
- package/src/registry/components.json +8616 -0
- package/src/registry/core.json +216 -0
- package/src/registry/css.json +319 -0
- package/src/registry/data.json +135 -0
- package/src/registry/foundations.json +11 -0
- package/src/registry/icons.json +463 -0
- package/src/registry/index.json +101 -0
- package/src/registry/patterns/activity-feed.json +37 -0
- package/src/registry/patterns/article-content.json +27 -0
- package/src/registry/patterns/auth-form.json +37 -0
- package/src/registry/patterns/author-card.json +20 -0
- package/src/registry/patterns/card-grid.json +127 -0
- package/src/registry/patterns/category-nav.json +26 -0
- package/src/registry/patterns/chart-grid.json +36 -0
- package/src/registry/patterns/chat-interface.json +37 -0
- package/src/registry/patterns/checklist-card.json +55 -0
- package/src/registry/patterns/comparison-panel.json +27 -0
- package/src/registry/patterns/component-showcase.json +24 -0
- package/src/registry/patterns/contact-form.json +31 -0
- package/src/registry/patterns/cta-section.json +20 -0
- package/src/registry/patterns/data-table.json +37 -0
- package/src/registry/patterns/detail-header.json +83 -0
- package/src/registry/patterns/detail-panel.json +27 -0
- package/src/registry/patterns/explorer-shell.json +22 -0
- package/src/registry/patterns/filter-bar.json +33 -0
- package/src/registry/patterns/filter-sidebar.json +27 -0
- package/src/registry/patterns/form-sections.json +110 -0
- package/src/registry/patterns/goal-tracker.json +27 -0
- package/src/registry/patterns/hero.json +107 -0
- package/src/registry/patterns/index.json +47 -0
- package/src/registry/patterns/kpi-grid.json +36 -0
- package/src/registry/patterns/media-gallery.json +20 -0
- package/src/registry/patterns/order-history.json +20 -0
- package/src/registry/patterns/pagination.json +19 -0
- package/src/registry/patterns/photo-to-recipe.json +36 -0
- package/src/registry/patterns/pipeline-tracker.json +28 -0
- package/src/registry/patterns/post-list.json +27 -0
- package/src/registry/patterns/pricing-table.json +32 -0
- package/src/registry/patterns/scorecard.json +28 -0
- package/src/registry/patterns/search-bar.json +20 -0
- package/src/registry/patterns/specimen-grid.json +19 -0
- package/src/registry/patterns/stat-card.json +55 -0
- package/src/registry/patterns/stats-bar.json +55 -0
- package/src/registry/patterns/steps-card.json +55 -0
- package/src/registry/patterns/table-of-contents.json +19 -0
- package/src/registry/patterns/testimonials.json +21 -0
- package/src/registry/patterns/timeline.json +27 -0
- package/src/registry/patterns/token-inspector.json +21 -0
- package/src/registry/patterns/wizard.json +27 -0
- package/src/registry/recipe-auradecantism.json +69 -0
- package/src/registry/recipe-clean.json +65 -0
- package/src/registry/recipe-command-center.json +78 -0
- package/src/registry/router.json +73 -0
- package/src/registry/schema/README.md +197 -0
- package/src/registry/skeletons.json +259 -0
- package/src/registry/state.json +137 -0
- package/src/registry/tokens.json +40 -0
- package/src/router/hash.js +17 -0
- package/src/router/history.js +18 -0
- package/src/router/index.js +598 -0
- package/src/ssr/index.js +922 -0
- package/src/state/arrays.js +181 -0
- package/src/state/devtools.js +647 -0
- package/src/state/index.js +498 -0
- package/src/state/middleware.js +288 -0
- package/src/state/scheduler.js +206 -0
- package/src/state/store.js +300 -0
- package/src/tags/index.js +19 -0
- package/src/tannins/auth.js +396 -0
- package/src/test/dom.js +352 -0
- package/src/test/index.js +62 -0
- package/src/test/state.js +306 -0
- package/tools/a11y-audit.js +487 -0
- package/tools/analyzer.js +315 -0
- package/tools/audit.js +706 -0
- package/tools/builder.js +1422 -0
- package/tools/css-extract.js +188 -0
- package/tools/dev-server.js +316 -0
- package/tools/dts-gen.js +1260 -0
- package/tools/figma-components.js +329 -0
- package/tools/figma-patterns.js +516 -0
- package/tools/figma-plugin/code.js +453 -0
- package/tools/figma-plugin/manifest.json +14 -0
- package/tools/figma-plugin/ui.html +268 -0
- package/tools/figma-render.js +293 -0
- package/tools/figma-tokens.js +712 -0
- package/tools/figma-upload.js +318 -0
- package/tools/generate.js +738 -0
- package/tools/icons.js +133 -0
- package/tools/init-templates.js +265 -0
- package/tools/install-hooks.sh +5 -0
- package/tools/migrations/0.5.0.js +53 -0
- package/tools/migrations/0.6.0.js +95 -0
- package/tools/minify.js +170 -0
- package/tools/pre-commit +4 -0
- package/tools/registry.js +662 -0
- package/tools/reset-playground.js +61 -0
- package/tools/starter-templates/content-site/app.js +49 -0
- package/tools/starter-templates/content-site/essence.js +19 -0
- package/tools/starter-templates/content-site/pages.js +31 -0
- package/tools/starter-templates/ecommerce/app.js +50 -0
- package/tools/starter-templates/ecommerce/essence.js +19 -0
- package/tools/starter-templates/ecommerce/pages.js +31 -0
- package/tools/starter-templates/landing-page/app.js +38 -0
- package/tools/starter-templates/landing-page/essence.js +18 -0
- package/tools/starter-templates/landing-page/pages.js +21 -0
- package/tools/starter-templates/portfolio/app.js +45 -0
- package/tools/starter-templates/portfolio/essence.js +19 -0
- package/tools/starter-templates/portfolio/pages.js +33 -0
- package/tools/starter-templates/saas-dashboard/app.js +70 -0
- package/tools/starter-templates/saas-dashboard/essence.js +19 -0
- package/tools/starter-templates/saas-dashboard/pages.js +31 -0
- package/tools/verify-pack.js +203 -0
- package/types/chart.d.ts +77 -0
- package/types/components.d.ts +587 -0
- package/types/core.d.ts +89 -0
- package/types/css.d.ts +149 -0
- package/types/data.d.ts +238 -0
- package/types/form.d.ts +164 -0
- package/types/i18n.d.ts +51 -0
- package/types/icons.d.ts +27 -0
- package/types/index.d.ts +13 -0
- package/types/router.d.ts +116 -0
- package/types/ssr.d.ts +102 -0
- package/types/state.d.ts +83 -0
- package/types/tags.d.ts +62 -0
- package/types/tannins.d.ts +63 -0
- package/types/test.d.ts +48 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene Graph — renderer-agnostic intermediate representation.
|
|
3
|
+
* Plain objects with `type` discriminant. Serializable, diffable, minimal.
|
|
4
|
+
* All layout functions produce scene nodes; renderers consume them.
|
|
5
|
+
* @module _scene
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Root scene node — container for all chart elements.
|
|
10
|
+
* @param {number} width
|
|
11
|
+
* @param {number} height
|
|
12
|
+
* @param {Object[]} children
|
|
13
|
+
* @param {Object} [meta] — chart-level metadata (type, margins, scales)
|
|
14
|
+
* @returns {Object}
|
|
15
|
+
*/
|
|
16
|
+
export function scene(width, height, children, meta) {
|
|
17
|
+
return { type: 'scene', width, height, children: children || [], meta: meta || {} };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Group node — logical grouping with optional transform.
|
|
22
|
+
* @param {Object} attrs — { transform, class, clip, opacity }
|
|
23
|
+
* @param {Object[]} children
|
|
24
|
+
* @returns {Object}
|
|
25
|
+
*/
|
|
26
|
+
export function group(attrs, children) {
|
|
27
|
+
return { type: 'group', ...attrs, children: children || [] };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Path node — arbitrary SVG path data.
|
|
32
|
+
* @param {Object} attrs — { d, fill, stroke, strokeWidth, strokeDash, strokeLinecap, strokeLinejoin, class, data, key, opacity }
|
|
33
|
+
* @returns {Object}
|
|
34
|
+
*/
|
|
35
|
+
export function path(attrs) {
|
|
36
|
+
return { type: 'path', ...attrs };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Rectangle node.
|
|
41
|
+
* @param {Object} attrs — { x, y, w, h, rx, ry, fill, stroke, strokeWidth, class, data, key, opacity }
|
|
42
|
+
* @returns {Object}
|
|
43
|
+
*/
|
|
44
|
+
export function rect(attrs) {
|
|
45
|
+
return { type: 'rect', ...attrs };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Circle node.
|
|
50
|
+
* @param {Object} attrs — { cx, cy, r, fill, stroke, strokeWidth, class, data, key, opacity }
|
|
51
|
+
* @returns {Object}
|
|
52
|
+
*/
|
|
53
|
+
export function circle(attrs) {
|
|
54
|
+
return { type: 'circle', ...attrs };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Text node.
|
|
59
|
+
* @param {Object} attrs — { x, y, content, anchor, baseline, fill, class, fontSize, fontWeight, rotate }
|
|
60
|
+
* @returns {Object}
|
|
61
|
+
*/
|
|
62
|
+
export function text(attrs) {
|
|
63
|
+
return { type: 'text', ...attrs };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Line node.
|
|
68
|
+
* @param {Object} attrs — { x1, y1, x2, y2, stroke, strokeWidth, strokeDash, class }
|
|
69
|
+
* @returns {Object}
|
|
70
|
+
*/
|
|
71
|
+
export function line(attrs) {
|
|
72
|
+
return { type: 'line', ...attrs };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Arc node — for pie/donut/gauge/radial charts.
|
|
77
|
+
* @param {Object} attrs — { cx, cy, innerR, outerR, startAngle, endAngle, fill, stroke, class, data, key, opacity }
|
|
78
|
+
* @returns {Object}
|
|
79
|
+
*/
|
|
80
|
+
export function arc(attrs) {
|
|
81
|
+
return { type: 'arc', ...attrs };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Polygon node — closed shape from point array.
|
|
86
|
+
* @param {Object} attrs — { points: [{x,y}], fill, stroke, strokeWidth, class, data, key, opacity }
|
|
87
|
+
* @returns {Object}
|
|
88
|
+
*/
|
|
89
|
+
export function polygon(attrs) {
|
|
90
|
+
return { type: 'polygon', ...attrs };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Image node — for embedding icons/images in charts.
|
|
95
|
+
* @param {Object} attrs — { x, y, w, h, href, class, data, key }
|
|
96
|
+
* @returns {Object}
|
|
97
|
+
*/
|
|
98
|
+
export function image(attrs) {
|
|
99
|
+
return { type: 'image', ...attrs };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// --- Utility builders ---
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build axis tick scene nodes.
|
|
106
|
+
* @param {Array} ticks — [{ value, position, label }]
|
|
107
|
+
* @param {'x'|'y'} axis
|
|
108
|
+
* @param {number} innerW
|
|
109
|
+
* @param {number} innerH
|
|
110
|
+
* @returns {Object[]} scene nodes
|
|
111
|
+
*/
|
|
112
|
+
export function axisTicks(ticks, axis, innerW, innerH) {
|
|
113
|
+
const nodes = [];
|
|
114
|
+
if (axis === 'x') {
|
|
115
|
+
for (const t of ticks) {
|
|
116
|
+
nodes.push(line({ x1: t.position, y1: innerH, x2: t.position, y2: innerH + 4, stroke: 'var(--d-border)', class: 'd-chart-tick' }));
|
|
117
|
+
nodes.push(text({ x: t.position, y: innerH + 18, content: t.label, anchor: 'middle', class: 'd-chart-axis' }));
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
for (const t of ticks) {
|
|
121
|
+
nodes.push(line({ x1: -4, y1: t.position, x2: 0, y2: t.position, stroke: 'var(--d-border)', class: 'd-chart-tick' }));
|
|
122
|
+
nodes.push(text({ x: -8, y: t.position + 4, content: t.label, anchor: 'end', class: 'd-chart-axis' }));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return nodes;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Build grid line scene nodes from Y ticks.
|
|
130
|
+
* @param {Array} ticks — [{ position }]
|
|
131
|
+
* @param {number} innerW
|
|
132
|
+
* @returns {Object[]}
|
|
133
|
+
*/
|
|
134
|
+
export function gridLines(ticks, innerW) {
|
|
135
|
+
return ticks.map(t => line({ x1: 0, y1: t.position, x2: innerW, y2: t.position, class: 'd-chart-grid' }));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Convert arc params to SVG path data string.
|
|
140
|
+
* @param {number} cx
|
|
141
|
+
* @param {number} cy
|
|
142
|
+
* @param {number} outerR
|
|
143
|
+
* @param {number} innerR
|
|
144
|
+
* @param {number} startAngle
|
|
145
|
+
* @param {number} endAngle
|
|
146
|
+
* @returns {string}
|
|
147
|
+
*/
|
|
148
|
+
export function arcToPath(cx, cy, outerR, innerR, startAngle, endAngle) {
|
|
149
|
+
const x1 = cx + outerR * Math.cos(startAngle);
|
|
150
|
+
const y1 = cy + outerR * Math.sin(startAngle);
|
|
151
|
+
const x2 = cx + outerR * Math.cos(endAngle);
|
|
152
|
+
const y2 = cy + outerR * Math.sin(endAngle);
|
|
153
|
+
const largeArc = endAngle - startAngle > Math.PI ? 1 : 0;
|
|
154
|
+
|
|
155
|
+
let d = `M${x1},${y1}A${outerR},${outerR},0,${largeArc},1,${x2},${y2}`;
|
|
156
|
+
|
|
157
|
+
if (innerR > 0) {
|
|
158
|
+
const x3 = cx + innerR * Math.cos(endAngle);
|
|
159
|
+
const y3 = cy + innerR * Math.sin(endAngle);
|
|
160
|
+
const x4 = cx + innerR * Math.cos(startAngle);
|
|
161
|
+
const y4 = cy + innerR * Math.sin(startAngle);
|
|
162
|
+
d += `L${x3},${y3}A${innerR},${innerR},0,${largeArc},0,${x4},${y4}Z`;
|
|
163
|
+
} else {
|
|
164
|
+
d += `L${cx},${cy}Z`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return d;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Convert points array to SVG path data string (M/L commands).
|
|
172
|
+
* @param {{x:number, y:number}[]} points
|
|
173
|
+
* @returns {string}
|
|
174
|
+
*/
|
|
175
|
+
export function pointsToPathD(points) {
|
|
176
|
+
if (!points.length) return '';
|
|
177
|
+
let d = `M${points[0].x},${points[0].y}`;
|
|
178
|
+
for (let i = 1; i < points.length; i++) d += `L${points[i].x},${points[i].y}`;
|
|
179
|
+
return d;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Convert points to smooth Catmull-Rom spline path.
|
|
184
|
+
* @param {{x:number, y:number}[]} points
|
|
185
|
+
* @param {number} [tension=0.5]
|
|
186
|
+
* @returns {string}
|
|
187
|
+
*/
|
|
188
|
+
export function smoothPathD(points, tension = 0.5) {
|
|
189
|
+
if (points.length < 2) return pointsToPathD(points);
|
|
190
|
+
if (points.length === 2) return pointsToPathD(points);
|
|
191
|
+
|
|
192
|
+
let d = `M${points[0].x},${points[0].y}`;
|
|
193
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
194
|
+
const p0 = points[Math.max(0, i - 1)];
|
|
195
|
+
const p1 = points[i];
|
|
196
|
+
const p2 = points[i + 1];
|
|
197
|
+
const p3 = points[Math.min(points.length - 1, i + 2)];
|
|
198
|
+
|
|
199
|
+
const cp1x = p1.x + (p2.x - p0.x) / 6 * tension;
|
|
200
|
+
const cp1y = p1.y + (p2.y - p0.y) / 6 * tension;
|
|
201
|
+
const cp2x = p2.x - (p3.x - p1.x) / 6 * tension;
|
|
202
|
+
const cp2y = p2.y - (p3.y - p1.y) / 6 * tension;
|
|
203
|
+
|
|
204
|
+
d += `C${cp1x},${cp1y},${cp2x},${cp2y},${p2.x},${p2.y}`;
|
|
205
|
+
}
|
|
206
|
+
return d;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Convert area points (with y0/y1) to closed polygon path data.
|
|
211
|
+
* @param {{x:number, y0:number, y1:number}[]} points — y0=top, y1=bottom
|
|
212
|
+
* @returns {string}
|
|
213
|
+
*/
|
|
214
|
+
export function areaPathD(points) {
|
|
215
|
+
if (!points.length) return '';
|
|
216
|
+
let d = `M${points[0].x},${points[0].y0}`;
|
|
217
|
+
for (let i = 1; i < points.length; i++) d += `L${points[i].x},${points[i].y0}`;
|
|
218
|
+
for (let i = points.length - 1; i >= 0; i--) d += `L${points[i].x},${points[i].y1}`;
|
|
219
|
+
d += 'Z';
|
|
220
|
+
return d;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Convert area points to smooth closed polygon path.
|
|
225
|
+
* Top edge uses Catmull-Rom spline, bottom edge stays straight.
|
|
226
|
+
* @param {{x:number, y0:number, y1:number}[]} points — y0=top, y1=bottom
|
|
227
|
+
* @param {number} [tension=0.5]
|
|
228
|
+
* @returns {string}
|
|
229
|
+
*/
|
|
230
|
+
export function smoothAreaPathD(points, tension = 0.5) {
|
|
231
|
+
if (!points.length) return '';
|
|
232
|
+
// Top edge — smooth Catmull-Rom
|
|
233
|
+
const topPts = points.map(p => ({ x: p.x, y: p.y0 }));
|
|
234
|
+
let d = smoothPathD(topPts, tension);
|
|
235
|
+
// Bottom edge — straight, reversed
|
|
236
|
+
for (let i = points.length - 1; i >= 0; i--) d += `L${points[i].x},${points[i].y1}`;
|
|
237
|
+
d += 'Z';
|
|
238
|
+
return d;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Gradient scene node — for SVG <linearGradient> definitions.
|
|
243
|
+
* @param {Object} attrs — { id, x1, y1, x2, y2, stops: [{ offset, color, opacity }] }
|
|
244
|
+
* @returns {Object}
|
|
245
|
+
*/
|
|
246
|
+
export function gradient(attrs) {
|
|
247
|
+
return { type: 'gradient', ...attrs };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Step path (horizontal-first, for step line charts).
|
|
252
|
+
* @param {{x:number, y:number}[]} points
|
|
253
|
+
* @returns {string}
|
|
254
|
+
*/
|
|
255
|
+
export function stepPathD(points) {
|
|
256
|
+
if (points.length < 2) return pointsToPathD(points);
|
|
257
|
+
let d = `M${points[0].x},${points[0].y}`;
|
|
258
|
+
for (let i = 1; i < points.length; i++) {
|
|
259
|
+
d += `H${points[i].x}V${points[i].y}`;
|
|
260
|
+
}
|
|
261
|
+
return d;
|
|
262
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { createEffect } from '../state/index.js';
|
|
2
|
+
import { getTheme } from '../css/theme-registry.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a prop that may be a signal getter or a static value.
|
|
6
|
+
* @template T
|
|
7
|
+
* @param {T|Function} prop
|
|
8
|
+
* @returns {T}
|
|
9
|
+
*/
|
|
10
|
+
export function resolve(prop) {
|
|
11
|
+
return typeof prop === 'function' ? prop() : prop;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// --- Scales ---
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Linear scale: maps [domainMin, domainMax] → [rangeMin, rangeMax].
|
|
18
|
+
* @param {number[]} domain — [min, max]
|
|
19
|
+
* @param {number[]} range — [min, max] in pixels
|
|
20
|
+
* @returns {{ (v: number) => number, ticks: (count?: number) => number[], invert: (px: number) => number }}
|
|
21
|
+
*/
|
|
22
|
+
export function scaleLinear(domain, range) {
|
|
23
|
+
const [d0, d1] = domain;
|
|
24
|
+
const [r0, r1] = range;
|
|
25
|
+
const span = d1 - d0 || 1;
|
|
26
|
+
const rSpan = r1 - r0;
|
|
27
|
+
|
|
28
|
+
function scale(v) {
|
|
29
|
+
return r0 + ((v - d0) / span) * rSpan;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
scale.invert = function (px) {
|
|
33
|
+
return d0 + ((px - r0) / rSpan) * span;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
scale.ticks = function (count = 5) {
|
|
37
|
+
const step = niceStep(span / count);
|
|
38
|
+
const start = Math.ceil(d0 / step) * step;
|
|
39
|
+
const result = [];
|
|
40
|
+
for (let v = start; v <= d1; v += step) {
|
|
41
|
+
result.push(+v.toPrecision(12));
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return scale;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Band scale: maps discrete values → pixel bands with padding.
|
|
51
|
+
* @param {any[]} domain — discrete values
|
|
52
|
+
* @param {number[]} range — [min, max] in pixels
|
|
53
|
+
* @param {number} padding — 0..1, space between bands
|
|
54
|
+
* @returns {{ (v: any) => number, bandwidth: () => number }}
|
|
55
|
+
*/
|
|
56
|
+
export function scaleBand(domain, range, padding = 0.2) {
|
|
57
|
+
const [r0, r1] = range;
|
|
58
|
+
const n = domain.length || 1;
|
|
59
|
+
const totalPad = padding * (n + 1);
|
|
60
|
+
const bandWidth = (r1 - r0) / (n + totalPad);
|
|
61
|
+
const step = bandWidth * (1 + padding);
|
|
62
|
+
const offset = bandWidth * padding;
|
|
63
|
+
const map = new Map();
|
|
64
|
+
for (let i = 0; i < domain.length; i++) {
|
|
65
|
+
map.set(domain[i], r0 + offset + i * step);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function scale(v) {
|
|
69
|
+
return map.get(v) ?? r0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
scale.bandwidth = function () {
|
|
73
|
+
return bandWidth;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return scale;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Time scale: maps Date values → pixel positions.
|
|
81
|
+
* @param {Date[]} domain — [min, max] dates
|
|
82
|
+
* @param {number[]} range — [min, max] in pixels
|
|
83
|
+
* @returns {{ (v: Date|number) => number, ticks: (count?: number) => Date[] }}
|
|
84
|
+
*/
|
|
85
|
+
export function scaleTime(domain, range) {
|
|
86
|
+
const d0 = +domain[0];
|
|
87
|
+
const d1 = +domain[1];
|
|
88
|
+
const inner = scaleLinear([d0, d1], range);
|
|
89
|
+
|
|
90
|
+
function scale(v) {
|
|
91
|
+
return inner(+v);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
scale.invert = function (px) {
|
|
95
|
+
return new Date(inner.invert(px));
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
scale.ticks = function (count = 5) {
|
|
99
|
+
return inner.ticks(count).map(t => new Date(t));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return scale;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --- Extended scales ---
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Logarithmic scale.
|
|
109
|
+
* @param {number[]} domain
|
|
110
|
+
* @param {number[]} range
|
|
111
|
+
* @param {number} [base=10]
|
|
112
|
+
* @returns {Function}
|
|
113
|
+
*/
|
|
114
|
+
export function scaleLog(domain, range, base = 10) {
|
|
115
|
+
const [d0, d1] = domain;
|
|
116
|
+
const [r0, r1] = range;
|
|
117
|
+
const logBase = Math.log(base);
|
|
118
|
+
const ld0 = Math.log(Math.max(1e-10, d0)) / logBase;
|
|
119
|
+
const ld1 = Math.log(Math.max(1e-10, d1)) / logBase;
|
|
120
|
+
const span = ld1 - ld0 || 1;
|
|
121
|
+
const rSpan = r1 - r0;
|
|
122
|
+
|
|
123
|
+
function scale(v) {
|
|
124
|
+
const lv = Math.log(Math.max(1e-10, v)) / logBase;
|
|
125
|
+
return r0 + ((lv - ld0) / span) * rSpan;
|
|
126
|
+
}
|
|
127
|
+
scale.invert = px => Math.pow(base, ld0 + ((px - r0) / rSpan) * span);
|
|
128
|
+
scale.ticks = (count = 5) => {
|
|
129
|
+
const result = [];
|
|
130
|
+
const start = Math.ceil(ld0);
|
|
131
|
+
const end = Math.floor(ld1);
|
|
132
|
+
for (let i = start; i <= end; i++) result.push(Math.pow(base, i));
|
|
133
|
+
return result;
|
|
134
|
+
};
|
|
135
|
+
return scale;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Square root scale.
|
|
140
|
+
* @param {number[]} domain
|
|
141
|
+
* @param {number[]} range
|
|
142
|
+
* @returns {Function}
|
|
143
|
+
*/
|
|
144
|
+
export function scaleSqrt(domain, range) {
|
|
145
|
+
const [d0, d1] = domain;
|
|
146
|
+
const [r0, r1] = range;
|
|
147
|
+
const sd0 = Math.sqrt(Math.max(0, d0));
|
|
148
|
+
const sd1 = Math.sqrt(Math.max(0, d1));
|
|
149
|
+
const span = sd1 - sd0 || 1;
|
|
150
|
+
const rSpan = r1 - r0;
|
|
151
|
+
|
|
152
|
+
function scale(v) {
|
|
153
|
+
const sv = Math.sqrt(Math.max(0, v));
|
|
154
|
+
return r0 + ((sv - sd0) / span) * rSpan;
|
|
155
|
+
}
|
|
156
|
+
scale.invert = px => Math.pow(sd0 + ((px - r0) / rSpan) * span, 2);
|
|
157
|
+
scale.ticks = (count = 5) => scaleLinear(domain, range).ticks(count);
|
|
158
|
+
return scale;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Point scale — discrete values → evenly spaced points.
|
|
163
|
+
* @param {any[]} domain
|
|
164
|
+
* @param {number[]} range
|
|
165
|
+
* @param {number} [padding=0.5]
|
|
166
|
+
* @returns {Function}
|
|
167
|
+
*/
|
|
168
|
+
export function scalePoint(domain, range, padding = 0.5) {
|
|
169
|
+
const [r0, r1] = range;
|
|
170
|
+
const n = domain.length || 1;
|
|
171
|
+
const step = (r1 - r0) / (n - 1 + padding * 2) || 0;
|
|
172
|
+
const offset = r0 + step * padding;
|
|
173
|
+
const map = new Map();
|
|
174
|
+
for (let i = 0; i < domain.length; i++) {
|
|
175
|
+
map.set(domain[i], offset + i * step);
|
|
176
|
+
}
|
|
177
|
+
function scale(v) { return map.get(v) ?? r0; }
|
|
178
|
+
scale.step = () => step;
|
|
179
|
+
return scale;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Ordinal scale — discrete → discrete mapping.
|
|
184
|
+
* @param {any[]} domain
|
|
185
|
+
* @param {any[]} range
|
|
186
|
+
* @returns {Function}
|
|
187
|
+
*/
|
|
188
|
+
export function scaleOrdinal(domain, range) {
|
|
189
|
+
const map = new Map();
|
|
190
|
+
for (let i = 0; i < domain.length; i++) {
|
|
191
|
+
map.set(domain[i], range[i % range.length]);
|
|
192
|
+
}
|
|
193
|
+
return function scale(v) { return map.get(v) ?? range[0]; };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Diverging scale — maps a domain with a midpoint.
|
|
198
|
+
* @param {number[]} domain — [min, mid, max]
|
|
199
|
+
* @param {any[]} range — [minVal, midVal, maxVal]
|
|
200
|
+
* @returns {Function}
|
|
201
|
+
*/
|
|
202
|
+
export function scaleDiverging(domain, range) {
|
|
203
|
+
const [d0, dMid, d1] = domain;
|
|
204
|
+
const [r0, rMid, r1] = range;
|
|
205
|
+
|
|
206
|
+
return function scale(v) {
|
|
207
|
+
if (v <= dMid) {
|
|
208
|
+
const t = (v - d0) / (dMid - d0 || 1);
|
|
209
|
+
return typeof r0 === 'number' ? r0 + (rMid - r0) * t : r0;
|
|
210
|
+
}
|
|
211
|
+
const t = (v - dMid) / (d1 - dMid || 1);
|
|
212
|
+
return typeof rMid === 'number' ? rMid + (r1 - rMid) * t : rMid;
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// --- Nice step for axis ticks ---
|
|
217
|
+
|
|
218
|
+
function niceStep(rawStep) {
|
|
219
|
+
const mag = Math.pow(10, Math.floor(Math.log10(rawStep)));
|
|
220
|
+
const norm = rawStep / mag;
|
|
221
|
+
let nice;
|
|
222
|
+
if (norm <= 1.5) nice = 1;
|
|
223
|
+
else if (norm <= 3) nice = 2;
|
|
224
|
+
else if (norm <= 7) nice = 5;
|
|
225
|
+
else nice = 10;
|
|
226
|
+
return nice * mag;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// --- Data utilities ---
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Compute extent (min, max) of a numeric field.
|
|
233
|
+
* @param {Object[]} data
|
|
234
|
+
* @param {string} field
|
|
235
|
+
* @returns {[number, number]}
|
|
236
|
+
*/
|
|
237
|
+
export function extent(data, field) {
|
|
238
|
+
let min = Infinity, max = -Infinity;
|
|
239
|
+
for (let i = 0; i < data.length; i++) {
|
|
240
|
+
const v = +data[i][field];
|
|
241
|
+
if (v < min) min = v;
|
|
242
|
+
if (v > max) max = v;
|
|
243
|
+
}
|
|
244
|
+
return [min, max];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get unique values of a field (preserving order).
|
|
249
|
+
* @param {Object[]} data
|
|
250
|
+
* @param {string} field
|
|
251
|
+
* @returns {any[]}
|
|
252
|
+
*/
|
|
253
|
+
export function unique(data, field) {
|
|
254
|
+
const seen = new Set();
|
|
255
|
+
const result = [];
|
|
256
|
+
for (let i = 0; i < data.length; i++) {
|
|
257
|
+
const v = data[i][field];
|
|
258
|
+
if (!seen.has(v)) {
|
|
259
|
+
seen.add(v);
|
|
260
|
+
result.push(v);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Group data by a field value.
|
|
268
|
+
* @param {Object[]} data
|
|
269
|
+
* @param {string} field
|
|
270
|
+
* @returns {Map<any, Object[]>}
|
|
271
|
+
*/
|
|
272
|
+
export function groupBy(data, field) {
|
|
273
|
+
const groups = new Map();
|
|
274
|
+
for (let i = 0; i < data.length; i++) {
|
|
275
|
+
const key = data[i][field];
|
|
276
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
277
|
+
groups.get(key).push(data[i]);
|
|
278
|
+
}
|
|
279
|
+
return groups;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* LTTB downsampling — Largest Triangle Three Buckets.
|
|
284
|
+
* Preserves visual shape of time series for line/area charts.
|
|
285
|
+
* @param {Object[]} data — sorted by x
|
|
286
|
+
* @param {string} xField
|
|
287
|
+
* @param {string} yField
|
|
288
|
+
* @param {number} targetCount — desired output length
|
|
289
|
+
* @returns {Object[]}
|
|
290
|
+
*/
|
|
291
|
+
export function downsampleLTTB(data, xField, yField, targetCount) {
|
|
292
|
+
if (data.length <= targetCount) return data;
|
|
293
|
+
|
|
294
|
+
const result = [data[0]]; // Always keep first
|
|
295
|
+
const bucketSize = (data.length - 2) / (targetCount - 2);
|
|
296
|
+
|
|
297
|
+
let prevIndex = 0;
|
|
298
|
+
for (let i = 1; i < targetCount - 1; i++) {
|
|
299
|
+
const bucketStart = Math.floor((i - 1) * bucketSize) + 1;
|
|
300
|
+
const bucketEnd = Math.min(Math.floor(i * bucketSize) + 1, data.length - 1);
|
|
301
|
+
const nextBucketStart = Math.min(Math.floor(i * bucketSize) + 1, data.length - 1);
|
|
302
|
+
const nextBucketEnd = Math.min(Math.floor((i + 1) * bucketSize) + 1, data.length - 1);
|
|
303
|
+
|
|
304
|
+
// Average of next bucket
|
|
305
|
+
let avgX = 0, avgY = 0, count = 0;
|
|
306
|
+
for (let j = nextBucketStart; j <= nextBucketEnd; j++) {
|
|
307
|
+
avgX += +data[j][xField];
|
|
308
|
+
avgY += +data[j][yField];
|
|
309
|
+
count++;
|
|
310
|
+
}
|
|
311
|
+
avgX /= count;
|
|
312
|
+
avgY /= count;
|
|
313
|
+
|
|
314
|
+
// Find point in current bucket with largest triangle area
|
|
315
|
+
let maxArea = -1;
|
|
316
|
+
let maxIndex = bucketStart;
|
|
317
|
+
const px = +data[prevIndex][xField];
|
|
318
|
+
const py = +data[prevIndex][yField];
|
|
319
|
+
|
|
320
|
+
for (let j = bucketStart; j <= bucketEnd; j++) {
|
|
321
|
+
const area = Math.abs(
|
|
322
|
+
(px - avgX) * (+data[j][yField] - py) -
|
|
323
|
+
(px - +data[j][xField]) * (avgY - py)
|
|
324
|
+
);
|
|
325
|
+
if (area > maxArea) {
|
|
326
|
+
maxArea = area;
|
|
327
|
+
maxIndex = j;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
result.push(data[maxIndex]);
|
|
332
|
+
prevIndex = maxIndex;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
result.push(data[data.length - 1]); // Always keep last
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Pad a numeric extent by a fraction for visual breathing room.
|
|
341
|
+
* @param {[number, number]} ext
|
|
342
|
+
* @param {number} fraction — default 0.05 (5%)
|
|
343
|
+
* @returns {[number, number]}
|
|
344
|
+
*/
|
|
345
|
+
export function padExtent(ext, fraction = 0.05) {
|
|
346
|
+
const span = ext[1] - ext[0] || 1;
|
|
347
|
+
const pad = span * fraction;
|
|
348
|
+
return [ext[0] - pad, ext[1] + pad];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Detect if values look like dates.
|
|
353
|
+
* @param {any[]} values
|
|
354
|
+
* @returns {boolean}
|
|
355
|
+
*/
|
|
356
|
+
export function isDateLike(values) {
|
|
357
|
+
if (!values.length) return false;
|
|
358
|
+
const v = values[0];
|
|
359
|
+
if (v instanceof Date) return true;
|
|
360
|
+
if (typeof v === 'string' && !isNaN(Date.parse(v))) return true;
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Parse a value as a Date if needed.
|
|
366
|
+
* @param {any} v
|
|
367
|
+
* @returns {Date}
|
|
368
|
+
*/
|
|
369
|
+
export function toDate(v) {
|
|
370
|
+
return v instanceof Date ? v : new Date(v);
|
|
371
|
+
}
|