<script>
import { LayerCake, Svg, Canvas } from 'layercake';
import ScatterSvg from './_components/Scatter.svg.svelte';
import ScatterCanvas from './_components/Scatter.canvas.svelte';
import Voronoi from './_components/Voronoi.svelte';
import AxisX from './_components/AxisX.svelte';
import AxisY from './_components/AxisY.svelte';
import data from './_data/points.csv';
const xKey = 'myX';
const yKey = 'myY';
data.forEach(d => {
d[yKey] = +d[yKey];
});
const r = 3;
const padding = 10;
const color = '#fff';
function logEvent(d) {
console.log('dispatched event', d, d.detail);
}
</script>
<div class="chart-container">
<LayerCake
padding={{ top: 10, right: 5, bottom: 20, left: 25 }}
x={xKey}
y={yKey}
xPadding={[padding, padding]}
yPadding={[padding, padding]}
{data}
>
<Svg>
<AxisX gridlines={false} />
<AxisY gridlines={false} ticks={4} />
</Svg>
<Canvas>
<ScatterCanvas r={r * 1.5} fill="#0cf" />
</Canvas>
<Svg>
<ScatterSvg {r} fill={color} />
<Voronoi stroke="#333" on:voronoi-mouseover={logEvent} />
</Svg>
</LayerCake>
</div>
<style>
.chart-container {
width: 100%;
height: 250px;
}
</style>
<script>
import { getContext } from 'svelte';
const { data, xGet, yGet, xScale, yScale } = getContext('LayerCake');
export let r = 5;
export let fill = '#0cf';
export let stroke = '#000';
export let strokeWidth = 0;
</script>
<g class="scatter-group">
{#each $data as d}
<circle
cx={$xGet(d) + ($xScale.bandwidth ? $xScale.bandwidth() / 2 : 0)}
cy={$yGet(d) + ($yScale.bandwidth ? $yScale.bandwidth() / 2 : 0)}
{r}
{fill}
{stroke}
stroke-width={strokeWidth}
/>
{/each}
</g>
<script>
import { getContext } from 'svelte';
import { scaleCanvas } from 'layercake';
const { data, xGet, yGet, width, height } = getContext('LayerCake');
const { ctx } = getContext('canvas');
export let r = 5;
export let fill = '#0cf';
export let stroke = '#000';
export let strokeWidth = 1;
$: {
if ($ctx) {
scaleCanvas($ctx, $width, $height);
$ctx.clearRect(0, 0, $width, $height);
$data.forEach(d => {
$ctx.beginPath();
$ctx.arc($xGet(d), $yGet(d), r, 0, 2 * Math.PI, false);
$ctx.lineWidth = strokeWidth;
$ctx.strokeStyle = stroke;
$ctx.stroke();
$ctx.fillStyle = fill;
$ctx.fill();
});
}
}
</script>
<script>
import { getContext, createEventDispatcher } from 'svelte';
import { uniques } from 'layercake';
import { Delaunay } from 'd3-delaunay';
const { data, xGet, yGet, width, height } = getContext('LayerCake');
export let stroke = undefined;
let dispatcher = createEventDispatcher();
function log(point) {
console.log(point, point.data);
dispatcher('voronoi-mouseover', point);
}
$: points = $data.map(d => {
const point = [$xGet(d), $yGet(d)];
point.data = d;
return point;
});
$: uniquePoints = uniques(points, d => d.join(), false);
$: voronoi = Delaunay.from(uniquePoints).voronoi([0, 0, $width, $height]);
</script>
{#each uniquePoints as point, i}
<path
style="stroke: {stroke}"
class="voronoi-cell"
d={voronoi.renderCell(i)}
on:mouseover={() => {
log(point);
}}
on:focus={() => {
log(point);
}}
role="tooltip"
></path>
{/each}
<style>
.voronoi-cell {
fill: none;
stroke: none;
pointer-events: all;
outline: none;
}
.voronoi-cell:hover {
stroke: #333 !important;
stroke-width: 3px;
}
</style>
<script>
import { getContext } from 'svelte';
const { width, height, xScale, yRange } = getContext('LayerCake');
export let tickMarks = false;
export let gridlines = true;
export let tickMarkLength = 6;
export let baseline = false;
export let snapLabels = false;
export let format = d => d;
export let ticks = undefined;
export let tickGutter = 0;
export let dx = 0;
export let dy = 12;
function textAnchor(i, sl) {
if (sl === true) {
if (i === 0) {
return 'start';
}
if (i === tickVals.length - 1) {
return 'end';
}
}
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: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 === true}
<line class="gridline" x1={halfBand} x2={halfBand} y1={-$height} y2="0" />
{/if}
{#if tickMarks === true}
<line
class="tick-mark"
x1={halfBand}
x2={halfBand}
y1={tickGutter}
y2={tickGutter + tickLen}
/>
{/if}
<text x={halfBand} y={tickGutter + tickLen} {dx} {dy} text-anchor={textAnchor(i, snapLabels)}
>{format(tick)}</text
>
</g>
{/each}
</g>
<style>
.tick {
font-size: 11px;
}
line,
.tick line {
stroke: #aaa;
stroke-dasharray: 2;
}
.tick text {
fill: #666;
}
.tick .tick-mark,
.baseline {
stroke-dasharray: 0;
}
.axis.snapLabels .tick:last-child text {
transform: translateX(3px);
}
.axis.snapLabels .tick.tick-0 text {
transform: translateX(-3px);
}
</style>
<script>
import { getContext } from 'svelte';
const { xRange, yScale, width } = getContext('LayerCake');
export let tickMarks = false;
export let labelPosition = 'even';
export let snapBaselineLabel = false;
export let gridlines = true;
export let tickMarkLength = undefined;
export let format = d => d;
export let ticks = 4;
export let tickGutter = 0;
export let dx = 0;
export let dy = 0;
export let charPixelWidth = 7.25;
$: isBandwidth = typeof $yScale.bandwidth === 'function';
$: tickVals = Array.isArray(ticks)
? ticks
: isBandwidth
? $yScale.domain()
: typeof ticks === 'function'
? ticks($yScale.ticks())
: $yScale.ticks(ticks);
function calcStringLength(sum, val) {
if (val === ',' || val === '.') return sum + charPixelWidth * 0.5;
return sum + charPixelWidth;
}
$: tickLen =
tickMarks === true
? labelPosition === 'above'
? tickMarkLength ?? widestTickLen
: tickMarkLength ?? 6
: 0;
$: widestTickLen = Math.max(
10,
Math.max(...tickVals.map(d => format(d).toString().split('').reduce(calcStringLength, 0)))
);
$: x1 = -tickGutter - (labelPosition === 'above' ? widestTickLen : tickLen);
$: y = isBandwidth ? $yScale.bandwidth() / 2 : 0;
$: maxTickValPx = Math.max(...tickVals.map($yScale));
</script>
<g class="axis y-axis">
{#each tickVals as tick (tick)}
{@const tickValPx = $yScale(tick)}
<g class="tick tick-{tick}" transform="translate({$xRange[0]}, {tickValPx})">
{#if gridlines === true}
<line class="gridline" {x1} x2={$width} y1={y} y2={y}></line>
{/if}
{#if tickMarks === true}
<line class="tick-mark" {x1} x2={x1 + tickLen} y1={y} y2={y}></line>
{/if}
<text
x={x1}
{y}
dx={dx + (labelPosition === 'even' ? -3 : 0)}
text-anchor={labelPosition === 'above' ? 'start' : 'end'}
dy={dy +
(labelPosition === 'above' || (snapBaselineLabel === true && tickValPx === maxTickValPx)
? -3
: 4)}>{format(tick)}</text
>
</g>
{/each}
</g>
<style>
.tick {
font-size: 11px;
}
.tick line {
stroke: #aaa;
}
.tick .gridline {
stroke-dasharray: 2;
}
.tick text {
fill: #666;
}
.tick.tick-0 line {
stroke-dasharray: 0;
}
</style>
myX,myY
1979,7.19
1980,7.83
1981,7.24
1982,7.44
1983,7.51
1984,7.1
1985,6.91
1986,7.53
1987,7.47
1988,7.48
1989,7.03
1990,6.23
1991,6.54
1992,7.54
1993,6.5
1994,7.18
1995,6.12
1996,7.87
1997,6.73
1998,6.55
1999,6.23
2000,6.31
2001,6.74
2002,5.95
2003,6.13
2004,6.04
2005,5.56
2006,5.91
2007,4.29
2008,4.72
2009,5.38
2010,4.92
2011,4.61
2012,3.62
2013,5.35
2014,5.28
2015,4.63
2016,4.72