From ca1d41ecd1ad62fa7c1009f461c54a467c096566 Mon Sep 17 00:00:00 2001 From: Wouter Kayser Date: Mon, 19 May 2025 14:08:17 +0200 Subject: [PATCH 01/66] Fix type import Importing a type with the same name causes wrong types to be emitted. --- src/lib/marks/Line.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/marks/Line.svelte b/src/lib/marks/Line.svelte index 6b0156b9..9c54ffff 100644 --- a/src/lib/marks/Line.svelte +++ b/src/lib/marks/Line.svelte @@ -41,7 +41,7 @@ import MarkerPath from './helpers/MarkerPath.svelte'; import { getContext } from 'svelte'; import { resolveProp, resolveStyles } from '../helpers/resolve.js'; - import { line, type CurveFactory, type Line } from 'd3-shape'; + import { line, type CurveFactory, type Line as D3Line } from 'd3-shape'; import { geoPath } from 'd3-geo'; import callWithProps from '$lib/helpers/callWithProps.js'; import { maybeCurve } from '$lib/helpers/curves.js'; @@ -100,7 +100,7 @@ const { getPlotState } = getContext('svelteplot'); const plot = $derived(getPlotState()); - const linePath: Line = $derived( + const linePath: D3Line = $derived( plot.scales.projection && curve === 'auto' ? sphereLine(plot.scales.projection) : callWithProps(line, [], { From 0d86158af445754161a73bd0f313402ff9fd0338 Mon Sep 17 00:00:00 2001 From: gka Date: Thu, 22 May 2025 15:03:28 +0200 Subject: [PATCH 02/66] fix: use @emotion/css in axes ticks --- src/lib/core/Plot.svelte | 34 +++++++-------- src/lib/helpers/getBaseStyles.ts | 1 + src/lib/marks/helpers/BaseAxisX.svelte | 36 +++++++++++----- src/lib/marks/helpers/BaseAxisY.svelte | 40 +++++++++++------ src/lib/types.ts | 60 +++++++++++++------------- 5 files changed, 102 insertions(+), 69 deletions(-) diff --git a/src/lib/core/Plot.svelte b/src/lib/core/Plot.svelte index 22f86974..6a694e00 100644 --- a/src/lib/core/Plot.svelte +++ b/src/lib/core/Plot.svelte @@ -28,22 +28,6 @@ import mergeDeep from '../helpers/mergeDeep.js'; import { computeScales, projectXY } from '../helpers/scales.js'; import { CHANNEL_SCALE, SCALES } from '../constants.js'; - import { scale } from 'svelte/transition'; - - let { - header, - footer, - overlay, - underlay, - children, - facetAxes, - testid, - facet, - class: className = '', - css, - width: fixedWidth, - ...initialOpts - }: Partial = $props(); // automatic margins can be applied by the marks, registered // with their respective unique identifier as keys @@ -95,6 +79,21 @@ ...getContext>('svelteplot/defaults') }; + let { + header, + footer, + overlay, + underlay, + children, + facetAxes, + testid, + facet, + class: className = '', + css = DEFAULTS.css, + width: fixedWidth, + ...initialOpts + }: Partial = $props(); + let width = $state(DEFAULTS.initialWidth); setContext('svelteplot/_defaults', DEFAULTS); @@ -454,7 +453,8 @@ symbol: { type: 'ordinal' }, fx: { type: 'band', axis: 'top' }, fy: { type: 'band', axis: 'right' }, - locale: DEFAULTS.locale + locale: DEFAULTS.locale, + css: DEFAULTS.css }; } diff --git a/src/lib/helpers/getBaseStyles.ts b/src/lib/helpers/getBaseStyles.ts index bc141cec..cbe3c601 100644 --- a/src/lib/helpers/getBaseStyles.ts +++ b/src/lib/helpers/getBaseStyles.ts @@ -18,6 +18,7 @@ const styleProps: Partial> = { fontWeight: 'font-weight', fontStyle: 'font-style', textAnchor: 'text-anchor', + fontVariant: 'font-variant', cursor: 'cursor', pointerEvents: 'pointer-events' }; diff --git a/src/lib/marks/helpers/BaseAxisX.svelte b/src/lib/marks/helpers/BaseAxisX.svelte index 2e002978..85c7a99c 100644 --- a/src/lib/marks/helpers/BaseAxisX.svelte +++ b/src/lib/marks/helpers/BaseAxisX.svelte @@ -11,7 +11,7 @@ RawValue, ScaleType } from '$lib/types.js'; - import { resolveScaledStyles, resolveProp } from '$lib/helpers/resolve.js'; + import { resolveProp, resolveStyles } from '$lib/helpers/resolve.js'; import { max } from 'd3-array'; import { randomId, testFilter } from '$lib/helpers/index.js'; @@ -142,7 +142,7 @@ - {#each positionedTicks as tick, t} + {#each positionedTicks as tick, t (t)} {#if testFilter(tick.value, options) && !tick.hidden} {@const textLines = tick.text} {@const prevTextLines = t && positionedTicks[t - 1].text} @@ -152,27 +152,41 @@ {@const moveDown = (tickSize + tickPadding + (tickRotate !== 0 ? tickFontSize * 0.35 : 0)) * (anchor === 'bottom' ? 1 : -1)} + {@const [textStyle, textClass] = resolveStyles( + plot, + tick, + { + fontVariant: isQuantitative ? 'tabular-nums' : 'normal', + ...options, + fontSize: tickFontSize, + stroke: null + }, + 'fill', + { x: true } + )} 0 ? 'start' : 'middle'}> {#if tickSize} + {@const [tickLineStyle, tickLineClass] = resolveStyles( + plot, + tick, + options, + 'stroke', + { x: true } + )} {/if} - import { getContext, untrack } from 'svelte'; + import { getContext, tick, untrack } from 'svelte'; import { randomId, testFilter } from '$lib/helpers/index.js'; - import { resolveProp, resolveScaledStyles } from '$lib/helpers/resolve.js'; + import { resolveProp, resolveStyles } from '$lib/helpers/resolve.js'; import { max } from 'd3-array'; import type { AutoMarginStores, @@ -84,6 +84,8 @@ let tickTexts = $state([] as SVGTextElement[]); + const isQuantitative = $derived(scaleType !== 'point' && scaleType !== 'band'); + // generate id used for registering margins const id = randomId(); @@ -141,29 +143,43 @@ - {#each positionedTicks as tick, t} + {#each positionedTicks as tick, t (t)} {#if testFilter(tick.value, options) && !tick.hidden} {@const tickClass_ = resolveProp(tickClass, tick.value)} + {@const [textStyle, textClass] = resolveStyles( + plot, + tick, + { + fontVariant: isQuantitative ? 'tabular-nums' : 'normal', + ...options, + fontSize: tickFontSize, + stroke: null + }, + 'fill', + { y: true } + )} {#if tickSize} + {@const [tickLineStyle, tickLineClass] = resolveStyles( + plot, + tick, + options, + 'stroke', + { y: true } + )} {/if} {Array.isArray(tick.text) ? tick.text.join(' ') : tick.text} diff --git a/src/lib/types.ts b/src/lib/types.ts index 7e83e2cf..9b30d915 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -151,12 +151,12 @@ export type ScaleOptions = { base?: number; // sorting for band and point scales sort?: - | ChannelAccessor - | ((a: RawValue, b: RawValue) => number) - | { - channel: string; - order: 'ascending' | 'descending'; - }; + | ChannelAccessor + | ((a: RawValue, b: RawValue) => number) + | { + channel: string; + order: 'ascending' | 'descending'; + }; // symlog scales constant?: number; }; @@ -164,18 +164,18 @@ export type ScaleOptions = { export type ColorScaleOptions = ScaleOptions & { legend: boolean; type: - | ScaleType - | 'categorical' - | 'sequential' - | 'cyclical' - | 'threshold' - | 'quantile' - | 'quantize' - | 'diverging' - | 'diverging-log' - | 'diverging-pow' - | 'diverging-sqrt' - | 'diverging-symlog'; + | ScaleType + | 'categorical' + | 'sequential' + | 'cyclical' + | 'threshold' + | 'quantile' + | 'quantize' + | 'diverging' + | 'diverging-log' + | 'diverging-pow' + | 'diverging-sqrt' + | 'diverging-symlog'; scheme: string; /** * fallback color used for null/undefined @@ -394,9 +394,9 @@ export type PlotOptions = { */ locale: string; /** - * + * pass a @emotion/css function to style plot using dynamic classes */ - css: (d: string) => string; + css: (d: string) => string | undefined; }; export type PlotDefaults = { @@ -438,6 +438,7 @@ export type PlotDefaults = { */ numberFormat: Intl.NumberFormatOptions; markerDotRadius: number; + css: (d: string) => string | undefined; }; export type GenericMarkOptions = Record; @@ -474,9 +475,9 @@ export type PlotScale = { uniqueScaleProps: Set; skip: Map>; fn: ScaleLinear & - ScaleBand & - ScaleOrdinal & - ScaleOrdinal; + ScaleBand & + ScaleOrdinal & + ScaleOrdinal; }; export type CurveName = @@ -703,11 +704,11 @@ export type BaseMarkProps = Partial<{ export type BorderRadius = | number | { - topLeft?: number; - topRight?: number; - bottomRight?: number; - bottomLeft?: number; - }; + topLeft?: number; + topRight?: number; + bottomRight?: number; + bottomLeft?: number; + }; export type BaseRectMarkProps = { inset?: ConstantAccessor; @@ -795,6 +796,7 @@ export type MarkStyleProps = | 'fill' | 'fillOpacity' | 'fontWeight' + | 'fontVariant' | 'fontSize' | 'fontStyle' | 'stroke' @@ -894,4 +896,4 @@ export type MapMethod = export type MapOptions = Partial>; -export type UsedScales = Record; \ No newline at end of file +export type UsedScales = Record; From d57dbd2407c79098e7145515be73ef092c44b9b7 Mon Sep 17 00:00:00 2001 From: gka Date: Thu, 22 May 2025 15:04:00 +0200 Subject: [PATCH 03/66] 0.2.6-next.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b2d188f..fd69316e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelteplot", - "version": "0.2.5", + "version": "0.2.6-next.0", "license": "ISC", "author": { "name": "Gregor Aisch", From d63a9ebfe7e7ec715161c510d3963b58b1566b29 Mon Sep 17 00:00:00 2001 From: James Scott-Brown Date: Thu, 22 May 2025 14:58:19 +0100 Subject: [PATCH 04/66] fix typos --- src/lib/Plot.svelte | 2 +- src/lib/helpers/autoScales.ts | 2 +- src/lib/helpers/callWithProps.ts | 2 +- src/lib/helpers/group.test.ts | 2 +- src/lib/helpers/scales.ts | 6 +++--- src/lib/marks/GridX.svelte | 4 ++-- src/lib/marks/helpers/BaseAxisX.svelte | 2 +- src/lib/marks/helpers/BaseAxisY.svelte | 2 +- src/lib/marks/helpers/MarkerPath.svelte | 2 +- src/lib/transforms/bin.test.ts | 4 ++-- src/lib/transforms/bin.ts | 2 +- src/routes/features/plot/+page.md | 2 +- src/routes/features/scales/+page.md | 6 +++--- src/routes/getting-started/+page.md | 2 +- src/routes/introduction/+page.md | 4 ++-- src/routes/marks/dot/+page.md | 2 +- src/routes/marks/frame/+page.md | 2 +- src/routes/marks/line/+page.md | 2 +- src/routes/marks/text/+page.md | 2 +- src/routes/marks/vector/+page.md | 2 +- 20 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/lib/Plot.svelte b/src/lib/Plot.svelte index 65c9e2bb..ae81faf6 100644 --- a/src/lib/Plot.svelte +++ b/src/lib/Plot.svelte @@ -4,7 +4,7 @@ their data and channels and computes the shared scales. The Plot component is split into two parts. This is the outer Plot which - provides convenient defaults and automatically adds axes etc to the grapihcs. + provides convenient defaults and automatically adds axes etc to the graphics. The downside is that it adds a bunch of imports that you may not be using. To help with this you can use the core/Plot component directly for a more low-level Plot wrapper. diff --git a/src/lib/helpers/autoScales.ts b/src/lib/helpers/autoScales.ts index 9c1418f0..2577fed4 100644 --- a/src/lib/helpers/autoScales.ts +++ b/src/lib/helpers/autoScales.ts @@ -321,7 +321,7 @@ function getScaleRange( : name === 'r' ? [0, 10] : name === 'symbol' - ? // Plot is smart enough to pick different default shapes depending on wether + ? // Plot is smart enough to pick different default shapes depending on whether // or not there are filled dot marks in the plot, so we have to pass this // information all the way here plotHasFilledDotMarks diff --git a/src/lib/helpers/callWithProps.ts b/src/lib/helpers/callWithProps.ts index 78f3259d..25d707fb 100644 --- a/src/lib/helpers/callWithProps.ts +++ b/src/lib/helpers/callWithProps.ts @@ -4,7 +4,7 @@ type Setter = (v: any) => void; /** * Helper function to call a D3 "function class" while also calling - * porperty setter functions on the result. + * property setter functions on the result. */ export default function ( d3func: () => Record, diff --git a/src/lib/helpers/group.test.ts b/src/lib/helpers/group.test.ts index 102e6517..432a7240 100644 --- a/src/lib/helpers/group.test.ts +++ b/src/lib/helpers/group.test.ts @@ -27,7 +27,7 @@ describe('groupFacetsAndZ', () => { expect(result).toEqual([2, 1, 1, 1]); }); - it('implicitely groups by fill and stroke if z is not present', () => { + it('implicitly groups by fill and stroke if z is not present', () => { const items = [ { color: 'red', value: 10 }, { color: 'red', value: 15 }, diff --git a/src/lib/helpers/scales.ts b/src/lib/helpers/scales.ts index 706a3ccb..fbed5e58 100644 --- a/src/lib/helpers/scales.ts +++ b/src/lib/helpers/scales.ts @@ -402,9 +402,9 @@ const scaledChannelNames: ScaledChannelName[] = [ ]; /** - * Mark channels can explicitely or implicitely be exempt from being + * Mark channels can explicitly or implicitly be exempt from being * mapped to a scale, so everywhere where values are being mapped to - * scales, we need to check if the the scale is supposed to be used + * scales, we need to check if the scale is supposed to be used * not. That's what this function is used for. */ export function getUsedScales( @@ -450,7 +450,7 @@ export function projectXY( ): [number, number] { if (scales.projection) { // TODO: pretty sure this is not how projection streams are supposed to be used - // efficiantly, in observable plot, all data points of a mark are projected using + // efficiently, in observable plot, all data points of a mark are projected using // the same stream let x_, y_; const stream = scales.projection.stream({ diff --git a/src/lib/marks/GridX.svelte b/src/lib/marks/GridX.svelte index d3a7605c..6ae6e5f9 100644 --- a/src/lib/marks/GridX.svelte +++ b/src/lib/marks/GridX.svelte @@ -7,12 +7,12 @@ import { testFilter } from '$lib/helpers/index.js'; import { RAW_VALUE } from '$lib/transforms/recordize.js'; - type GrixXMarkProps = BaseMarkProps & { + type GridXMarkProps = BaseMarkProps & { data?: RawValue[]; automatic?: boolean; }; - let { data = [], automatic = false, ...options }: GrixXMarkProps = $props(); + let { data = [], automatic = false, ...options }: GridXMarkProps = $props(); const { getPlotState } = getContext('svelteplot'); const plot = $derived(getPlotState()); diff --git a/src/lib/marks/helpers/BaseAxisX.svelte b/src/lib/marks/helpers/BaseAxisX.svelte index 85c7a99c..fe531fb2 100644 --- a/src/lib/marks/helpers/BaseAxisX.svelte +++ b/src/lib/marks/helpers/BaseAxisX.svelte @@ -88,7 +88,7 @@ const T = tickObjects.length; for (let i = 0; i < T; i++) { let j = i; - // find the preceeding tick that was not hidden + // find the preceding tick that was not hidden do { j--; } while (j >= 0 && tickObjects[j].hidden); diff --git a/src/lib/marks/helpers/BaseAxisY.svelte b/src/lib/marks/helpers/BaseAxisY.svelte index d0e10f53..d0f85d53 100644 --- a/src/lib/marks/helpers/BaseAxisY.svelte +++ b/src/lib/marks/helpers/BaseAxisY.svelte @@ -70,7 +70,7 @@ const T = tickObjects.length; for (let i = 0; i < T; i++) { let j = i; - // find the preceeding tick that was not hidden + // find the preceding tick that was not hidden do { j--; } while (j >= 0 && tickObjects[j].hidden); diff --git a/src/lib/marks/helpers/MarkerPath.svelte b/src/lib/marks/helpers/MarkerPath.svelte index 72d4c691..d7d3ef2e 100644 --- a/src/lib/marks/helpers/MarkerPath.svelte +++ b/src/lib/marks/helpers/MarkerPath.svelte @@ -25,7 +25,7 @@ datum: DataRecord; /** * the marker shape to use at the start of the path, defaults to - * cirlce + * circle */ markerStart?: boolean | MarkerShape; /** diff --git a/src/lib/transforms/bin.test.ts b/src/lib/transforms/bin.test.ts index 3f554a29..d8478715 100644 --- a/src/lib/transforms/bin.test.ts +++ b/src/lib/transforms/bin.test.ts @@ -73,7 +73,7 @@ describe('binX', () => { y: d })); - it('bins dailys into weekly data', () => { + it('bins daily into weekly data', () => { const { data, ...channels } = binX( { data: dailyData, @@ -119,7 +119,7 @@ describe('binX', () => { expect(binDuration).toBeLessThanOrEqual(7 * 24 * 60 * 60 * 1000); }); - // it.only('bins dailys into weekly data', () => { + // it.only('bins daily into weekly data', () => { // const { data, ...channels } = binX( // { // data: dailyData, diff --git a/src/lib/transforms/bin.ts b/src/lib/transforms/bin.ts index c4898276..e5fba5fe 100644 --- a/src/lib/transforms/bin.ts +++ b/src/lib/transforms/bin.ts @@ -159,7 +159,7 @@ export function binY( } /** - * for binning in x and y dimension simulatenously + * for binning in x and y dimension simultaneously */ export function bin( { data, ...channels }: TransformArg, diff --git a/src/routes/features/plot/+page.md b/src/routes/features/plot/+page.md index 68070ccc..dbdaafbc 100644 --- a/src/routes/features/plot/+page.md +++ b/src/routes/features/plot/+page.md @@ -336,7 +336,7 @@ to add events and scoped styles. ## Core plot -SveltePlot provides a lot of convenience features with the unfortunate side-effect of blowing up the bundle size a bit. In situations where this is a concern, you may use a more light-weight version of the Plot component. Note that you need to explicitely include all marks that you want, such as grids or axis marks. +SveltePlot provides a lot of convenience features with the unfortunate side-effect of blowing up the bundle size a bit. In situations where this is a concern, you may use a more light-weight version of the Plot component. Note that you need to explicitly include all marks that you want, such as grids or axis marks. ```svelte live From 52e6fcec9b269f18ed8738c6703ac9985c8c6676 Mon Sep 17 00:00:00 2001 From: James Scott-Brown Date: Fri, 23 May 2025 21:40:38 +0100 Subject: [PATCH 20/66] put day nuber above month in x-axis label --- src/routes/transforms/interval/+page.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/routes/transforms/interval/+page.md b/src/routes/transforms/interval/+page.md index cbe417dd..16ffd80f 100644 --- a/src/routes/transforms/interval/+page.md +++ b/src/routes/transforms/interval/+page.md @@ -16,7 +16,7 @@ The interval transform is often used for time-series bar charts. For example, co month: 'short', }); - const tickFormat = (date: Date) => DAY_MONTH.format(date).split(' '); + const tickFormat = (date: Date) => DAY_MONTH.format(date).split(' ').reverse(); import { page } from '$app/state'; let { aapl } = $derived(page.data.data); @@ -37,9 +37,15 @@ In contrast, a [rectY](/marks/rect) mark with the interval option and the day in import { page } from '$app/state'; let { aapl } = $derived(page.data.data); + + const DAY_MONTH = new Intl.DateTimeFormat('en-US', { + day: 'numeric', + month: 'short', + }); + const tickFormat = (date: Date) => DAY_MONTH.format(date).split(' ').reverse(); - + + DAY_MONTH.format(date).split(' '); + const tickFormat = (date: Date) => DAY_MONTH.format(date).split(' ').reverse(); From b1f6c5a3fdd86618a978c01eec47e93d992e9128 Mon Sep 17 00:00:00 2001 From: Johan Ronsse Date: Sun, 25 May 2025 08:16:57 -0600 Subject: [PATCH 21/66] Facets fixes * Consistency between page title and sidebar title * Add period to end of sentence --- config/sidebar.ts | 2 +- src/routes/features/facets/+page.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/sidebar.ts b/config/sidebar.ts index 8ece7481..10f7c36f 100644 --- a/config/sidebar.ts +++ b/config/sidebar.ts @@ -40,7 +40,7 @@ export default { to: '/features/transforms' }, { - title: 'Faceting', + title: 'Facets', to: '/features/facets' } ] diff --git a/src/routes/features/facets/+page.md b/src/routes/features/facets/+page.md index 0a1077d1..81ff0ae8 100644 --- a/src/routes/features/facets/+page.md +++ b/src/routes/features/facets/+page.md @@ -2,7 +2,7 @@ title: Facets --- -Facets are a way to split a plot into multiple panels +Facets are a way to split a plot into multiple panels. ```svelte live + - - From 7ab586bfbb9bd1ee71dc0a0135cb535f7642a40f Mon Sep 17 00:00:00 2001 From: gka Date: Sun, 25 May 2025 18:42:35 +0200 Subject: [PATCH 26/66] refine & export arrow mark props --- src/lib/marks/Arrow.svelte | 46 ++++++++++++++++---------------- src/lib/types.ts | 54 +++++++++++++++++++------------------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/lib/marks/Arrow.svelte b/src/lib/marks/Arrow.svelte index 211a176d..c8197cb1 100644 --- a/src/lib/marks/Arrow.svelte +++ b/src/lib/marks/Arrow.svelte @@ -1,32 +1,14 @@ - + + + @@ -102,7 +107,12 @@ You can compare the metric to a different "baseline" by providing a constant _y1 ```svelte live @@ -80,10 +82,11 @@ The meaning of the interval mark option depends on the associated mark, such as const DAY_MONTH = new Intl.DateTimeFormat('en-US', { day: 'numeric', - month: 'short', + month: 'short' }); - const tickFormat = (date: Date) => DAY_MONTH.format(date).split(' ').reverse(); + const tickFormat = (date: Date) => + DAY_MONTH.format(date).split(' ').reverse(); diff --git a/src/tests/barX.test.svelte b/src/tests/barX.test.svelte index c642cfe5..d16a8d1c 100644 --- a/src/tests/barX.test.svelte +++ b/src/tests/barX.test.svelte @@ -6,7 +6,7 @@ plotArgs: ComponentProps; barArgs: ComponentProps; } - + let { plotArgs, barArgs }: Props = $props(); diff --git a/src/tests/barX.test.ts b/src/tests/barX.test.ts index 8884e19e..5b186d83 100644 --- a/src/tests/barX.test.ts +++ b/src/tests/barX.test.ts @@ -3,15 +3,18 @@ import { render } from '@testing-library/svelte'; import BarXTest from './barX.test.svelte'; import { parseSVG, makeAbsolute } from 'svg-path-parser'; -const testData = [{ - year: '2010', - low: 2, - high: 5 -}, { - year: '2011', - low: 4, - high: 7 -}] +const testData = [ + { + year: '2010', + low: 2, + high: 5 + }, + { + year: '2011', + low: 4, + high: 7 + } +]; describe('BarX mark', () => { it('simple bar chart from number array', () => { @@ -29,10 +32,16 @@ describe('BarX mark', () => { expect(bars.length).toBe(5); const barDims = Array.from(bars).map(getRectDims); // check that bar height are equal - expect(barDims.map(d => d.h)).toStrictEqual(new Array(5).fill(barDims[0].h)) + expect(barDims.map((d) => d.h)).toStrictEqual(new Array(5).fill(barDims[0].h)); // check that bar length match data - expect(barDims.map(d => d.w)).toStrictEqual([1, 2, 3, 4, 5].map(m => barDims[0].w * m)) - expect(barDims.map(d => d.strokeWidth)).toStrictEqual(['1px', '2px', '3px', '4px', '5px']); + expect(barDims.map((d) => d.w)).toStrictEqual([1, 2, 3, 4, 5].map((m) => barDims[0].w * m)); + expect(barDims.map((d) => d.strokeWidth)).toStrictEqual([ + '1px', + '2px', + '3px', + '4px', + '5px' + ]); }); it('bar chart from objects', () => { @@ -71,9 +80,9 @@ describe('BarX mark', () => { expect(bars.length).toBe(5); const barDims = Array.from(bars).map(getPathDims); // // check that bar height are equal - expect(barDims.map(d => d.h)).toStrictEqual(new Array(5).fill(barDims[0].h)) + expect(barDims.map((d) => d.h)).toStrictEqual(new Array(5).fill(barDims[0].h)); // // check that bar length match data - expect(barDims.map(d => d.w)).toStrictEqual([1, 2, 3, 4, 5].map(m => barDims[0].w * m)) + expect(barDims.map((d) => d.w)).toStrictEqual([1, 2, 3, 4, 5].map((m) => barDims[0].w * m)); }); }); @@ -88,14 +97,14 @@ function getRectDims(rect: SVGRectElement) { h: Math.round(+rect.getAttribute('height')), fill: rect.style.fill, stroke: rect.style.stroke, - strokeWidth: rect.style.strokeWidth, - } + strokeWidth: rect.style.strokeWidth + }; } function getPathDims(path: SVGPathElement) { const r = makeAbsolute(parseSVG(path.getAttribute('d'))); - const x = r.flatMap(d => [d.x, d.x0, d.x1]).filter(x => x != null); - const y = r.flatMap(d => [d.y, d.y0, d.y1]).filter(y => y != null); + const x = r.flatMap((d) => [d.x, d.x0, d.x1]).filter((x) => x != null); + const y = r.flatMap((d) => [d.y, d.y0, d.y1]).filter((y) => y != null); const t = path ?.getAttribute('transform') ?.match(/translate\((\d+(?:\.\d+)?),(\d+(?:\.\d+)?)\)/); @@ -107,6 +116,6 @@ function getPathDims(path: SVGPathElement) { h: Math.round(Math.max(...y) - Math.min(...y)), fill: path.style.fill, stroke: path.style.stroke, - strokeWidth: path.style.strokeWidth, + strokeWidth: path.style.strokeWidth }; } diff --git a/src/tests/barY.test.ts b/src/tests/barY.test.ts index 972d6452..118b2725 100644 --- a/src/tests/barY.test.ts +++ b/src/tests/barY.test.ts @@ -27,7 +27,13 @@ describe('BarY mark', () => { expect(barDims[2].h).toBe(barDims[0].h * 3); expect(barDims[3].h).toBe(barDims[0].h * 4); expect(barDims[4].h).toBe(barDims[0].h * 5); - expect(barDims.map(d => d.strokeWidth)).toStrictEqual(['1px', '2px', '3px', '4px', '5px']); + expect(barDims.map((d) => d.strokeWidth)).toStrictEqual([ + '1px', + '2px', + '3px', + '4px', + '5px' + ]); }); it('stacked bar chart', () => { @@ -117,14 +123,14 @@ function getRectDims(rect: SVGRectElement) { h: Math.round(+rect.getAttribute('height')), fill: rect.style.fill, stroke: rect.style.stroke, - strokeWidth: rect.style.strokeWidth, - } + strokeWidth: rect.style.strokeWidth + }; } function getPathDims(path: SVGPathElement) { const r = makeAbsolute(parseSVG(path.getAttribute('d'))); - const x = r.flatMap(d => [d.x, d.x0, d.x1]).filter(x => x != null); - const y = r.flatMap(d => [d.y, d.y0, d.y1]).filter(y => y != null); + const x = r.flatMap((d) => [d.x, d.x0, d.x1]).filter((x) => x != null); + const y = r.flatMap((d) => [d.y, d.y0, d.y1]).filter((y) => y != null); const t = path ?.getAttribute('transform') ?.match(/translate\((\d+(?:\.\d+)?),(\d+(?:\.\d+)?)\)/); @@ -136,6 +142,6 @@ function getPathDims(path: SVGPathElement) { h: Math.round(Math.max(...y) - Math.min(...y)), fill: path.style.fill, stroke: path.style.stroke, - strokeWidth: path.style.strokeWidth, + strokeWidth: path.style.strokeWidth }; } diff --git a/src/tests/brush.svelte.test.ts b/src/tests/brush.svelte.test.ts index 956c0998..f2eaaca4 100644 --- a/src/tests/brush.svelte.test.ts +++ b/src/tests/brush.svelte.test.ts @@ -4,14 +4,13 @@ import { userEvent } from '@testing-library/user-event'; import BrushTest from './brush.test.svelte'; import { tick, type ComponentProps } from 'svelte'; - describe('Brush mark', () => { it('single brush with basic properties', async () => { const props: ComponentProps = $state({ plotArgs: { x: { domain: [0, 10] }, y: { domain: [0, 10] } }, brushArgs: {}, brush: { enabled: false } - }) + }); const { container } = render(BrushTest, props); @@ -21,12 +20,11 @@ describe('Brush mark', () => { }); it('brush reacts to state changes', async () => { - const props: ComponentProps = $state({ plotArgs: { x: { domain: [0, 10] }, y: { domain: [0, 10] } }, brushArgs: {}, brush: { enabled: false } - }) + }); const { container } = render(BrushTest, props); @@ -48,7 +46,7 @@ describe('Brush mark', () => { plotArgs: { width: 400, x: { domain: [0, 10] }, y: { domain: [0, 10] } }, brushArgs: {}, brush: { enabled: false } - }) + }); const { container } = render(BrushTest, props); @@ -81,6 +79,4 @@ describe('Brush mark', () => { const draggedRect = container.querySelectorAll('rect.brush-rect'); expect(draggedRect.length).toBe(1); }); - - }); diff --git a/src/tests/colors.test.ts b/src/tests/colors.test.ts index 14204cbf..4229456b 100644 --- a/src/tests/colors.test.ts +++ b/src/tests/colors.test.ts @@ -10,11 +10,15 @@ describe('Colors', () => { const { container } = render(ColorsTest, { props: { dotOptions: { - data: [{ - x: 1, sex: 'male' - }, { - x: 2, sex: 'female' - } + data: [ + { + x: 1, + sex: 'male' + }, + { + x: 2, + sex: 'female' + } ], x: 'x', y: 0, @@ -34,13 +38,19 @@ describe('Colors', () => { const { container } = render(ColorsTest, { props: { dotOptions: { - data: [{ - x: 1, sex: 'male' - }, { - x: 2, sex: 'female' - }, { - x: 3, sex: 'in-between' - } + data: [ + { + x: 1, + sex: 'male' + }, + { + x: 2, + sex: 'female' + }, + { + x: 3, + sex: 'in-between' + } ], x: 'x', y: 0, @@ -66,6 +76,6 @@ describe('Colors', () => { function getDotStyle(path: SVGPathElement) { return { fill: path.style.fill, - stroke: path.style.stroke, - } + stroke: path.style.stroke + }; } diff --git a/src/tests/gridX.test.svelte b/src/tests/gridX.test.svelte index 575fb611..6df26db0 100644 --- a/src/tests/gridX.test.svelte +++ b/src/tests/gridX.test.svelte @@ -1,15 +1,15 @@ - + diff --git a/src/tests/gridX.test.ts b/src/tests/gridX.test.ts index 3d7645c8..42c34fea 100644 --- a/src/tests/gridX.test.ts +++ b/src/tests/gridX.test.ts @@ -2,27 +2,28 @@ import { describe, it, expect } from 'vitest'; import { render } from '@testing-library/svelte'; import GridXTest from './gridX.test.svelte'; - describe('GridX mark', () => { - it('simple x grid with stroke styles', () => { - const { container } = render(GridXTest, { - props: { - plotArgs: { - x: { domain: [0, 10] }, - y: { domain: [0, 10] }, - }, - gridArgs: { - stroke: '#008000', - strokeOpacity: 0.5, - strokeDasharray: '5, 5', - data: [0, 5, 10], - }, - }, + it('simple x grid with stroke styles', () => { + const { container } = render(GridXTest, { + props: { + plotArgs: { + x: { domain: [0, 10] }, + y: { domain: [0, 10] } + }, + gridArgs: { + stroke: '#008000', + strokeOpacity: 0.5, + strokeDasharray: '5, 5', + data: [0, 5, 10] + } + } + }); + const gridLines = container.querySelectorAll( + 'g.grid-x > line' + ) as NodeListOf; + expect(gridLines.length).toBe(3); + expect(gridLines[0].style.strokeDasharray).toBe('5, 5'); + expect(gridLines[0].style.stroke).toBe('#008000'); + expect(gridLines[0].style.strokeOpacity).toBe('0.5'); }); - const gridLines = container.querySelectorAll('g.grid-x > line') as NodeListOf; - expect(gridLines.length).toBe(3); - expect(gridLines[0].style.strokeDasharray).toBe('5, 5'); - expect(gridLines[0].style.stroke).toBe('#008000'); - expect(gridLines[0].style.strokeOpacity).toBe('0.5'); - }) -}) \ No newline at end of file +}); diff --git a/src/tests/gridY.test.svelte b/src/tests/gridY.test.svelte index af06a78a..7f6cfeea 100644 --- a/src/tests/gridY.test.svelte +++ b/src/tests/gridY.test.svelte @@ -1,15 +1,15 @@ - + diff --git a/src/tests/gridY.test.ts b/src/tests/gridY.test.ts index c774c167..aece75c0 100644 --- a/src/tests/gridY.test.ts +++ b/src/tests/gridY.test.ts @@ -3,25 +3,27 @@ import { render } from '@testing-library/svelte'; import GridYTest from './gridY.test.svelte'; describe('GridY mark', () => { - it('simple y grid with stroke styles', () => { - const { container } = render(GridYTest, { - props: { - plotArgs: { - x: { domain: [0, 10] }, - y: { domain: [0, 10] }, - }, - gridArgs: { - stroke: '#008000', - strokeOpacity: 0.5, - strokeDasharray: '5, 5', - data: [0, 5, 10], - }, - }, + it('simple y grid with stroke styles', () => { + const { container } = render(GridYTest, { + props: { + plotArgs: { + x: { domain: [0, 10] }, + y: { domain: [0, 10] } + }, + gridArgs: { + stroke: '#008000', + strokeOpacity: 0.5, + strokeDasharray: '5, 5', + data: [0, 5, 10] + } + } + }); + const gridLines = container.querySelectorAll( + 'g.grid-y > line' + ) as NodeListOf; + expect(gridLines.length).toBe(3); + expect(gridLines[0].style.strokeDasharray).toBe('5, 5'); + expect(gridLines[0].style.stroke).toBe('#008000'); + expect(gridLines[0].style.strokeOpacity).toBe('0.5'); }); - const gridLines = container.querySelectorAll('g.grid-y > line') as NodeListOf; - expect(gridLines.length).toBe(3); - expect(gridLines[0].style.strokeDasharray).toBe('5, 5'); - expect(gridLines[0].style.stroke).toBe('#008000'); - expect(gridLines[0].style.strokeOpacity).toBe('0.5'); - }) -}) \ No newline at end of file +}); diff --git a/src/tests/line.test.ts b/src/tests/line.test.ts index 51fe1363..bf43e795 100644 --- a/src/tests/line.test.ts +++ b/src/tests/line.test.ts @@ -35,7 +35,9 @@ describe('Line mark', () => { } }); - const lines = container.querySelectorAll('g.lines > g > path') as NodeListOf; + const lines = container.querySelectorAll( + 'g.lines > g > path' + ) as NodeListOf; expect(lines).toHaveLength(1); expect(lines[0]?.getAttribute('d')).toBe( 'M1,95L8.917,80C16.833,65,32.667,35,48.5,27.5C64.333,20,80.167,35,88.083,42.5L96,50' @@ -78,7 +80,9 @@ describe('Line mark', () => { } }); - const lines = container.querySelectorAll('g.lines > g > path') as NodeListOf; + const lines = container.querySelectorAll( + 'g.lines > g > path' + ) as NodeListOf; expect(lines).toHaveLength(1); const line = lines[0]; expect(line?.getAttribute('d')).toBe('M1,95L48.5,50L96,5'); @@ -102,7 +106,9 @@ describe('Line mark', () => { }); // The implementation might differ from our expectation - look for any path elements - const paths = container.querySelectorAll('g.lines > g > path') as NodeListOf;; + const paths = container.querySelectorAll( + 'g.lines > g > path' + ) as NodeListOf; expect(paths.length).toBeGreaterThan(0); // Check if at least one path has the expected styles @@ -214,7 +220,9 @@ describe('Line mark', () => { } }); - const lines = container.querySelectorAll('g.lines > g > path') as NodeListOf; + const lines = container.querySelectorAll( + 'g.lines > g > path' + ) as NodeListOf; expect(lines).toHaveLength(2); // Verify we have two distinct lines with different stroke colors expect(lines[0]?.style.stroke).not.toBe(lines[1]?.style.stroke); diff --git a/src/tests/plot.test.ts b/src/tests/plot.test.ts index 47f59252..9beb0b10 100644 --- a/src/tests/plot.test.ts +++ b/src/tests/plot.test.ts @@ -133,7 +133,7 @@ describe('Plot component', () => { const { container } = render(PlotTest, { props: { width: 100, - height: 150, + height: 150 } }); diff --git a/src/tests/polyfill.d.ts b/src/tests/polyfill.d.ts index 16470312..cd8347ff 100644 --- a/src/tests/polyfill.d.ts +++ b/src/tests/polyfill.d.ts @@ -1,7 +1,7 @@ declare module 'resize-observer-polyfill' { - const ResizeObserver: { - new (callback: ResizeObserverCallback): ResizeObserver; - prototype: ResizeObserver; - }; - export default ResizeObserver; -} \ No newline at end of file + const ResizeObserver: { + new (callback: ResizeObserverCallback): ResizeObserver; + prototype: ResizeObserver; + }; + export default ResizeObserver; +} diff --git a/src/tests/setup.ts b/src/tests/setup.ts index 321cb0d1..6d879976 100644 --- a/src/tests/setup.ts +++ b/src/tests/setup.ts @@ -2,14 +2,14 @@ import ResizeObserver from 'resize-observer-polyfill'; import MatchMediaMock from 'vitest-matchmedia-mock'; import type MatchMedia from 'vitest-matchmedia-mock'; -import { afterEach, beforeAll } from "vitest"; +import { afterEach, beforeAll } from 'vitest'; let matchMedia: MatchMedia = new MatchMediaMock(); beforeAll(() => { global.ResizeObserver = ResizeObserver; -}) +}); afterEach(() => { matchMedia.clear(); -}); \ No newline at end of file +}); diff --git a/tsconfig.json b/tsconfig.json index 6b7a1779..20f50d38 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,10 +10,10 @@ "sourceMap": true, "strict": true, "moduleResolution": "bundler", - "module": "esnext" + "module": "esnext" } // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // from the referenced tsconfig.json - TypeScript does not merge them in -} \ No newline at end of file +} diff --git a/vite.config.js b/vite.config.js index af1879a0..1f4626b8 100644 --- a/vite.config.js +++ b/vite.config.js @@ -10,8 +10,8 @@ export default defineConfig({ resolve: { ...(process.env.VITEST ? { - conditions: ['browser'] - } + conditions: ['browser'] + } : undefined), alias: { svelteplot: path.resolve(__dirname, './src/lib/index.js'), From 6cee75237825ff77047b9f009237ef9639a6029a Mon Sep 17 00:00:00 2001 From: gka Date: Sun, 25 May 2025 22:18:32 +0200 Subject: [PATCH 30/66] chore: fix eslint errors --- src/lib/marks/BarX.svelte | 9 ++++-- src/lib/marks/Text.svelte | 29 +++++++++----------- src/lib/marks/TickX.svelte | 2 +- src/lib/marks/TickY.svelte | 2 +- src/lib/marks/Vector.svelte | 2 +- src/lib/marks/helpers/BaseAxisX.svelte | 2 +- src/lib/marks/helpers/LinearGradientX.svelte | 2 +- src/lib/marks/helpers/LinearGradientY.svelte | 2 +- src/lib/marks/helpers/MarkerPath.svelte | 2 +- src/lib/marks/helpers/RectPath.svelte | 4 +-- src/lib/ui/RadioInput.svelte | 2 +- src/lib/ui/Select.svelte | 2 +- src/routes/features/marks/+page.md | 4 ++- 13 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/lib/marks/BarX.svelte b/src/lib/marks/BarX.svelte index cf6b6b92..a6cfb01a 100644 --- a/src/lib/marks/BarX.svelte +++ b/src/lib/marks/BarX.svelte @@ -6,7 +6,12 @@ import Mark from '../Mark.svelte'; import { getContext } from 'svelte'; import { stackX, recordizeX, intervalX, sort } from '$lib/index.js'; - import type { PlotContext, BaseMarkProps, RectMarkProps, ChannelAccessor } from '../types.js'; + import type { + PlotContext, + BaseMarkProps, + BaseRectMarkProps, + ChannelAccessor + } from '../types.js'; import type { StackOptions } from '$lib/transforms/stack.js'; import type { DataRow } from '$lib/types.js'; import GroupMultiple from './helpers/GroupMultiple.svelte'; @@ -19,7 +24,7 @@ x2?: ChannelAccessor; y?: ChannelAccessor; stack?: StackOptions; - } & RectMarkProps; + } & BaseRectMarkProps; let { data = [{}], class: className = null, stack, ...options }: BarXProps = $props(); diff --git a/src/lib/marks/Text.svelte b/src/lib/marks/Text.svelte index d1e51d8a..b01245b1 100644 --- a/src/lib/marks/Text.svelte +++ b/src/lib/marks/Text.svelte @@ -78,7 +78,7 @@ {...args}> {#snippet children({ mark, scaledData, usedScales })} - {#each scaledData as d} + {#each scaledData as d, i (i)} {#if d.valid} {@const title = resolveProp(args.title, d.datum, '')} {@const frameAnchor = resolveProp(args.frameAnchor, d.datum)} @@ -148,20 +148,17 @@ {#each textLines as line, l}{#each textLines as line, l (l)}{textLines[0]}{#if title}{title}{/if} {/if} diff --git a/src/lib/marks/TickX.svelte b/src/lib/marks/TickX.svelte index dcf0ec19..dca5df17 100644 --- a/src/lib/marks/TickX.svelte +++ b/src/lib/marks/TickX.svelte @@ -47,7 +47,7 @@ {#snippet children({ mark, usedScales })} - {#each args.data as datum} + {#each args.data as datum, i (i)} {#if testFacet(datum, mark.options) && testFilter(datum, args)} {@const x_ = resolveChannel('x', datum, args)} {@const y_ = resolveChannel('y', datum, args)} diff --git a/src/lib/marks/TickY.svelte b/src/lib/marks/TickY.svelte index 84e4c4c8..49305b3f 100644 --- a/src/lib/marks/TickY.svelte +++ b/src/lib/marks/TickY.svelte @@ -48,7 +48,7 @@ {#snippet children({ mark, usedScales })} - {#each args.data as datum} + {#each args.data as datum, i (i)} {#if testFacet(datum, mark.options) && testFilter(datum, args)} {@const y_ = resolveChannel('y', datum, args)} {@const x_ = resolveChannel('x', datum, args)} diff --git a/src/lib/marks/Vector.svelte b/src/lib/marks/Vector.svelte index 55502a19..8e92d569 100644 --- a/src/lib/marks/Vector.svelte +++ b/src/lib/marks/Vector.svelte @@ -170,7 +170,7 @@ implement canvas rendering for vector mark {:else} - {#each scaledData as d} + {#each scaledData as d, i (i)} {@const r = resolveChannel('r', d.datum, { r: 3, ...args })} {#if d.valid && isValid(r)} {@const dx = +resolveProp(args.dx, d.datum, 0)} diff --git a/src/lib/marks/helpers/BaseAxisX.svelte b/src/lib/marks/helpers/BaseAxisX.svelte index fe531fb2..daa2f33d 100644 --- a/src/lib/marks/helpers/BaseAxisX.svelte +++ b/src/lib/marks/helpers/BaseAxisX.svelte @@ -198,7 +198,7 @@ {#if textLines.length === 1} {textLines[0]} {:else} - {#each textLines as line, i} + {#each textLines as line, i (i)} {!prevTextLines || prevTextLines[i] !== line ? line diff --git a/src/lib/marks/helpers/LinearGradientX.svelte b/src/lib/marks/helpers/LinearGradientX.svelte index 2cc27bf6..4d64d50b 100644 --- a/src/lib/marks/helpers/LinearGradientX.svelte +++ b/src/lib/marks/helpers/LinearGradientX.svelte @@ -21,7 +21,7 @@ - {#each projectedStops as { px, color }} + {#each projectedStops as { px, color }, i (i)} {/each} diff --git a/src/lib/marks/helpers/LinearGradientY.svelte b/src/lib/marks/helpers/LinearGradientY.svelte index 5418e1ff..8ee1f6f0 100644 --- a/src/lib/marks/helpers/LinearGradientY.svelte +++ b/src/lib/marks/helpers/LinearGradientY.svelte @@ -21,7 +21,7 @@ - {#each projectedStops as { py, color }} + {#each projectedStops as { py, color }, i (i)} {/each} diff --git a/src/lib/marks/helpers/MarkerPath.svelte b/src/lib/marks/helpers/MarkerPath.svelte index d7d3ef2e..8ca944f6 100644 --- a/src/lib/marks/helpers/MarkerPath.svelte +++ b/src/lib/marks/helpers/MarkerPath.svelte @@ -96,7 +96,7 @@ class={className} stroke-width={strokeWidth_} use:addEventHandlers={{ getPlotState, options: mark.options, datum }}> - {#each Object.entries( { start: markerStart, mid: markerMid, end: markerEnd, all: marker } ) as [key, marker]} + {#each Object.entries( { start: markerStart, mid: markerMid, end: markerEnd, all: marker } ) as [key, marker] (key)} {@const markerId = `marker-${key === 'all' ? '' : `${key}-`}${id}`} {#if isSnippet(marker)} {@render marker(markerId, color)} diff --git a/src/lib/marks/helpers/RectPath.svelte b/src/lib/marks/helpers/RectPath.svelte index 565c76bc..5142ffe2 100644 --- a/src/lib/marks/helpers/RectPath.svelte +++ b/src/lib/marks/helpers/RectPath.svelte @@ -99,7 +99,7 @@ Helper component for rendering rectangular marks in SVG {#if hasBorderRadius} {:else} {label}: {/if} - {#each options as p} + {#each options as p (p)}