/* global d3 */ // This function simulates fetching with a 2 second delay. // You can replace stuff here with e.g. d3.csv. function fetchData(callback) { setTimeout(() => { callback([6, 5, 4, 3, 2, 1]); }, 2000); } // This function visualizes the data. function visualize(selection, data) { const rects = selection .selectAll('rect') .data(data); rects.exit().remove(); rects .enter().append('rect') .attr('x', (d, i) => (i * 100) + 182) .attr('y', d => 400) .attr('width', 50) .attr('height', 0) .merge(rects) .transition().duration(1000).ease(d3.easeBounce) .delay((d, i) => i * 500) .attr('y', d => 400 - (d * 50)) .attr('height', d => d * 50); } // The stuff below uses d3-component to display a spinner // while the data loads, then render the visualization after loading. // This stateless component renders a static "wheel" made of circles, // and rotates it depending on the value of props.angle. const wheel = d3.component('g') .create(function (selection) { const minRadius = 4; const maxRadius = 10; const numDots = 10; const wheelRadius = 40; const rotation = 0; const rotationIncrement = 3; const radius = d3.scaleLinear() .domain([0, numDots - 1]) .range([maxRadius, minRadius]); const angle = d3.scaleLinear() .domain([0, numDots]) .range([0, Math.PI * 2]); selection .selectAll('circle').data(d3.range(numDots)) .enter().append('circle') .attr('cx', d => Math.sin(angle(d)) * wheelRadius) .attr('cy', d => Math.cos(angle(d)) * wheelRadius) .attr('r', radius); }) .render(function (selection, d) { selection.attr('transform', `rotate(${d})`); }); // This component with a local timer makes the wheel spin. const spinner = ((() => { const timer = d3.local(); return d3.component('g') .create(function (selection, d) { timer.set(selection.node(), d3.timer((elapsed) => { selection.call(wheel, elapsed * d.speed); })); }) .render(function (selection, d) { selection.attr('transform', `translate(${d.x},${d.y})`); }) .destroy(function (selection, d) { timer.get(selection.node()).stop(); return selection .attr('fill-opacity', 1) .transition().duration(3000) .attr('transform', `translate(${d.x},${d.y}) scale(10)`) .attr('fill-opacity', 0); }); })()); // This component displays the visualization. const visualization = d3.component('g') .render(function (selection, d) { selection.call(visualize, d.data); }); // This component manages an svg element, and // either displays a spinner or text, // depending on the value of the `loading` state. const app = d3.component('g') .render(function (selection, d) { selection .call(spinner, !d.loading ? [] : { x: d.width / 2, y: d.height / 2, speed: 0.2, }) .call(visualization, d.loading ? [] : d); }); // Kick off the app. function main() { const svg = d3.select('svg'); const width = svg.attr('width'); const height = svg.attr('height'); // Initialize the app to be "loading". svg.call(app, { width, height, loading: true, }); // Invoke the data fetching logic. fetchData((data) => { svg.call(app, { width, height, loading: false, data, }); }); } main();