<script>
import { LayerCake, Svg, WebGL, Html } from 'layercake';
import ScatterWebGL from './_components/Scatter.webgl.svelte';
import AxisX from './_components/AxisX.svelte';
import AxisY from './_components/AxisY.svelte';
import QuadTree from './_components/QuadTree.html.svelte';
import data from './_data/points.csv';
const xKey = 'myX';
const yKey = 'myY';
const r = 3;
const padding = 6;
data.forEach(d => {
d[yKey] = +d[yKey];
});
</script>
<style>
.chart-container {
width: 100%;
height: 250px;
}
.circle {
position: absolute;
border-radius: 50%;
background-color: rgba(171,0, 214);
transform: translate(-50%, -50%);
pointer-events: none;
width: 10px;
height: 10px;
}
</style>
<div class="chart-container">
<LayerCake
padding={{ top: 0, right: 5, bottom: 20, left: 25 }}
x={xKey}
y={yKey}
xPadding={[padding, padding]}
yPadding={[padding, padding]}
data={data}
>
<Svg>
<AxisX/>
<AxisY
ticks={5}
/>
</Svg>
<WebGL>
<ScatterWebGL
{r}
/>
</WebGL>
<Html>
<QuadTree
let:x
let:y
let:visible
>
<div
class="circle"
style="top:{y}px;left:{x}px;display: { visible ? 'block' : 'none' };"
></div>
</QuadTree>
</Html>
</LayerCake>
</div>
<script>
import reglWrapper from 'regl';
import { getContext } from 'svelte';
const { data, xGet, yGet, width, height } = getContext('LayerCake');
export let r = 5;
export let fill = '#0cf';
export let stroke = '#000';
function hexToRgbPercent (hex) {
let str = hex.replace('#', '');
if (str.length === 3) {
str = str[0] + str[0] + str[1] + str[1] + str[2] + str[2];
}
return str.match(/.{1,2}/g).map(d => parseInt(d, 16) / 255);
}
const { gl } = getContext('gl');
function resize () {
if ($gl) {
const canvas = $gl.canvas;
const displayWidth = canvas.clientWidth;
const displayHeight = canvas.clientHeight;
if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
canvas.width = displayWidth;
canvas.height = displayHeight;
}
$gl.viewport(0, 0, canvas.width, canvas.height);
}
}
let regl;
function render () {
if ($gl) {
regl = reglWrapper({
gl: $gl,
extensions: ['oes_standard_derivatives']
});
regl.clear({
color: [0, 0, 0, 0],
depth: 1
});
const draw = regl({
frag: `
#extension GL_OES_standard_derivatives : enable
precision mediump float;
uniform vec3 fill_color;
uniform vec3 stroke_color;
varying float s_s;
void main () {
vec2 cxy = 2.0 * gl_PointCoord - 1.0;
float dist = dot(cxy, cxy);
float delta = fwidth(dist);
float alpha = 1.0 - smoothstep(1.0 - delta, 1.0 + delta, dist);
float outer_edge_center = 1.0 - s_s;
float stroke = 1.0 - smoothstep(outer_edge_center - delta, outer_edge_center + delta, dist);
// gl_FragColor = vec4(fill_color,1.0) * alpha;
gl_FragColor = vec4( mix(stroke_color, fill_color, stroke), 1.0 ) * alpha;
gl_FragColor.rgb *= gl_FragColor.a;
}`,
vert: `
precision mediump float;
attribute vec2 position;
attribute float r;
attribute float stroke_size;
varying float s_s;
uniform float stage_width;
uniform float stage_height;
// http://peterbeshai.com/beautifully-animate-points-with-webgl-and-regl.html
vec2 normalizeCoords(vec2 position) {
// read in the positions into x and y vars
float x = position[0];
float y = position[1];
return vec2(
2.0 * ((x / stage_width) - 0.5),
// invert y to treat [0,0] as bottom left in pixel space
-(2.0 * ((y / stage_height) - 0.5))
);
}
void main () {
s_s = stroke_size;
gl_PointSize = r;
gl_Position = vec4(normalizeCoords(position), 0.0, 1.0);
}`,
attributes: {
position: (context, props) => {
return props.points.map(point => {
return [$xGet(point), $yGet(point)];
});
},
r: (context, props) => {
return props.points.map(point => props.pointWidth);
},
stroke_size: (context, props) => {
return props.points.map(point => 0);
}
},
uniforms: {
fill_color: hexToRgbPercent(fill),
stroke_color: hexToRgbPercent(stroke),
stage_width: regl.context('drawingBufferWidth'),
stage_height: regl.context('drawingBufferHeight')
},
count: (context, props) => {
return props.points.length;
},
primitive: 'points',
blend: {
enable: true,
func: {
srcRGB: 'src alpha',
srcAlpha: 'src alpha',
dstRGB: 'one minus src alpha',
dstAlpha: 'one minus src alpha'
}
},
depth: { enable: false }
});
draw({
pointWidth: r * 2,
points: $data
});
}
}
$: ($width, $height, $gl, resize(), render());
</script>
<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 { getContext } from 'svelte';
import { quadtree } from 'd3-quadtree';
const { data, xGet, yGet, width, height } = getContext('LayerCake');
let visible = false;
let found = {};
let e = {};
export let x = 'x';
export let y = 'y';
export let searchRadius = undefined;
export let dataset = undefined;
$: xGetter = x === 'x' ? $xGet : $yGet;
$: yGetter = y === 'y' ? $yGet : $xGet;
function findItem (evt) {
e = evt;
const xLayerKey = `layer${x.toUpperCase()}`;
const yLayerKey = `layer${y.toUpperCase()}`;
found = finder.find(evt[xLayerKey], evt[yLayerKey], searchRadius) || {};
visible = Object.keys(found).length > 0;
}
$: finder = quadtree()
.extent([[-1, -1], [$width + 1, $height + 1]])
.x(xGetter)
.y(yGetter)
.addAll(dataset || $data);
</script>
<style>
.bg {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
</style>
<div
class="bg"
on:mousemove="{findItem}"
on:mouseout="{() => visible = false}"
on:blur="{() => visible = false}"
></div>
<slot
x={xGetter(found) || 0}
y={yGetter(found) || 0}
{found}
{visible}
{e}
></slot>
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