@sveltejs/pancake
Advanced tools
+5
-0
| # @sveltejs/pancake changelog | ||
| ## 0.0.16 | ||
| * Add optional `radius` prop for `<Quadtree>` | ||
| * Expose `width` and `height` on context | ||
| ## 0.0.15 | ||
@@ -4,0 +9,0 @@ |
@@ -10,3 +10,3 @@ <script> | ||
| const { x: x_scale, y: y_scale } = getChartContext(); | ||
| const { x_scale, y_scale } = getChartContext(); | ||
@@ -13,0 +13,0 @@ let style; $: { |
+30
-19
@@ -29,11 +29,6 @@ <script context="module"> | ||
| const pointer = writable({ | ||
| left: undefined, | ||
| top: undefined, | ||
| x: undefined, | ||
| y: undefined | ||
| }); | ||
| const width = writable(); | ||
| const height = writable(); | ||
| const pointer = writable(null); | ||
| // $: console.log({ x: $pointer.x, y: $pointer.y }); | ||
| const handle_mousemove = e => { | ||
@@ -44,8 +39,12 @@ const bcr = chart.getBoundingClientRect(); | ||
| const x = $x_inverse(100 * left / (bcr.right - bcr.left)); | ||
| const y = $y_inverse(100 * top / (bcr.bottom - bcr.top)); | ||
| const x = $x_scale_inverse(100 * left / (bcr.right - bcr.left)); | ||
| const y = $y_scale_inverse(100 * top / (bcr.bottom - bcr.top)); | ||
| pointer.set({ x, y }); | ||
| pointer.set({ x, y, left, top }); | ||
| }; | ||
| const handle_mouseleave = () => { | ||
| pointer.set(null); | ||
| }; | ||
| $: _x1.set(x1); | ||
@@ -56,12 +55,12 @@ $: _y1.set(y1); | ||
| const x = derived([_x1, _x2], ([$x1, $x2]) => { | ||
| const x_scale = derived([_x1, _x2], ([$x1, $x2]) => { | ||
| return yootils.linearScale([$x1, $x2], [0, 100]); | ||
| }); | ||
| const y = derived([_y1, _y2], ([$y1, $y2]) => { | ||
| const y_scale = derived([_y1, _y2], ([$y1, $y2]) => { | ||
| return yootils.linearScale([$y1, $y2], [100, 0]); | ||
| }); | ||
| const x_inverse = derived(x, $x => $x.inverse()); | ||
| const y_inverse = derived(y, $y => $y.inverse()); | ||
| const x_scale_inverse = derived(x_scale, $x_scale => $x_scale.inverse()); | ||
| const y_scale_inverse = derived(y_scale, $y_scale => $y_scale.inverse()); | ||
@@ -73,9 +72,21 @@ setContext(key, { | ||
| y2: _y2, | ||
| x, | ||
| y, | ||
| pointer | ||
| x_scale, | ||
| y_scale, | ||
| x_scale_inverse, | ||
| y_scale_inverse, | ||
| pointer, | ||
| width, | ||
| height | ||
| }); | ||
| </script> | ||
| <div class="pancake-chart" bind:this={chart} on:mousemove={handle_mousemove} class:clip> | ||
| <div | ||
| class="pancake-chart" | ||
| bind:this={chart} | ||
| bind:clientWidth={$width} | ||
| bind:clientHeight={$height} | ||
| on:mousemove={handle_mousemove} | ||
| on:mouseleave={handle_mouseleave} | ||
| class:clip | ||
| > | ||
| <slot></slot> | ||
@@ -82,0 +93,0 @@ </div> |
@@ -10,3 +10,3 @@ <script> | ||
| const { x1, y1, x2, y2, x, y } = getChartContext(); | ||
| const { x1, y1, x2, y2, x_scale, y_scale } = getChartContext(); | ||
@@ -27,4 +27,4 @@ const VERTICAL = {}; | ||
| $: style = orientation === HORIZONTAL | ||
| ? (n, i) => `width: 100%; height: 0; top: ${$y(n, i)}%` | ||
| : (n, i) => `width: 0; height: 100%; left: ${$x(n, i)}%`; | ||
| ? (n, i) => `width: 100%; height: 0; top: ${$y_scale(n, i)}%` | ||
| : (n, i) => `width: 0; height: 100%; left: ${$x_scale(n, i)}%`; | ||
| </script> | ||
@@ -31,0 +31,0 @@ |
| <script> | ||
| import { getChartContext } from './Chart.svelte'; | ||
| const { x: x_scale, y: y_scale } = getChartContext(); | ||
| const { x_scale, y_scale } = getChartContext(); | ||
@@ -6,0 +6,0 @@ export let x; |
| <script> | ||
| import { createEventDispatcher } from 'svelte'; | ||
| import { getChartContext } from './Chart.svelte'; | ||
@@ -9,2 +10,3 @@ import Quadtree from '../utils/Quadtree.mjs' | ||
| export let y = default_y; | ||
| export let radius = Infinity; | ||
@@ -14,8 +16,21 @@ // exposing the prop lets consumers use let: or bind: | ||
| const { pointer } = getChartContext(); | ||
| const { pointer, x_scale, y_scale, x_scale_inverse, y_scale_inverse, width, height } = getChartContext(); | ||
| const dispatch = createEventDispatcher(); | ||
| $: quadtree = new Quadtree(data, x, y); // TODO map data here so we don't need to pass accessors around, and accessors can access index | ||
| $: closest = $pointer.x !== undefined ? quadtree.find($pointer.x, $pointer.y) : null; | ||
| $: quadtree = new Quadtree(data); | ||
| $: quadtree.update(x, y, $x_scale, $y_scale); | ||
| // track reference changes, to trigger updates sparingly | ||
| let prev_closest; | ||
| let next_closest; | ||
| $: next_closest = $pointer !== null | ||
| ? quadtree.find($pointer.left, $pointer.top, $width, $height, radius) | ||
| : null; | ||
| $: if (next_closest !== prev_closest) { | ||
| closest = prev_closest = next_closest; | ||
| } | ||
| </script> | ||
| <slot {closest}></slot> |
@@ -5,3 +5,3 @@ <script> | ||
| const { x: x_scale, y: y_scale } = getChartContext(); | ||
| const { x_scale, y_scale } = getChartContext(); | ||
@@ -8,0 +8,0 @@ export let data; |
| <script> | ||
| import { getChartContext } from './Chart.svelte'; | ||
| const { x: x_scale, y: y_scale } = getChartContext(); | ||
| const { x_scale, y_scale } = getChartContext(); | ||
@@ -6,0 +6,0 @@ export let x; |
@@ -5,3 +5,3 @@ <script> | ||
| const { x: x_scale, y: y_scale } = getChartContext(); | ||
| const { x_scale, y_scale } = getChartContext(); | ||
@@ -8,0 +8,0 @@ export let data; |
@@ -5,3 +5,3 @@ <script> | ||
| const { x: x_scale, y: y_scale } = getChartContext(); | ||
| const { x_scale, y_scale } = getChartContext(); | ||
@@ -8,0 +8,0 @@ export let data; |
+1
-1
| { | ||
| "name": "@sveltejs/pancake", | ||
| "version": "0.0.15", | ||
| "version": "0.0.16", | ||
| "description": "Experimental charting library for Svelte", | ||
@@ -5,0 +5,0 @@ "module": "index.mjs", |
+109
-212
@@ -1,9 +0,3 @@ | ||
| /* | ||
| Adapted from https://github.com/d3/d3-quadtree | ||
| Released under BSD license | ||
| */ | ||
| class Quad { | ||
| constructor(node, x0, y0, x1, y1) { | ||
| this.node = node; | ||
| class Node { | ||
| constructor(x0, y0, x1, y1) { | ||
| this.x0 = x0; | ||
@@ -13,240 +7,143 @@ this.y0 = y0; | ||
| this.y1 = y1; | ||
| } | ||
| } | ||
| this.xm = (x0 + x1) / 2; | ||
| this.ym = (y0 + y1) / 2; | ||
| export default class Quadtree { | ||
| constructor(nodes, _x, _y) { | ||
| this._x = _x; | ||
| this._y = _y; | ||
| this._x0 = NaN; | ||
| this._y0 = NaN; | ||
| this._x1 = NaN; | ||
| this._y1 = NaN; | ||
| this._root = undefined; | ||
| this.addAll(nodes); | ||
| this.empty = true; | ||
| this.leaf = null; | ||
| this.children = null; | ||
| } | ||
| addAll(data) { | ||
| var d, | ||
| i, | ||
| n = data.length, | ||
| x, | ||
| y, | ||
| xz = new Array(n), | ||
| yz = new Array(n), | ||
| x0 = Infinity, | ||
| y0 = Infinity, | ||
| x1 = -Infinity, | ||
| y1 = -Infinity; | ||
| add(p) { | ||
| const { x0, y0, x1, y1, xm, ym, leaf } = this; | ||
| // Compute the points and their extent. | ||
| for (i = 0; i < n; ++i) { | ||
| if ( | ||
| isNaN((x = +this._x.call(null, (d = data[i])))) || | ||
| isNaN((y = +this._y.call(null, d))) | ||
| ) | ||
| continue; | ||
| xz[i] = x; | ||
| yz[i] = y; | ||
| if (x < x0) x0 = x; | ||
| if (x > x1) x1 = x; | ||
| if (y < y0) y0 = y; | ||
| if (y > y1) y1 = y; | ||
| if (this.empty) { | ||
| this.leaf = p; | ||
| this.empty = false; | ||
| return; | ||
| } | ||
| // If there were no (valid) points, abort. | ||
| if (x0 > x1 || y0 > y1) return this; | ||
| if (leaf) { | ||
| // need to subdivide | ||
| this.children = { | ||
| nw: new Node(x0, y0, xm, ym), | ||
| ne: new Node(xm, y0, x1, ym), | ||
| sw: new Node(x0, ym, xm, y1), | ||
| se: new Node(xm, ym, x1, y1) | ||
| }; | ||
| // Expand the tree to cover the new points. | ||
| this.cover(x0, y0).cover(x1, y1); | ||
| // Add the new points. | ||
| for (i = 0; i < n; ++i) { | ||
| this._add(xz[i], yz[i], data[i]); | ||
| this.leaf = null; | ||
| this.add(leaf); | ||
| } | ||
| return this; | ||
| const child = p.x < xm | ||
| ? p.y < ym ? this.children.nw : this.children.sw | ||
| : p.y < ym ? this.children.ne : this.children.se; | ||
| child.add(p); | ||
| } | ||
| } | ||
| cover(x, y) { | ||
| if (isNaN((x = +x)) || isNaN((y = +y))) return this; // ignore invalid points | ||
| function build_tree(data, x, y, x_scale, y_scale) { | ||
| const points = data.map((d, i) => ({ | ||
| d, | ||
| x: x_scale(x(d, i)), | ||
| y: y_scale(y(d, i)) | ||
| })); | ||
| var x0 = this._x0, | ||
| y0 = this._y0, | ||
| x1 = this._x1, | ||
| y1 = this._y1; | ||
| let x0 = Infinity; | ||
| let y0 = Infinity; | ||
| let x1 = -Infinity; | ||
| let y1 = -Infinity; | ||
| // If the quadtree has no extent, initialize them. | ||
| // Integer extent are necessary so that if we later double the extent, | ||
| // the existing quadrant boundaries don’t change due to floating point error! | ||
| if (isNaN(x0)) { | ||
| x1 = (x0 = Math.floor(x)) + 1; | ||
| y1 = (y0 = Math.floor(y)) + 1; | ||
| } | ||
| for (let i = 0; i < points.length; i += 1) { | ||
| const p = points[i]; | ||
| // Otherwise, double repeatedly to cover. | ||
| else { | ||
| var z = x1 - x0, | ||
| node = this._root, | ||
| parent, | ||
| i; | ||
| if (p.x < x0) x0 = p.x; | ||
| if (p.y < y0) y0 = p.y; | ||
| if (p.x > x1) x1 = p.x; | ||
| if (p.y > y1) y1 = p.y; | ||
| } | ||
| while (x0 > x || x >= x1 || y0 > y || y >= y1) { | ||
| i = ((y < y0) << 1) | (x < x0); | ||
| (parent = new Array(4)), (parent[i] = node), (node = parent), (z *= 2); | ||
| switch (i) { | ||
| case 0: | ||
| (x1 = x0 + z), (y1 = y0 + z); | ||
| break; | ||
| case 1: | ||
| (x0 = x1 - z), (y1 = y0 + z); | ||
| break; | ||
| case 2: | ||
| (x1 = x0 + z), (y0 = y1 - z); | ||
| break; | ||
| case 3: | ||
| (x0 = x1 - z), (y0 = y1 - z); | ||
| break; | ||
| } | ||
| } | ||
| const root = new Node(x0, y0, x1, y1); | ||
| if (this._root && this._root.length) this._root = node; | ||
| } | ||
| for (let i = 0; i < points.length; i += 1) { | ||
| const p = points[i]; | ||
| if (isNaN(p.x) || isNaN(p.y)) continue; | ||
| this._x0 = x0; | ||
| this._y0 = y0; | ||
| this._x1 = x1; | ||
| this._y1 = y1; | ||
| return this; | ||
| root.add(p); | ||
| } | ||
| find(x, y, radius) { | ||
| var data, | ||
| x0 = this._x0, | ||
| y0 = this._y0, | ||
| x1, | ||
| y1, | ||
| x2, | ||
| y2, | ||
| x3 = this._x1, | ||
| y3 = this._y1, | ||
| quads = [], | ||
| node = this._root, | ||
| q, | ||
| i; | ||
| return root; | ||
| } | ||
| if (node) quads.push(new Quad(node, x0, y0, x3, y3)); | ||
| if (radius == null) radius = Infinity; | ||
| else { | ||
| (x0 = x - radius), (y0 = y - radius); | ||
| (x3 = x + radius), (y3 = y + radius); | ||
| radius *= radius; | ||
| } | ||
| export default class Quadtree { | ||
| constructor(data) { | ||
| this.data = data; | ||
| this.x = null; | ||
| this.y = null; | ||
| this.x_scale = null; | ||
| this.y_scale = null; | ||
| } | ||
| while ((q = quads.pop())) { | ||
| // Stop searching if this quadrant can’t contain a closer node. | ||
| if ( | ||
| !(node = q.node) || | ||
| (x1 = q.x0) > x3 || | ||
| (y1 = q.y0) > y3 || | ||
| (x2 = q.x1) < x0 || | ||
| (y2 = q.y1) < y0 | ||
| ) | ||
| continue; | ||
| update(x, y, x_scale, y_scale) { | ||
| this.root = null; | ||
| this.x = x; | ||
| this.y = y; | ||
| this.x_scale = x_scale; | ||
| this.y_scale = y_scale; | ||
| } | ||
| // Bisect the current quadrant. | ||
| if (node.length) { | ||
| var xm = (x1 + x2) / 2, | ||
| ym = (y1 + y2) / 2; | ||
| find(left, top, width, height, radius) { | ||
| if (!this.root) this.root = build_tree(this.data, this.x, this.y, this.x_scale, this.y_scale); | ||
| quads.push( | ||
| new Quad(node[3], xm, ym, x2, y2), | ||
| new Quad(node[2], x1, ym, xm, y2), | ||
| new Quad(node[1], xm, y1, x2, ym), | ||
| new Quad(node[0], x1, y1, xm, ym) | ||
| ); | ||
| const queue = [this.root]; | ||
| // Visit the closest quadrant first. | ||
| if ((i = ((y >= ym) << 1) | (x >= xm))) { | ||
| q = quads[quads.length - 1]; | ||
| quads[quads.length - 1] = quads[quads.length - 1 - i]; | ||
| quads[quads.length - 1 - i] = q; | ||
| } | ||
| } | ||
| let node; | ||
| let closest; | ||
| let min_d_squared = Infinity; | ||
| // Visit this point. (Visiting coincident points isn’t necessary!) | ||
| else { | ||
| var dx = x - +this._x.call(null, node.data), | ||
| dy = y - +this._y.call(null, node.data), | ||
| d2 = dx * dx + dy * dy; | ||
| if (d2 < radius) { | ||
| var d = Math.sqrt((radius = d2)); | ||
| (x0 = x - d), (y0 = y - d); | ||
| (x3 = x + d), (y3 = y + d); | ||
| data = node.data; | ||
| } | ||
| } | ||
| } | ||
| const x_to_px = x => x * width / 100; | ||
| const y_to_px = y => y * height / 100; | ||
| return data; | ||
| } | ||
| while (node = queue.shift()) { | ||
| if (node.empty) continue; | ||
| _add(x, y, d) { | ||
| if (isNaN(x) || isNaN(y)) return this; // ignore invalid points | ||
| const left0 = x_to_px(node.x0); | ||
| const left1 = x_to_px(node.x1); | ||
| const top0 = y_to_px(node.y0); | ||
| const top1 = y_to_px(node.y1); | ||
| var parent, | ||
| node = this._root, | ||
| leaf = { data: d }, | ||
| x0 = this._x0, | ||
| y0 = this._y0, | ||
| x1 = this._x1, | ||
| y1 = this._y1, | ||
| xm, | ||
| ym, | ||
| xp, | ||
| yp, | ||
| right, | ||
| bottom, | ||
| i, | ||
| j; | ||
| const out_of_bounds = ( | ||
| left < (Math.min(left0, left1) - radius) || | ||
| left > (Math.max(left0, left1) + radius) || | ||
| top < (Math.min(top0, top1) - radius) || | ||
| top > (Math.max(top0, top1) + radius) | ||
| ); | ||
| // If the tree is empty, initialize the root as a leaf. | ||
| if (!node) return (this._root = leaf), this; | ||
| if (out_of_bounds) continue; | ||
| // Find the existing leaf for the new point, or add it. | ||
| while (node.length) { | ||
| if ((right = x >= (xm = (x0 + x1) / 2))) x0 = xm; | ||
| else x1 = xm; | ||
| if ((bottom = y >= (ym = (y0 + y1) / 2))) y0 = ym; | ||
| else y1 = ym; | ||
| if (((parent = node), !(node = node[(i = (bottom << 1) | right)]))) | ||
| return (parent[i] = leaf), this; | ||
| } | ||
| if (node.leaf) { | ||
| const dl = x_to_px(node.leaf.x) - left; | ||
| const dt = y_to_px(node.leaf.y) - top; | ||
| // Is the new point is exactly coincident with the existing point? | ||
| xp = +this._x.call(null, node.data); | ||
| yp = +this._y.call(null, node.data); | ||
| if (x === xp && y === yp) | ||
| return ( | ||
| (leaf.next = node), | ||
| parent ? (parent[i] = leaf) : (this._root = leaf), | ||
| this | ||
| ); | ||
| const d_squared = (dl * dl + dt * dt); | ||
| // Otherwise, split the leaf node until the old and new point are separated. | ||
| do { | ||
| parent = parent | ||
| ? (parent[i] = new Array(4)) | ||
| : (this._root = new Array(4)); | ||
| if ((right = x >= (xm = (x0 + x1) / 2))) x0 = xm; | ||
| else x1 = xm; | ||
| if ((bottom = y >= (ym = (y0 + y1) / 2))) y0 = ym; | ||
| else y1 = ym; | ||
| } while ( | ||
| (i = (bottom << 1) | right) === (j = ((yp >= ym) << 1) | (xp >= xm)) | ||
| ); | ||
| if (d_squared < min_d_squared) { | ||
| closest = node.leaf.d; | ||
| min_d_squared = d_squared; | ||
| } | ||
| } else { | ||
| queue.push( | ||
| node.children.nw, | ||
| node.children.ne, | ||
| node.children.sw, | ||
| node.children.se | ||
| ); | ||
| } | ||
| } | ||
| return (parent[j] = node), (parent[i] = leaf), this; | ||
| return min_d_squared < (radius * radius) | ||
| ? closest | ||
| : null; | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
15971
-10.43%204
-33.11%1
Infinity%