Published
Edited
Mar 28, 2021
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
context = {
return DOM.canvas(canvas.width, canvas.height).getContext('2d');
}

Insert cell
Insert cell
Insert cell
Insert cell
context.canvas
Insert cell
learning_d3_mapping_8_3 =
/* function* eachObservableBlockIsImplicitGeneratorFunction() */ {
/**
* Learning D3.js 4 Mapping by larsvers
* Example of animating without d3 data control
* From: https://github.com/larsvers/Learning-D3.js-4-Mapping/blob/master/chapter-8/08_03.html
*/
let clicked = false;
context.canvas.addEventListener('click', d => clicked = true);
/* Helper functions */
/* ================ */
function circle(ctx, x, y, r, color) {
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fill();
} // circle()
function update() {
if (!rain.items.length) {
// initialisation
rain.createDrops();
} else {
// from 2nd update forward
rain.items.forEach(function(el) {
rain.updateDrop(el); // update drops' position
});
} // initialisation vs repeat update conditional
} // update()
function animate() {
context.save();
context.fillStyle = "transparent";
context.strokeStyle = "transparent";
context.rect(0, 0, canvas.width/2, canvas.height);
context.clip();
context.clearRect(0, 0, canvas.width/2, canvas.height);
drawBackground(canvas.width/4, -10);
rain.items.forEach(function(el) {
circle(context, el.x, el.y, 1.5, 'blue');
});
context.restore();
} // animate()
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
let iterationCount = 0;
for (let i = start; i < end; i += step) {
iterationCount++;
yield i;
}
return iterationCount;
}
/* Generator code (iterator) */
/* ======================== */
//
while (true) {
if (!clicked) {
animate();
update();
context.fillStyle = "black";
context.fillText("Animation with manual data control", 10, 10);
}
await Promises.delay(33);
yield (rain.items);
} // Time-delayed

