Skip to content

Commit c206e58

Browse files
committed
feat: allow disabling of axis labels using text={false} or text={null}
1 parent 3470b39 commit c206e58

File tree

6 files changed

+135
-71
lines changed

6 files changed

+135
-71
lines changed

src/lib/marks/AxisX.svelte

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
tickClass?: ConstantAccessor<string>;
2626
/** ticks is a shorthand for defining data, tickCount or interval */
2727
ticks?: number | string | RawValue[];
28+
/** set to false or null to disable tick labels */
29+
text: boolean | null;
2830
} & XOR<
2931
{
3032
/** approximate number of ticks to be generated */
@@ -79,6 +81,7 @@
7981
class: className,
8082
tickCount = typeof magicTicks === 'number' ? magicTicks : undefined,
8183
tickSpacing,
84+
text = true,
8285
...options
8386
}: AxisXMarkProps = $props();
8487
@@ -220,6 +223,7 @@
220223
{tickPadding}
221224
{tickFontSize}
222225
{tickClass}
226+
{text}
223227
{options}
224228
title={useTitle}
225229
{plot} />

src/lib/marks/AxisY.svelte

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
tickClass?: ConstantAccessor<string>;
2727
/** ticks is a shorthand for defining data, tickCount or interval */
2828
ticks?: number | string | RawValue[];
29+
/** set to false or null to disable tick labels */
30+
text: boolean | null;
2931
} & XOR<
3032
{
3133
/** approximate number of ticks to be generated */
@@ -78,6 +80,7 @@
7880
tickClass,
7981
tickCount = typeof magicTicks === 'number' ? magicTicks : undefined,
8082
tickSpacing,
83+
text = true,
8184
...options
8285
}: AxisYMarkProps = $props();
8386
@@ -200,6 +203,7 @@
200203
{tickFontSize}
201204
{tickClass}
202205
{options}
206+
{text}
203207
title={useTitle}
204208
{plot} />
205209
{/if}

src/lib/marks/helpers/BaseAxisX.svelte

