layerchart 0.6.8 → 0.7.1
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 +103 -53
- package/components/Tooltip.svelte.d.ts +1 -1
- package/package.json +7 -7
- 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 = {
|
|
@@ -143,7 +188,7 @@ $: if (mode === 'voronoi') {
|
|
|
143
188
|
point.data = d;
|
|
144
189
|
return point;
|
|
145
190
|
});
|
|
146
|
-
voronoi = Delaunay.from(points).voronoi([0, 0, $width, $height]);
|
|
191
|
+
voronoi = Delaunay.from(points).voronoi([0, 0, Math.max($width, 0), Math.max($height, 0)]); // width and/or height can sometimes be negative (when loading data remotely and updately)
|
|
147
192
|
}
|
|
148
193
|
let quadtree;
|
|
149
194
|
$: if (mode === 'quadtree') {
|
|
@@ -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,7 +313,8 @@ $: 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}
|
|
@@ -283,7 +331,8 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
283
331
|
y={rect.y}
|
|
284
332
|
width={rect.width}
|
|
285
333
|
height={rect.height}
|
|
286
|
-
style:fill=
|
|
334
|
+
style:fill={debug ? 'red' : 'transparent'}
|
|
335
|
+
style:fill-opacity={debug ? 0.1 : 0}
|
|
287
336
|
style:stroke={debug ? 'red' : 'transparent'}
|
|
288
337
|
on:mousemove={(e) => handleTooltip(e, rect.data)}
|
|
289
338
|
on:mouseleave={hideTooltip}
|
|
@@ -303,8 +352,9 @@ $: if (mode === 'bounds' || mode === 'band') {
|
|
|
303
352
|
y={rect.y}
|
|
304
353
|
width={rect.width}
|
|
305
354
|
height={rect.height}
|
|
355
|
+
style:fill={debug ? 'red' : 'transparent'}
|
|
356
|
+
style:fill-opacity={debug ? 0.1 : 0}
|
|
306
357
|
stroke="red"
|
|
307
|
-
fill="none"
|
|
308
358
|
/>
|
|
309
359
|
{/each}
|
|
310
360
|
</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
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"author": "Sean Lynch <techniq35@gmail.com>",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "techniq/layerchart",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.7.1",
|
|
7
7
|
"devDependencies": {
|
|
8
8
|
"@rollup/plugin-dsv": "^2.0.3",
|
|
9
|
-
"@sveltejs/adapter-vercel": "^1.0.0-next.
|
|
10
|
-
"@sveltejs/kit": "^1.0.0-next.
|
|
9
|
+
"@sveltejs/adapter-vercel": "^1.0.0-next.59",
|
|
10
|
+
"@sveltejs/kit": "^1.0.0-next.354",
|
|
11
11
|
"@tailwindcss/typography": "^0.5.2",
|
|
12
12
|
"@types/d3-array": "^3.0.3",
|
|
13
13
|
"@types/d3-delaunay": "^6.0.1",
|
|
@@ -20,16 +20,16 @@
|
|
|
20
20
|
"@types/lodash-es": "^4.17.6",
|
|
21
21
|
"autoprefixer": "^10.4.7",
|
|
22
22
|
"mdsvex": "^0.10.6",
|
|
23
|
-
"prettier": "^2.
|
|
23
|
+
"prettier": "^2.7.1",
|
|
24
24
|
"prettier-plugin-svelte": "^2.7.0",
|
|
25
25
|
"prism-themes": "^1.9.0",
|
|
26
26
|
"svelte": "^3.48.0",
|
|
27
27
|
"svelte-check": "^2.7.2",
|
|
28
28
|
"svelte-preprocess": "^4.10.7",
|
|
29
29
|
"svelte2tsx": "^0.5.10",
|
|
30
|
-
"tailwindcss": "^3.1.
|
|
30
|
+
"tailwindcss": "^3.1.4",
|
|
31
31
|
"tslib": "^2.4.0",
|
|
32
|
-
"typescript": "^4.7.
|
|
32
|
+
"typescript": "^4.7.4",
|
|
33
33
|
"unist-util-visit": "^4.1.0",
|
|
34
34
|
"vite-plugin-sveld": "^1.0.3"
|
|
35
35
|
},
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"date-fns": "^2.28.0",
|
|
50
50
|
"layercake": "^6.1.0",
|
|
51
51
|
"lodash-es": "^4.17.21",
|
|
52
|
-
"svelte-ux": "^0.5
|
|
52
|
+
"svelte-ux": "^0.6.5"
|
|
53
53
|
},
|
|
54
54
|
"exports": {
|
|
55
55
|
"./package.json": "./package.json",
|
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
|