<script>
import SyncedBrushWrapper from './_components/SyncedBrushWrapper.svelte';
import pointsOne from './_data/points.csv';
import pointsTwo from './_data/pointsTwo.csv';
import pointsThree from './_data/pointsThree.csv';
import pointsFour from './_data/pointsFour.csv';
let brushExtents = [null, null];
const xKey = 'myX';
const yKey = 'myY';
const datasets = [
pointsOne,
pointsTwo,
pointsThree,
pointsFour
];
datasets.forEach(dataset => {
dataset.forEach(d => {
d[yKey] = +d[yKey];
});
});
const colors = ['#00e047', '#00bbff', '#ff00cc', '#ffcc00'];
</script>
<style>
.chart-container {
width: 100%;
height: 250px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
}
</style>
<div class="chart-container">
{#each datasets as dataset, i}
<SyncedBrushWrapper
data={dataset}
{xKey}
{yKey}
bind:min={brushExtents[0]}
bind:max={brushExtents[1]}
stroke={colors[i]}
/>
{/each}
</div>
<script>
import { LayerCake, Svg, Html } from 'layercake';
import Line from './Line.svelte';
import Area from './Area.svelte';
import AxisX from './AxisX.svelte';
import AxisY from './AxisY.svelte';
import Brush from './Brush.html.svelte';
export let min = null;
export let max = null;
export let xKey = 'x';
export let yKey = 'y';
export let data = [];
export let stroke = '#00e047';
let brushedData;
$: {
brushedData = data.slice((min || 0) * data.length, (max || 1) * data.length);
if (brushedData.length < 2) {
brushedData = data.slice(min * data.length, min * data.length + 2)
}
}
</script>
<style>
.chart-wrapper {
width: 48%;
height: 40%;
}
.chart-container {
width: 100%;
height: 80%;
}
.brush-container {
width: 100%;
height: 20%;
}
</style>
<div class="chart-wrapper">
<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={2}
/>
<Line
{stroke}
/>
<Area
fill={`${stroke}10`}
/>
</Svg>
</LayerCake>
</div>
<div class="brush-container">
<LayerCake
padding={{ top: 5 }}
x={xKey}
y={yKey}
yDomain={[0, null]}
data={data}
>
<Svg>
<Line
{stroke}
/>
<Area
fill={`${stroke}10`}
/>
</Svg>
<Html>
<Brush
bind:min={min}
bind:max={max}
/>
</Html>
</LayerCake>
</div>
</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, yRange } = getContext('LayerCake');
export let gridlines = true;
export let tickMarks = false;
export let baseline = false;
export let snapTicks = false;
export let formatTick = d => d;
export let ticks = undefined;
export let xTick = 0;
export let yTick = 16;
$: 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" class:snapTicks>
{#each tickVals as tick, i (tick)}
<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}
{#if tickMarks === true}
<line
class="tick-mark"
y1={0}
y2={6}
x1={isBandwidth ? $xScale.bandwidth() / 2 : 0}
x2={isBandwidth ? $xScale.bandwidth() / 2 : 0}
/>
{/if}
<text
x={isBandwidth ? ($xScale.bandwidth() / 2 + xTick) : xTick}
y={yTick}
dx=""
dy=""
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} />
{/if}
</g>
<style>
.tick {
font-size: 0.725em;
font-weight: 200;
}
line,
.tick line {
stroke: #aaa;
stroke-dasharray: 2;
}
.tick text {
fill: #666;
}
.tick .tick-mark,
.baseline {
stroke-dasharray: 0;
}
.axis.snapTicks .tick:last-child text {
transform: translateX(3px);
}
.axis.snapTicks .tick.tick-0 text {
transform: translateX(-3px);
}
</style>
<script>
import { getContext } from 'svelte';
const { padding, xRange, yScale } = getContext('LayerCake');
export let gridlines = true;
export let tickMarks = false;
export let formatTick = d => d;
export let ticks = 4;
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 (tick)}
<g class='tick tick-{tick}' transform='translate({$xRange[0] + (isBandwidth ? $padding.left : 0)}, {$yScale(tick)})'>
{#if gridlines !== false}
<line
class="gridline"
x2='100%'
y1={(isBandwidth ? ($yScale.bandwidth() / 2) : 0)}
y2={(isBandwidth ? ($yScale.bandwidth() / 2) : 0)}
></line>
{/if}
{#if tickMarks === true}
<line
class='tick-mark'
x1='0'
x2='{isBandwidth ? -6 : 6}'
y1={(isBandwidth ? ($yScale.bandwidth() / 2) : 0)}
y2={(isBandwidth ? ($yScale.bandwidth() / 2) : 0)}
></line>
{/if}
<text
x='{xTick}'
y='{(isBandwidth ? ($yScale.bandwidth() / 2) + yTick : yTick)}'
dx='{isBandwidth ? -9 : 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;
}
.tick .gridline {
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
myX,myY
1979,5.03
1980,3.99
1981,10.35
1982,4.06
1983,10.11
1984,11.66
1985,5.95
1986,7.27
1987,2.77
1988,3.43
1989,6.49
1990,8.27
1991,3.85
1992,6.21
1993,0.14
1994,7.65
1995,5.57
1996,15.61
1997,12.42
1998,5.2
1999,9.81
2000,8.64
2001,10.07
2002,1.71
2003,3
2004,11.82
2005,4.74
2006,4.25
2007,0.16
2008,7.97
2009,9.75
2010,3.34
2011,3.46
2012,1.73
2013,5.97
2014,2.17
2015,0.88
2016,8.19
myX,myY
1979,5.43
1980,7.27
1981,8.82
1982,13.14
1983,14.63
1984,5.41
1985,4.76
1986,12.46
1987,10.38
1988,0.06
1989,9.35
1990,5.84
1991,6.94
1992,2.78
1993,9.84
1994,6.48
1995,9.97
1996,4.85
1997,5.35
1998,4.12
1999,1.56
2000,10.11
2001,2.04
2002,4.03
2003,3.85
2004,8.61
2005,1.44
2006,9.41
2007,3.51
2008,4.12
2009,6.76
2010,3.65
2011,6.76
2012,6.46
2013,0.75
2014,9.93
2015,8.11
2016,1.4
myX,myY
1979,10.27
1980,1.99
1981,2.25
1982,3.56
1983,0.32
1984,4.18
1985,1.75
1986,2.35
1987,2.54
1988,6.53
1989,5.02
1990,3.4
1991,4.79
1992,1.29
1993,12.65
1994,5.26
1995,3.11
1996,14.92
1997,13.21
1998,10.34
1999,5.02
2000,9.11
2001,13.24
2002,8.02
2003,1.54
2004,0.25
2005,6.02
2006,5.91
2007,6.83
2008,6.76
2009,3.7
2010,2.3
2011,3.37
2012,4.02
2013,2.15
2014,2.33
2015,5.98
2016,6.27