By Tom Coombs
Built with blockbuilder.org
forked from enjalot's block: d3 anniversary animation
| license: mit |
By Tom Coombs
Built with blockbuilder.org
forked from enjalot's block: d3 anniversary animation
| <!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", 1920); | |
| container_svg.attr("height", 1080); | |
| var svg = container_svg.append('g').attr('transform', 'translate(900,200)scale(2.2)') | |
| 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> |