diff --git a/packages/layerchart/package.json b/packages/layerchart/package.json index af6336e3f..b6e272e14 100644 --- a/packages/layerchart/package.json +++ b/packages/layerchart/package.json @@ -84,7 +84,7 @@ "@layerstack/svelte-actions": "1.0.1-next.14", "@layerstack/svelte-state": "0.1.0-next.19", "@layerstack/tailwind": "2.0.0-next.17", - "@layerstack/utils": "2.0.0-next.14", + "@layerstack/utils": "2.0.0-next.15", "d3-array": "^3.2.4", "d3-color": "^3.1.0", "d3-delaunay": "^6.0.4", diff --git a/packages/layerchart/src/lib/components/BrushContext.svelte b/packages/layerchart/src/lib/components/BrushContext.svelte index 1eaee348e..1dc16709c 100644 --- a/packages/layerchart/src/lib/components/BrushContext.svelte +++ b/packages/layerchart/src/lib/components/BrushContext.svelte @@ -1,47 +1,26 @@ @@ -142,23 +120,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 './Chart.svelte'; - import type { Snippet } from 'svelte'; const ctx = getChartContext(); let { + // xDomain, + // yDomain, + x, + y, brushContext: brushContextProp = $bindable(), + axis = 'x', handleSize = 5, resetOnEnd = false, ignoreResetClick = false, - xDomain: xDomain, - yDomain: yDomain, mode = 'integrated', disabled = false, range = {}, @@ -173,89 +154,59 @@ 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(); + const brushState = new BrushState(ctx, { x, y, axis }); + + // 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; }); - $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, - }); - - 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; + // brushContextProp = brushContext; + brushContextProp = brushState; - setBrushContext(brushContext); + // setBrushContext(brushContext); + setBrushContext(brushState); 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 +234,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 +257,7 @@ y: scaleInvert(ctx.yScale, currentPoint?.y ?? 0), }); - onChange({ xDomain, yDomain }); + onChange({ brush: brushState }); }; const onPointerUp = (e: PointerEvent) => { @@ -315,8 +272,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 +281,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 +315,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?.({ brushContext: brushState })} {:else}
- {@render children?.({ brushContext })} + {@render children?.({ brushContext: brushState })}
- {#if brushContext.isActive} + {#if brushState.active}
reset()} @@ -498,36 +446,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 +484,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/Chart.svelte b/packages/layerchart/src/lib/components/Chart.svelte index cce083856..87d103b9f 100644 --- a/packages/layerchart/src/lib/components/Chart.svelte +++ b/packages/layerchart/src/lib/components/Chart.svelte @@ -40,7 +40,8 @@ import { unique } from '@layerstack/utils'; import { geoFitObjectTransform } from '$lib/utils/geo.js'; import TransformContext, { type TransformContextValue } from './TransformContext.svelte'; - import BrushContext, { type BrushContextValue } from './BrushContext.svelte'; + import BrushContext from './BrushContext.svelte'; + import { type BrushState } from '$lib/states/brush.svelte.js'; import type { TimeInterval } from 'd3-time'; const defaultPadding = { top: 0, right: 0, bottom: 0, left: 0 }; @@ -156,7 +157,7 @@ radial: boolean; tooltip: TooltipContextValue; geo: GeoContextValue; - brush: BrushContextValue; + brush: BrushState; transform: TransformContextValue; }; @@ -1092,7 +1093,7 @@ let geoContext = $state(null!); let transformContext = $state(null!); let tooltipContext = $state(null!); - let brushContext = $state(null!); + let brushContext = $state(null!); const context: ChartContextValue = { get activeGetters() { diff --git a/packages/layerchart/src/lib/components/charts/AreaChart.svelte b/packages/layerchart/src/lib/components/charts/AreaChart.svelte index 61d1e4c9c..6c04e8b2f 100644 --- a/packages/layerchart/src/lib/components/charts/AreaChart.svelte +++ b/packages/layerchart/src/lib/components/charts/AreaChart.svelte @@ -97,6 +97,7 @@ import { setTooltipMetaContext } from '../tooltip/tooltipMetaContext.js'; import DefaultTooltip from './DefaultTooltip.svelte'; import ChartAnnotations from './ChartAnnotations.svelte'; + import type { BrushDomainType } from '../../states/brush.svelte.js'; let { data = [], @@ -423,10 +424,10 @@ ? { axis: 'x', resetOnEnd: true, - xDomain, + x: xDomain as BrushDomainType, ...brushProps, onBrushEnd: (e) => { - xDomain = e.xDomain; + xDomain = e.brush.x; brushProps.onBrushEnd?.(e); }, } diff --git a/packages/layerchart/src/lib/components/charts/BarChart.svelte b/packages/layerchart/src/lib/components/charts/BarChart.svelte index e035095e9..f995f4a75 100644 --- a/packages/layerchart/src/lib/components/charts/BarChart.svelte +++ b/packages/layerchart/src/lib/components/charts/BarChart.svelte @@ -114,6 +114,7 @@ import { setTooltipMetaContext } from '../tooltip/tooltipMetaContext.js'; import DefaultTooltip from './DefaultTooltip.svelte'; import ChartAnnotations from './ChartAnnotations.svelte'; + import type { BrushDomainType } from '../../states/brush.svelte.js'; let { data = [], @@ -458,11 +459,18 @@ ? { axis: 'x', resetOnEnd: true, - xDomain, + x: xDomain as BrushDomainType, ...brushProps, onBrushEnd: (e) => { // TOOD: This should set xRange instead of xDomain, and/or xDomain should be all values, not just bounds of brush range - xDomain = e.xDomain; + // const values = context?.xScale.domain() ?? []; + // console.log('domain', values, e.xDomain); + // const i0 = values?.indexOf(e.xDomain[0]); + // const i1 = values?.indexOf(e.xDomain[1]); + // xDomain = values.slice(i0, i1); + + xDomain = e.brush.x; + brushProps.onBrushEnd?.(e); }, } diff --git a/packages/layerchart/src/lib/components/charts/LineChart.svelte b/packages/layerchart/src/lib/components/charts/LineChart.svelte index 4c296de25..6152eb1ea 100644 --- a/packages/layerchart/src/lib/components/charts/LineChart.svelte +++ b/packages/layerchart/src/lib/components/charts/LineChart.svelte @@ -104,6 +104,7 @@ import DefaultTooltip from './DefaultTooltip.svelte'; import ChartAnnotations from './ChartAnnotations.svelte'; import { isScaleTime } from '../../utils/scales.svelte.js'; + import type { BrushDomainType } from '../../states/brush.svelte.js'; let { data = [], @@ -342,10 +343,10 @@ ? { axis: 'x', resetOnEnd: true, - xDomain, + x: xDomain as BrushDomainType, ...brushProps, onBrushEnd: (e) => { - xDomain = e.xDomain; + xDomain = e.brush.x; brushProps.onBrushEnd?.(e); }, } diff --git a/packages/layerchart/src/lib/components/charts/ScatterChart.svelte b/packages/layerchart/src/lib/components/charts/ScatterChart.svelte index c15a46ed9..5ce19d872 100644 --- a/packages/layerchart/src/lib/components/charts/ScatterChart.svelte +++ b/packages/layerchart/src/lib/components/charts/ScatterChart.svelte @@ -38,8 +38,8 @@ 'radial' > & { props?: ScatterChartPropsObjProp; - yDomain?: ComponentProps['yDomain']; - yScale?: AnyScale; + // yDomain?: ComponentProps['yDomain']; + // yScale?: AnyScale; }; @@ -48,7 +48,6 @@ import { cls } from '@layerstack/tailwind'; import Axis from '../Axis.svelte'; - import BrushContext from '../BrushContext.svelte'; import Chart from '../Chart.svelte'; import ChartAnnotations from './ChartAnnotations.svelte'; import ChartClipPath from '../ChartClipPath.svelte'; @@ -65,6 +64,7 @@ import { asAny } from '../../utils/types.js'; import { SeriesState } from '$lib/states/series.svelte.js'; import { createLegendProps } from './utils.svelte.js'; + import type { BrushDomainType } from '../../states/brush.svelte.js'; let { data = [], @@ -235,12 +235,12 @@ ? { axis: 'both', resetOnEnd: true, - xDomain, - yDomain, + x: xDomain as BrushDomainType, + y: yDomain as BrushDomainType, ...brushProps, onBrushEnd: (e) => { - xDomain = e.xDomain; - yDomain = e.yDomain; + xDomain = e.brush.x; + yDomain = e.brush.y; brushProps.onBrushEnd?.(e); }, } diff --git a/packages/layerchart/src/lib/components/charts/types.ts b/packages/layerchart/src/lib/components/charts/types.ts index e3120fc1f..913f8f881 100644 --- a/packages/layerchart/src/lib/components/charts/types.ts +++ b/packages/layerchart/src/lib/components/charts/types.ts @@ -137,12 +137,6 @@ export type BaseChartProps< */ y?: Accessor; - xScale?: AnyScale; - /** - * The x domain to be used for the chart. - * - */ - xDomain?: ComponentProps['xDomain']; /** * Use radial instead of cartesian coordinates, mapping `x` to `angle` and `y`` to * radial. Radial lines are positioned relative to the origin, use transform @@ -151,18 +145,21 @@ export type BaseChartProps< * @default false */ radial?: boolean; + /** * The series data to be used for the chart. * * @default [{ key: 'default', value: y, color: 'var(--color-primary)' }] */ series?: SeriesData[]; + /** * The layout of the series. * * @default 'overlap' */ seriesLayout?: 'overlap' | 'stack' | 'stackExpand' | 'stackDiverging'; + /** * The axis to be used for the chart. * @@ -174,6 +171,7 @@ export type BaseChartProps< | 'y' | boolean | SimplifiedChartSnippet; + /** * The brush to be used for the chart. * @@ -194,6 +192,7 @@ export type BaseChartProps< * @default false */ labels?: ComponentProps> | boolean | ChartSnippet; + /** * The legend to be used for the chart. * diff --git a/packages/layerchart/src/lib/states/brush.svelte.ts b/packages/layerchart/src/lib/states/brush.svelte.ts new file mode 100644 index 000000000..33f426ce8 --- /dev/null +++ b/packages/layerchart/src/lib/states/brush.svelte.ts @@ -0,0 +1,58 @@ +import { getChartContext } from '$lib/components/Chart.svelte'; + +// TODO: Should we support the full `DomainType` (`string`, etc) +// type BrushDomainType = NonNullable; +export type BrushDomainType = Array; + +export type BrushRange = { + x: number; + y: number; + width: number; + height: number; +}; + +export class BrushState { + ctx: ReturnType | null; + + x = $state([null, null]); + y = $state([null, null]); + active = $state(); + axis = $state<'x' | 'y' | 'both'>('x'); + handleSize = $state(0); + + constructor( + ctx: typeof this.ctx, + options?: { + x?: BrushDomainType; + y?: BrushDomainType; + active?: boolean; + axis?: 'x' | 'y' | 'both'; + } + ) { + this.ctx = ctx; + + this.x = options?.x ?? [null, null]; + this.y = options?.y ?? [null, null]; + // this.active = options?.active ?? (this.x !== null || this.y !== null); + this.active = options?.active; + this.axis = options?.axis ?? 'x'; + } + + get range() { + if (!this.ctx) { + return { x: 0, y: 0, width: 0, height: 0 }; + } + + const top = this.ctx.yScale(this.y?.[1]); + const bottom = this.ctx.yScale(this.y?.[0]); + const left = this.ctx.xScale(this.x?.[0]); + const right = this.ctx.xScale(this.x?.[1]); + + return { + x: this.axis === 'both' || this.axis === 'x' ? left : 0, + y: this.axis === 'both' || this.axis === 'y' ? top : 0, + width: this.axis === 'both' || this.axis === 'x' ? right - left : this.ctx.width, + height: this.axis === 'both' || this.axis === 'y' ? bottom - top : this.ctx.height, + }; + } +} diff --git a/packages/layerchart/src/lib/states/series.svelte.ts b/packages/layerchart/src/lib/states/series.svelte.ts index 8cbe491be..fd1346e88 100644 --- a/packages/layerchart/src/lib/states/series.svelte.ts +++ b/packages/layerchart/src/lib/states/series.svelte.ts @@ -3,14 +3,6 @@ import type { SeriesData } from '../components/charts/types.js'; import { SelectionState } from '@layerstack/svelte-state'; -class HighlightKey { - current = $state['key'] | null>(null); - - set = (seriesKey: typeof this.current) => { - this.current = seriesKey; - }; -} - export class SeriesState { #series = $state.raw[]>([]); selectedKeys = new SelectionState(); @@ -68,3 +60,11 @@ export class SeriesState { >; } } + +class HighlightKey { + current = $state['key'] | null>(null); + + set = (seriesKey: typeof this.current) => { + this.current = seriesKey; + }; +} diff --git a/packages/layerchart/src/lib/utils/types.ts b/packages/layerchart/src/lib/utils/types.ts index dfb41385d..cf1f3a714 100644 --- a/packages/layerchart/src/lib/utils/types.ts +++ b/packages/layerchart/src/lib/utils/types.ts @@ -19,12 +19,22 @@ export function asAny(x: any): any { * * @template T - The base object type from which properties will be omitted. * @template U - The object type whose properties will be omitted from 'T'. + * * @example * type Result = Without<{ a: number; b: string; }, { b: string; }>; * // Result type will be { a: number; } */ export type Without = Omit; +/** + * Extracts the non-nullable array type from a type that may include nulls. + * + * @example + * type Result = NonNullArray<(number | null)[]>; + * // Result type will be number[]. + */ +export type NonNullArray = T extends (infer U | null)[] ? U[] : never; + export type AxisKey = 'x' | 'y' | 'z' | 'r'; export type Extents = { diff --git a/packages/layerchart/src/routes/docs/components/AreaChart/+page.svelte b/packages/layerchart/src/routes/docs/components/AreaChart/+page.svelte index b726efe71..004f42b28 100644 --- a/packages/layerchart/src/routes/docs/components/AreaChart/+page.svelte +++ b/packages/layerchart/src/routes/docs/components/AreaChart/+page.svelte @@ -1292,7 +1292,7 @@ x="date" y="value" {xDomain} - brush={{ onBrushEnd: (e) => (xDomain = e.xDomain) }} + brush={{ onBrushEnd: (e) => (xDomain = e.brush.x) }} props={{ area: { motion: { type: 'tween', duration: 200 } }, xAxis: { motion: { type: 'tween', duration: 200 }, tickMultiline: true }, @@ -1307,8 +1307,7 @@ data={denseDateSeriesData2} x="date" y="value" - {xDomain} - brush={{ onBrushEnd: (e) => (xDomain = e.xDomain) }} + brush={{ onBrushEnd: (e) => (xDomain = e.brush.x) }} props={{ area: { motion: { type: 'tween', duration: 200 } }, xAxis: { motion: { type: 'tween', duration: 200 }, tickMultiline: true }, diff --git a/packages/layerchart/src/routes/docs/components/Axis/+page.svelte b/packages/layerchart/src/routes/docs/components/Axis/+page.svelte index 405bbeebf..3c5ca0d88 100644 --- a/packages/layerchart/src/routes/docs/components/Axis/+page.svelte +++ b/packages/layerchart/src/routes/docs/components/Axis/+page.svelte @@ -848,7 +848,7 @@ brush={{ resetOnEnd: true, onBrushEnd: (e) => { - xDomain = asAny(e.xDomain); + xDomain = asAny(e.brush.x); }, }} > @@ -865,9 +865,9 @@ padding={{ top: 20, bottom: 20, left: 20, right: 20 }} brush={{ mode: 'separated', - xDomain, + x: xDomain, onChange: (e) => { - xDomain = asAny(e.xDomain); + xDomain = asAny(e.brush.x); }, }} > @@ -901,7 +901,7 @@ brush={{ resetOnEnd: true, onBrushEnd: (e) => { - xDomain = asAny(e.xDomain); + xDomain = asAny(e.brush.x); }, }} > @@ -918,9 +918,9 @@ padding={{ top: 20, bottom: 20, left: 20, right: 20 }} brush={{ mode: 'separated', - xDomain, + x: xDomain, onChange: (e) => { - xDomain = asAny(e.xDomain); + xDomain = asAny(e.brush.x); }, }} > diff --git a/packages/layerchart/src/routes/docs/components/BrushContext/+page.svelte b/packages/layerchart/src/routes/docs/components/BrushContext/+page.svelte index 281efa7eb..f028927cc 100644 --- a/packages/layerchart/src/routes/docs/components/BrushContext/+page.svelte +++ b/packages/layerchart/src/routes/docs/components/BrushContext/+page.svelte @@ -1,9 +1,9 @@

Examples

+

Html click

+ + + +
+ { + console.log('onBrushEnd', e); + xDomain2 = e.brush.x; + }, + }} + > + {#snippet children({ context })} + + {#each dataSeriesData as d} + {@const start = d.date} + {@const end = endOfInterval('day', d.date)} + + {@const x = context.xScale(start)} + {@const width = context.xScale(end) - x} + {@const height = context.height} + + +
{ + console.log('clicked', start); + }} + onpointerenter={(e) => { + // console.log(start); + }} + >
+ {/each} +
+ {/snippet} +
+
+
+ +

Band scale (WIP)

+ + +
+ { + console.log(e); + }, + }} + > + + + + +
+
+

Basic

@@ -132,7 +212,7 @@ - {#if context.brush.isActive} + {#if context.brush.active} - {#if context.brush.isActive} + {#if context.brush.active} @@ -204,11 +284,11 @@ - {#if context.brush.isActive} + {#if context.brush.active} @@ -245,7 +325,7 @@ resetOnEnd: true, onBrushEnd: (e) => { // @ts-expect-error - set(e.xDomain); + set(e.brush.x); }, }} > @@ -283,7 +363,7 @@ resetOnEnd: true, onBrushEnd: (e) => { // @ts-expect-error - set(e.yDomain); + set(e.brush.y); }, }} > @@ -323,9 +403,9 @@ onBrushEnd: (e) => { set({ // @ts-expect-error - xDomain: e.xDomain, + xDomain: e.brush.x, // @ts-expect-error - yDomain: e.yDomain, + yDomain: e.brush.y, }); }, }} @@ -384,7 +464,7 @@ brush={{ onChange: (e) => { // @ts-expect-error - set(e.xDomain); + set(e.brush.x); }, }} > @@ -412,7 +492,7 @@ axis: 'y', onChange: (e) => { // @ts-expect-error - set(e.yDomain); + set(e.brush.y); }, }} > @@ -494,7 +574,7 @@ brush={{ onChange: (e) => { // @ts-expect-error - set(e.xDomain); + set(e.brush.x); }, }} > @@ -555,9 +635,9 @@ padding={{ left: 16 }} brush={{ mode: 'separated', - xDomain, - onChange: (e) => (xDomain = e.xDomain), - onReset: (e) => (xDomain = null), + x: xDomain, + onChange: (e) => (xDomain = e.brush.x), + onReset: (e) => (xDomain = [null, null]), }} > @@ -565,7 +645,6 @@ line={{ class: 'stroke-2 stroke-(--chart-color)' }} class="fill-(--chart-color) opacity-20" /> -
@@ -592,7 +671,7 @@ resetOnEnd: true, onBrushEnd: (e) => { // @ts-expect-error - set(e.xDomain); + set(e.brush.x); }, }} > @@ -658,9 +737,9 @@ onChange: (e) => { set({ // @ts-expect-error - xDomain: e.xDomain, + xDomain: e.brush.x, // @ts-expect-error - yDomain: e.yDomain, + yDomain: e.brush.y, }); }, }} @@ -717,9 +796,9 @@ onBrushEnd: (e) => { set({ // @ts-expect-error - xDomain: e.xDomain, + xDomain: e.brush.x, // @ts-expect-error - yDomain: e.yDomain, + yDomain: e.brush.y, }); }, }} @@ -744,14 +823,14 @@ brush={{ axis: 'both', mode: 'separated', - xDomain: value?.xDomain, - yDomain: value?.yDomain, + x: value?.xDomain, + y: value?.yDomain, onChange: (e) => { set({ // @ts-expect-error - xDomain: e.xDomain, + xDomain: e.brush.x, // @ts-expect-error - yDomain: e.yDomain, + yDomain: e.brush.y, }); }, }} diff --git a/packages/layerchart/src/routes/docs/examples/Candlestick/+page.svelte b/packages/layerchart/src/routes/docs/examples/Candlestick/+page.svelte index eb1623753..965dbbda9 100644 --- a/packages/layerchart/src/routes/docs/examples/Candlestick/+page.svelte +++ b/packages/layerchart/src/routes/docs/examples/Candlestick/+page.svelte @@ -108,7 +108,7 @@ yNice brush={{ onChange: (e) => { - xDomain = e.xDomain; + xDomain = e.brush.x; }, }} > diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e99c12151..6146b81ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -297,8 +297,8 @@ importers: specifier: 2.0.0-next.17 version: 2.0.0-next.17 '@layerstack/utils': - specifier: 2.0.0-next.14 - version: 2.0.0-next.14 + specifier: 2.0.0-next.15 + version: 2.0.0-next.15 d3-array: specifier: ^3.2.4 version: 3.2.4 @@ -4205,7 +4205,7 @@ snapshots: '@layerstack/tailwind@2.0.0-next.17': dependencies: - '@layerstack/utils': 2.0.0-next.14 + '@layerstack/utils': 2.0.0-next.15 clsx: 2.1.1 d3-array: 3.2.4 lodash-es: 4.17.21