Skip to content

Instantly share code, notes, and snippets.

@enjalot
Last active February 17, 2021 04:51
Show Gist options
  • Save enjalot/3bd27b3e1be167239781a49578c885da to your computer and use it in GitHub Desktop.
Save enjalot/3bd27b3e1be167239781a49578c885da to your computer and use it in GitHub Desktop.
d3 anniversary animation
license: mit
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
function main() {
//document.getElementById("button1").addEventListener("click", function () { controller.flash_d3() });
//document.getElementById("button2").addEventListener("click", function () { controller.flash_ten() });
var controller = new Controller()
}
class Controller {
constructor() {
var container_svg = d3.select("#svg1");
container_svg.attr("width", 1600);
container_svg.attr("height", 1600);
var svg = container_svg.append('g').attr('transform', 'translate(200,200)')
this.shapes = {}
this.drawn_lines_ten = []
this.drawn_lines_d3 = []
this.shapes.dee = new ShapeCircle(svg, "dee", 10, .6).add_clip_path(dee.svg_path) //.debug_show_clip();
this.shapes.three = new ShapeCircle(svg, "three", 10, .6).add_clip_path(three.svg_path)
this.shapes.one = new ShapeCircle(svg, "one", 230, .6).add_clip_path(one.svg_path)
this.shapes.zero = new ShapeCircle(svg, "zero", 230, .6).add_clip_path(zero.svg_path)
for (let index = 0; index < line_paths_d3.length; index++) {
this.drawn_lines_d3[index] = new Line(svg, line_paths_d3[index].path, line_paths_d3[index].delay)
}
for (let index = 0; index < line_paths_ten.length; index++) {
this.drawn_lines_ten[index] = new Line(svg, line_paths_ten[index].path, line_paths_ten[index].delay)
}
var tid = setInterval(loop_sequence, 5000);
var is_d3_next = true
var thisobject = this
function loop_sequence() {
if (is_d3_next) {
thisobject.flash_d3()
} else {
thisobject.flash_ten()
}
is_d3_next = !is_d3_next
}
loop_sequence()
// function abortTimer() {
// clearInterval(tid);
// }
}
flash_d3() {
for (let index = 0; index < this.drawn_lines_d3.length; index++) {
this.drawn_lines_d3[index].flash()
}
this.shapes.dee.flash()
this.shapes.three.flash()
}
flash_ten() {
for (let index = 0; index < this.drawn_lines_ten.length; index++) {
this.drawn_lines_ten[index].flash()
}
this.shapes.one.flash()
this.shapes.zero.flash()
}
}
class Line {
constructor(svg, shape_path, delay) {
var path = svg.append("path")
.attr("d", shape_path)
.attr("stroke", "#888")
.attr("stroke-width", "1")
.attr("fill", "none");
var totalLength = path.node().getTotalLength();
this.path = path
this.total_length = totalLength
this.delay = delay
this.path.attr("stroke-dasharray", totalLength + " " + totalLength)
this.path.attr("stroke-dashoffset", this.total_length)
}
debug_draw() {
this.path.attr("stroke-dasharray", "2 2")
}
flash() {
this.path.attr("stroke-dashoffset", this.total_length)
this.path.transition()
.delay(this.delay)
.duration(3500)
.ease(d3.easePolyInOut.exponent(0.4))
.attr("stroke-dashoffset", -this.total_length)
// .on("end", repeat);
}
}
class ShapeCircle {
constructor(svg, name, color_start, start_angle) {
this.start_angle = start_angle
//this.conf = { orange: {}, arcs: { count: 6, start: 0, end: 1, size: 0.22 }, col: { range: 40 }, radius: { inner: 0, outer: 1200 }, offset:{x:-300,y:-300} }
this.conf = { swing: 130, arcs: { centre_angle: 2.7, count: 25, start: 0, end: 1, size: 0.22 }, col: { range: 40 }, radius: { inner: 0, outer: 1200 }, offset: { x: -300, y: -300 } }
this.conf.col.start = color_start
this.y_offset = 0
this.clip_path_id = "clip_path_" + name
this.clipped_container_group = svg.append('g').attr("clip-path", `url(#${this.clip_path_id})`)
this.rotator_container_group = this.clipped_container_group.append('g').attr('transform', `translate(${this.conf.offset.x}, ${this.conf.offset.y})`)
//this.rotating_inner_group = this.rotator_container_group.append('g')
this.arc = d3.arc()
this.arc_def = { innerRadius: this.conf.radius.inner, outerRadius: this.conf.radius.outer }
this.data = []
for (let index = 0; index < this.conf.arcs.count; index++) {
this.data.push({ now: Math.random() })
}
this.add_color_wheel()
}
fadeupdown(t) {
return t < 0.5 ? 2 * t : 2 * (1 - t) // converts 0->1 to 0->1->0
}
add_color_wheel() {
var thisobject = this
this.pie_arcs = this.rotator_container_group.selectAll(".pie_arc")
.data(this.data)
.enter()
.append('path')
.attr("class", "pie_arc")
.attr('d', function (d) {
thisobject.arc_def.startAngle = d.now
thisobject.arc_def.endAngle = d.now + 0.2
return thisobject.arc(thisobject.arc_def)
})
.style('fill', (d, i) => `hsl(${this.conf.col.start + Math.random() * this.conf.col.range},100%,70%)`)
.style('opacity', 0.9)
}
debug_show_clip() {
if (this.clip_path) {
this.clipped_container_group.append('path')
.attr('d', this.clip_path)
.style('fill', 'none')
.style('stroke', 'green')
.style('opacity', 0.5)
}
return this
}
gaussian_rand(rounds) {
var rounds = rounds ? rounds : 6
var rand = 0;
for (var i = 0; i < rounds; i += 1) {
rand += Math.random();
}
return rand / rounds;
}
randomize_data() {
var prior
for (let index = 0; index < this.data.length; index++) {
prior = this.data[index].now
// rand = (Math.random() + Math.random() + Math.random() + Math.random() + Math.random() + Math.random())/7
this.data[index].now = this.conf.arcs.centre_angle - Math.PI + 2 * Math.PI * this.gaussian_rand()
this.data[index].interpolator = d3.interpolate(prior, this.data[index].now)
}
}
flash() {
var thisobject = this
this.randomize_data()
this.pie_arcs.merge(this.pie_arcs)
.transition()
.duration(3500)
.ease(d3.easePolyInOut.exponent(0.4))
.styleTween("opacity", function (d, i) {
return function (t) {
return thisobject.fadeupdown(t)
}
})
//.style('fill', (d, i) => `hsl(${Math.random() * thisobject.color_start},100%,70%)`)
// .attr('d', this.arc({ innerRadius: this.conf.radius.inner, outerRadius: this.conf.radius.outer, startAngle: 0.5, endAngle: 0.22 }))
.attrTween("d", function (d, i) {
return function (t) {
thisobject.arc_def.startAngle = d.interpolator(t)
thisobject.arc_def.endAngle = thisobject.arc_def.startAngle + 0.2
return thisobject.arc(thisobject.arc_def)
}
})
}
add_clip_path(clip_path) {
this.clip_path = clip_path
this.clipped_container_group.append("svg:clipPath")
.attr("id", this.clip_path_id)
.append('path')
.attr('d', this.clip_path)
return this
}
}
var dee = {}
dee.height = 200
dee.thickness = dee.height * 20 / 90
dee.dtrailer = dee.thickness * 7.75 / 20
dee.overshoot = dee.height / 2
dee.radius_inner = dee.height / 2 - dee.thickness
dee.radius_mid = dee.height / 2 - dee.thickness / 2
dee.radius_outer = dee.height / 2
dee.svg_path = ''
dee.svg_path += `M0,0`
dee.svg_path += `h${dee.dtrailer}`
dee.svg_path += `a${dee.radius_outer}, ${dee.radius_outer} 0 1 1 0, ${2 * dee.radius_outer}`
dee.svg_path += `h${-dee.dtrailer}`
dee.svg_path += `v${-dee.thickness}`
dee.svg_path += `h${dee.dtrailer}`
dee.svg_path += `a${dee.radius_inner}, ${dee.radius_inner} 0 1 0 0, ${-2 * dee.radius_inner}`
dee.svg_path += `h${-dee.dtrailer} z`//"M0,0 h7.75 a45.5, 45.5 0 1 1 0, 91 h-7.75 v-20 h7.75 a25.5, 25.5 0 1 0 0, -51 h -7.75 z"
dee.lines = {}
dee.lines.outer_circle = ''
dee.lines.outer_circle += `M0,0`
dee.lines.outer_circle += `h${dee.dtrailer}`
dee.lines.outer_circle += `a${dee.radius_outer}, ${dee.radius_outer} 0 1 1 0, ${2 * dee.radius_outer}`
dee.lines.outer_circle += `a${dee.radius_outer}, ${dee.radius_outer} 0 1 1 0, -${2 * dee.radius_outer}`
dee.lines.inner_circle = ''
dee.lines.inner_circle += `M${dee.dtrailer},${dee.thickness}`
dee.lines.inner_circle += `a${dee.radius_inner}, ${dee.radius_inner} 0 1 1 0, ${2 * dee.radius_inner}`
dee.lines.inner_circle += `a${dee.radius_inner}, ${dee.radius_inner} 0 1 1 0, -${2 * dee.radius_inner}`
dee.lines.path3 = `M0,-1000 v2000`
var three = {}
three.height = 200
three.thickness = three.height * 20 / 91
three.hollow_height = three.height * 15.5 / 91
three.x_offset = three.height * 36.2510 / 91
three.outer_radius = three.height * 27.75 / 91
three.inner_radius = three.height * 7.75 / 91
three.mid_radius = (three.thickness + three.hollow_height) / 2
three.left_cutoff_radius = three.height * 53.6895 / 91
three.inner_h = three.height * 7.75 / 91
three.outer_h = three.height * 32 / 91
three.mid_h = three.height * 13.2526 / 91
three.vertex_dx = three.height * 21.331 / 91
three.vertex_dy = three.height * 45.5 / 91
three.cutoff_dx = three.height * 18.7464 / 91
three.overshoot = 100
// a x_radius y_radius x_axis_rotation large_arc_flag sweep_flag dx_of_end_point dy_of_end_point
three.svg_path = `M${three.x_offset},0` // initial offset
three.svg_path += `h${three.outer_h}` // top
three.svg_path += `a${three.outer_radius},${three.outer_radius} 0 0 1 ${three.vertex_dx},${three.vertex_dy}` // right outer top arc
three.svg_path += `a${three.outer_radius},${three.outer_radius} 0 0 1 ${-three.vertex_dx},${three.vertex_dy}` // right outer bottom arc
three.svg_path += `h${-three.outer_h}` // bottom
three.svg_path += `a${three.left_cutoff_radius},${three.left_cutoff_radius} 0 0 0 ${three.cutoff_dx}, ${-three.thickness}` // bottom cutoff arc
three.svg_path += `h${three.mid_h}` // lower hollow bottom
three.svg_path += `a${three.inner_radius},${three.inner_radius} 0 1 0 0, ${-three.hollow_height}` // lower hollow arc
three.svg_path += `h${-three.inner_h}` // lower hollow top
three.svg_path += `a${three.left_cutoff_radius},${three.left_cutoff_radius} 0 0 0 0, ${-three.thickness}` // mid cuttoff arc
three.svg_path += `h${three.inner_h}` // upper hollow bottom
three.svg_path += `a${three.inner_radius},${three.inner_radius} 0 1 0 0, ${-three.hollow_height}` // upper hollow arc
three.svg_path += `h${-three.mid_h}` // upper hollow top
three.svg_path += `a${three.left_cutoff_radius},${three.left_cutoff_radius} 0 0 0 ${-three.cutoff_dx}, ${-three.thickness}` // top cutoff arc
three.svg_path += `z`
three.lines = {}
three.lines.outer_upper = `M${three.x_offset + three.outer_h},0` // initial offset
three.lines.outer_upper += `a${three.outer_radius},${three.outer_radius} 0 0 1 ${0},${2 * three.outer_radius}` // right outer top arc
three.lines.outer_upper += `a${three.outer_radius},${three.outer_radius} 0 0 1 ${0},${-2 * three.outer_radius}` // right outer top arc
three.lines.outer_lower = `M${three.x_offset + three.outer_h},${three.height}` // initial offset
three.lines.outer_lower += `a${three.outer_radius},${three.outer_radius} 0 0 0 ${0},-${2 * three.outer_radius}` // right outer top arc
three.lines.outer_lower += `a${three.outer_radius},${three.outer_radius} 0 0 0 ${0},${2 * three.outer_radius}` // right outer top arc
three.lines.top = `M${-500},${0} h${700}`
three.lines.inner_upper_top = `M${-1000},${three.thickness} h${900}`
three.lines.inner_upper_bottom = `M${-500},${three.height - three.thickness} h${1200}`
three.lines.inner_lower_top = `M${-1600},${three.height} h${1000}`
three.lines.bottom = `M${-1600},${three.height} h${1000}`
three.lines.cutoff = `M${-101},${three.height / 2}` // initial offset
three.lines.cutoff += `a${three.left_cutoff_radius},${three.left_cutoff_radius} 0 0 0 ${2 * three.left_cutoff_radius},${0}` // right outer top arc
three.lines.cutoff += `a${three.left_cutoff_radius},${three.left_cutoff_radius} 0 0 0 ${-2 * three.left_cutoff_radius},${0}` // right outer top arc
three.lines.inner_upper = `M${three.x_offset + three.outer_h},${three.thickness}` // initial offset
three.lines.inner_upper += `a${three.inner_radius},${three.inner_radius} 0 0 1 ${0},${2 * three.inner_radius}` // right outer top arc
three.lines.inner_upper += `a${three.inner_radius},${three.inner_radius} 0 0 1 ${0},${-2 * three.inner_radius}` // right outer top arc
three.lines.inner_lower = `M${three.x_offset + three.outer_h},${2 * three.inner_radius + 2 * three.thickness}` // initial offset
three.lines.inner_lower += `a${three.inner_radius},${three.inner_radius} 0 0 1 ${0},${2 * three.inner_radius}` // right outer top arc
three.lines.inner_lower += `a${three.inner_radius},${three.inner_radius} 0 0 1 ${0},${-2 * three.inner_radius}` // right outer top arc
var zero = {}
zero.height = 200
zero.x_offset = zero.height * 0.45
zero.aspect_ratio = 0.75
zero.thickness = zero.height * 20 / 91
zero.outer_radius = zero.height / 2
zero.outer_y_radius = zero.height / 2
zero.outer_x_radius = zero.outer_y_radius * zero.aspect_ratio
zero.inner_radius = zero.outer_radius - zero.thickness
zero.inner_x_radius = zero.outer_x_radius - zero.thickness
zero.inner_y_radius = zero.outer_radius - zero.thickness
zero.mid_radius = zero.outer_radius - zero.thickness / 2
zero.svg_path = ''
zero.svg_path += `M${zero.x_offset},${zero.outer_radius}` //
zero.svg_path += `a${zero.outer_x_radius},${zero.outer_y_radius} 0 0 1 ${2 * zero.outer_x_radius} ,0` //
zero.svg_path += `a${zero.outer_x_radius},${zero.outer_y_radius} 0 0 1 -${2 * zero.outer_x_radius},0` //
zero.svg_path += `h${zero.thickness}` //
zero.svg_path += `a${zero.inner_x_radius},${zero.inner_y_radius} 0 0 0 ${2 * zero.inner_x_radius} ,0` //
zero.svg_path += `a${zero.inner_x_radius},${zero.inner_y_radius} 0 0 0 -${2 * zero.inner_x_radius},0` //
zero.svg_path += `h-${zero.thickness}` //
zero.svg_path += `z`
zero.lines = {}
zero.lines.outer = ''
zero.lines.outer += `M${zero.x_offset},${zero.outer_radius}` //
zero.lines.outer += `a${zero.outer_x_radius},${zero.outer_y_radius} 0 0 0 ${2 * zero.outer_x_radius} ,0` //
zero.lines.outer += `a${zero.outer_x_radius},${zero.outer_y_radius} 0 0 0 -${2 * zero.outer_x_radius},0` //
zero.lines.outer += `z`
zero.lines.inner = ''
zero.lines.inner += `M${zero.x_offset + zero.thickness},${zero.outer_radius}` //
zero.lines.inner += `a${zero.inner_x_radius},${zero.inner_y_radius} 0 0 1 ${2 * zero.inner_x_radius} ,0` //
zero.lines.inner += `a${zero.inner_x_radius},${zero.inner_y_radius} 0 0 1 -${2 * zero.inner_x_radius},0` //
zero.lines.inner += `z`
var one = {}
one.height = 200
one.thickness = zero.height * 20 / 91
one.notch_height = one.height / 4
one.notch_width = one.height / 12
one.ledge_width = one.notch_width
one.ledge_height = one.height / 5
one.svg_path = `M0,${one.notch_height}`
one.svg_path += `l${one.notch_width},${-one.notch_height}` // slope of notch
one.svg_path += `h${one.thickness}` // top
one.svg_path += `v${one.height - one.ledge_height}` // right side
one.svg_path += `h${one.ledge_width}` // right ledge
one.svg_path += `v${one.ledge_height}` // right side bottom
one.svg_path += `h-${one.thickness + 2 * one.ledge_width}` // base
one.svg_path += `v${-one.ledge_height}` // left side bottom
one.svg_path += `h${one.ledge_width}` // left ledge
one.svg_path += `v-${one.height - one.ledge_height - one.notch_height}` // left side
one.svg_path += `h${-one.notch_width}` // notch underside
one.lines = {}
one.lines.top = `M-1000,0 h2000`
one.lines.bottom = `M-1000,${one.height} h2000`
one.lines.left = `M${one.notch_width},-1000 v 2000`
one.lines.right = `M${one.notch_width + one.thickness},-1000 v 2000`
one.lines.notch = `M-${one.notch_width * (12 - 1)},${one.notch_height * 12} l${one.notch_width * 24},-${one.notch_height * 24}`
one.lines.ledge = `M-1000,${one.height - one.ledge_height} h2000`
var line_paths_d3 = []
var line_paths_ten = []
for (const line_path in one.lines) {
line_paths_ten.push({ delay: Math.random() * 1000, path: one.lines[line_path] })
}
for (const line_path in zero.lines) {
line_paths_ten.push({ delay: Math.random() * 1000, path: zero.lines[line_path] })
}
for (const line_path in dee.lines) {
line_paths_d3.push({ delay: Math.random() * 1000, path: dee.lines[line_path] })
}
for (const line_path in three.lines) {
line_paths_d3.push({ delay: Math.random() * 1000, path: three.lines[line_path] })
}
</script>
</head>
<body onLoad="main()">
<!-- <button id="button1">D3</button>
<button id="button2">10</button>
<br> -->
<div>
<svg id='svg1'></svg>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment