Skip to content

Commit

Permalink
Merge pull request #165 from mhkeller/axes
Browse files Browse the repository at this point in the history
Improved Axes components + AxisTop and AxisRight
  • Loading branch information
mhkeller authored Mar 8, 2024
2 parents d2e5eab + aafd4c3 commit 10d7fd5
Show file tree
Hide file tree
Showing 49 changed files with 2,066 additions and 471 deletions.
103 changes: 0 additions & 103 deletions src/_components/AxisX.html.svelte

This file was deleted.

131 changes: 131 additions & 0 deletions src/_components/AxisX.percent-range.html.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<!--
@component
Generates an HTML x-axis, useful for server-side rendered charts. This component is also configured to detect if your x-scale is an ordinal scale. If so, it will place the markers in the middle of the bandwidth.
-->
<script>
import { getContext } from 'svelte';
const { width, height, xScale, yRange } = getContext('LayerCake');
/** @type {Boolean} [tickMarks=false] - Show a vertical mark for each tick. */
export let tickMarks = false;
/** @type {Boolean} [gridlines=true] - Show gridlines extending into the chart area. */
export let gridlines = true;
/** @type {Number} [tickMarkLength=6] - The length of the tick mark. */
export let tickMarkLength = 6;
/** @type {Boolean} [baseline=false] – Show a solid line at the bottom. */
export let baseline = false;
/** @type {Boolean} [snapLabels=false] - Instead of centering the text labels on the first and the last items, align them to the edges of the chart. */
export let snapLabels = false;
/** @type {Function} [format=d => d] - A function that passes the current tick value and expects a nicely formatted value in return. */
export let format = d => d;
/** @type {Number|Array|Function} [ticks] - If this is a number, it passes that along to the [d3Scale.ticks](https://github.com/d3/d3-scale) function. If this is an array, hardcodes the ticks to those values. If it's a function, passes along the default tick values and expects an array of tick values in return. If nothing, it uses the default ticks supplied by the D3 function. */
export let ticks = undefined;
/** @type {Number} [tickGutter=0] - The amount of whitespace between the start of the tick and the chart drawing area (the yRange min). */
export let tickGutter = 0;
/** @type {Number} [dx=0] - Any optional value passed to the `dx` attribute on the text label. */
export let dx = 0;
/** @type {Number} [dy=0] - Any optional value passed to the `dy` attribute on the text label. */
export let dy = 1;
$: tickLen = tickMarks === true
? tickMarkLength ?? 6
: 0;
$: isBandwidth = typeof $xScale.bandwidth === 'function';
$: tickVals = Array.isArray(ticks) ? ticks :
isBandwidth ?
$xScale.domain() :
typeof ticks === 'function' ?
ticks($xScale.ticks()) :
$xScale.ticks(ticks);
$: halfBand = isBandwidth ? $xScale.bandwidth() / 2 : 0
</script>

<div class='axis x-axis' class:snapLabels>
{#each tickVals as tick, i (tick)}
{@const tickValPx = $xScale(tick)}

{#if baseline === true}
<div class="baseline" style='top:100%; width:100%;'></div>
{/if}

{#if gridlines === true}
<div
class="gridline"
style:left='{tickValPx}%'
style='top:0; bottom:0;'></div>
{/if}
{#if tickMarks === true}
<div
class="tick-mark"
style:left='{tickValPx + halfBand}%'
style:height='{tickLen}px'
style:bottom='{-tickLen - tickGutter}px'
></div>
{/if}
<div
class='tick tick-{i}'
style:left='{tickValPx + halfBand}%'
style='top:calc(100% + {tickGutter}px);'>
<div
class="text"
style:top='{tickLen}px'
style:transform='translate(calc(-50% + {dx}px), {dy}px)'
>{format(tick)}</div>
</div>
{/each}
</div>

<style>
.axis,
.tick,
.tick-mark,
.gridline,
.baseline {
position: absolute;
}
.axis {
width: 100%;
height: 100%;
}
.tick {
font-size: 11px;
}
.gridline {
border-left: 1px dashed #aaa;
}
.tick-mark {
border-left: 1px solid #aaa;
}
.baseline {
border-top: 1px solid #aaa;
}
.tick .text {
color: #666;
position: relative;
white-space: nowrap;
transform: translateX(-50%);
}
/* This looks a little better at 40 percent than 50 */
.axis.snapLabels .tick:last-child {
transform: translateX(-40%);
}
.axis.snapLabels .tick.tick-0 {
transform: translateX(40%);
}
</style>
105 changes: 65 additions & 40 deletions src/_components/AxisX.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,41 @@
-->
<script>
import { getContext } from 'svelte';
const { width, height, xScale, yRange } = getContext('LayerCake');
/** @type {Boolean} [gridlines=true] - Extend lines from the ticks into the chart space */
export let gridlines = true;
const { width, height, xScale, yRange } = getContext('LayerCake');
/** @type {Boolean} [tickMarks=false] - Show a vertical mark for each tick. */
export let tickMarks = false;
/** @type {Boolean} [gridlines=true] - Show gridlines extending into the chart area. */
export let gridlines = true;
/** @type {Number} [tickMarkLength=6] - The length of the tick mark. */
export let tickMarkLength = 6;
/** @type {Boolean} [baseline=false] – Show a solid line at the bottom. */
export let baseline = false;
/** @type {Boolean} [snapTicks=false] - Instead of centering the text on the first and the last items, align them to the edges of the chart. */
export let snapTicks = false;
/** @type {Boolean} [snapLabels=false] - Instead of centering the text labels on the first and the last items, align them to the edges of the chart. */
export let snapLabels = false;
/** @type {Function} [formatTick=d => d] - A function that passes the current tick value and expects a nicely formatted value in return. */
export let formatTick = d => d;
/** @type {Function} [format=d => d] - A function that passes the current tick value and expects a nicely formatted value in return. */
export let format = d => d;
/** @type {Number|Array|Function} [ticks] - If this is a number, it passes that along to the [d3Scale.ticks](https://github.com/d3/d3-scale) function. If this is an array, hardcodes the ticks to those values. If it's a function, passes along the default tick values and expects an array of tick values in return. If nothing, it uses the default ticks supplied by the D3 function. */
export let ticks = undefined;
/** @type {Number} [xTick=0] - How far over to position the text marker. */
export let xTick = 0;
/** @type {Number} [yTick=16] - The distance from the baseline to place each tick value. */
export let yTick = 16;
/** @type {Number} [tickGutter=0] - The amount of whitespace between the start of the tick and the chart drawing area (the yRange min). */
export let tickGutter = 0;
$: isBandwidth = typeof $xScale.bandwidth === 'function';
/** @type {Number} [dx=0] - Any optional value passed to the `dx` attribute on the text label. */
export let dx = 0;
$: tickVals = Array.isArray(ticks) ? ticks :
isBandwidth ?
$xScale.domain() :
typeof ticks === 'function' ?
ticks($xScale.ticks()) :
$xScale.ticks(ticks);
/** @type {Number} [dy=12] - Any optional value passed to the `dy` attribute on the text label. */
export let dy = 12;
function textAnchor(i) {
if (snapTicks === true) {
function textAnchor(i, sl) {
if (sl === true) {
if (i === 0) {
return 'start';
}
Expand All @@ -50,41 +48,68 @@
}
return 'middle';
}
$: tickLen = tickMarks === true
? tickMarkLength ?? 6
: 0;
$: isBandwidth = typeof $xScale.bandwidth === 'function';
$: tickVals = Array.isArray(ticks) ? ticks :
isBandwidth ?
$xScale.domain() :
typeof ticks === 'function' ?
ticks($xScale.ticks()) :
$xScale.ticks(ticks);
$: halfBand = isBandwidth ? $xScale.bandwidth() / 2 : 0
</script>

<g class="axis x-axis" class:snapTicks>
<g class="axis x-axis" class:snapLabels>
{#each tickVals as tick, i (tick)}
{#if baseline === true}
<line
class="baseline"
y1={$height}
y2={$height}
x1="0"
x2={$width}
/>
{/if}

<g class="tick tick-{i}" transform="translate({$xScale(tick)},{Math.max(...$yRange)})">
{#if gridlines !== false}
<line class="gridline" y1={$height * -1} y2="0" x1="0" x2="0" />
{#if gridlines === true}
<line
class="gridline"
x1={halfBand}
x2={halfBand}
y1={-$height}
y2="0"
/>
{/if}
{#if tickMarks === true}
<line
class="tick-mark"
y1={0}
y2={6}
x1={isBandwidth ? $xScale.bandwidth() / 2 : 0}
x2={isBandwidth ? $xScale.bandwidth() / 2 : 0}
x1={halfBand}
x2={halfBand}
y1={tickGutter}
y2={tickGutter + tickLen}
/>
{/if}
<text
x={isBandwidth ? ($xScale.bandwidth() / 2 + xTick) : xTick}
y={yTick}
dx=""
dy=""
text-anchor={textAnchor(i)}>{formatTick(tick)}</text
x={halfBand}
y={tickGutter + tickLen}
{dx}
{dy}
text-anchor={textAnchor(i, snapLabels)}>{format(tick)}</text
>
</g>
{/each}
{#if baseline === true}
<line class="baseline" y1={$height + 0.5} y2={$height + 0.5} x1="0" x2={$width} />
{/if}
</g>

<style>
.tick {
font-size: 0.725em;
font-weight: 200;
font-size: 11px;
}
line,
Expand All @@ -102,10 +127,10 @@
stroke-dasharray: 0;
}
/* This looks slightly better */
.axis.snapTicks .tick:last-child text {
.axis.snapLabels .tick:last-child text {
transform: translateX(3px);
}
.axis.snapTicks .tick.tick-0 text {
.axis.snapLabels .tick.tick-0 text {
transform: translateX(-3px);
}
</style>
Loading

0 comments on commit 10d7fd5

Please sign in to comment.