layerchart 0.6.9 → 0.7.2
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 +9 -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 +108 -52
- package/components/Tooltip.svelte.d.ts +1 -1
- package/package.json +1 -1
- package/utils/scales.d.ts +3 -1
- package/utils/scales.js +5 -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,14 @@ $: 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
|
+
$: yReverse = yScale ? !isScaleBand(yScale) : true;
|
|
46
54
|
</script>
|
|
47
55
|
|
|
48
|
-
<LayerCake {data} {y} {
|
|
56
|
+
<LayerCake {data} {x} {xDomain} {y} {yScale} {yDomain} {yReverse} {...$$restProps}>
|
|
49
57
|
<slot />
|
|
50
58
|
</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,108 @@ $: 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
|
+
tooltipData = findData(previousValue, currentValue, xValueAtPoint, $x);
|
|
154
|
+
}
|
|
155
|
+
else if (mode === 'bisect-y') {
|
|
156
|
+
// `y` value at mouse/touch coordinate
|
|
157
|
+
const yValueAtPoint = scaleInvert($yScale, localY);
|
|
158
|
+
const index = bisectY($flatData, yValueAtPoint, 1);
|
|
159
|
+
const previousValue = $flatData[index - 1];
|
|
160
|
+
const currentValue = $flatData[index];
|
|
161
|
+
tooltipData = findData(previousValue, currentValue, yValueAtPoint, $y);
|
|
162
|
+
}
|
|
118
163
|
}
|
|
119
164
|
if (tooltipData) {
|
|
120
165
|
tooltip = {
|
|
@@ -182,7 +227,8 @@ $: if (mode === 'quadtree') {
|
|
|
182
227
|
}
|
|
183
228
|
let rects = [];
|
|
184
229
|
$: if (mode === 'bounds' || mode === 'band') {
|
|
185
|
-
rects = $flatData
|
|
230
|
+
rects = $flatData
|
|
231
|
+
.map((d) => {
|
|
186
232
|
const xValue = $xGet(d);
|
|
187
233
|
const yValue = $yGet(d);
|
|
188
234
|
const x = Array.isArray(xValue) ? min(xValue) : xValue;
|
|
@@ -219,7 +265,8 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
219
265
|
data: d
|
|
220
266
|
};
|
|
221
267
|
}
|
|
222
|
-
})
|
|
268
|
+
})
|
|
269
|
+
.sort(createPropertySortFunc('x'));
|
|
223
270
|
// console.log({ rects });
|
|
224
271
|
}
|
|
225
272
|
</script>
|
|
@@ -246,7 +293,7 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
246
293
|
</Svg>
|
|
247
294
|
{/if}
|
|
248
295
|
|
|
249
|
-
{#if
|
|
296
|
+
{#if ['bisect-x', 'bisect-y', 'bisect-band', 'quadtree'].indexOf(mode) != -1}
|
|
250
297
|
<Html>
|
|
251
298
|
<div
|
|
252
299
|
class="absolute"
|
|
@@ -266,10 +313,14 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
266
313
|
<g class="tooltip-voronoi">
|
|
267
314
|
<path
|
|
268
315
|
d={voronoi.renderCell(i)}
|
|
269
|
-
style:fill=
|
|
316
|
+
style:fill={debug ? 'red' : 'transparent'}
|
|
317
|
+
style:fill-opacity={debug ? 0.1 : 0}
|
|
270
318
|
style:stroke={debug ? 'red' : 'transparent'}
|
|
271
319
|
on:mousemove={(e) => handleTooltip(e, point.data)}
|
|
272
320
|
on:mouseleave={hideTooltip}
|
|
321
|
+
on:click={(e) => {
|
|
322
|
+
dispatch('click', { data: point.data });
|
|
323
|
+
}}
|
|
273
324
|
/>
|
|
274
325
|
</g>
|
|
275
326
|
{/each}
|
|
@@ -283,10 +334,14 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
283
334
|
y={rect.y}
|
|
284
335
|
width={rect.width}
|
|
285
336
|
height={rect.height}
|
|
286
|
-
style:fill=
|
|
337
|
+
style:fill={debug ? 'red' : 'transparent'}
|
|
338
|
+
style:fill-opacity={debug ? 0.1 : 0}
|
|
287
339
|
style:stroke={debug ? 'red' : 'transparent'}
|
|
288
340
|
on:mousemove={(e) => handleTooltip(e, rect.data)}
|
|
289
341
|
on:mouseleave={hideTooltip}
|
|
342
|
+
on:click={(e) => {
|
|
343
|
+
dispatch('click', { data: rect.data });
|
|
344
|
+
}}
|
|
290
345
|
/>
|
|
291
346
|
{/each}
|
|
292
347
|
</g>
|
|
@@ -303,8 +358,9 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
303
358
|
y={rect.y}
|
|
304
359
|
width={rect.width}
|
|
305
360
|
height={rect.height}
|
|
361
|
+
style:fill={debug ? 'red' : 'transparent'}
|
|
362
|
+
style:fill-opacity={debug ? 0.1 : 0}
|
|
306
363
|
stroke="red"
|
|
307
|
-
fill="none"
|
|
308
364
|
/>
|
|
309
365
|
{/each}
|
|
310
366
|
</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,10 +3,12 @@ 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
|
|
10
|
+
* https://github.com/d3/d3-scale/blob/11777dac7d4b0b3e229d658aee3257ea67bd5ffa/src/band.js#L32
|
|
11
|
+
* https://gist.github.com/LuisSevillano/d53a1dc529eef518780c6df99613e2fd
|
|
10
12
|
*/
|
|
11
13
|
export declare function scaleBandInvert(scale: any): (value: any) => any;
|
|
12
14
|
export declare function isScaleBand(scale: any): boolean;
|
package/utils/scales.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
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
|
|
12
|
+
* https://github.com/d3/d3-scale/blob/11777dac7d4b0b3e229d658aee3257ea67bd5ffa/src/band.js#L32
|
|
13
|
+
* https://gist.github.com/LuisSevillano/d53a1dc529eef518780c6df99613e2fd
|
|
11
14
|
*/
|
|
12
15
|
export function scaleBandInvert(scale) {
|
|
13
16
|
const domain = scale.domain();
|
|
14
|
-
const paddingOuter = scale(domain
|
|
17
|
+
const paddingOuter = scale(min(domain));
|
|
15
18
|
const eachBand = scale.step();
|
|
16
19
|
return function (value) {
|
|
17
20
|
// TODO: Should this use Math.round to better select? https://stackoverflow.com/questions/38633082/d3-getting-invert-value-of-band-scales/50846323#comment104743795_50846323
|