diff --git a/.changeset/pretty-roses-invent.md b/.changeset/pretty-roses-invent.md new file mode 100644 index 000000000..875b83e83 --- /dev/null +++ b/.changeset/pretty-roses-invent.md @@ -0,0 +1,5 @@ +--- +'layerchart': patch +--- + +breaking(Chart): Rename `tooltip` prop to `tooltipContext` to better describe purpose and fix conflict with new `tooltip` snippet diff --git a/.changeset/silver-baboons-smile.md b/.changeset/silver-baboons-smile.md new file mode 100644 index 000000000..c60426e34 --- /dev/null +++ b/.changeset/silver-baboons-smile.md @@ -0,0 +1,5 @@ +--- +'layerchart': patch +--- + +breaking(Arc|Pie|Calendar|GeoPath): Rename `tooltipContext` to simple `tooltip` (boolean), simplifying use case diff --git a/docs/src/content/components/GeoContext.md b/docs/src/content/components/GeoProjection.md similarity index 65% rename from docs/src/content/components/GeoContext.md rename to docs/src/content/components/GeoProjection.md index ec050d3ba..8febc8bbd 100644 --- a/docs/src/content/components/GeoContext.md +++ b/docs/src/content/components/GeoProjection.md @@ -10,6 +10,8 @@ order: 1 import Example from '$lib/components/Example.svelte'; +> Geographic projections / state are integrated into `` but `GeoProjection` can be used to provide a secondary projection / context, such as for a translucent globe effect + ## Playground diff --git a/docs/src/examples/components/ArcChart/series-labels.svelte b/docs/src/examples/components/ArcChart/series-labels.svelte index b946e9a7a..d8d014e18 100644 --- a/docs/src/examples/components/ArcChart/series-labels.svelte +++ b/docs/src/examples/components/ArcChart/series-labels.svelte @@ -25,12 +25,12 @@ cornerRadius={10} height={180} > - {#snippet arc({ props, seriesIndex, visibleSeries })} + {#snippet arc({ context, props, seriesIndex })} {#snippet children({ getArcTextProps })} diff --git a/docs/src/examples/components/AreaChart/series-individual-tooltip.svelte b/docs/src/examples/components/AreaChart/series-individual-tooltip.svelte index 3e6f613f3..b61b138d7 100644 --- a/docs/src/examples/components/AreaChart/series-individual-tooltip.svelte +++ b/docs/src/examples/components/AreaChart/series-individual-tooltip.svelte @@ -50,8 +50,8 @@ padding={{ ...defaultChartPadding(), right: 10 }} height={300} > - {#snippet marks({ series, context })} - {#each series as s} + {#snippet marks({ context })} + {#each context.series.series as s} {@const activeSeries = context.tooltip?.data == null || context.tooltip?.data?.fruit === s.key} @@ -61,13 +61,17 @@ {/each} {/snippet} - {#snippet highlight({ series, context })} - {@const activeSeries = series.find((s) => s.key === context.tooltip?.data?.fruit)} + {#snippet highlight({ context })} + {@const activeSeries = context.series.series.find( + (s) => s.key === context.tooltip?.data?.fruit + )} {/snippet} - {#snippet tooltip({ series, context })} - {@const activeSeries = series.find((s) => s.key === context.tooltip?.data?.fruit)} + {#snippet tooltip({ context })} + {@const activeSeries = context.series.series.find( + (s) => s.key === context.tooltip?.data?.fruit + )} {#snippet children({ data })} {format(context.x(data))} diff --git a/docs/src/examples/components/AreaChart/series-stack-gradient.svelte b/docs/src/examples/components/AreaChart/series-stack-gradient.svelte index 8cf742e05..1ed5cf018 100644 --- a/docs/src/examples/components/AreaChart/series-stack-gradient.svelte +++ b/docs/src/examples/components/AreaChart/series-stack-gradient.svelte @@ -30,8 +30,8 @@ padding={{ ...defaultChartPadding(), right: 10 }} height={300} > - {#snippet marks({ series, getAreaProps })} - {#each series as s, i (s.key)} + {#snippet marks({ context, getAreaProps })} + {#each context.series.series as s, i (s.key)} - {#snippet marks({ series, getBarsProps })} - {#each series as s, i (s.key)} + {#snippet marks({ context, getBarsProps })} + {#each context.series.series as s, i (s.key)} {#snippet children({ gradient })} diff --git a/docs/src/examples/components/BarChart/group-series-labels.svelte b/docs/src/examples/components/BarChart/group-series-labels.svelte index e60d47cca..43efd4961 100644 --- a/docs/src/examples/components/BarChart/group-series-labels.svelte +++ b/docs/src/examples/components/BarChart/group-series-labels.svelte @@ -37,8 +37,8 @@ height={300} > - {#snippet aboveMarks({ context, visibleSeries })} - {#each visibleSeries as s} + {#snippet aboveMarks({ context })} + {#each context.series.visibleSeries as s} {#each wideData as d} {@const valueAccessor = accessor(s.key)} {@const value = valueAccessor(d)} diff --git a/docs/src/examples/components/BarChart/series-horizontal-diverging-as-percent.svelte b/docs/src/examples/components/BarChart/series-horizontal-diverging-as-percent.svelte index b4818c9fb..99bb2f1cd 100644 --- a/docs/src/examples/components/BarChart/series-horizontal-diverging-as-percent.svelte +++ b/docs/src/examples/components/BarChart/series-horizontal-diverging-as-percent.svelte @@ -53,12 +53,12 @@ ]} height={600} > - {#snippet tooltip({ series, context })} + {#snippet tooltip({ context })} {#snippet children({ data })} Age: {format(context.y(data))} - {#each series as s} + {#each context.series.series as s} {@const valueAccessor = accessor(s.value ?? s.key)} {@const value = Math.abs(valueAccessor(data))} diff --git a/docs/src/examples/components/BrushContext/constant-labels.svelte b/docs/src/examples/components/BrushContext/constant-labels.svelte index 44aa4315b..fe3d6f6e0 100644 --- a/docs/src/examples/components/BrushContext/constant-labels.svelte +++ b/docs/src/examples/components/BrushContext/constant-labels.svelte @@ -12,11 +12,11 @@ - {#if context.brush.isActive} + {#if context.brush.active} diff --git a/docs/src/examples/components/BrushContext/handle-arrows.svelte b/docs/src/examples/components/BrushContext/handle-arrows.svelte index 1b7e4c86d..10f61771c 100644 --- a/docs/src/examples/components/BrushContext/handle-arrows.svelte +++ b/docs/src/examples/components/BrushContext/handle-arrows.svelte @@ -21,7 +21,7 @@ - {#if context.brush.isActive} + {#if context.brush.active} - {#if context.brush.isActive} + {#if context.brush.active} diff --git a/docs/src/examples/components/BrushContext/integrated-brush-(both-axis-area).svelte b/docs/src/examples/components/BrushContext/integrated-brush-(both-axis-area).svelte index b77d44706..8f49fd35a 100644 --- a/docs/src/examples/components/BrushContext/integrated-brush-(both-axis-area).svelte +++ b/docs/src/examples/components/BrushContext/integrated-brush-(both-axis-area).svelte @@ -28,8 +28,8 @@ axis: 'both', resetOnEnd: true, onBrushEnd: (e) => { - xDomain = e.xDomain; - yDomain = e.yDomain; + xDomain = e.brush.x; + yDomain = e.brush.y; } }} height={300} diff --git a/docs/src/examples/components/BrushContext/integrated-brush-(x-axis).svelte b/docs/src/examples/components/BrushContext/integrated-brush-(x-axis).svelte index 13fb7b492..ca975af9e 100644 --- a/docs/src/examples/components/BrushContext/integrated-brush-(x-axis).svelte +++ b/docs/src/examples/components/BrushContext/integrated-brush-(x-axis).svelte @@ -26,7 +26,7 @@ brush={{ resetOnEnd: true, onBrushEnd: (e) => { - xDomain = e.xDomain; + xDomain = e.brush.x; } }} height={300} diff --git a/docs/src/examples/components/BrushContext/integrated-brush-(y-axis).svelte b/docs/src/examples/components/BrushContext/integrated-brush-(y-axis).svelte index f2c044715..6ba043a11 100644 --- a/docs/src/examples/components/BrushContext/integrated-brush-(y-axis).svelte +++ b/docs/src/examples/components/BrushContext/integrated-brush-(y-axis).svelte @@ -27,7 +27,7 @@ axis: 'y', resetOnEnd: true, onBrushEnd: (e) => { - yDomain = e.yDomain; + yDomain = e.brush.y; } }} height={300} diff --git a/docs/src/examples/components/BrushContext/minimap.svelte b/docs/src/examples/components/BrushContext/minimap.svelte index 4b039f144..256fe2095 100644 --- a/docs/src/examples/components/BrushContext/minimap.svelte +++ b/docs/src/examples/components/BrushContext/minimap.svelte @@ -25,8 +25,8 @@ axis: 'both', resetOnEnd: true, onBrushEnd: (e) => { - xDomain = e.xDomain; - yDomain = e.yDomain; + xDomain = e.brush.x; + yDomain = e.brush.y; } }} height={400} @@ -50,11 +50,11 @@ brush={{ axis: 'both', mode: 'separated', - xDomain: xDomain, - yDomain: yDomain, + x: xDomain as any, + y: yDomain as any, onChange: (e) => { - xDomain = e.xDomain; - yDomain = e.yDomain; + xDomain = e.brush.x; + yDomain = e.brush.y; } }} > diff --git a/docs/src/examples/components/BrushContext/selection.svelte b/docs/src/examples/components/BrushContext/selection.svelte index 9ee462326..ea05102d8 100644 --- a/docs/src/examples/components/BrushContext/selection.svelte +++ b/docs/src/examples/components/BrushContext/selection.svelte @@ -22,8 +22,8 @@ brush={{ axis: 'both', onChange: (e) => { - xDomain = e.xDomain; - yDomain = e.yDomain; + xDomain = e.brush.x; + yDomain = e.brush.y; } }} height={400} diff --git a/docs/src/examples/components/BrushContext/separate-chart-(clip-data).svelte b/docs/src/examples/components/BrushContext/separate-chart-(clip-data).svelte index a59f502e7..4a977c019 100644 --- a/docs/src/examples/components/BrushContext/separate-chart-(clip-data).svelte +++ b/docs/src/examples/components/BrushContext/separate-chart-(clip-data).svelte @@ -45,7 +45,7 @@ padding={{ left: 16 }} brush={{ onChange: (e) => { - xDomain = e.xDomain; + xDomain = e.brush.x; } }} height={40} diff --git a/docs/src/examples/components/BrushContext/separate-chart-(clip-data-y-axis).svelte b/docs/src/examples/components/BrushContext/separate-chart-(clip-data-y-axis).svelte index 147b8a8a8..44a7fd7dd 100644 --- a/docs/src/examples/components/BrushContext/separate-chart-(clip-data-y-axis).svelte +++ b/docs/src/examples/components/BrushContext/separate-chart-(clip-data-y-axis).svelte @@ -26,7 +26,7 @@ brush={{ axis: 'y', onChange: (e) => { - yDomain = e.yDomain; + yDomain = e.brush.y; } }} height={300} diff --git a/docs/src/examples/components/BrushContext/separate-chart-(filter-data).svelte b/docs/src/examples/components/BrushContext/separate-chart-(filter-data).svelte index 6e7f5530b..09de6d889 100644 --- a/docs/src/examples/components/BrushContext/separate-chart-(filter-data).svelte +++ b/docs/src/examples/components/BrushContext/separate-chart-(filter-data).svelte @@ -45,7 +45,7 @@ padding={{ left: 16 }} brush={{ onChange: (e) => { - xDomain = e.xDomain; + xDomain = e.brush.x; } }} height={40} diff --git a/docs/src/examples/components/BrushContext/sync-brushes-with-bind-xdomain.svelte b/docs/src/examples/components/BrushContext/sync-brushes-with-bind-xdomain.svelte index 361a25d3f..b8a984c1c 100644 --- a/docs/src/examples/components/BrushContext/sync-brushes-with-bind-xdomain.svelte +++ b/docs/src/examples/components/BrushContext/sync-brushes-with-bind-xdomain.svelte @@ -85,8 +85,8 @@ padding={{ left: 16 }} brush={{ mode: 'separated', - xDomain, - onChange: (e) => (xDomain = e.xDomain), + x: xDomain as any, + onChange: (e) => (xDomain = e.brush.x), onReset: (e) => (xDomain = null) }} height={20} diff --git a/docs/src/examples/components/BrushContext/tooltip-interop.svelte b/docs/src/examples/components/BrushContext/tooltip-interop.svelte index 1b9618f8a..bb242375e 100644 --- a/docs/src/examples/components/BrushContext/tooltip-interop.svelte +++ b/docs/src/examples/components/BrushContext/tooltip-interop.svelte @@ -30,7 +30,7 @@ brush={{ resetOnEnd: true, onBrushEnd: (e) => { - xDomain = e.xDomain; + xDomain = e.brush.x; } }} height={300} diff --git a/docs/src/examples/components/Calendar/90-days.svelte b/docs/src/examples/components/Calendar/90-days.svelte index da336648b..8c439e953 100644 --- a/docs/src/examples/components/Calendar/90-days.svelte +++ b/docs/src/examples/components/Calendar/90-days.svelte @@ -34,21 +34,19 @@ padding={{ top: 20, bottom: 20 }} height={200} > - {#snippet children({ context })} - - - + + + - - {#snippet children({ data })} - + + {#snippet children({ data })} + - {#if data.value != null} - - - - {/if} - {/snippet} - - {/snippet} + {#if data.value != null} + + + + {/if} + {/snippet} + diff --git a/docs/src/examples/components/Calendar/fixed-cell-size.svelte b/docs/src/examples/components/Calendar/fixed-cell-size.svelte index 598b90d83..7f5e37aa4 100644 --- a/docs/src/examples/components/Calendar/fixed-cell-size.svelte +++ b/docs/src/examples/components/Calendar/fixed-cell-size.svelte @@ -36,27 +36,19 @@ padding={{ top: 20 }} height={140} > - {#snippet children({ context })} - - - + + + - - {#snippet children({ data })} - + + {#snippet children({ data })} + - {#if data.value != null} - - - - {/if} - {/snippet} - - {/snippet} + {#if data.value != null} + + + + {/if} + {/snippet} + diff --git a/docs/src/examples/components/Calendar/html-with-padding.svelte b/docs/src/examples/components/Calendar/html-with-padding.svelte index 1ba4adc5d..1f8441368 100644 --- a/docs/src/examples/components/Calendar/html-with-padding.svelte +++ b/docs/src/examples/components/Calendar/html-with-padding.svelte @@ -38,7 +38,7 @@ > {#snippet children({ context })} - + {#snippet children({ cells, cellSize })} {#each cells as cell}
- {#snippet children({ context })} - - - + + + - - {#snippet children({ data })} - + + {#snippet children({ data })} + - {#if data.value != null} - - - - {/if} - {/snippet} - - {/snippet} + {#if data.value != null} + + + + {/if} + {/snippet} + diff --git a/docs/src/examples/components/Calendar/multiple-years.svelte b/docs/src/examples/components/Calendar/multiple-years.svelte index 3502f63f7..413f673bf 100644 --- a/docs/src/examples/components/Calendar/multiple-years.svelte +++ b/docs/src/examples/components/Calendar/multiple-years.svelte @@ -32,36 +32,34 @@ padding={{ top: 20, left: 20 }} height={450} > - {#snippet children({ context })} - - {#each range(2021, 2024) as year, i} - {@const start = new Date(year, 0, 1)} - {@const end = endOfInterval('year', start)} - - - - - {/each} - + + {#each range(2021, 2024) as year, i} + {@const start = new Date(year, 0, 1)} + {@const end = endOfInterval('year', start)} + + + + + {/each} + - - {#snippet children({ data })} - + + {#snippet children({ data })} + - {#if data.value != null} - - - - {/if} - {/snippet} - - {/snippet} + {#if data.value != null} + + + + {/if} + {/snippet} + diff --git a/docs/src/examples/components/Calendar/responsive-cell-size-default.svelte b/docs/src/examples/components/Calendar/responsive-cell-size-default.svelte index e63363b9f..33a46e04e 100644 --- a/docs/src/examples/components/Calendar/responsive-cell-size-default.svelte +++ b/docs/src/examples/components/Calendar/responsive-cell-size-default.svelte @@ -36,26 +36,19 @@ padding={{ top: 20 }} height={140} > - {#snippet children({ context })} - - - + + + - - {#snippet children({ data })} - + + {#snippet children({ data })} + - {#if data.value != null} - - - - {/if} - {/snippet} - - {/snippet} + {#if data.value != null} + + + + {/if} + {/snippet} + diff --git a/docs/src/examples/components/Chart/data-chart-multi.svelte b/docs/src/examples/components/Chart/data-chart-multi.svelte new file mode 100644 index 000000000..22e71d71b --- /dev/null +++ b/docs/src/examples/components/Chart/data-chart-multi.svelte @@ -0,0 +1,19 @@ + + + + {#snippet marks()} + + + {/snippet} + diff --git a/docs/src/examples/components/Chart/data-chart-single.svelte b/docs/src/examples/components/Chart/data-chart-single.svelte new file mode 100644 index 000000000..f2f672d5e --- /dev/null +++ b/docs/src/examples/components/Chart/data-chart-single.svelte @@ -0,0 +1,18 @@ + + + + {#snippet marks()} + + {/snippet} + diff --git a/docs/src/examples/components/Chart/data-series-chart-data.svelte b/docs/src/examples/components/Chart/data-series-chart-data.svelte new file mode 100644 index 000000000..0497a72c9 --- /dev/null +++ b/docs/src/examples/components/Chart/data-series-chart-data.svelte @@ -0,0 +1,28 @@ + + + + + + diff --git a/docs/src/examples/components/Chart/data-series-separate-data.svelte b/docs/src/examples/components/Chart/data-series-separate-data.svelte new file mode 100644 index 000000000..4c6877338 --- /dev/null +++ b/docs/src/examples/components/Chart/data-series-separate-data.svelte @@ -0,0 +1,37 @@ + + + + + + diff --git a/docs/src/examples/components/GeoPath/animated-globe.svelte b/docs/src/examples/components/GeoPath/animated-globe.svelte index e47fef053..1bb420f37 100644 --- a/docs/src/examples/components/GeoPath/animated-globe.svelte +++ b/docs/src/examples/components/GeoPath/animated-globe.svelte @@ -11,7 +11,7 @@ Layer, Tooltip, defaultChartPadding, - type ChartContextValue + type ChartState } from 'layerchart'; import { Button } from 'svelte-ux'; import { sortFunc } from '@layerstack/utils'; @@ -26,7 +26,7 @@ const topology = await getCountriesTopology(); const countries = feature(topology, topology.objects.countries); - let context = $state(null!); + let context = $state(null!); let selectedFeature: (typeof countries.features)[0] | null = $state(null); @@ -136,44 +136,42 @@ padding={{ ...defaultChartPadding, left: 5, right: 5 }} height={600} > - {#snippet children()} - {#if debug} -
- -
- {/if} - - - - - - {#each countries.features as country (country)} - (selectedFeature = country)} - tooltipContext={context.tooltip} - /> - {/each} + {#if debug} +
+ +
+ {/if} + + + + + + {#each countries.features as country (country)} + (selectedFeature = country)} + tooltip + /> + {/each} + + + {#if layer === 'canvas'} + + + {#if context.tooltip.data} + + {/if} + {/if} - {#if layer === 'canvas'} - - - {#if context.tooltip.data} - - {/if} - - {/if} - - - {context.tooltip.data.properties.name} - - {/snippet} + + {context.tooltip.data.properties.name} +
diff --git a/docs/src/examples/components/GeoPath/bubble-map.svelte b/docs/src/examples/components/GeoPath/bubble-map.svelte index a222df86e..2d7813714 100644 --- a/docs/src/examples/components/GeoPath/bubble-map.svelte +++ b/docs/src/examples/components/GeoPath/bubble-map.svelte @@ -110,7 +110,7 @@ {#each enrichedCountiesFeatures as feature} diff --git a/docs/src/examples/components/GeoPath/choropleth.svelte b/docs/src/examples/components/GeoPath/choropleth.svelte index c42d9e4f3..0bfd06609 100644 --- a/docs/src/examples/components/GeoPath/choropleth.svelte +++ b/docs/src/examples/components/GeoPath/choropleth.svelte @@ -79,7 +79,7 @@ fill={colorScale(feature.properties.data?.population ?? 0)} class="stroke-none hover:stroke-white" {strokeWidth} - tooltipContext={context.tooltip} + tooltip /> {/each} diff --git a/docs/src/examples/components/GeoPath/spike-map.svelte b/docs/src/examples/components/GeoPath/spike-map.svelte index 47d775c2c..12f0c448c 100644 --- a/docs/src/examples/components/GeoPath/spike-map.svelte +++ b/docs/src/examples/components/GeoPath/spike-map.svelte @@ -91,7 +91,7 @@ {#each enrichedCountiesFeatures as feature} diff --git a/docs/src/examples/components/GeoPath/timezones.svelte b/docs/src/examples/components/GeoPath/timezones.svelte index 444d0b9ee..af3f67a74 100644 --- a/docs/src/examples/components/GeoPath/timezones.svelte +++ b/docs/src/examples/components/GeoPath/timezones.svelte @@ -98,58 +98,50 @@ padding={{ left: 10, right: 10 }} height={600} > - {#snippet children({ context })} - - - - - - {#each timezoneGeojson.features as feature} - - {/each} - + + - {#each countriesGeojson.features as feature} + + + {#each timezoneGeojson.features as feature} {/each} + - {#each statesGeojson.features as feature} - - {/each} + {#each countriesGeojson.features as feature} + + {/each} + + {#each statesGeojson.features as feature} + + {/each} - {#if showDaylight} - - - - - - {/if} - - - - {#snippet children({ data })} - {@const { tz_name1st, time_zone, places } = data.properties} - - - - - - - {/snippet} - - {/snippet} + {#if showDaylight} + + + + + + {/if} + + + + {#snippet children({ data })} + {@const { tz_name1st, time_zone, places } = data.properties} + + + + + + + {/snippet} + diff --git a/docs/src/examples/components/GeoPath/tooltip.svelte b/docs/src/examples/components/GeoPath/tooltip.svelte index 9660aa5be..05b309b78 100644 --- a/docs/src/examples/components/GeoPath/tooltip.svelte +++ b/docs/src/examples/components/GeoPath/tooltip.svelte @@ -24,7 +24,7 @@ {/each} diff --git a/docs/src/examples/components/GeoPath/transform-canvas.svelte b/docs/src/examples/components/GeoPath/transform-canvas.svelte index 77c57b434..5421db2b6 100644 --- a/docs/src/examples/components/GeoPath/transform-canvas.svelte +++ b/docs/src/examples/components/GeoPath/transform-canvas.svelte @@ -67,7 +67,7 @@ geojson={feature} class="stroke-surface-content fill-surface-100 hover:fill-surface-content/10" strokeWidth={1 / context.transform.scale} - tooltipContext={context.tooltip} + tooltip onclick={(e, geoPath) => { context.tooltip.hide(); if (selectedStateId === feature.id) { @@ -96,7 +96,7 @@ { diff --git a/docs/src/examples/components/GeoPath/transform-projection.svelte b/docs/src/examples/components/GeoPath/transform-projection.svelte index 28002054e..0549eaf5a 100644 --- a/docs/src/examples/components/GeoPath/transform-projection.svelte +++ b/docs/src/examples/components/GeoPath/transform-projection.svelte @@ -67,7 +67,7 @@ { context.tooltip.hide(); if (selectedStateId === feature.id) { @@ -94,7 +94,7 @@ { selectedStateId = null; diff --git a/docs/src/examples/components/GeoPath/translucent-globe.svelte b/docs/src/examples/components/GeoPath/translucent-globe.svelte index 60a8f7e15..4fcbeb74e 100644 --- a/docs/src/examples/components/GeoPath/translucent-globe.svelte +++ b/docs/src/examples/components/GeoPath/translucent-globe.svelte @@ -4,13 +4,13 @@ import { Chart, - GeoContext, + GeoProjection, GeoPath, Graticule, Layer, Tooltip, defaultChartPadding, - type ChartContextValue + type ChartState } from 'layerchart'; import GeoPathTranslucentControls from '$lib/components/controls/GeoPathGlobeControls2.svelte'; import { TimerState } from '@layerstack/svelte-state'; @@ -19,7 +19,7 @@ const topology = await getCountriesTopology(); const countries = feature(topology, topology.objects.countries); - let context = $state(); + let context = $state(); let velocity = $state(3); const timer = new TimerState({ @@ -60,7 +60,7 @@ - {/each} - + @@ -78,7 +78,7 @@ {/each}
diff --git a/docs/src/examples/components/GeoPath/us-state-with-counties.svelte b/docs/src/examples/components/GeoPath/us-state-with-counties.svelte index 9b2fed17a..3312761a3 100644 --- a/docs/src/examples/components/GeoPath/us-state-with-counties.svelte +++ b/docs/src/examples/components/GeoPath/us-state-with-counties.svelte @@ -51,7 +51,7 @@ {/each} {/each} {#each states.features as feature} diff --git a/docs/src/examples/components/GeoContext/geojson-preview.svelte b/docs/src/examples/components/GeoProjection/geojson-preview.svelte similarity index 76% rename from docs/src/examples/components/GeoContext/geojson-preview.svelte rename to docs/src/examples/components/GeoProjection/geojson-preview.svelte index 96ae11bb3..39cc46299 100644 --- a/docs/src/examples/components/GeoContext/geojson-preview.svelte +++ b/docs/src/examples/components/GeoProjection/geojson-preview.svelte @@ -97,42 +97,40 @@ }} padding={{ top: 8, bottom: 8, left: 8, right: 8 }} > - {#snippet children({ context })} - {#if projection === geoMercator && serviceUrl} - - - - - - - - {/if} - - - + {#if projection === geoMercator && serviceUrl} - {#if geojson?.features} - {#each geojson?.features as feature} - - {/each} - {/if} + + + + + + {/if} + + + + + {#if geojson?.features} + {#each geojson?.features as feature} + + {/each} + {/if} + - - {#snippet children({ data })} - - {#each Object.entries(data.properties) as [key, value]} - - {/each} - - {/snippet} - - {/snippet} + + {#snippet children({ data })} + + {#each Object.entries(data.properties) as [key, value]} + + {/each} + + {/snippet} + {:else} Please enter input below diff --git a/docs/src/examples/components/GeoContext/projection-playground.svelte b/docs/src/examples/components/GeoProjection/projection-playground.svelte similarity index 98% rename from docs/src/examples/components/GeoContext/projection-playground.svelte rename to docs/src/examples/components/GeoProjection/projection-playground.svelte index b597e33ae..73ca5c049 100644 --- a/docs/src/examples/components/GeoContext/projection-playground.svelte +++ b/docs/src/examples/components/GeoProjection/projection-playground.svelte @@ -78,7 +78,7 @@ {#each features as feature} {/each} diff --git a/docs/src/examples/components/GeoContext/shapefile-preview.svelte b/docs/src/examples/components/GeoProjection/shapefile-preview.svelte similarity index 100% rename from docs/src/examples/components/GeoContext/shapefile-preview.svelte rename to docs/src/examples/components/GeoProjection/shapefile-preview.svelte diff --git a/docs/src/examples/components/GeoContext/topojson-preview.svelte b/docs/src/examples/components/GeoProjection/topojson-preview.svelte similarity index 80% rename from docs/src/examples/components/GeoContext/topojson-preview.svelte rename to docs/src/examples/components/GeoProjection/topojson-preview.svelte index d16e1f140..43a9b9f27 100644 --- a/docs/src/examples/components/GeoContext/topojson-preview.svelte +++ b/docs/src/examples/components/GeoProjection/topojson-preview.svelte @@ -105,42 +105,40 @@ }} padding={{ top: 8, bottom: 8, left: 8, right: 8 }} > - {#snippet children({ context })} - {#if projection === geoMercator && serviceUrl} - - - - - - - - {/if} - - - + {#if projection === geoMercator && serviceUrl} - {#if geojson?.features} - {#each geojson.features as feature} - - {/each} - {/if} + + + + + + {/if} + + + + + {#if geojson?.features} + {#each geojson.features as feature} + + {/each} + {/if} + - - {#snippet children({ data })} - - {#each Object.entries(data.properties) as [key, value]} - - {/each} - - {/snippet} - - {/snippet} + + {#snippet children({ data })} + + {#each Object.entries(data.properties) as [key, value]} + + {/each} + + {/snippet} + {:else} Please enter input below diff --git a/docs/src/examples/components/GeoTile/basic.svelte b/docs/src/examples/components/GeoTile/basic.svelte index a8072056e..8e3da5d3c 100644 --- a/docs/src/examples/components/GeoTile/basic.svelte +++ b/docs/src/examples/components/GeoTile/basic.svelte @@ -45,7 +45,7 @@ (selectedFeature = selectedFeature === feature ? filteredStates : feature)} /> diff --git a/docs/src/examples/components/GeoTile/clipped.svelte b/docs/src/examples/components/GeoTile/clipped.svelte index 7584b6086..361bbc2e3 100644 --- a/docs/src/examples/components/GeoTile/clipped.svelte +++ b/docs/src/examples/components/GeoTile/clipped.svelte @@ -46,7 +46,7 @@ {#each filteredStates.features as feature} (selectedFeature = selectedFeature === feature ? filteredStates : feature)} /> diff --git a/docs/src/examples/components/GeoTile/zoomable-seamless-layers.svelte b/docs/src/examples/components/GeoTile/zoomable-seamless-layers.svelte index b7adab6de..0edcd15d2 100644 --- a/docs/src/examples/components/GeoTile/zoomable-seamless-layers.svelte +++ b/docs/src/examples/components/GeoTile/zoomable-seamless-layers.svelte @@ -72,7 +72,7 @@ { if (!context.geo.projection) return; const featureTransform = geoFitObjectTransform( diff --git a/docs/src/examples/components/GeoTile/zoomable-with-padding.svelte b/docs/src/examples/components/GeoTile/zoomable-with-padding.svelte index 0810fda8d..db537d722 100644 --- a/docs/src/examples/components/GeoTile/zoomable-with-padding.svelte +++ b/docs/src/examples/components/GeoTile/zoomable-with-padding.svelte @@ -75,7 +75,7 @@ { if (!context.geo.projection) return; const featureTransform = geoFitObjectTransform( diff --git a/docs/src/examples/components/GeoTile/zoomable.svelte b/docs/src/examples/components/GeoTile/zoomable.svelte index beadb3e00..7caf2c7c9 100644 --- a/docs/src/examples/components/GeoTile/zoomable.svelte +++ b/docs/src/examples/components/GeoTile/zoomable.svelte @@ -68,7 +68,7 @@ { if (!context.geo.projection) return; const featureTransform = geoFitObjectTransform( diff --git a/docs/src/examples/components/LineChart/series-individual-tooltip.svelte b/docs/src/examples/components/LineChart/series-individual-tooltip.svelte index 82fd8b83b..08c183647 100644 --- a/docs/src/examples/components/LineChart/series-individual-tooltip.svelte +++ b/docs/src/examples/components/LineChart/series-individual-tooltip.svelte @@ -41,22 +41,26 @@ padding={{ ...defaultChartPadding(), right: 10 }} height={300} > - {#snippet marks({ context, visibleSeries, highlightKey })} - {#each visibleSeries as s} + {#snippet marks({ context })} + {#each context.series.visibleSeries as s} {@const active = (context.tooltip.data == null || s.key === context.tooltip.data?.fruit) && - (highlightKey === null || s.key === highlightKey)} + (context.series.highlightKey === null || s.key === context.series.highlightKey)} {/each} {/snippet} - {#snippet highlight({ series, context })} - {@const activeSeriesColor = series.find((s) => s.key === context.tooltip.data?.fruit)?.color} + {#snippet highlight({ context })} + {@const activeSeriesColor = context.series.series.find( + (s) => s.key === context.tooltip.data?.fruit + )?.color} {/snippet} - {#snippet tooltip({ context, series })} - {@const activeSeriesColor = series.find((s) => s.key === context.tooltip.data?.fruit)?.color} + {#snippet tooltip({ context })} + {@const activeSeriesColor = context.series.series.find( + (s) => s.key === context.tooltip.data?.fruit + )?.color} {#snippet children({ data })} {format(context.x(data))} diff --git a/docs/src/examples/components/LineChart/series-labels-hover.svelte b/docs/src/examples/components/LineChart/series-labels-hover.svelte index 3fc9a9255..488c5a084 100644 --- a/docs/src/examples/components/LineChart/series-labels-hover.svelte +++ b/docs/src/examples/components/LineChart/series-labels-hover.svelte @@ -29,10 +29,15 @@ padding={20} height={300} > - {#snippet aboveMarks({ getLabelsProps, series, highlightKey })} - {#if highlightKey} - {@const activeSeriesIndex = series.findIndex((s) => s.key === highlightKey)} - + {#snippet aboveMarks({ context, getLabelsProps })} + {#if context.series.highlightKey} + {@const activeSeriesIndex = context.series.series.findIndex( + (s) => s.key === context.series.highlightKey + )} + {/if} {/snippet} diff --git a/docs/src/examples/components/LineChart/series-with-nulls.svelte b/docs/src/examples/components/LineChart/series-with-nulls.svelte index b39a9f879..fe396567d 100644 --- a/docs/src/examples/components/LineChart/series-with-nulls.svelte +++ b/docs/src/examples/components/LineChart/series-with-nulls.svelte @@ -32,15 +32,15 @@ height={300} padding={{ ...defaultChartPadding(), right: 10 }} > - {#snippet belowMarks({ visibleSeries, highlightKey })} - {#each visibleSeries as s} + {#snippet belowMarks({ context })} + {#each context.series.visibleSeries as s} d[s.key] !== null)} y={s.key} stroke={s.color} class={cls( '[stroke-dasharray:3,3] transition-opacity', - highlightKey && highlightKey !== s.key && 'opacity-10' + context.series.highlightKey && context.series.highlightKey !== s.key && 'opacity-10' )} /> {/each} diff --git a/docs/src/examples/components/Pie/tooltip.svelte b/docs/src/examples/components/Pie/tooltip.svelte index e6b314151..a57406d52 100644 --- a/docs/src/examples/components/Pie/tooltip.svelte +++ b/docs/src/examples/components/Pie/tooltip.svelte @@ -17,23 +17,21 @@ - {#snippet children({ context })} - - - - - {#snippet children({ data })} - - - - - - {/snippet} - - {/snippet} + + + + + {#snippet children({ data })} + + + + + + {/snippet} + diff --git a/docs/src/lib/components/DocsMenu.svelte b/docs/src/lib/components/DocsMenu.svelte index 414432fd8..a5a0ee836 100644 --- a/docs/src/lib/components/DocsMenu.svelte +++ b/docs/src/lib/components/DocsMenu.svelte @@ -25,6 +25,7 @@ { name: 'Features', path: 'features' }, { name: 'Layers', path: 'layers' }, { name: 'Primitives', path: 'primitives' }, + { name: 'Data', path: 'data' }, { name: 'Simplified charts', path: 'simplified-charts' }, { name: 'Scales', path: 'scales' }, { name: 'State', path: 'state' }, diff --git a/docs/src/routes/docs/guides/data/+page.md b/docs/src/routes/docs/guides/data/+page.md new file mode 100644 index 000000000..51eccde7a --- /dev/null +++ b/docs/src/routes/docs/guides/data/+page.md @@ -0,0 +1,84 @@ + + +# Data + +LayerChart supports passing data in a variety of ways including: + +- Chart + ```svelte + + ``` +- Per-series + ```svelte + + ``` +- Per-mark + ```svelte + + ``` + +all supporting single and multiple marks per Chart. + +## Chart data + +### Single mark + + + +### Multiple marks + + + +> TODO: improve tooltip, legend support without requiring series + +> TODO: do not require passing in `y={[...]}` as well + +## Marks data + +> Can also provide accessors Spline. Currently need to pass overall chart data (or series) (see next) + +```svelte + + {#snippet marks()} + + {/snippet} + +``` + +> TODO: need to support adding marks data to overall Chart context (implicit series?) + +Similar to SveltePlot + +```svelte + + {#snippet marks()} + + {/snippet} + +``` + +## Series + +- useful to define common color (for marks, legend, tooltip) + - define key, color, label (optional), data (optional) +- can simplify by passing series props (not needing custom marks) + - debatable? +- Passing `series.data` instead of `` might be faster (don't have to wait for all components to register on the context before determining the extents (scales, axis, etc)) + +### Unified data with per-series values + +A single data array with per-series values as separate properties + + + +### Per-series data + +Each series has it's own data + + diff --git a/docs/src/routes/docs/guides/state/+page.md b/docs/src/routes/docs/guides/state/+page.md index f48eca287..60e264505 100644 --- a/docs/src/routes/docs/guides/state/+page.md +++ b/docs/src/routes/docs/guides/state/+page.md @@ -1,10 +1,69 @@ # State / Context -> TODO - -- Chart -- Brush -- Transform -- Geo -- Tooltip -- Series +## Settings state / context + +Global setttings / defaults + +- Default `` type +- Debug + +```svelte + +``` + +## Chart state / context + +Includes all chart state including + +- Chart scales (domain, range), value accessors, dimensions (width/height) +- Sub state including: + - Geo (projection) + - Tooltip + - Transform + - Series + - Brush + +### Access + +#### Composition + +```svelte + + {#snippet children({ context })} + + {/snippet} + +``` + +#### Within a custom component + +```svelte + +``` + +## Layer + +Get nearest `` type (fallback to settings default) + +Typically only needed for custom components. + +```svelte + +``` diff --git a/examples/shadcn-svelte-1/src/lib/components/ui/chart/chart-tooltip.svelte b/examples/shadcn-svelte-1/src/lib/components/ui/chart/chart-tooltip.svelte index efef55c75..ef4041d9e 100644 --- a/examples/shadcn-svelte-1/src/lib/components/ui/chart/chart-tooltip.svelte +++ b/examples/shadcn-svelte-1/src/lib/components/ui/chart/chart-tooltip.svelte @@ -2,7 +2,7 @@ import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements"; import { getPayloadConfigFromPayload, useChart, type TooltipPayload } from "./chart-utils.js"; - import { getTooltipContext, Tooltip as TooltipPrimitive } from "layerchart"; + import { getChartContext, Tooltip as TooltipPrimitive } from "layerchart"; import type { Snippet } from "svelte"; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -48,12 +48,12 @@ } = $props(); const chart = useChart(); - const tooltipCtx = getTooltipContext(); + const ctx = getChartContext(); const formattedLabel = $derived.by(() => { - if (hideLabel || !tooltipCtx.payload?.length) return null; + if (hideLabel || !ctx.tooltip.payload?.length) return null; - const [item] = tooltipCtx.payload; + const [item] = ctx.tooltip.payload; const key = labelKey ?? item?.label ?? item?.name ?? "value"; const itemConfig = getPayloadConfigFromPayload(chart.config, item, key); @@ -65,10 +65,10 @@ if (value === undefined) return null; if (!labelFormatter) return value; - return labelFormatter(value, tooltipCtx.payload); + return labelFormatter(value, ctx.tooltip.payload); }); - const nestLabel = $derived(tooltipCtx.payload.length === 1 && indicator !== "dot"); + const nestLabel = $derived(ctx.tooltip.payload.length === 1 && indicator !== "dot"); {#snippet TooltipLabel()} @@ -86,7 +86,7 @@
- {#each tooltipCtx.payload as item, i (item.key + i)} + {#each ctx.tooltip.payload as item, i (item.key + i)} {@const key = `${nameKey || item.key || item.name || "value"}`} {@const itemConfig = getPayloadConfigFromPayload(chart.config, item, key)} {@const indicatorColor = color || item.payload?.color || item.color} @@ -111,7 +111,7 @@ name: item.name, item, index: i, - payload: tooltipCtx.payload, + payload: ctx.tooltip.payload, })} {:else} {#if itemConfig?.icon} diff --git a/packages/layerchart/src/lib/components/Arc.svelte b/packages/layerchart/src/lib/components/Arc.svelte index c0421e208..d352fc2a7 100644 --- a/packages/layerchart/src/lib/components/Arc.svelte +++ b/packages/layerchart/src/lib/components/Arc.svelte @@ -3,7 +3,6 @@ import type { PointerEventHandler, SVGAttributes } from 'svelte/elements'; import Path, { type PathPropsWithoutHTML } from './Path.svelte'; - import type { TooltipContextValue } from '$lib/contexts/tooltip.js'; import { createMotion, type MotionProp } from '$lib/utils/motion.svelte.js'; import type { CommonStyleProps, Without } from '$lib/utils/types.js'; @@ -113,11 +112,11 @@ offset?: number; /** - * Tooltip context to setup pointer events to show tooltip for related data. + * Setup pointer events to show tooltip for related data. * * **Must set `data` prop as well** */ - tooltipContext?: TooltipContextValue; + tooltip?: boolean; /** * Data to set when showing tooltip @@ -230,7 +229,7 @@ onpointermove = () => {}, onpointerleave = () => {}, ontouchmove = () => {}, - tooltipContext, + tooltip, track = false, children, class: className, @@ -353,17 +352,17 @@ const onPointerEnter: PointerEventHandler = (e) => { onpointerenter?.(e); - tooltipContext?.show(e, data); + if (tooltip) ctx.tooltip.show(e, data); }; const onPointerMove: PointerEventHandler = (e) => { onpointermove?.(e); - tooltipContext?.show(e, data); + if (tooltip) ctx.tooltip.show(e, data); }; const onPointerLeave: PointerEventHandler = (e) => { onpointerleave?.(e); - tooltipContext?.hide(); + if (tooltip) ctx.tooltip.hide(); }; function getTrackTextProps(position: ArcTextPosition, opts: ArcTextOptions = {}) { @@ -422,9 +421,10 @@ onpointerleave={onPointerLeave} ontouchmove={(e) => { ontouchmove?.(e); - if (!tooltipContext) return; - // Prevent touch to not interfere with pointer when using tooltip - e.preventDefault(); + if (tooltip) { + // Prevent touch to not interfere with pointer when using tooltip + e.preventDefault(); + } }} /> diff --git a/packages/layerchart/src/lib/components/BrushContext.svelte b/packages/layerchart/src/lib/components/BrushContext.svelte index 8706b1de3..27faed73b 100644 --- a/packages/layerchart/src/lib/components/BrushContext.svelte +++ b/packages/layerchart/src/lib/components/BrushContext.svelte @@ -1,47 +1,13 @@ @@ -142,23 +107,26 @@ import { clamp, localPoint } from '@layerstack/utils'; import { cls } from '@layerstack/tailwind'; import { Logger } from '@layerstack/utils'; + import type { NonNullArray } from 'layerchart/utils/types.js'; - import { scaleInvert, type DomainType } from '../utils/scales.svelte.js'; + import { scaleInvert } from '../utils/scales.svelte.js'; import { add } from '../utils/math.js'; import type { HTMLAttributes } from 'svelte/elements'; import { getChartContext } from '$lib/contexts/chart.js'; - import type { Snippet } from 'svelte'; const ctx = getChartContext(); let { - brushContext: brushContextProp = $bindable(), + // xDomain, + // yDomain, + x, + y, + state: stateProp = $bindable(), + axis = 'x', handleSize = 5, resetOnEnd = false, ignoreResetClick = false, - xDomain: xDomain, - yDomain: yDomain, mode = 'integrated', disabled = false, range = {}, @@ -173,89 +141,54 @@ let rootEl = $state(); - if (xDomain === undefined) { - xDomain = ctx.xScale.domain(); - } - if (yDomain === undefined) { - yDomain = ctx.yScale.domain(); - } - - $effect.pre(() => { - if (xDomain !== undefined) return; - xDomain = ctx.xScale.domain(); - }); - - $effect.pre(() => { - if (yDomain !== undefined) return; - yDomain = ctx.yScale.domain(); - }); - - const ogXDomain = xDomain; - const ogYDomain = yDomain; - const originalXDomain = ctx.config.xDomain; - const originalYDomain = ctx.config.yDomain; - - const xDomainMinMax = $derived(extent(ctx.xScale.domain()) as [number, number]); - const xDomainMin = $derived(xDomainMinMax[0]); - const xDomainMax = $derived(xDomainMinMax[1]); - - const yDomainMinMax = $derived(extent(ctx.yScale.domain()) as [number, number]); - const yDomainMin = $derived(yDomainMinMax[0]); - const yDomainMax = $derived(yDomainMinMax[1]); - - const top = $derived(ctx.yScale(yDomain?.[1])); - const bottom = $derived(ctx.yScale(yDomain?.[0])); - const left = $derived(ctx.xScale(xDomain?.[0])); - const right = $derived(ctx.xScale(xDomain?.[1])); - - const _range = $derived({ - x: axis === 'both' || axis === 'x' ? left : 0, - y: axis === 'both' || axis === 'y' ? top : 0, - width: axis === 'both' || axis === 'x' ? right - left : ctx.width, - height: axis === 'both' || axis === 'y' ? bottom - top : ctx.height, + const brushState = new BrushState(ctx, { x, y, axis }); + stateProp = brushState; + + // if (xDomain === undefined) { + // xDomain = ctx.xScale.domain(); + // } + // if (yDomain === undefined) { + // yDomain = ctx.yScale.domain(); + // } + + // $effect.pre(() => { + // if (xDomain !== undefined) return; + // xDomain = ctx.xScale.domain(); + // }); + + // $effect.pre(() => { + // if (yDomain !== undefined) return; + // yDomain = ctx.yScale.domain(); + // }); + + // const ogXDomain = xDomain; + // const ogYDomain = yDomain; + // const originalXDomain = ctx.config.xDomain; + // const originalYDomain = ctx.config.yDomain; + + // const xDomainMinMax = $derived(extent(ctx.xScale.domain()) as [number, number]); + // const xDomainMin = $derived(xDomainMinMax[0]); + // const xDomainMax = $derived(xDomainMinMax[1]); + const [xDomainMin, xDomainMax] = $derived(ctx.xScale.domain()); + + // const yDomainMinMax = $derived(extent(ctx.yScale.domain()) as [number, number]); + // const yDomainMin = $derived(yDomainMinMax[0]); + // const yDomainMax = $derived(yDomainMinMax[1]); + const [yDomainMin, yDomainMax] = $derived(ctx.yScale.domain()); + + $effect(() => { + brushState.handleSize = handleSize; }); - let isActive = $state(false); - - const brushContext = { - get xDomain() { - return xDomain!; - }, - set xDomain(v: DomainType) { - xDomain = v; - }, - get yDomain() { - return yDomain!; - }, - set yDomain(v: DomainType) { - yDomain = v; - }, - get isActive() { - return isActive; - }, - set isActive(v: boolean) { - isActive = v; - }, - get range() { - return _range; - }, - get handleSize() { - return handleSize; - }, - }; - - brushContextProp = brushContext; - - setBrushContext(brushContext); - const logger = new Logger('BrushContext'); const RESET_THRESHOLD = 1; // size of pointer delta to ignore function handler( + /** Callback on pointer move */ fn: ( start: { - xDomain: [number, number]; - yDomain: [number, number]; + x: NonNullArray; + y: NonNullArray; value: { x: number; y: number }; }, value: { x: number; y: number } @@ -283,15 +216,21 @@ } const start = { - xDomain: [xDomain?.[0] ?? xDomainMin, xDomain?.[1] ?? xDomainMax] as [number, number], - yDomain: [yDomain?.[0] ?? yDomainMin, yDomain?.[1] ?? yDomainMax] as [number, number], + x: [ + brushState.x[0] ?? ctx.xScale.domain()[0], + brushState.x[1] ?? ctx.xScale.domain()[1], + ] as Parameters[0]['x'], + y: [ + brushState.y[0] ?? ctx.yScale.domain()[0], + brushState.y[1] ?? ctx.yScale.domain()[1], + ] as Parameters[0]['y'], value: { x: scaleInvert(ctx.xScale, startPoint?.x ?? 0), y: scaleInvert(ctx.yScale, startPoint?.y ?? 0), }, }; - onBrushStart({ xDomain, yDomain }); + onBrushStart({ brush: brushState }); const onPointerMove = (e: PointerEvent) => { const currentPoint = localPoint(e, rootEl); @@ -300,7 +239,7 @@ y: scaleInvert(ctx.yScale, currentPoint?.y ?? 0), }); - onChange({ xDomain, yDomain }); + onChange({ brush: brushState }); }; const onPointerUp = (e: PointerEvent) => { @@ -315,8 +254,8 @@ if ( (isClickOutside && xPointDelta < RESET_THRESHOLD && yPointDelta < RESET_THRESHOLD) || - _range.width < RESET_THRESHOLD || - _range.height < RESET_THRESHOLD + brushState.range.width < RESET_THRESHOLD || + brushState.range.height < RESET_THRESHOLD ) { // Clicked on frame, or pointer delta was less than threshold (default: 1px) if (ignoreResetClick) { @@ -324,24 +263,24 @@ } else { logger.debug('resetting due to frame click'); reset(); - onChange({ xDomain, yDomain }); + onChange({ brush: brushState }); } } else { logger.debug('drag end', { target: e.target, xPointDelta, yPointDelta, - rangeWidth: _range.width, - rangeHeight: _range.height, + rangeWidth: brushState.range.width, + rangeHeight: brushState.range.height, }); } - onBrushEnd({ xDomain, yDomain }); + onBrushEnd({ brush: brushState }); if (resetOnEnd) { if (ignoreResetClick) { // Still hide brush, but do not reset domain - brushContext.isActive = false; + brushState.active = false; } else { reset(); } @@ -358,109 +297,100 @@ const createRange = handler((start, value) => { logger.debug('createRange'); - brushContext.isActive = true; + brushState.active = true; - xDomain = [ - // @ts-expect-error + brushState.x = [ clamp(min([start.value.x, value.x]), xDomainMin, xDomainMax), - // @ts-expect-error clamp(max([start.value.x, value.x]), xDomainMin, xDomainMax), ]; // xDomain = [start.value.x, value.x]; - yDomain = [ - // @ts-expect-error + brushState.y = [ clamp(min([start.value.y, value.y]), yDomainMin, yDomainMax), - // @ts-expect-error clamp(max([start.value.y, value.y]), yDomainMin, yDomainMax), ]; }); const adjustRange = handler((start, value) => { logger.debug('adjustRange'); - const dx = clamp( - value.x - start.value.x, - xDomainMin - start.xDomain[0], - xDomainMax - start.xDomain[1] - ); - xDomain = [add(start.xDomain[0], dx), add(start.xDomain[1], dx)]; - - const dy = clamp( - value.y - start.value.y, - yDomainMin - start.yDomain[0], - yDomainMax - start.yDomain[1] - ); - yDomain = [add(start.yDomain[0], dy), add(start.yDomain[1], dy)]; + const dx = clamp(value.x - start.value.x, xDomainMin - +start.x[0], xDomainMax - +start.x[1]); + brushState.x = [add(start.x[0], dx), add(start.x[1], dx)]; + + const dy = clamp(value.y - start.value.y, yDomainMin - +start.y[0], yDomainMax - +start.y[1]); + brushState.y = [add(start.y[0], dy), add(start.y[1], dy)]; }); const adjustTop = handler((start, value) => { logger.debug('adjustTop'); - yDomain = [ - clamp(value.y < start.yDomain[0] ? value.y : start.yDomain[0], yDomainMin, yDomainMax), - clamp(value.y < start.yDomain[0] ? start.yDomain[0] : value.y, yDomainMin, yDomainMax), + brushState.y = [ + clamp(value.y < +start.y[0] ? value.y : start.y[0], yDomainMin, yDomainMax), + clamp(value.y < +start.y[0] ? start.y[0] : value.y, yDomainMin, yDomainMax), ]; }); const adjustBottom = handler((start, value) => { logger.debug('adjustBottom'); - yDomain = [ - clamp(value.y > start.yDomain[1] ? start.yDomain[1] : value.y, yDomainMin, yDomainMax), - clamp(value.y > start.yDomain[1] ? value.y : start.yDomain[1], yDomainMin, yDomainMax), + brushState.y = [ + clamp(value.y > +start.y[1] ? start.y[1] : value.y, yDomainMin, yDomainMax), + clamp(value.y > +start.y[1] ? value.y : start.y[1], yDomainMin, yDomainMax), ]; }); const adjustLeft = handler((start, value) => { logger.debug('adjustLeft'); - xDomain = [ - clamp(value.x > start.xDomain[1] ? start.xDomain[1] : value.x, xDomainMin, xDomainMax), - clamp(value.x > start.xDomain[1] ? value.x : start.xDomain[1], xDomainMin, xDomainMax), + brushState.x = [ + clamp(value.x > +start.x[1] ? start.x[1] : value.x, xDomainMin, xDomainMax), + clamp(value.x > +start.x[1] ? value.x : start.x[1], xDomainMin, xDomainMax), ]; }); const adjustRight = handler((start, value) => { logger.debug('adjustRight'); - xDomain = [ - clamp(value.x < start.xDomain[0] ? value.x : start.xDomain[0], xDomainMin, xDomainMax), - clamp(value.x < start.xDomain[0] ? start.xDomain[0] : value.x, xDomainMin, xDomainMax), + brushState.x = [ + clamp(value.x < +start.x[0] ? value.x : start.x[0], xDomainMin, xDomainMax), + clamp(value.x < +start.x[0] ? start.x[0] : value.x, xDomainMin, xDomainMax), ]; }); function reset() { logger.debug('reset'); - brushContext.isActive = false; + brushState.active = false; - onReset({ xDomain, yDomain }); + onReset({ brush: brushState }); - xDomain = ogXDomain; - yDomain = ogYDomain; + // xDomain = ogXDomain; + // yDomain = ogYDomain; + // brushState.x = [ctx.xScale.domain()[0], ctx.xScale.domain()[1]]; + // brushState.y = [ctx.yScale.domain()[0], ctx.yScale.domain()[1]]; + brushState.x = [null, null]; + brushState.y = [null, null]; } function selectAll() { logger.debug('selectedAll'); - xDomain = [xDomainMin, xDomainMax]; - yDomain = [yDomainMin, yDomainMax]; + brushState.x = [xDomainMin, xDomainMax]; + brushState.y = [yDomainMin, yDomainMax]; } $effect.pre(() => { if (mode === 'separated') { // Set reactively to handle cases where xDomain/yDomain are set externally (ex. `bind:xDomain`) - const isXAxisActive = - xDomain?.[0]?.valueOf() !== originalXDomain?.[0]?.valueOf() || - xDomain?.[1]?.valueOf() !== originalXDomain?.[1]?.valueOf(); - - const isYAxisActive = - yDomain?.[0]?.valueOf() !== originalYDomain?.[0]?.valueOf() || - yDomain?.[1]?.valueOf() !== originalYDomain?.[1]?.valueOf(); - - const result = - axis === 'x' ? isXAxisActive : axis == 'y' ? isYAxisActive : isXAxisActive || isYAxisActive; - brushContext.isActive = result; + // TODO: Update + // const isXAxisActive = + // brushState.x[0]?.valueOf() !== originalXDomain?.[0]?.valueOf() || + // brushState.x[1]?.valueOf() !== originalXDomain?.[1]?.valueOf(); + // const isYAxisActive = + // brushState.y[0]?.valueOf() !== originalYDomain?.[0]?.valueOf() || + // brushState.y[1]?.valueOf() !== originalYDomain?.[1]?.valueOf(); + // const result = + // axis === 'x' ? isXAxisActive : axis == 'y' ? isYAxisActive : isXAxisActive || isYAxisActive; + // brushState.active = result; } }); {#if disabled} - {@render children?.({ brushContext })} + {@render children?.({ state: brushState })} {:else}
- {@render children?.({ brushContext })} + {@render children?.({ state: brushState })}
- {#if brushContext.isActive} + {#if brushState.active}
reset()} @@ -498,36 +428,36 @@ {#if axis === 'both' || axis === 'y'}
{ e.stopPropagation(); - if (yDomain) { - yDomain[0] = yDomainMin; - onChange({ xDomain, yDomain }); + if (brushState.y[0]) { + brushState.y[0] = ctx.yScale.domain()[0]; + onChange({ brush: brushState }); } }} >
{ e.stopPropagation(); - if (yDomain) { - yDomain[1] = yDomainMax; - onChange({ xDomain, yDomain }); + if (brushState.y[1]) { + brushState.y[1] = ctx.yScale.domain()[1]; + onChange({ brush: brushState }); } }} >
@@ -536,36 +466,36 @@ {#if axis === 'both' || axis === 'x'}
{ e.stopPropagation(); - if (xDomain) { - xDomain[0] = xDomainMin; - onChange({ xDomain, yDomain }); + if (brushState.x[0]) { + brushState.x[0] = ctx.xScale.domain()[0]; + onChange({ brush: brushState }); } }} >
{ e.stopPropagation(); - if (xDomain) { - xDomain[1] = xDomainMax; - onChange({ xDomain: xDomain, yDomain: yDomain }); + if (brushState.x[1]) { + brushState.x[1] = ctx.xScale.domain()[1]; + onChange({ brush: brushState }); } }} >
diff --git a/packages/layerchart/src/lib/components/Calendar.svelte b/packages/layerchart/src/lib/components/Calendar.svelte index b341a7cd6..a5ac3d056 100644 --- a/packages/layerchart/src/lib/components/Calendar.svelte +++ b/packages/layerchart/src/lib/components/Calendar.svelte @@ -39,9 +39,9 @@ monthLabel?: boolean | Partial>; /** - * Tooltip context to setup mouse events to show tooltip for related data + * Setup pointer events to show tooltip for related data */ - tooltipContext?: TooltipContextValue; + tooltip?: boolean; children?: Snippet<[{ cells: CalendarCell[]; cellSize: [number, number] }]>; } & Omit< @@ -60,7 +60,6 @@ import { format } from '@layerstack/utils'; import Rect, { type RectPropsWithoutHTML } from './Rect.svelte'; - import type { TooltipContextValue } from '$lib/contexts/tooltip.js'; import MonthPath from './MonthPath.svelte'; import Text from './Text.svelte'; import { chartDataArray } from '../utils/common.js'; @@ -75,7 +74,7 @@ cellSize: cellSizeProp, monthPath = false, monthLabel = true, - tooltipContext: tooltip, + tooltip, children, ...restProps }: CalendarPropsWithoutHTML = $props(); @@ -126,8 +125,8 @@ width={cellSize[0]} height={cellSize[1]} fill={cell.color} - onpointermove={(e) => tooltip?.show(e, cell.data)} - onpointerleave={(e) => tooltip?.hide()} + onpointermove={(e) => tooltip && ctx.tooltip?.show(e, cell.data)} + onpointerleave={(e) => tooltip && ctx.tooltip?.hide()} strokeWidth={1} {...extractLayerProps(restProps, 'lc-calendar-cell')} /> diff --git a/packages/layerchart/src/lib/components/Chart.svelte b/packages/layerchart/src/lib/components/Chart.svelte index 7ee8c12c7..8e80180f7 100644 --- a/packages/layerchart/src/lib/components/Chart.svelte +++ b/packages/layerchart/src/lib/components/Chart.svelte @@ -1,55 +1,33 @@ {#if ssr === true || typeof window !== 'undefined'} @@ -1308,16 +691,16 @@ style:bottom={position === 'absolute' ? 0 : null} style:left={position === 'absolute' ? 0 : null} style:pointer-events={pointerEvents === false ? 'none' : null} - style:width={widthProp ? `${widthProp}px` : '100%'} - style:height={heightProp ? `${heightProp}px` : '100%'} - bind:clientWidth={_containerWidth} - bind:clientHeight={_containerHeight} + style:width={width ? `${width}px` : '100%'} + style:height={height ? `${height}px` : '100%'} + bind:clientWidth={chartState._containerWidth} + bind:clientHeight={chartState._containerHeight} class={['lc-root-container', className]} > - {#key isMounted} + {#key chartState.isMounted} - + - - - - {@render _children?.({ - context, - })} - - - + + + + {/key}
diff --git a/packages/layerchart/src/lib/components/ChartChildren.svelte b/packages/layerchart/src/lib/components/ChartChildren.svelte new file mode 100644 index 000000000..0223c25e1 --- /dev/null +++ b/packages/layerchart/src/lib/components/ChartChildren.svelte @@ -0,0 +1,285 @@ + + + + +{#if childrenProp} + {@render childrenProp(snippetProps)} +{:else} + {@render belowContext?.(snippetProps)} + + + {#if typeof grid === 'function'} + {@render grid(snippetProps)} + {:else if grid} + + + {/if} + + + + + {@render belowMarks?.(snippetProps)} + {@render marks?.(snippetProps)} + {@render aboveMarks?.(snippetProps)} + + + {#if typeof axis === 'function'} + {@render axis(snippetProps)} + + {#if typeof rule === 'function'} + {@render rule(snippetProps)} + {:else if rule} + + + {/if} + {:else if axis} + {#if axis !== 'x'} + + + + {/if} + + {#if axis !== 'y'} + + + + {/if} + + {#if typeof rule === 'function'} + {@render rule(snippetProps)} + {:else if rule} + + + {/if} + {/if} + + + + {#if typeof points === 'function'} + {@render points(snippetProps)} + {:else if points} + {#each context.series.visibleSeries as s, i (s.key)} + + + + {/each} + {/if} + + {#if typeof labels === 'function'} + {@render labels(snippetProps)} + {:else if labels} + {#each context.series.visibleSeries as s, i (s.key)} + + + + {/each} + {/if} + + {#if typeof highlight === 'function'} + {@render highlight(snippetProps)} + {:else if highlight} + {#each context.series.visibleSeries as s, i (s.key)} + + + + {/each} + {/if} + + + + + + {@render aboveContext?.(snippetProps)} + + {#if typeof legend === 'function'} + {@render legend(snippetProps)} + {:else if legend} + + + {/if} + + {#if typeof tooltip === 'function'} + {@render tooltip(snippetProps)} + {:else if tooltipContext} + + {/if} +{/if} diff --git a/packages/layerchart/src/lib/components/GeoContext.svelte b/packages/layerchart/src/lib/components/GeoContext.svelte deleted file mode 100644 index a041dae03..000000000 --- a/packages/layerchart/src/lib/components/GeoContext.svelte +++ /dev/null @@ -1,160 +0,0 @@ - - - - -{@render children({ - geoContext, -})} diff --git a/packages/layerchart/src/lib/components/GeoEdgeFade.svelte b/packages/layerchart/src/lib/components/GeoEdgeFade.svelte index e3cde9cb5..f0512958b 100644 --- a/packages/layerchart/src/lib/components/GeoEdgeFade.svelte +++ b/packages/layerchart/src/lib/components/GeoEdgeFade.svelte @@ -1,7 +1,6 @@ @@ -145,9 +152,9 @@ {pathData} {...restProps} onclick={_onClick} - onpointerenter={tooltipContext || onpointerenter ? _onPointerEnter : undefined} - onpointermove={tooltipContext || onpointermove ? _onPointerMove : undefined} - onpointerleave={tooltipContext || onpointerleave ? _onPointerLeave : undefined} + onpointerenter={tooltip || onpointerenter ? _onPointerEnter : undefined} + onpointermove={tooltip || onpointermove ? _onPointerMove : undefined} + onpointerleave={tooltip || onpointerleave ? _onPointerLeave : undefined} class={cls('lc-geo-path', className)} pathRef={refProp} /> diff --git a/packages/layerchart/src/lib/components/GeoPoint.svelte b/packages/layerchart/src/lib/components/GeoPoint.svelte index 5ca0ad7a0..65f42ddfd 100644 --- a/packages/layerchart/src/lib/components/GeoPoint.svelte +++ b/packages/layerchart/src/lib/components/GeoPoint.svelte @@ -42,9 +42,9 @@ refProp = ref; }); - const geoCtx = getGeoContext(); + const geo = getGeoContext(); - const points = $derived(geoCtx.projection?.([long, lat]) ?? [0, 0]); + const points = $derived(geo.projection?.([long, lat]) ?? [0, 0]); const x = $derived(points[0]); const y = $derived(points[1]); diff --git a/packages/layerchart/src/lib/components/GeoProjection.svelte b/packages/layerchart/src/lib/components/GeoProjection.svelte new file mode 100644 index 000000000..2c504511f --- /dev/null +++ b/packages/layerchart/src/lib/components/GeoProjection.svelte @@ -0,0 +1,37 @@ + + + + +{@render children()} diff --git a/packages/layerchart/src/lib/components/GeoSpline.svelte b/packages/layerchart/src/lib/components/GeoSpline.svelte index 157a7561e..18ad9e397 100644 --- a/packages/layerchart/src/lib/components/GeoSpline.svelte +++ b/packages/layerchart/src/lib/components/GeoSpline.svelte @@ -40,27 +40,27 @@ let { link, loft = 1.0, curve = curveNatural, ...restProps }: GeoSplineProps = $props(); - const geoCtx = getGeoContext(); + const geo = getGeoContext(); const loftedProjection = $derived( - geoCtx.projection + geo.projection ? geoOrthographic() - .translate(geoCtx.projection.translate()) - .rotate(geoCtx.projection.rotate()) - .scale(geoCtx.projection.scale() * loft) + .translate(geo.projection.translate()) + .rotate(geo.projection.rotate()) + .scale(geo.projection.scale() * loft) : undefined ); - const source = $derived(geoCtx.projection ? geoCtx.projection(link.source) : [0, 0]) as [ + const source = $derived(geo.projection ? geo.projection(link.source) : [0, 0]) as [ number, number, ]; - const target = $derived(geoCtx.projection ? geoCtx.projection(link.target) : [0, 0]) as [ + const target = $derived(geo.projection ? geo.projection(link.target) : [0, 0]) as [ number, number, ]; const middle = $derived( - geoCtx.projection ? loftedProjection!(geoInterpolate(link.source, link.target)(0.5)) : [0, 0] + geo.projection ? loftedProjection!(geoInterpolate(link.source, link.target)(0.5)) : [0, 0] ) as [number, number]; diff --git a/packages/layerchart/src/lib/components/GeoTile.svelte b/packages/layerchart/src/lib/components/GeoTile.svelte index 92b1c7c6f..e39b42bae 100644 --- a/packages/layerchart/src/lib/components/GeoTile.svelte +++ b/packages/layerchart/src/lib/components/GeoTile.svelte @@ -45,11 +45,11 @@ import { tile as d3Tile } from 'd3-tile'; import { getChartContext } from '$lib/contexts/chart.js'; + import { getGeoContext } from '$lib/contexts/geo.js'; import { getLayerContext } from '$lib/contexts/layer.js'; import { registerCanvasComponent } from './layers/Canvas.svelte'; import Group from './Group.svelte'; import TileImage from './TileImage.svelte'; - import { getGeoContext } from '$lib/contexts/geo.js'; import { extractLayerProps } from '$lib/utils/attributes.js'; let { @@ -63,17 +63,17 @@ }: GeoTilePropsWithoutHTML = $props(); const ctx = getChartContext(); - const geoCtx = getGeoContext(); + const geo = getGeoContext(); const layerCtx = getLayerContext(); - const center = $derived(geoCtx.projection?.([0, 0]) ?? [0, 0]); + const center = $derived(geo.projection?.([0, 0]) ?? [0, 0]); const tiles = $derived( d3Tile() .size([ctx.containerWidth, ctx.containerHeight]) .translate([center[0] + ctx.padding.left, center[1] + ctx.padding.top]) // TODO: is this fine to add the 0 as a default? - .scale(geoCtx.projection ? geoCtx.projection.scale() * 2 * Math.PI : undefined) + .scale(geo.projection ? geo.projection.scale() * 2 * Math.PI : undefined) .tileSize(tileSize) .zoomDelta(zoomDelta)() ); diff --git a/packages/layerchart/src/lib/components/GeoVisible.svelte b/packages/layerchart/src/lib/components/GeoVisible.svelte index 0e0bd69dc..af864b8c2 100644 --- a/packages/layerchart/src/lib/components/GeoVisible.svelte +++ b/packages/layerchart/src/lib/components/GeoVisible.svelte @@ -15,9 +15,9 @@ import { getGeoContext } from '$lib/contexts/geo.js'; let { lat, long, children }: GeoVisibleProps = $props(); - const geoCtx = getGeoContext(); + const geo = getGeoContext(); -{#if geoCtx.projection && isVisible(geoCtx.projection)([long, lat])} +{#if geo.projection && isVisible(geo.projection)([long, lat])} {@render children?.()} {/if} diff --git a/packages/layerchart/src/lib/components/Highlight.svelte b/packages/layerchart/src/lib/components/Highlight.svelte index 49a2684df..9438f7b34 100644 --- a/packages/layerchart/src/lib/components/Highlight.svelte +++ b/packages/layerchart/src/lib/components/Highlight.svelte @@ -123,13 +123,11 @@ import { isScaleBand, isScaleTime } from '$lib/utils/scales.svelte.js'; import { asAny } from '$lib/utils/types.js'; import { getChartContext } from '$lib/contexts/chart.js'; - import { getTooltipContext } from '$lib/contexts/tooltip.js'; import { extractLayerProps } from '$lib/utils/attributes.js'; import type { MotionProp } from '$lib/utils/motion.svelte.js'; import Arc from './Arc.svelte'; const ctx = getChartContext(); - const tooltipCtx = getTooltipContext(); let { data, @@ -152,7 +150,7 @@ const x = $derived(accessor(xProp)); const y = $derived(accessor(yProp)); - const highlightData = $derived(data ?? tooltipCtx.data); + const highlightData = $derived(data ?? ctx.tooltip.data); const xValue = $derived(x(highlightData)); const xCoord = $derived( Array.isArray(xValue) ? xValue.map((v) => ctx.xScale(v)) : ctx.xScale(xValue) diff --git a/packages/layerchart/src/lib/components/Hull.svelte b/packages/layerchart/src/lib/components/Hull.svelte index 80551dadf..66225ec89 100644 --- a/packages/layerchart/src/lib/components/Hull.svelte +++ b/packages/layerchart/src/lib/components/Hull.svelte @@ -85,7 +85,7 @@ }); const ctx = getChartContext(); - const geoCtx = getGeoContext(); + const geo = getGeoContext(); const points = $derived( (data ?? ctx.flatData).map((d: any) => { @@ -104,7 +104,7 @@ - {#if geoCtx.projection} + {#if geo.projection} {@const polygon = geoVoronoi().hull(points)} {/each} {/if} diff --git a/packages/layerchart/src/lib/components/Spline.svelte b/packages/layerchart/src/lib/components/Spline.svelte index 7d8d22ab9..32988e4ed 100644 --- a/packages/layerchart/src/lib/components/Spline.svelte +++ b/packages/layerchart/src/lib/components/Spline.svelte @@ -21,6 +21,11 @@ */ y?: Accessor; + /** + * Series key to use for accessor. Only applicable if `` uses `series` and `x`/`y` are not set. + */ + seriesKey?: string; + /** * Function to determine if a point is defined * @@ -57,7 +62,7 @@ const ctx = getChartContext(); - let { data, x, y, defined, curve, ...restProps }: SplineProps = $props(); + let { data, x, y, seriesKey, defined, curve, ...restProps }: SplineProps = $props(); function getScaleValue( data: any, @@ -79,8 +84,13 @@ } } - const xAccessor = $derived(x ? accessor(x) : ctx.x); - const yAccessor = $derived(y ? accessor(y) : ctx.y); + let series = $derived(ctx.series.series.find((s) => s.key === seriesKey)); + let seriesAccessor = $derived(series?.value ?? (series?.data ? undefined : series?.key)); + + const xAccessor = $derived(accessor(x ?? (ctx.isVertical ? seriesAccessor : undefined) ?? ctx.x)); + const yAccessor = $derived( + accessor(y ?? (!ctx.isVertical ? seriesAccessor : undefined) ?? ctx.y) + ); const xOffset = $derived(isScaleBand(ctx.xScale) ? ctx.xScale.bandwidth() / 2 : 0); const yOffset = $derived(isScaleBand(ctx.yScale) ? ctx.yScale.bandwidth() / 2 : 0); @@ -98,8 +108,22 @@ path.defined(defined ?? ((d) => xAccessor(d) != null && yAccessor(d) != null)); if (curve) path.curve(curve); - return path(data ?? ctx.data) ?? ''; + return path(data ?? series?.data ?? ctx.data) ?? ''; }); - + + + + diff --git a/packages/layerchart/src/lib/components/TransformContext.svelte b/packages/layerchart/src/lib/components/TransformContext.svelte index 6b8e578ff..b6c15c0d2 100644 --- a/packages/layerchart/src/lib/components/TransformContext.svelte +++ b/packages/layerchart/src/lib/components/TransformContext.svelte @@ -1,74 +1,18 @@
- {@render children?.({ transformContext: transformContext })} + {@render children?.({ transformState })}