Published
Edited
Apr 28, 2020
Insert cell
md`# 100 Days of Code 90- 3D vector graphics and animations.`
Insert cell
md`For the remaining days of code challenge I would like to create animations using randomness and learn more about vector graphics.
We search for structure and predictability in many of the problems we try to solve with data. So being open minded about the randomness of things may let us see more.

`
Insert cell
viewof color1 = colorPicker("#05ECF6")
Insert cell
viewof color2 = colorPicker("#FCE86B")
Insert cell
viewof color3 = colorPicker("#CCE86B")
Insert cell
viewof color4 = colorPicker("#E5ECF6")
Insert cell
md`**Day 90**

These last 10 days will be trying out 3D vector graphics and animations.

Follow this tutorial [Projection of 3D Models using JavaScript and HTML5 Canvas](https://observablehq.com/@kelleyvanevert/projection-of-3d-models-using-javascript-and-html5-canvas) for the code.

First draw 3D cube with polygons and an oblique projection.
`
Insert cell
{
var canvas = DOM.canvas(640, 250);
var context = canvas.getContext('2d');

// Remap the position of the canvas object to the centre
context.translate(canvas.width / 2, canvas.height / 2);
// Set the object to be cube line colour to the blue palette colour
context.strokeStyle =color1;

var size = canvas.width / 6;
var scale = size / 2;
var c = 0.5;
var fx = oblique.gx(scale, c);
var fy = oblique.gy(scale, c);
// Then draw the polygons using HTML canvas closed path

for (var i = 0; i < modelPolygons.length; ++i) {
drawPolygon(context, modelPolygons[i], fx, fy);
}

return canvas;
}
Insert cell
oblique = ({
gx: (scale, zc) => (vertex) => (vertex.x + vertex.z * zc) * scale,
gy: (scale, zc) => (vertex) => (vertex.y + vertex.z * zc) * scale,
})
Insert cell
function drawPolygon2(context, polygon, matrix, fx, fy) {
context.beginPath();
var vertex = Vertex.transform(polygon.vertex(0), matrix);
context.moveTo(fx(vertex), -1 * fy(vertex));
for (var i = 1; i < polygon.count(); ++i) {
vertex = Vertex.transform(polygon.vertex(i), matrix);
context.lineTo(fx(vertex), -1 * fy(vertex));
}
context.closePath();
context.stroke();
}
Insert cell
md`**Day 91**

Spin the cube with colourful vectors to get an idea of the 3rd dimension`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
modelVerts = vertices
Insert cell
Insert cell
Insert cell
Insert cell
md`**Day 92**

Trying out another library pimJS, which creates a [a single-page fast-loading protected html experience](https://enkimute.github.io/packaging3Dcontent.pimJS/) , load 3D content using the pimjs-element html tag.`
Insert cell
html2`<pimjs-element align=bottom>
myDir.scene.addParticle({
parent : this.obj.inst,
Version : 8,
alphaName : 'https://enkimute.github.io/res/star.jpg',
birthRate : 40,
Intensity : 20,
lifeTime : 5,
Size : 0.55,
velocity : 0.5,
randomColor : true,
GravityY : -0.65,
Weight : 0.5,
direction : [0,1,0],
coneAngle : 1,
scale : [0.01,0.01,0.01]
});
</pimjs-element>


``
Insert cell

md`**Day 93**

Animating with a CSS example. Are these rotating petals or rotating circles?

https://observablehq.com/@lemonnish/rotating-geometry

`
Insert cell
{
const itemWidth = 50;
// set spacing, to be frequent dashes
const numDashesPerSection = 1;
const dashRatio = 1;
const gapLength = 5;
const lineLength = gapLength * dashRatio;
let dashArray = Array(numDashesPerSection * 2 + 1).fill(0);
dashArray = dashArray.map((x,i) => {
if (i === dashArray.length - 1) {
return gapLength;
} else {
return (i % 2 === 0) ? lineLength : gapLength;
}
})
return (html`
${stylesheet('sashiko', `20s`, 20, "ease-in-out","none")}
<style>
#sashiko .item > * {
stroke-width: 4;
stroke: white;
stroke-dasharray: ${dashArray.join(' ')};
stroke-dashoffset: -6;
}
</style>
${draw('sashiko', width, 500, 8, 100, 0, "none", color1, true, true, eye)}
`
)}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`**Day 94**

Chained transitions with D3 and own colour palette and parameters. I removed the checkboxes and options to simplify the code.