Lines changed: 61 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
dy: ConstantAccessor<number>;
3434
filter: ChannelAccessor;
3535
};
36+
text: boolean;
3637
plot: PlotState;
3738
};
3839
@@ -50,7 +51,8 @@
5051
height,
5152
options,
5253
plot,
53-
title
54+
title,
55+
text = true
5456
}: BaseAxisXProps = $props();
5557
5658
function splitTick(tick: string | string[]) {
@@ -86,22 +88,25 @@
8688
})
8789
);
8890
const T = tickObjects.length;
89-
for (let i = 0; i < T; i++) {
90-
let j = i;
91-
// find the preceding tick that was not hidden
92-
do {
93-
j--;
94-
} while (j >= 0 && tickObjects[j].hidden);
95-
if (j >= 0) {
96-
const tickLabelSpace = Math.abs(tickObjects[i].x - tickObjects[j].x);
97-
tickObjects[i].hidden = tickLabelSpace < 15;
91+
if (text) {
92+
for (let i = 0; i < T; i++) {
93+
let j = i;
94+
// find the preceding tick that was not hidden
95+
do {
96+
j--;
97+
} while (j >= 0 && tickObjects[j].hidden);
98+
if (j >= 0) {
99+
const tickLabelSpace = Math.abs(tickObjects[i].x - tickObjects[j].x);
100+
tickObjects[i].hidden = tickLabelSpace < 15;
101+
}
98102
}
99103
}
100104
return tickObjects;
101105
});
102106
103107
$effect(() => {
104108
untrack(() => [$autoMarginTop, $autoMarginBottom]);
109+
if (!text) return;
105110
const outsideTextAnchor = anchor === 'top' ? 'end' : 'start';
106111
// measure tick label heights
107112
const maxLabelHeight =
@@ -132,6 +137,8 @@
132137
}
133138
});
134139
140+
$inspect({ ticks, positionedTicks });
141+
135142
$effect(() => {
136143
// clear margins on destroy
137144
return () => {
@@ -144,26 +151,7 @@
144151
<g class="axis-x">
145152
{#each positionedTicks as tick, t (t)}
146153
{#if testFilter(tick.value, options) && !tick.hidden}
147-
{@const textLines = tick.text}
148-
{@const prevTextLines = t && positionedTicks[t - 1].text}
149-
{@const fontSize = resolveProp(tickFontSize, tick)}
150154
{@const tickClass_ = resolveProp(tickClass, tick.value)}
151-
{@const estLabelWidth = max(textLines.map((t) => t.length)) * fontSize * 0.2}
152-
{@const moveDown =
153-
(tickSize + tickPadding + (tickRotate !== 0 ? tickFontSize * 0.35 : 0)) *
154-
(anchor === 'bottom' ? 1 : -1)}
155-
{@const [textStyle, textClass] = resolveStyles(
156-
plot,
157-
tick,
158-
{
159-
fontVariant: isQuantitative ? 'tabular-nums' : 'normal',
160-
...options,
161-
fontSize: tickFontSize,
162-
stroke: null
163-
},
164-
'fill',
165-
{ x: true }
166-
)}
167155
<g
168156
class="tick {tickClass_ || ''}"
169157
transform="translate({tick.x + tick.dx}, {tickY + tick.dy})"
@@ -182,31 +170,51 @@
182170
y2={anchor === 'bottom' ? tickSize : -tickSize} />
183171
{/if}
184172

185-
<text
186-
bind:this={tickTextElements[t]}
187-
transform="translate(0, {moveDown}) rotate({tickRotate})"
188-
style={textStyle}
189-
class={textClass}
190-
x={0}
191-
y={0}
192-
dominant-baseline={tickRotate !== 0
193-
? 'central'
194-
: anchor === 'bottom'
195-
? 'hanging'
196-
: 'auto'}>
197-
{#if ticks.length > 0 || t === 0 || t === ticks.length - 1}
198-
{#if textLines.length === 1}
199-
{textLines[0]}
200-
{:else}
201-
{#each textLines as line, i (i)}
202-
<tspan x="0" dy={i ? 12 : 0}
203-
>{!prevTextLines || prevTextLines[i] !== line
204-
? line
205-
: ''}</tspan>
206-
{/each}
173+
{#if text}
174+
{@const textLines = tick.text}
175+
{@const prevTextLines = t && positionedTicks[t - 1].text}
176+
177+
{@const moveDown =
178+
(tickSize + tickPadding + (tickRotate !== 0 ? tickFontSize * 0.35 : 0)) *
179+
(anchor === 'bottom' ? 1 : -1)}
180+
{@const [textStyle, textClass] = resolveStyles(
181+
plot,
182+
tick,
183+
{
184+
fontVariant: isQuantitative ? 'tabular-nums' : 'normal',
185+
...options,
186+
fontSize: tickFontSize,
187+
stroke: null
188+
},
189+
'fill',
190+
{ x: true }
191+
)}
192+
<text
193+
bind:this={tickTextElements[t]}
194+
transform="translate(0, {moveDown}) rotate({tickRotate})"
195+
style={textStyle}
196+
class={textClass}
197+
x={0}
198+
y={0}
199+
dominant-baseline={tickRotate !== 0
200+
? 'central'
201+
: anchor === 'bottom'
202+
? 'hanging'
203+
: 'auto'}>
204+
{#if ticks.length > 0 || t === 0 || t === ticks.length - 1}
205+
{#if textLines.length === 1}
206+
{textLines[0]}
207+
{:else}
208+
{#each textLines as line, i (i)}
209+
<tspan x="0" dy={i ? 12 : 0}
210+
>{!prevTextLines || prevTextLines[i] !== line
211+
? line
212+
: ''}</tspan>
213+
{/each}
214+
{/if}
207215
{/if}
208-
{/if}
209-
</text>
216+
</text>
217+
{/if}
210218
</g>
211219
{/if}
212220
{/each}

src/lib/marks/helpers/BaseAxisY.svelte

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
dy: ConstantAccessor<number>;
3030
};
3131
plot: PlotState;
32+
text: boolean | null;
3233
};
3334
3435
let {
@@ -46,7 +47,8 @@
4647
width,
4748
title,
4849
plot,
49-
options
50+
options,
51+
text = true
5052
}: BaseAxisYProps = $props();
5153
5254
const LINE_ANCHOR = {
@@ -67,16 +69,18 @@
6769
element: null as SVGTextElement | null
6870
};
6971
});
70-
const T = tickObjects.length;
71-
for (let i = 0; i < T; i++) {
72-
let j = i;
73-
// find the preceding tick that was not hidden
74-
do {
75-
j--;
76-
} while (j >= 0 && tickObjects[j].hidden);
77-
if (j >= 0) {
78-
const tickLabelSpace = Math.abs(tickObjects[i].y - tickObjects[j].y);
79-
tickObjects[i].hidden = tickLabelSpace < 15;
72+
if (text) {
73+
const T = tickObjects.length;
74+
for (let i = 0; i < T; i++) {
75+
let j = i;
76+
// find the preceding tick that was not hidden
77+
do {
78+
j--;
79+
} while (j >= 0 && tickObjects[j].hidden);
80+
if (j >= 0) {
81+
const tickLabelSpace = Math.abs(tickObjects[i].y - tickObjects[j].y);
82+
tickObjects[i].hidden = tickLabelSpace < 15;
83+
}
8084
}
8185
}
8286
return tickObjects;
@@ -176,13 +180,15 @@
176180
class={tickLineClass}
177181
x2={anchor === 'left' ? -tickSize : tickSize} />
178182
{/if}
179-
<text
180-
bind:this={tickTexts[t]}
181-
class={[textClass, { 'is-left': anchor === 'left' }]}
182-
style={textStyle}
183-
x={(tickSize + tickPadding) * (anchor === 'left' ? -1 : 1)}
184-
dominant-baseline={LINE_ANCHOR[lineAnchor]}
185-
>{Array.isArray(tick.text) ? tick.text.join(' ') : tick.text}</text>
183+
{#if text}
184+
<text
185+
bind:this={tickTexts[t]}
186+
class={[textClass, { 'is-left': anchor === 'left' }]}
187+
style={textStyle}
188+
x={(tickSize + tickPadding) * (anchor === 'left' ? -1 : 1)}
189+
dominant-baseline={LINE_ANCHOR[lineAnchor]}
190+
>{Array.isArray(tick.text) ? tick.text.join(' ') : tick.text}</text>
191+
{/if}
186192
</g>
187193
{/if}
188194
{/each}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script module>
2+
export const title = 'Major and minor ticks';
3+
export const description =
4+
'You can add minor ticks lines by adding a second axis and disabling the text labels.';
5+
</script>
6+
7+
<script>
8+
import { Plot, AxisX, AxisY, Line } from 'svelteplot';
9+
import { page } from '$app/state';
10+
let { aapl } = $derived(page.data.data);
11+
</script>
12+
13+
<Plot grid inset={10}>
14+
<AxisX />
15+
<AxisX interval="1 month" text={false} tickSize={3} />
16+
<AxisY />
17+
<AxisY interval={5} text={false} tickSize={3} />
18+
<Line data={aapl} x="Date" y="Close" />
19+
</Plot>

src/tests/axisX.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,27 @@ describe('AxisX mark', () => {
153153
expect(ticks.length).toBe(5);
154154
expect(tickValues).toStrictEqual(['Jan2000', 'Jul', 'Jan2001', 'Jul', 'Jan2002']);
155155
});
156+
157+
it('disable tick texts', () => {
158+
const { container } = render(AxisXTest, {
159+
props: {
160+
plotArgs: {
161+
width: 400,
162+
x: { domain: [new Date(2000, 0, 1), new Date(2002, 0, 1)] }
163+
},
164+
axisArgs: { ticks: '1 month', text: null }
165+
}
166+
});
167+
168+
const ticks = container.querySelectorAll('g.axis-x > g.tick') as NodeListOf<SVGGElement>;
169+
const tickLines = container.querySelectorAll(
170+
'g.axis-x > g.tick line'
171+
) as NodeListOf<SVGLineElement>;
172+
const tickLabels = container.querySelectorAll(
173+
'g.axis-x > g.tick text'
174+
) as NodeListOf<SVGTextElement>;
175+
expect(ticks.length).toBe(23);
176+
expect(tickLines.length).toBe(23);
177+
expect(tickLabels.length).toBe(0);
178+
});
156179
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy