Synced brushEdit
1980
1990
2000
2010
0
5
1980
1990
2000
2010
0
10
1980
1990
2000
2010
0
10
1980
1990
2000
2010
0
10
- index.svelte
- ./components/SyncedBrushWrapper.percent-range.svelte
- ./components/Line.svelte
- ./components/Area.svelte
- ./components/AxisX.html.svelte
- ./components/AxisY.html.svelte
- ./components/Brush.svelte
- ./data/points.csv
- ./data/pointsTwo.csv
- ./data/pointsThree.csv
- ./data/pointsFour.csv
<script>
import SyncedBrushWrapper from './components/SyncedBrushWrapper.percent-range.svelte';
// This example loads csv data as json using @rollup/plugin-dsv
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];
});
});
</script>
<style>
.small-multiple-container {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
}
</style>
<div class="small-multiple-container">
{#each datasets as dataset}
<SyncedBrushWrapper
data={dataset}
{xKey}
{yKey}
bind:min={brushExtents[0]}
bind:max={brushExtents[1]}
/>
{/each}
</div>
<script>
import { LayerCake, ScaledSvg, Html } from 'layercake';
import Line from './Line.svelte';
import Area from './Area.svelte';
import AxisX from './AxisX.html.svelte';
import AxisY from './AxisY.html.svelte';
import Brush from './Brush.svelte';
export let min = null;
export let max = null;
export let xKey = 'x';
export let yKey = 'y';
export let data = [];
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: 48%;
}
.chart-container {
width: 100%;
height: 80%;
}
.brush-container {
width: 100%;
height: 20%;
}
</style>
<div class="chart-wrapper">
<div class="chart-container">
<LayerCake
ssr={true}
percentRange={true}
padding={{ right: 10, bottom: 20, left: 25 }}
x={xKey}
y={yKey}
yDomain={[0, null]}
data={brushedData}
>
<Html>
<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}
/>
</Html>
<ScaledSvg>
<Line
stroke='#00e047'
/>
<Area
fill='#00e04710'
/>
</ScaledSvg>
</LayerCake>
</div>
<div class="brush-container">
<LayerCake
ssr={true}
percentRange={true}
padding={{ top: 5 }}
x={xKey}
y={yKey}
yDomain={[0, null]}
data={data}
>
<ScaledSvg>
<Line
stroke='#00e047'
/>
<Area
fill='#00e04710'
/>
</ScaledSvg>
<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, yScale, padding } = 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 yTick = 7;
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);
</script>
<div class='axis x-axis' class:snapTicks>
{#each tickVals as tick, i}
{#if gridlines !== false}
<div class="gridline" style='left:{$xScale(tick)}%;top: -{$padding.top}px;bottom: 0;'></div>
{/if}
<div
class='tick tick-{ i }'
style='left:{$xScale(tick) + (isBandwidth ? $xScale.bandwidth() / 2 : 0)}%;top:100%;'>
<div
class="text"
style='top:{(yTick + dyTick)}px;'>{formatTick(tick)}</div>
</div>
{/each}
{#if baseline === true}
<div class="baseline" style='top: 100%;width: 100%;'></div>
{/if}
</div>
<style>
.axis,
.tick,
.gridline,
.baseline {
position: absolute;
}
.axis {
width: 100%;
height: 100%;
}
.tick {
font-size: .725em;
font-weight: 200;
}
.gridline {
border-left: 1px dashed #aaa;
}
.baseline {
border-top: 1px solid #aaa;
}
.tick .text {
color: #666;
position: relative;
white-space: nowrap;
transform: translateX(-50%);
}
.axis.snapTicks .tick:last-child {
transform: translateX(-50%);
}
.axis.snapTicks .tick:first-child {
transform: translateX(50%);
}
</style>
<script>
import { getContext } from 'svelte';
const { padding, xRange, xScale, yScale } = getContext('LayerCake');
export let ticks = 4;
export let gridlines = true;
export let baseline = false;
export let formatTick = d => d;
export let xTick = -4;
export let yTick = 2;
// 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>
<div class='axis y-axis' style='transform:translate(-{$padding.left}px, 0)'>
{#each tickVals as tick, i}
<div class='tick tick-{i}' style='top:{$yScale(tick) + (isBandwidth ? $yScale.bandwidth () / 2 : 0)}%;left:{$xRange[0]}%;'>
{#if gridlines !== false}
<div class="gridline" style='top:0;left:{isBandwidth ? $padding.left : 0}px;right:-{$padding.left + $padding.right}px;'></div>
{/if}
{#if baseline !== false && i === 0}
<div class="gridline baseline" style='top:0;left:{isBandwidth ? $padding.left : 0};right:-{$padding.left + $padding.right}px;'></div>
{/if}
<div
class="text"
style='
top:{yTick - 3}px;
left:{isBandwidth ? ($padding.left + xTick) : 0}px;
transform: translate({isBandwidth ? '-100%' : 0}, {isBandwidth ? -50 - Math.floor($yScale.bandwidth() / -2) : '-100'}%);
'
>{formatTick(tick)}</div>
</div>
{/each}
</div>
<style>
.axis,
.tick,
.gridline,
.baseline,
.text {
position: absolute;
}
.axis {
width: 100%;
height: 100%;
}
.tick {
font-size: 12px;
width: 100%;
font-weight: 100;
}
.gridline {
border-top: 1px dashed #aaa;
}
.baseline.gridline {
border-top-style: solid;
}
.tick .text {
color: #666;
}
</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;
/* mix-blend-mode: difference; */
background-color: #cccccc90;
/* border: 1px solid #000; */
}
.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