`
Insert cell
chained = {
"play";
const clamp = (a, b, v) => v < a ? a : v > b ? b : v;
const height = Math.ceil(width * screen.height / screen.width);
const svg = d3.select(DOM.svg(width/2, height/2));
const color_t1 = d3.interpolateRgb(color1, color2),
color_t2 = d3.interpolateRgb(color2, color3),
color_t3 = d3.interpolateRgb(color3, color1);
const w = 20;
const cols = Math.floor(width / w)
const rows = Math.floor(height / w);
const n = cols*rows;
const sa = width / cols;
const pad = clamp(.75, w, Math.floor((w/2) * 1/100));
const delay = 2500;
const stroke_width = 0.5;
const stroke = "#00000";
const delay_m = 7;
const sel = svg.selectAll("rect")
.data(d3.range(n))
.enter().append("rect")
.attr("x", (d,i)=>(i % cols)*sa)
.attr("y", (d,i)=>(i / cols | 0)*sa)
// Start animation with fill color1
.attr ("fill",color1)
.attr("width", w-pad*2)
.attr("height", w-pad*2)
.attr("stroke", stroke)
.attr("stroke-width", stroke_width)
.transition()
.delay(function(d, i) { return i + Math.random() * n / delay_m; })
.ease(d3.easeLinear)
.on("start", function repeat() {
d3.active(this)
.styleTween("fill", function() { return color_t1; })
.transition()
.delay(delay)
.styleTween("fill", function() { return color_t2; })
.transition()
.delay(delay)
.styleTween("fill", function() { return color_t3; })
.transition()
.delay(delay)
.on("start", repeat);
});

while ("play") {
yield svg.node();
}
return svg.node();
}
Insert cell
Insert cell
mondrianColor = () => {
let colors = [
'#fff001',
'#ff0101',
'#0101fd',
'#30303a'
];
return colors.sort(() => Math.random() - 0.5)[0];
}
Insert cell
Insert cell
html`
<style>
.container {
background:#000;
}
.one {
background-color:#FFF;
}
</style>

<!-- Create a container box -->
<div class='container' style='width:${width/6}px;height:${width/9}px'>
<!-- Create a colour box -->
<div class='one'> 1</div>
</div>

`
Insert cell
md`Define the HTML and CSS Styling for a container class with a black background with 5 boxes using the grid.`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const width = 200;
const height = width/2;
const a=10;
const b=60;
const c=100;
const svg = d3.select(DOM.svg(width, height));

svg.append("rect")
.style("fill",mondrianColor())
.attr("x", a)
.attr("y", a)
.attr("width", c)
.attr("height", c)
svg.append("rect")
.style("fill",mondrianColor())
.attr("x", a)
.attr("y", b)
.attr("width", c)
.attr("height", c)
svg.append("rect")
.style("fill",mondrianColor())
.attr("x", b)
.attr("y", a)
.attr("width", c)
.attr("height", c)
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
html`
<svg height="900" width="900" id="solar-system">
<rect height=900 width=900 class="solar-system-background"></rect>
</svg>
`
Insert cell
{
let ssSvg = d3.select('#solar-system');
let sortedSolarSystem = solarSystem.sort((a,b)=> a.distanceFromSun - b.distanceFromSun);
const sun = sortedSolarSystem[0];
sortedSolarSystem = sortedSolarSystem.slice(1)
const planetSizeScale = d3.scaleLinear().domain([2370, 142984]).range([5, 20])
const distFromSunScale = d3.scalePow()
.exponent(0.1)
.domain([57.9, 5906.4])
.range([60, 400])
sortedSolarSystem.forEach(planet => {
const X = getRadialX(distFromSunScale(planet.distanceFromSun));
const Y = getRadialY(X, distFromSunScale(planet.distanceFromSun));
planet.X = X;
planet.Y = Y;
});
const solarSystemGroup = ssSvg.append('g').attr('transform', 'translate(450, 450)');

solarSystemGroup.append('circle').attr('r', 30).style('fill', sun.color);
const planets = solarSystemGroup.selectAll('.planet').data(sortedSolarSystem);
planets.join('circle')
.classed('planet', true)
.attr('r', d => planetSizeScale(d.diameter))
.attr('cx', d=> d.X)
.attr('cy', d => d.Y)
.style('fill', d => d.color);
const orbits = solarSystemGroup.selectAll('.orbit').data(sortedSolarSystem);
orbits.join(
enter => enter
.append('circle')
.classed('orbit', true)
.attr('stroke', d=> d.color)
.attr('r', d => distFromSunScale(d.distanceFromSun))
.attr('stroke-width', 1),
update => update.classed('orbit', true)
.attr('stroke', d=> d.color)
.attr('stroke-width', 1),
exit => exit.remove()
)
return sortedSolarSystem;
}
Insert cell
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