<script>
import { LayerCake, Svg, Html } from 'layercake';
import Line from './components/Line.svelte';
import Area from './components/Area.svelte';
import AxisX from './components/AxisX.svelte';
import AxisY from './components/AxisY.svelte';
import Brush from './components/Brush.svelte';
import data from './data/points.csv';
let brushExtents = [null, null];
const xKey = 'myX';
const yKey = 'myY';
data.forEach(d => {
d[yKey] = +d[yKey];
});
let brushedData;
$: {
brushedData = data.slice((brushExtents[0] || 0) * data.length, (brushExtents[1] || 1) * data.length);
if (brushedData.length < 2) {
brushedData = data.slice(brushExtents[0] * data.length, brushExtents[0] * data.length + 2)
}
}
</script>
<style>
.chart-container {
width: 100%;
height: 80%;
}
.brush-container {
width: 100%;
height: 20%;
}
</style>
<div class="chart-container">
<LayerCake
padding={{ right: 10, bottom: 20, left: 25 }}
x={xKey}
y={yKey}
yDomain={[0, null]}
data={brushedData}
>
<Svg>
<AxisX
ticks={ticks => {
const filtered = ticks.filter(t => t % 1 === 0);
if (filtered.length > 7) {
return filtered.filter((t, i) => i % 2 === 0);
}
return filtered;
}}
/>
<AxisY
ticks={4}
/>
<Line
stroke='#00e047'
/>
<Area
fill='#00e04710'
/>
</Svg>
</LayerCake>
</div>
<div class="brush-container">
<LayerCake
padding={{ top: 5 }}
x={xKey}
y={yKey}
yDomain={[0, null]}
data={data}
>
<Svg>
<Line
stroke='#00e047'
/>
<Area
fill='#00e04710'
/>
</Svg>
<Html>
<Brush bind:min={brushExtents[0]} bind:max={brushExtents[1]}/>
</Html>
</LayerCake>
</div>
<script>
import { getContext } from 'svelte';
const { data, xGet, yGet } = getContext('LayerCake');
export let stroke = '#ab00d6';
$: path = 'M' + $data
.map(d => {
return $xGet(d) + ',' + $yGet(d);
})
.join('L');
</script>
<path class='path-line' d='{path}' {stroke}></path>
<style>
.path-line {
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
stroke-width: 2;
}
</style>
<script>
import { getContext } from 'svelte';
const { data, xGet, yGet, xScale, yScale, extents } = getContext('LayerCake');
export let fill = '#ab00d610';
$: path = 'M' + $data
.map(d => {
return $xGet(d) + ',' + $yGet(d);
})
.join('L');
let area;
$: {
const yRange = $yScale.range();
area = path + (
'L' + $xScale($extents.x ? $extents.x[1] : 0) + ',' + yRange[0] +
'L' + $xScale($extents.x ? $extents.x[0] : 0) + ',' + yRange[0] +
'Z'
);
}
</script>
<path class='path-area' d='{area}' {fill}></path>
<script>
import { getContext } from 'svelte';
const { width, height, xScale, yScale, yRange } = getContext('LayerCake');
export let gridlines = true;
export let formatTick = d => d;
export let baseline = false;
export let snapTicks = false;
export let ticks = undefined;
export let xTick = undefined;
export let yTick = 16;
export let dxTick = 0;
export let dyTick = 0;
$: isBandwidth = typeof $xScale.bandwidth === 'function';
$: tickVals = Array.isArray(ticks) ? ticks :
isBandwidth ?
$xScale.domain() :
typeof ticks === 'function' ?
ticks($xScale.ticks()) :
$xScale.ticks(ticks);
function textAnchor(i) {
if (snapTicks === true) {
if (i === 0) {
return 'start';
}
if (i === tickVals.length - 1) {
return 'end';
}
}
return 'middle';
}
</script>
<g class='axis x-axis'>
{#each tickVals as tick, i}
<g class='tick tick-{ tick }' transform='translate({$xScale(tick)},{$yRange[0]})'>
{#if gridlines !== false}
<line y1='{$height * -1}' y2='0' x1='0' x2='0'></line>
{/if}
<text
x="{xTick || isBandwidth ? $xScale.bandwidth() / 2 : 0 }"
y='{yTick}'
dx='{dxTick}'
dy='{dyTick}'
text-anchor='{textAnchor(i)}'>{formatTick(tick)}</text>
</g>
{/each}
{#if baseline === true}
<line class="baseline" y1='{$height + 0.5}' y2='{$height + 0.5}' x1='0' x2='{$width}'></line>
{/if}
</g>
<style>
.tick {
font-size: .725em;
font-weight: 200;
}
line,
.tick line {
stroke: #aaa;
stroke-dasharray: 2;
}
.tick text {
fill: #666;
}
.baseline {
stroke-dasharray: 0;
}
</style>
<script>
import { getContext } from 'svelte';
const { padding, xRange, xScale, yScale } = getContext('LayerCake');
export let ticks = 4;
export let gridlines = true;
export let formatTick = d => d;
export let xTick = 0;
export let yTick = 0;
export let dxTick = 0;
export let dyTick = -4;
export let textAnchor = 'start';
$: isBandwidth = typeof $yScale.bandwidth === 'function';
$: tickVals = Array.isArray(ticks) ? ticks :
isBandwidth ?
$yScale.domain() :
typeof ticks === 'function' ?
ticks($yScale.ticks()) :
$yScale.ticks(ticks);
</script>
<g class='axis y-axis' transform='translate({-$padding.left}, 0)'>
{#each tickVals as tick, i}
<g class='tick tick-{tick}' transform='translate({$xRange[0] + (isBandwidth ? $padding.left : 0)}, {$yScale(tick)})'>
{#if gridlines !== false}
<line
x2='100%'
y1={yTick + (isBandwidth ? ($yScale.bandwidth() / 2) : 0)}
y2={yTick + (isBandwidth ? ($yScale.bandwidth() / 2) : 0)}
></line>
{/if}
<text
x='{xTick}'
y='{yTick + (isBandwidth ? $yScale.bandwidth() / 2 : 0)}'
dx='{isBandwidth ? -5 : dxTick}'
dy='{isBandwidth ? 4 : dyTick}'
style="text-anchor:{isBandwidth ? 'end' : textAnchor};"
>{formatTick(tick)}</text>
</g>
{/each}
</g>
<style>
.tick {
font-size: .725em;
font-weight: 200;
}
.tick line {
stroke: #aaa;
stroke-dasharray: 2;
}
.tick text {
fill: #666;
}
.tick.tick-0 line {
stroke-dasharray: 0;
}
</style>
<script>
import { clamp } from 'yootils';
export let min;
export let max;
let brush;
const p = x => {
const { left, right } = brush.getBoundingClientRect();
return clamp((x - left) / (right - left), 0, 1);
};
const handler = fn => {
return e => {
if (e.type === 'touchstart') {
if (e.touches.length !== 1) return;
e = e.touches[0];
}
const id = e.identifier;
const start = { min, max, p: p(e.clientX) };
const handle_move = e => {
if (e.type === 'touchmove') {
if (e.changedTouches.length !== 1) return;
e = e.changedTouches[0];
if (e.identifier !== id) return;
}
fn(start, p(e.clientX));
};
const handle_end = e => {
if (e.type === 'touchend') {
if (e.changedTouches.length !== 1) return;
if (e.changedTouches[0].identifier !== id) return;
} else if (e.target === brush) {
clear();
}
window.removeEventListener('mousemove', handle_move);
window.removeEventListener('mouseup', handle_end);
window.removeEventListener('touchmove', handle_move);
window.removeEventListener('touchend', handle_end);
};
window.addEventListener('mousemove', handle_move);
window.addEventListener('mouseup', handle_end);
window.addEventListener('touchmove', handle_move);
window.addEventListener('touchend', handle_end);
};
};
const clear = () => {
min = null;
max = null;
}
const reset = handler((start, p) => {
min = clamp(Math.min(start.p, p), 0, 1);
max = clamp(Math.max(start.p, p), 0, 1);
});
const move = handler((start, p) => {
const d = clamp(p - start.p, -start.min, 1 - start.max);
min = start.min + d;
max = start.max + d;
});
const adjust_min = handler((start, p) => {
min = p > start.max ? start.max : p;
max = p > start.max ? p : start.max;
});
const adjust_max = handler((start, p) => {
min = p < start.min ? p : start.min;
max = p < start.min ? start.min : p;
});
$: left = 100 * min;
$: right = 100 * (1 - max);
</script>
<div bind:this={brush} class="brush-outer" on:mousedown|stopPropagation={reset} on:touchstart|stopPropagation={reset}>
{#if min !== null}
<div class="brush-inner" on:mousedown|stopPropagation={move} on:touchstart|stopPropagation={move} style="left: {left}%; right: {right}%"></div>
<div class="brush-handle" on:mousedown|stopPropagation={adjust_min} on:touchstart|stopPropagation={adjust_min} style="left: {left}%"></div>
<div class="brush-handle" on:mousedown|stopPropagation={adjust_max} on:touchstart|stopPropagation={adjust_max} style="right: {right}%"></div>
{/if}
</div>
<style>
.brush-outer {
position: relative;
width: 100%;
height: calc(100% + 5px);
top: -5px;
}
.brush-inner {
position: absolute;
height: 100%;
cursor: move;
background-color: #cccccc90;
}
.brush-handle {
position: absolute;
width: 0;
height: 100%;
cursor: ew-resize;
}
.brush-handle::before {
position: absolute;
content: '';
width: 8px;
left: -4px;
height: 100%;
background: transparent;
}
</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