layerchart 0.6.9 → 0.7.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/components/Arc.svelte +3 -2
- package/components/Area.svelte +2 -1
- package/components/AxisX.svelte +2 -1
- package/components/AxisY.svelte +2 -1
- package/components/Bars.svelte +2 -2
- package/components/Baseline.svelte +3 -2
- package/components/Chart.svelte +10 -1
- package/components/Chart.svelte.d.ts +1 -0
- package/components/HighlightRect.svelte +2 -2
- package/components/Labels.svelte +2 -2
- package/components/Pie.svelte +3 -2
- package/components/Threshold.svelte +3 -2
- package/components/Tooltip.svelte +103 -52
- package/components/Tooltip.svelte.d.ts +1 -1
- package/package.json +1 -1
- package/utils/scales.d.ts +1 -1
- package/utils/scales.js +3 -2
package/components/Arc.svelte
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { getContext } from 'svelte';
|
|
16
16
|
import { arc as d3arc } from 'd3-shape';
|
|
17
17
|
import { scaleLinear } from 'd3-scale';
|
|
18
|
+
import { min, max } from 'd3-array';
|
|
18
19
|
import { motionStore } from '../stores/motionStore';
|
|
19
20
|
import { degreesToRadians } from '../utils/math';
|
|
20
21
|
export let spring = undefined;
|
|
@@ -53,7 +54,7 @@ export let padAngle = 0;
|
|
|
53
54
|
export let track = false;
|
|
54
55
|
const { yRange } = getContext('LayerCake');
|
|
55
56
|
$: scale = scaleLinear().domain(domain).range(range);
|
|
56
|
-
$: _outerRadius = outerRadius ?? $yRange
|
|
57
|
+
$: _outerRadius = outerRadius ?? max($yRange) / 2;
|
|
57
58
|
$: _innerRadius =
|
|
58
59
|
(innerRadius > 1
|
|
59
60
|
? innerRadius
|
|
@@ -61,7 +62,7 @@ $: _innerRadius =
|
|
|
61
62
|
? _outerRadius * innerRadius
|
|
62
63
|
: innerRadius < 0
|
|
63
64
|
? _outerRadius - innerRadius
|
|
64
|
-
: innerRadius) ?? $yRange
|
|
65
|
+
: innerRadius) ?? min($yRange);
|
|
65
66
|
$: arc = d3arc()
|
|
66
67
|
.innerRadius(_innerRadius)
|
|
67
68
|
.outerRadius(_outerRadius)
|
package/components/Area.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>import { getContext } from 'svelte';
|
|
2
2
|
import { area as d3Area } from 'd3-shape';
|
|
3
|
+
import { max } from 'd3-array';
|
|
3
4
|
import { interpolatePath } from 'd3-interpolate-path';
|
|
4
5
|
import { motionStore } from '../stores/motionStore';
|
|
5
6
|
import Path from './Path.svelte';
|
|
@@ -22,7 +23,7 @@ $: tweened_d = motionStore('', { tweened: tweenedOptions });
|
|
|
22
23
|
$: {
|
|
23
24
|
const path = d3Area()
|
|
24
25
|
.x(x ?? $xGet)
|
|
25
|
-
.y0(y0 ?? $yRange
|
|
26
|
+
.y0(y0 ?? max($yRange))
|
|
26
27
|
.y1(y1 ?? $yGet);
|
|
27
28
|
if (curve)
|
|
28
29
|
path.curve(curve);
|
package/components/AxisX.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>import { getContext } from 'svelte';
|
|
2
2
|
import { format } from 'svelte-ux/utils/format';
|
|
3
|
+
import { max } from 'd3-array';
|
|
3
4
|
import Text from './Text.svelte';
|
|
4
5
|
import { isScaleBand } from '../utils/scales';
|
|
5
6
|
const { height, xScale, yRange } = getContext('LayerCake');
|
|
@@ -20,7 +21,7 @@ $: tickVals = Array.isArray(ticks)
|
|
|
20
21
|
|
|
21
22
|
<g class="axis x-axis">
|
|
22
23
|
{#each tickVals as tick, i}
|
|
23
|
-
<g class="tick tick-{tick}" transform="translate({$xScale(tick)},{$yRange
|
|
24
|
+
<g class="tick tick-{tick}" transform="translate({$xScale(tick)},{max($yRange)})">
|
|
24
25
|
{#if gridlines !== false}
|
|
25
26
|
<line y1={$height * -1} y2="0" x1="0" x2="0" {...gridlines} />
|
|
26
27
|
{/if}
|
package/components/AxisY.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>import { getContext } from 'svelte';
|
|
2
2
|
import { format } from 'svelte-ux/utils/format';
|
|
3
|
+
import { min } from 'd3-array';
|
|
3
4
|
import Text from './Text.svelte';
|
|
4
5
|
import { isScaleBand } from '../utils/scales';
|
|
5
6
|
const { padding, xRange, yScale, width } = getContext('LayerCake');
|
|
@@ -20,7 +21,7 @@ $: tickVals = Array.isArray(ticks)
|
|
|
20
21
|
|
|
21
22
|
<g class="axis y-axis" transform="translate({-$padding.left}, 0)">
|
|
22
23
|
{#each tickVals as tick, i}
|
|
23
|
-
<g class="tick tick-{tick}" transform="translate({$xRange
|
|
24
|
+
<g class="tick tick-{tick}" transform="translate({min($xRange)}, {$yScale(tick)})">
|
|
24
25
|
{#if gridlines !== false}
|
|
25
26
|
<line
|
|
26
27
|
x1={$padding.left}
|
package/components/Bars.svelte
CHANGED
|
@@ -58,11 +58,11 @@ $: getDimensions = (item) => {
|
|
|
58
58
|
else if (yValue > 0) {
|
|
59
59
|
// Positive value
|
|
60
60
|
yTop = yValue;
|
|
61
|
-
yBottom = $yRange
|
|
61
|
+
yBottom = min($yRange); // or `0`?
|
|
62
62
|
}
|
|
63
63
|
else {
|
|
64
64
|
// Negative value
|
|
65
|
-
yTop = $yRange
|
|
65
|
+
yTop = min($yRange); // or `0`?
|
|
66
66
|
yBottom = yValue;
|
|
67
67
|
}
|
|
68
68
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script>import { getContext } from 'svelte';
|
|
2
|
+
import { min, max } from 'd3-array';
|
|
2
3
|
const { xRange, yScale, yRange } = getContext('LayerCake');
|
|
3
4
|
export let x = false;
|
|
4
5
|
export let y = false;
|
|
@@ -6,11 +7,11 @@ export let y = false;
|
|
|
6
7
|
|
|
7
8
|
<g class="baseline">
|
|
8
9
|
{#if x}
|
|
9
|
-
<line x1={0} x2={$xRange
|
|
10
|
+
<line x1={0} x2={max($xRange) || 0} y1={max($yRange)} y2={max($yRange)} class="baseline" />
|
|
10
11
|
{/if}
|
|
11
12
|
|
|
12
13
|
{#if y}
|
|
13
|
-
<line x1={0} x2={0} y1={$yRange
|
|
14
|
+
<line x1={0} x2={0} y1={min($yRange) || 0} y2={max($yRange) || 0} class="baseline" />
|
|
14
15
|
{/if}
|
|
15
16
|
</g>
|
|
16
17
|
|
package/components/Chart.svelte
CHANGED
|
@@ -4,6 +4,7 @@ export { Svg, Html };
|
|
|
4
4
|
|
|
5
5
|
<script>import { max, min } from 'd3-array';
|
|
6
6
|
import { get } from 'lodash-es';
|
|
7
|
+
import { isScaleBand } from '../utils/scales';
|
|
7
8
|
/**
|
|
8
9
|
* Resolve a value from data based on the accessor type
|
|
9
10
|
*/
|
|
@@ -25,6 +26,7 @@ function getValue(accessor, d) {
|
|
|
25
26
|
export let data = [];
|
|
26
27
|
export let x;
|
|
27
28
|
export let y;
|
|
29
|
+
export let yScale;
|
|
28
30
|
/**
|
|
29
31
|
* xBaseline guaranteed to be visible in xDomain
|
|
30
32
|
*/
|
|
@@ -43,8 +45,15 @@ $: if (yBaseline != null) {
|
|
|
43
45
|
const yValues = data.flatMap((d) => getValue(y, d));
|
|
44
46
|
yDomain = [min([yBaseline, ...yValues]), max([yBaseline, ...yValues])];
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Reverse the default y range ([0, height] becomes [height, 0]). By default this is `true` unless using scaleBand y scale.
|
|
50
|
+
* see: https://layercake.graphics/guide#yreverse
|
|
51
|
+
* see: https://github.com/mhkeller/layercake/issues/83
|
|
52
|
+
|
|
53
|
+
*/
|
|
54
|
+
$: yReverse = yScale ? !isScaleBand(yScale) : true;
|
|
46
55
|
</script>
|
|
47
56
|
|
|
48
|
-
<LayerCake {data} {y} {
|
|
57
|
+
<LayerCake {data} {x} {xDomain} {y} {yScale} {yDomain} {yReverse} {...$$restProps}>
|
|
49
58
|
<slot />
|
|
50
59
|
</LayerCake>
|
|
@@ -7,6 +7,7 @@ declare const __propDef: {
|
|
|
7
7
|
data?: any[];
|
|
8
8
|
x: (string | ((d: any) => number)) | (string | ((d: any) => number))[];
|
|
9
9
|
y: (string | ((d: any) => number)) | (string | ((d: any) => number))[];
|
|
10
|
+
yScale: Function;
|
|
10
11
|
xBaseline?: number | null;
|
|
11
12
|
yBaseline?: number | null;
|
|
12
13
|
};
|
|
@@ -33,7 +33,7 @@ $: {
|
|
|
33
33
|
}
|
|
34
34
|
dimensions.x = xCoord - (isScaleBand($xScale) ? ($xScale.padding() * $xScale.step()) / 2 : 0);
|
|
35
35
|
if (axis === 'x') {
|
|
36
|
-
dimensions.height = $yRange
|
|
36
|
+
dimensions.height = max($yRange);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
if (axis === 'y' || axis === 'both') {
|
|
@@ -55,7 +55,7 @@ $: {
|
|
|
55
55
|
}
|
|
56
56
|
dimensions.y = yCoord - (isScaleBand($yScale) ? ($yScale.padding() * $yScale.step()) / 2 : 0);
|
|
57
57
|
if (axis === 'y') {
|
|
58
|
-
dimensions.width = $xRange
|
|
58
|
+
dimensions.width = max($xRange);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
}
|
package/components/Labels.svelte
CHANGED
|
@@ -46,11 +46,11 @@ $: getDimensions = (item) => {
|
|
|
46
46
|
else if (yValue > 0) {
|
|
47
47
|
// Positive value
|
|
48
48
|
yTop = yValue;
|
|
49
|
-
yBottom = $yRange
|
|
49
|
+
yBottom = min($yRange); // or `0`?
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
52
|
// Negative value
|
|
53
|
-
yTop = $yRange
|
|
53
|
+
yTop = min($yRange); // or `0`?
|
|
54
54
|
yBottom = yValue;
|
|
55
55
|
}
|
|
56
56
|
if (yBottom < 0) {
|
package/components/Pie.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>import { getContext } from 'svelte';
|
|
2
2
|
import { pie as d3pie } from 'd3-shape';
|
|
3
|
+
import { min, max } from 'd3-array';
|
|
3
4
|
import Arc from './Arc.svelte';
|
|
4
5
|
import Group from './Group.svelte';
|
|
5
6
|
import { degreesToRadians } from '../utils/math';
|
|
@@ -47,11 +48,11 @@ export let tweened = undefined;
|
|
|
47
48
|
*/
|
|
48
49
|
export let offset = 0;
|
|
49
50
|
const { data: contextData, x, y, xRange, rGet, config } = getContext('LayerCake');
|
|
50
|
-
$: resolved_endAngle = endAngle ?? degreesToRadians($config.xRange ? $xRange
|
|
51
|
+
$: resolved_endAngle = endAngle ?? degreesToRadians($config.xRange ? max($xRange) : max(range));
|
|
51
52
|
let tweened_endAngle = motionStore(0, { spring, tweened });
|
|
52
53
|
$: tweened_endAngle.set(resolved_endAngle);
|
|
53
54
|
$: pie = d3pie()
|
|
54
|
-
.startAngle(startAngle ?? degreesToRadians($config.xRange ? $xRange
|
|
55
|
+
.startAngle(startAngle ?? degreesToRadians($config.xRange ? min($xRange) : min(range)))
|
|
55
56
|
.endAngle($tweened_endAngle)
|
|
56
57
|
.padAngle(padAngle)
|
|
57
58
|
.value($x);
|
|
@@ -5,6 +5,7 @@ See also:
|
|
|
5
5
|
*/
|
|
6
6
|
import { getContext } from 'svelte';
|
|
7
7
|
import { area as d3Area, line as d3Line } from 'd3-shape';
|
|
8
|
+
import { max } from 'd3-array';
|
|
8
9
|
const { data: contextData, xGet, yGet, yRange } = getContext('LayerCake');
|
|
9
10
|
// Properties to override what is used from context
|
|
10
11
|
export let data = undefined; // TODO: Update Type
|
|
@@ -25,14 +26,14 @@ $: if (defined)
|
|
|
25
26
|
$: clipPathBelow = d3Area()
|
|
26
27
|
.x(x ?? $xGet)
|
|
27
28
|
.y0(y0 ?? ((d) => $yGet(d)[0]))
|
|
28
|
-
.y1(y1 ?? ((d) => $yRange
|
|
29
|
+
.y1(y1 ?? ((d) => max($yRange)));
|
|
29
30
|
$: if (curve)
|
|
30
31
|
clipPathBelow.curve(curve);
|
|
31
32
|
$: if (defined)
|
|
32
33
|
clipPathBelow.defined(defined);
|
|
33
34
|
$: clipPathAbove = d3Area()
|
|
34
35
|
.x(x ?? $xGet)
|
|
35
|
-
.y0(y0 ?? ((d) => $yRange
|
|
36
|
+
.y0(y0 ?? ((d) => max($yRange)))
|
|
36
37
|
.y1(y1 ?? ((d) => $yGet(d)[1]));
|
|
37
38
|
$: if (curve)
|
|
38
39
|
clipPathAbove.curve(curve);
|
|
@@ -10,8 +10,9 @@ import ChartClipPath from './ChartClipPath.svelte';
|
|
|
10
10
|
import { localPoint } from '../utils/event';
|
|
11
11
|
import { isScaleBand, scaleInvert } from '../utils/scales';
|
|
12
12
|
import { quadtreeRects } from '../utils/quadtree';
|
|
13
|
+
import { createPropertySortFunc, createSortFunc } from 'svelte-ux/utils/sort';
|
|
13
14
|
const dispatch = createEventDispatcher();
|
|
14
|
-
const { flatData, x, xScale, xGet, xRange, yScale, yGet, yRange, width, height, padding } = getContext('LayerCake');
|
|
15
|
+
const { flatData, x, xScale, xGet, xRange, y, yScale, yGet, yRange, width, height, padding } = getContext('LayerCake');
|
|
15
16
|
/*
|
|
16
17
|
TODO: Defaults to consider (if possible to detect scale type, which might not be possible)
|
|
17
18
|
- scaleTime / scaleLinear: bisect
|
|
@@ -22,7 +23,7 @@ const { flatData, x, xScale, xGet, xRange, yScale, yGet, yRange, width, height,
|
|
|
22
23
|
- scaleBand, scaleLinear: band (or bounds) - multiple (overlapping) bars
|
|
23
24
|
- scaleLinear, scaleLinear: voronoi (or quadtree)
|
|
24
25
|
*/
|
|
25
|
-
export let mode = 'bisect';
|
|
26
|
+
export let mode = 'bisect-x';
|
|
26
27
|
export let snapToDataX = false;
|
|
27
28
|
export let snapToDataY = false;
|
|
28
29
|
export let findTooltipData = 'closest';
|
|
@@ -57,64 +58,109 @@ $: if (tooltip) {
|
|
|
57
58
|
$left = tooltip.left + leftOffset;
|
|
58
59
|
}
|
|
59
60
|
}
|
|
61
|
+
$: bisectX = bisector((d) => {
|
|
62
|
+
const value = $x(d);
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
// `x` accessor with multiple properties (ex. `x={['start', 'end']})`)
|
|
65
|
+
// Using first value. Consider using average, max, etc
|
|
66
|
+
// const midpoint = new Date((value[1].valueOf() + value[0].getTime()) / 2);
|
|
67
|
+
// return midpoint;
|
|
68
|
+
return value[0];
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
}).left;
|
|
74
|
+
$: bisectY = bisector((d) => {
|
|
75
|
+
const value = $y(d);
|
|
76
|
+
if (Array.isArray(value)) {
|
|
77
|
+
// `x` accessor with multiple properties (ex. `x={['start', 'end']})`)
|
|
78
|
+
// Using first value. Consider using average, max, etc
|
|
79
|
+
// const midpoint = new Date((value[1].valueOf() + value[0].getTime()) / 2);
|
|
80
|
+
// return midpoint;
|
|
81
|
+
return value[0];
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
}).left;
|
|
87
|
+
function findData(previousValue, currentValue, valueAtPoint, accessor) {
|
|
88
|
+
switch (findTooltipData) {
|
|
89
|
+
case 'closest':
|
|
90
|
+
if (currentValue === undefined) {
|
|
91
|
+
return previousValue;
|
|
92
|
+
}
|
|
93
|
+
else if (previousValue === undefined) {
|
|
94
|
+
return currentValue;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
return Number(valueAtPoint) - Number(accessor(previousValue)) >
|
|
98
|
+
Number(accessor(currentValue)) - Number(valueAtPoint)
|
|
99
|
+
? currentValue
|
|
100
|
+
: previousValue;
|
|
101
|
+
}
|
|
102
|
+
case 'left':
|
|
103
|
+
return previousValue;
|
|
104
|
+
case 'right':
|
|
105
|
+
default:
|
|
106
|
+
return currentValue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
60
109
|
function handleTooltip(event, tooltipData) {
|
|
61
110
|
const point = localPoint(event.target, event);
|
|
62
111
|
const localX = point?.x ?? 0;
|
|
63
112
|
const localY = point?.y ?? 0;
|
|
64
113
|
// If tooltipData not provided already (voronoi, etc), attempt to find it
|
|
114
|
+
// TODO: When using bisect-x/y/band, values should be sorted. Tyipcally are for `x`, but not `y` (and band depends on if x or y scale)
|
|
65
115
|
if (tooltipData == null) {
|
|
66
116
|
if (mode === 'quadtree') {
|
|
67
117
|
tooltipData = quadtree.find(localX, localY, radius);
|
|
68
118
|
}
|
|
69
|
-
else if (mode === 'bisect') {
|
|
70
|
-
// `x`
|
|
71
|
-
const
|
|
119
|
+
else if (mode === 'bisect-band') {
|
|
120
|
+
// `x` and `y` values at mouse/touch coordinate
|
|
121
|
+
const xValueAtPoint = scaleInvert($xScale, localX);
|
|
122
|
+
const yValueAtPoint = scaleInvert($yScale, localY);
|
|
72
123
|
if (isScaleBand($xScale)) {
|
|
73
|
-
|
|
124
|
+
// Find point closest to pointer within the x band
|
|
125
|
+
const bandData = $flatData
|
|
126
|
+
.filter((d) => $x(d) === xValueAtPoint)
|
|
127
|
+
.sort(createSortFunc($y)); // sort for bisect
|
|
128
|
+
const index = bisectY(bandData, yValueAtPoint, 1);
|
|
129
|
+
const previousValue = bandData[index - 1];
|
|
130
|
+
const currentValue = bandData[index];
|
|
131
|
+
tooltipData = findData(previousValue, currentValue, yValueAtPoint, $y);
|
|
132
|
+
}
|
|
133
|
+
else if (isScaleBand($yScale)) {
|
|
134
|
+
// Find point closest to pointer within the y band
|
|
135
|
+
const bandData = $flatData
|
|
136
|
+
.filter((d) => $y(d) === yValueAtPoint)
|
|
137
|
+
.sort(createSortFunc($x)); // sort for bisect
|
|
138
|
+
const index = bisectX(bandData, xValueAtPoint, 1);
|
|
139
|
+
const previousValue = bandData[index - 1];
|
|
140
|
+
const currentValue = bandData[index];
|
|
141
|
+
tooltipData = findData(previousValue, currentValue, xValueAtPoint, $x);
|
|
74
142
|
}
|
|
75
143
|
else {
|
|
76
|
-
//
|
|
77
|
-
const bisectX = bisector((d) => {
|
|
78
|
-
const value = $x(d);
|
|
79
|
-
if (Array.isArray(value)) {
|
|
80
|
-
// `x` accessor with multiple properties (ex. `x={['start', 'end']})`)
|
|
81
|
-
// Using first value. Consider using average, max, etc
|
|
82
|
-
// const midpoint = new Date((value[1].valueOf() + value[0].getTime()) / 2);
|
|
83
|
-
// return midpoint;
|
|
84
|
-
return value[0];
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
return value;
|
|
88
|
-
}
|
|
89
|
-
}).left;
|
|
90
|
-
const index = bisectX($flatData, valueAtPoint, 1);
|
|
91
|
-
const data0 = $flatData[index - 1];
|
|
92
|
-
const data1 = $flatData[index];
|
|
93
|
-
switch (findTooltipData) {
|
|
94
|
-
case 'closest':
|
|
95
|
-
if (data1 === undefined) {
|
|
96
|
-
tooltipData = data0;
|
|
97
|
-
}
|
|
98
|
-
else if (data0 === undefined) {
|
|
99
|
-
tooltipData = data1;
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
tooltipData =
|
|
103
|
-
Number(valueAtPoint) - Number($x(data0)) >
|
|
104
|
-
Number($x(data1)) - Number(valueAtPoint)
|
|
105
|
-
? data1
|
|
106
|
-
: data0;
|
|
107
|
-
}
|
|
108
|
-
break;
|
|
109
|
-
case 'left':
|
|
110
|
-
tooltipData = data0;
|
|
111
|
-
break;
|
|
112
|
-
case 'right':
|
|
113
|
-
default:
|
|
114
|
-
tooltipData = data1;
|
|
115
|
-
}
|
|
144
|
+
// TODO: Support `bisect-band` without band? Fallback to bisect?
|
|
116
145
|
}
|
|
117
146
|
}
|
|
147
|
+
else if (mode === 'bisect-x') {
|
|
148
|
+
// `x` value at mouse/touch coordinate
|
|
149
|
+
const xValueAtPoint = scaleInvert($xScale, localX);
|
|
150
|
+
const index = bisectX($flatData, xValueAtPoint, 1);
|
|
151
|
+
const previousValue = $flatData[index - 1];
|
|
152
|
+
const currentValue = $flatData[index];
|
|
153
|
+
console.log({ index, previousValue, currentValue });
|
|
154
|
+
tooltipData = findData(previousValue, currentValue, xValueAtPoint, $x);
|
|
155
|
+
}
|
|
156
|
+
else if (mode === 'bisect-y') {
|
|
157
|
+
// `y` value at mouse/touch coordinate
|
|
158
|
+
const yValueAtPoint = scaleInvert($yScale, localY);
|
|
159
|
+
const index = bisectY($flatData, yValueAtPoint, 1);
|
|
160
|
+
const previousValue = $flatData[index - 1];
|
|
161
|
+
const currentValue = $flatData[index];
|
|
162
|
+
tooltipData = findData(previousValue, currentValue, yValueAtPoint, $y);
|
|
163
|
+
}
|
|
118
164
|
}
|
|
119
165
|
if (tooltipData) {
|
|
120
166
|
tooltip = {
|
|
@@ -182,7 +228,8 @@ $: if (mode === 'quadtree') {
|
|
|
182
228
|
}
|
|
183
229
|
let rects = [];
|
|
184
230
|
$: if (mode === 'bounds' || mode === 'band') {
|
|
185
|
-
rects = $flatData
|
|
231
|
+
rects = $flatData
|
|
232
|
+
.map((d) => {
|
|
186
233
|
const xValue = $xGet(d);
|
|
187
234
|
const yValue = $yGet(d);
|
|
188
235
|
const x = Array.isArray(xValue) ? min(xValue) : xValue;
|
|
@@ -219,7 +266,8 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
219
266
|
data: d
|
|
220
267
|
};
|
|
221
268
|
}
|
|
222
|
-
})
|
|
269
|
+
})
|
|
270
|
+
.sort(createPropertySortFunc('x'));
|
|
223
271
|
// console.log({ rects });
|
|
224
272
|
}
|
|
225
273
|
</script>
|
|
@@ -246,7 +294,7 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
246
294
|
</Svg>
|
|
247
295
|
{/if}
|
|
248
296
|
|
|
249
|
-
{#if
|
|
297
|
+
{#if ['bisect-x', 'bisect-y', 'bisect-band', 'quadtree'].indexOf(mode) != -1}
|
|
250
298
|
<Html>
|
|
251
299
|
<div
|
|
252
300
|
class="absolute"
|
|
@@ -266,7 +314,8 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
266
314
|
<g class="tooltip-voronoi">
|
|
267
315
|
<path
|
|
268
316
|
d={voronoi.renderCell(i)}
|
|
269
|
-
style:fill=
|
|
317
|
+
style:fill={debug ? 'red' : 'transparent'}
|
|
318
|
+
style:fill-opacity={debug ? 0.1 : 0}
|
|
270
319
|
style:stroke={debug ? 'red' : 'transparent'}
|
|
271
320
|
on:mousemove={(e) => handleTooltip(e, point.data)}
|
|
272
321
|
on:mouseleave={hideTooltip}
|
|
@@ -283,7 +332,8 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
283
332
|
y={rect.y}
|
|
284
333
|
width={rect.width}
|
|
285
334
|
height={rect.height}
|
|
286
|
-
style:fill=
|
|
335
|
+
style:fill={debug ? 'red' : 'transparent'}
|
|
336
|
+
style:fill-opacity={debug ? 0.1 : 0}
|
|
287
337
|
style:stroke={debug ? 'red' : 'transparent'}
|
|
288
338
|
on:mousemove={(e) => handleTooltip(e, rect.data)}
|
|
289
339
|
on:mouseleave={hideTooltip}
|
|
@@ -303,8 +353,9 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
303
353
|
y={rect.y}
|
|
304
354
|
width={rect.width}
|
|
305
355
|
height={rect.height}
|
|
356
|
+
style:fill={debug ? 'red' : 'transparent'}
|
|
357
|
+
style:fill-opacity={debug ? 0.1 : 0}
|
|
306
358
|
stroke="red"
|
|
307
|
-
fill="none"
|
|
308
359
|
/>
|
|
309
360
|
{/each}
|
|
310
361
|
</g>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
2
|
declare const __propDef: {
|
|
3
3
|
props: {
|
|
4
|
-
mode?: 'bisect' | '
|
|
4
|
+
mode?: 'bisect-x' | 'bisect-y' | 'band' | 'bisect-band' | 'bounds' | 'voronoi' | 'quadtree';
|
|
5
5
|
snapToDataX?: boolean;
|
|
6
6
|
snapToDataY?: boolean;
|
|
7
7
|
findTooltipData?: 'closest' | 'left' | 'right';
|
package/package.json
CHANGED
package/utils/scales.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { MotionOptions } from '../stores/motionStore';
|
|
|
3
3
|
/**
|
|
4
4
|
* Implemenation for missing `scaleBand().invert()`
|
|
5
5
|
*
|
|
6
|
-
* See: https://stackoverflow.com/
|
|
6
|
+
* See: https://stackoverflow.com/questions/38633082/d3-getting-invert-value-of-band-scales
|
|
7
7
|
* https://github.com/d3/d3-scale/pull/64
|
|
8
8
|
* https://github.com/vega/vega-scale/blob/master/src/scaleBand.js#L118
|
|
9
9
|
* https://observablehq.com/@d3/ordinal-brushing
|
package/utils/scales.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { derived } from 'svelte/store';
|
|
2
2
|
import { tweened, spring } from 'svelte/motion';
|
|
3
|
+
import { min } from 'd3-array';
|
|
3
4
|
import { motionStore } from '../stores/motionStore';
|
|
4
5
|
/**
|
|
5
6
|
* Implemenation for missing `scaleBand().invert()`
|
|
6
7
|
*
|
|
7
|
-
* See: https://stackoverflow.com/
|
|
8
|
+
* See: https://stackoverflow.com/questions/38633082/d3-getting-invert-value-of-band-scales
|
|
8
9
|
* https://github.com/d3/d3-scale/pull/64
|
|
9
10
|
* https://github.com/vega/vega-scale/blob/master/src/scaleBand.js#L118
|
|
10
11
|
* https://observablehq.com/@d3/ordinal-brushing
|
|
11
12
|
*/
|
|
12
13
|
export function scaleBandInvert(scale) {
|
|
13
14
|
const domain = scale.domain();
|
|
14
|
-
const paddingOuter = scale(domain
|
|
15
|
+
const paddingOuter = scale(min(domain));
|
|
15
16
|
const eachBand = scale.step();
|
|
16
17
|
return function (value) {
|
|
17
18
|
// TODO: Should this use Math.round to better select? https://stackoverflow.com/questions/38633082/d3-getting-invert-value-of-band-scales/50846323#comment104743795_50846323
|