Skip to content

Commit 7c501e6

Browse files
authored
Merge pull request #578 from techniq:tooltip-quadtree-x/y
feat(TooltipContext): Support `quadtree-x` and `quadtree-y` modes and update AreaChart, LineChart, and ScatterChart default modes. Resolves #525
2 parents a801b1d + b1b7365 commit 7c501e6

19 files changed

Lines changed: 126 additions & 54 deletions

File tree

.changeset/beige-doodles-shout.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'layerchart': patch
3+
---
4+
5+
fix(ScatterChart): Change default tooltip mode from `voronoi` to `quadtree`

.changeset/breezy-donuts-sniff.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'layerchart': patch
3+
---
4+
5+
feat(TooltipContext): Support `quadtree-x` and `quadtree-y` modes. Resolves #525

.changeset/honest-hoops-peel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'layerchart': patch
3+
---
4+
5+
fix(LineChart): Change default tooltip mode from `bisect-x` to `quadtree-x` (works with catagorical data and does not require data to be sorted)

.changeset/sharp-rockets-jam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'layerchart': patch
3+
---
4+
5+
fix(AreaChart): Change default tooltip mode from `bisect-x` to `quadtree-x` (works with catagorical data and does not require data to be sorted)

packages/layerchart/src/lib/components/charts/AreaChart.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@
443443
tooltip={tooltip === false
444444
? false
445445
: {
446-
mode: 'bisect-x',
446+
mode: 'quadtree-x',
447447
onclick: onTooltipClick,
448448
debug,
449449
...props.tooltip?.context,

packages/layerchart/src/lib/components/charts/LineChart.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@
343343
tooltip={tooltip === false
344344
? false
345345
: {
346-
mode: 'bisect-x',
346+
mode: 'quadtree-x',
347347
onclick: onTooltipClick,
348348
debug,
349349
...props.tooltip?.context,

packages/layerchart/src/lib/components/charts/ScatterChart.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@
253253
tooltip={tooltip === false
254254
? false
255255
: {
256-
mode: 'voronoi',
256+
mode: 'quadtree',
257257
onclick: onTooltipClick,
258258
debug,
259259
...props.tooltip?.context,

packages/layerchart/src/lib/components/tooltip/TooltipContext.svelte

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
const _TooltipContext = new Context<TooltipContextValue>('TooltipContext');
77
88
type TooltipMode =
9-
| 'bisect-x'
10-
| 'bisect-y'
9+
| 'bisect-x' // requires values to be sorted
10+
| 'bisect-y' // requires values to be sorted
1111
| 'band'
12-
| 'bisect-band'
12+
| 'bisect-band' // requires values to be sorted
1313
| 'bounds'
1414
| 'voronoi'
1515
| 'quadtree'
16+
| 'quadtree-x' // ignores y values (constant 0)
17+
| 'quadtree-y' // ignores x values (constant 0)
1618
| 'manual';
1719
1820
export type TooltipContextValue<T = any> = {
@@ -298,7 +300,6 @@
298300
}
299301
300302
// If tooltipData not provided already (voronoi, etc), attempt to find it
301-
// TODO: When using bisect-x/y/band, values should be sorted. Typically they are for `x`, but not `y` (and band depends on if x or y scale)
302303
if (tooltipData == null) {
303304
switch (mode) {
304305
case 'bisect-x': {
@@ -311,6 +312,7 @@
311312
xValueAtPoint = scaleInvert(ctx.xScale, point.x - ctx.padding.left);
312313
}
313314
315+
// Requires values to be sorted
314316
const index = bisectX(ctx.flatData, xValueAtPoint, 1);
315317
const previousValue = ctx.flatData[index - 1];
316318
const currentValue = ctx.flatData[index];
@@ -322,6 +324,7 @@
322324
// `y` value at pointer coordinate
323325
const yValueAtPoint = scaleInvert(ctx.yScale, point.y - ctx.padding.top);
324326
327+
// Requires values to be sorted
325328
const index = bisectY(ctx.flatData, yValueAtPoint, 1);
326329
const previousValue = ctx.flatData[index - 1];
327330
const currentValue = ctx.flatData[index];
@@ -339,6 +342,7 @@
339342
const bandData = ctx.flatData
340343
.filter((d) => ctx.x(d) === xValueAtPoint)
341344
.sort(sortFunc(ctx.y as () => any)); // sort for bisect
345+
// Requires values to be sorted
342346
const index = bisectY(bandData, yValueAtPoint, 1);
343347
const previousValue = bandData[index - 1];
344348
const currentValue = bandData[index];
@@ -348,6 +352,7 @@
348352
const bandData = ctx.flatData
349353
.filter((d) => ctx.y(d) === yValueAtPoint)
350354
.sort(sortFunc(ctx.x as () => any)); // sort for bisect
355+
// Requires values to be sorted
351356
const index = bisectX(bandData, xValueAtPoint, 1);
352357
const previousValue = bandData[index - 1];
353358
const currentValue = bandData[index];
@@ -358,6 +363,8 @@
358363
break;
359364
}
360365
366+
case 'quadtree-x':
367+
case 'quadtree-y':
361368
case 'quadtree': {
362369
tooltipData = quadtree?.find(
363370
point.x - ctx.padding.left,
@@ -406,9 +413,13 @@
406413
}
407414
408415
const quadtree: Quadtree<[number, number]> | undefined = $derived.by(() => {
409-
if (mode === 'quadtree') {
416+
if (['quadtree', 'quadtree-x', 'quadtree-y'].includes(mode)) {
410417
return d3Quadtree()
411418
.x((d) => {
419+
if (mode === 'quadtree-y') {
420+
return 0;
421+
}
422+
412423
if (geoCtx.projection) {
413424
const lat = ctx.x(d);
414425
const long = ctx.y(d);
@@ -429,6 +440,10 @@
429440
}
430441
})
431442
.y((d) => {
443+
if (mode === 'quadtree-x') {
444+
return 0;
445+
}
446+
432447
if (geoCtx.projection) {
433448
const lat = ctx.x(d);
434449
const long = ctx.y(d);
@@ -509,7 +524,7 @@
509524
});
510525
511526
const triggerPointerEvents = $derived(
512-
['bisect-x', 'bisect-y', 'bisect-band', 'quadtree'].includes(mode)
527+
['bisect-x', 'bisect-y', 'bisect-band', 'quadtree', 'quadtree-x', 'quadtree-y'].includes(mode)
513528
);
514529
515530
function onPointerEnter(e: PointerEvent | MouseEvent | TouchEvent) {
@@ -646,7 +661,7 @@
646661
{/each}
647662
</g>
648663
</Svg>
649-
{:else if mode === 'quadtree' && debug}
664+
{:else if ['quadtree', 'quadtree-x', 'quadtree-y'].includes(mode) && debug}
650665
<Svg pointerEvents={false}>
651666
<ChartClipPath>
652667
<g class={layerClass('tooltip-quadtree-g')}>

packages/layerchart/src/routes/docs/components/AreaChart/+page.svelte

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@
2222
import { curveBasis, curveCatmullRom, curveStepAfter } from 'd3-shape';
2323
import { group } from 'd3-array';
2424
import { timeDay } from 'd3-time';
25+
import { scaleBand, scalePoint } from 'd3-scale';
2526
import { Button, Field, Kbd, Switch } from 'svelte-ux';
2627
import { format, sortFunc } from '@layerstack/utils';
2728
import { cls } from '@layerstack/tailwind';
2829
2930
import Preview from '$lib/docs/Preview.svelte';
30-
import { createDateSeries, randomWalk } from '$lib/utils/genData.js';
31+
import { createDateSeries, longData, randomWalk } from '$lib/utils/genData.js';
3132
import type { DomainType } from '$lib/utils/scales.svelte.js';
3233
import Blockquote from '$lib/docs/Blockquote.svelte';
3334
import CurveMenuField from '$lib/docs/CurveMenuField.svelte';
@@ -348,7 +349,7 @@
348349
</div>
349350
</Preview>
350351

351-
<h2>Series (voronoi tooltip with highlight)</h2>
352+
<h2>Series (individual tooltip with highlight)</h2>
352353

353354
<Preview data={multiSeriesDataByFruit}>
354355
<div class="h-[300px] p-4 border rounded-sm">
@@ -372,7 +373,7 @@
372373
color: 'var(--color-warning)',
373374
},
374375
]}
375-
props={{ tooltip: { context: { mode: 'voronoi' } } }}
376+
props={{ tooltip: { context: { mode: 'quadtree' } } }}
376377
{renderContext}
377378
{debug}
378379
>
@@ -1322,6 +1323,40 @@
13221323
</div>
13231324
</Preview>
13241325

1326+
<h2>Point scale</h2>
1327+
1328+
<Preview data={dateSeriesData}>
1329+
<div class="h-[300px] p-4 border rounded-sm">
1330+
<AreaChart
1331+
data={longData.filter((d) => d.year === 2019)}
1332+
xScale={scalePoint()}
1333+
x="fruit"
1334+
y="value"
1335+
{renderContext}
1336+
{debug}
1337+
/>
1338+
</div>
1339+
</Preview>
1340+
1341+
<h2>Band scale</h2>
1342+
1343+
<Preview data={dateSeriesData}>
1344+
<div class="h-[300px] p-4 border rounded-sm">
1345+
<AreaChart
1346+
data={longData.filter((d) => d.year === 2019)}
1347+
xScale={scaleBand()}
1348+
x="fruit"
1349+
y="value"
1350+
tooltip={{
1351+
mode: 'band',
1352+
debug,
1353+
}}
1354+
{renderContext}
1355+
{debug}
1356+
/>
1357+
</div>
1358+
</Preview>
1359+
13251360
<h2>Custom chart</h2>
13261361

13271362
<Preview data={dateSeriesData}>

packages/layerchart/src/routes/docs/components/BrushContext/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@
629629
y="value"
630630
yDomain={[0, null]}
631631
padding={{ left: 16, bottom: 24 }}
632-
tooltip={{ mode: 'bisect-x' }}
632+
tooltip={{ mode: 'quadtree-x' }}
633633
brush={{
634634
resetOnEnd: true,
635635
onBrushEnd: (e) => {

0 commit comments

Comments
 (0)