/* No line is executed after 'yield' */
return context.canvas;
}
Insert cell
learning_d3_mapping_8_4 =
/* function* eachObservableBlockIsImplicitGeneratorFunction() */ {
/**
* Learning D3.js 4 Mapping by larsvers
* Example of animating with d3 data control
* From: https://github.com/larsvers/Learning-D3.js-4-Mapping/blob/master/chapter-8/08_04b.html
*/
/* Enter Exit Update and Draw the rain */
/* =================================== */
// Create in-memory base for elements
const houseSelection = d3.select(model);
const rainSelection = houseSelection.select('defs').append('g').attr('id', 'rain'); // d3.select(document.createElement('g')); // Offscreen svg element
const dur = 3000;
const rainDrops = rain.createDrops();
let running = false;
let started = false;
let clicked = false;
context.canvas.addEventListener('click', d => clicked = true);

function databind (data) {
console.log('Running? ', running);
if (running) return false;
const join = rainSelection.selectAll('circle.drop')
.data(data, d => d.id);

const enter = join.enter()
.append('circle')
.attr('class', 'drop')
.attr('cx', d => d.xCloud)
.attr('cy', d => d.yCloud)
.attr('r', (d, i) => {
return d.radiusCloud;
})
.text(d => '(x:' + Math.floor(d.xCloud) + ', y:' + Math.floor(d.yCloud) + ')')
.attr('fill', 'rgba(0, 0, 255)')
.attr('fill-opacity', '0')
.transition().delay((d, i) => i * 2)
.attr('fill', 'rgba(0, 0, 255)')
.attr('fill-opacity', '0.5');

const update = join.transition() // implicit update()
.duration(d => Math.random()*1000 + 900)
.delay((d, i) => (i/data.length)*dur)
.ease(d3.easeLinear)
.attr('cx', d => d.xPuddle)
.attr('cy', d => d.yPuddle)
.attr('r', (d, i) => {
return d.radiusPuddle;
})
.text(d => '(x:' + Math.floor(d.xPuddle) + ', y:' + Math.floor(d.yPuddle) + ')');

const exit = join.exit()
.transition()
.duration(dur)
.delay((d, i) => i)
.attr('r', d => d.radiusGrass)
.attr('fill', '#01A611');
} // databind()
function drawRainScene() {
// Cloud
// point cloud roughly from 60,10 to 540, 70
context.beginPath();
context.moveTo(65, 65);
context.lineTo(535, 65);
context.bezierCurveTo(560, 5, 441, 5, 441, 40);
context.bezierCurveTo(441, 0, 347, 0, 347, 40);
context.bezierCurveTo(347, 5, 253, 5, 253, 40);
context.bezierCurveTo(253, 10, 159, 10, 159, 40);
context.bezierCurveTo(159, 15, 40, 15, 65, 65);
context.closePath();
context.fillStyle = '#f7f7f7';
context.fill();

// Rain path
// cloud shape from 65, 65 to 535, 65
context.beginPath();
context.moveTo(65, 70);
context.lineTo(535, 70);
context.lineTo(530, canvas.height + 20);
context.lineTo(45, canvas.height + 20);
context.closePath();
context.fillStyle = '#ffffff';
context.fill();
// Puddle
// puddle shape from 30, 250 to 560, 270
context.save();
context.translate(0, -20);
context.beginPath();
context.moveTo(529.52,256.19);
context.bezierCurveTo(532.01,260.53,513.31,265.85,516.94,272.4);
context.bezierCurveTo(521.06,279.82,547.94,278.27,553.81,286.4);
context.bezierCurveTo(557.57,291.58,552.04,299.53,542.08,303.045);
context.bezierCurveTo(520.38,310.726,493.13,291.9,444.8,292.045);
context.bezierCurveTo(410.16,292.184,409.0,301.976,364.45,307.383);
context.bezierCurveTo(302.314,314.91,237.732,304.003,237.0,296.383);
context.bezierCurveTo(236.582,290.383,276.83,287.115,275.633,282.75);
context.bezierCurveTo(273.71,275.856,167.702,276.425,166.71,281.485);
context.bezierCurveTo(166.073,284.753,209.81,287.045,209.44,291.124);
context.bezierCurveTo(208.85,297.744,92.88,304.52,83.76,291.124);
context.bezierCurveTo(78.85,283.90,106.86,273.726,100.52,268.77);
context.bezierCurveTo(94.66,264.20,68.52,271.16,39.35,264.83);
context.bezierCurveTo(32.35,263.314,26.7303,261.36,25.118,258.26);
context.bezierCurveTo(23.78,255.71,34.735,251.362,43.54,250.814);
context.bezierCurveTo(56.446,250.982,453.02,250.753,458.2,250.883);
context.bezierCurveTo(506.06,249.68,525,248.36,529.52,256.19);
context.closePath();
context.fillStyle = "rgba(255, 255, 255, 0.75)";
context.fill();
context.restore();
} // drawRainScene()
function rainAnimation (data) {
const t = d3.timer(elapsed => {
running = elapsed > 1 && elapsed < (dur) ? true : false;
draw(context);
d3.selectAll('button').style('color', '#aaa');
if (elapsed > dur * 2) {
d3.selectAll('button').style('color', '#555');
t.stop();
}
});
} // rainAnimation()
function enterRain (data) {
databind(data);
rainAnimation(data);
}
function updateRain (data) {
databind(data);
rainAnimation(data);
}
function exitRain (data) {
databind(data);
rainAnimation(data);
}
function draw (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRainScene();
drawBackground(canvas.width/4, -10);
const elements = rainSelection
.selectAll('circle.drop')
.each(function (d, i) {
const node = d3.select(this);
const op = ctx.globalCompositeOperation;
ctx.save();
ctx.beginPath();
ctx.globalCompositeOperation = 'source-atop';
ctx.fillStyle = node.attr('fill');
if (node.attr('fill-opacity'))
ctx.fillStyle = ctx.fillStyle.replace(')', ', '+ node.attr('fill-opacity') +')');
ctx.arc(node.attr('cx'), node.attr('cy'), node.attr('r'), 0, 2*Math.PI);
ctx.fill();
ctx.globalCompositeOperation = op;
ctx.restore();
});
context.fillStyle = "black";
if (clicked) {
context.fillText("Animation with D3 data control", canvas.width - 150, 10);
} else {
context.fillText("*Click to begin animation with D3 data control", canvas.width - 180, 10);
}
}
/* Generator code (iterator) */
/* ======================== */
//
while (true) {
if (started === false && clicked === true) {
started = true;
enterRain(rainDrops);
setTimeout(updateRain, dur * 3, rainDrops);
setTimeout(exitRain, dur * 5, []);
} else {
draw(context);
}
await Promises.delay(33);
yield (rainSelection.node().children);
} // Time-delayed
}
Insert cell
model = svg`<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="512" height="256">
<title>House SVG</title>
<desc>Basic elements and styles in an SVG document</desc>
<style type="text/css">/*<![CDATA[*/
* {
stroke: #000;
stroke-opacity: 0.5;
fill:none;
fill-opacity: 1.0;
}

rect {
stroke: none;
}

rect#house {
fill: royalblue;
}

rect#door {
fill: brown;
fill-opacity: 0.75;
}

rect#window {
fill: white;
fill-opacity: 0.75;
}

circle {
fill: #00F;
}

polygon {
fill: #A52A2A;
}
/*]]>*/</style>

<defs>
<rect id="house" x="178" y="150" width="200" height="100" />

<rect id="door" x="188" y="180" width="40" height="60" />

<g transform="translate(128, 180)">
<rect id="window" x="128" y="0" width="60" height="30" />
</g>

<polygon id="roof"
points="178, 150
378, 150
278, 100" />
</defs>
</svg>
`
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more