Skip to content

Instantly share code, notes, and snippets.

@curran
Last active July 13, 2019 06:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save curran/685fa8300650c4324d571c6b0ecc55de to your computer and use it in GitHub Desktop.
Save curran/685fa8300650c4324d571c6b0ecc55de to your computer and use it in GitHub Desktop.
Spinner with d3-component
license: mit

This example demonstrates usage of d3-component to create a spinner component, and render it conditionally while something is loading. After loading finishes, the spinner goes away and is replaced by another component.

How often have you seen a data visualization show nothing but a blank screen while the data is loading? Showing a spinner with D3 and hiding it after data is loaded is not as easy as it seems. Here's a solution for doing this, feel free to use it in your projects!

This demonstrates the following patterns supported by d3-component:

  • Use of local state and lifecycle hooks (spinner timer).
  • Composition of components with conditional rendering (app component).

Built with blockbuilder.org

forked from curran's block: Posts with D3 Component

forked from curran's block: Spinny Loading Icon

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/d3@4"></script>
<script src="https://unpkg.com/d3-component@3.0.0"></script>
</head>
<body>
<svg width="960" height="500"></svg>
<script>
// This function simulates fetching with a 2 second delay.
// You can replace stuff here with e.g. d3.csv.
function fetchData(callback){
setTimeout(function (){
callback([6, 5, 4, 3, 2, 1]);
}, 2000);
}
// This function visualizes the data.
function visualize(selection, data){
var rects = selection
.selectAll("rect")
.data(data);
rects.exit().remove();
rects
.enter().append("rect")
.attr("x", function (d, i){ return i * 100 + 182; })
.attr("y", function (d){ return 400; })
.attr("width", 50)
.attr("height", 0)
.merge(rects)
.transition().duration(1000).ease(d3.easeBounce)
.delay(function (d, i){ return i * 500; })
.attr("y", function (d){ return 400 - d * 50; })
.attr("height", function (d){ return 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.
var wheel = d3.component("g")
.create(function (selection){
var minRadius = 4,
maxRadius = 10,
numDots = 10,
wheelRadius = 40,
rotation = 0,
rotationIncrement = 3,
radius = d3.scaleLinear()
.domain([0, numDots - 1])
.range([maxRadius, minRadius]),
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.round(Math.sin(angle(d)) * wheelRadius))
.attr("cy", d => Math.round(Math.cos(angle(d)) * wheelRadius))
.attr("r", d => Math.round(radius(d)));
})
.render(function (selection, d){
selection.attr("transform", "rotate(" + d + ")");
});
// This component with a local timer makes the wheel spin.
var spinner = (function (){
var timer = d3.local();
return d3.component("g")
.create(function (selection, d){
timer.set(selection.node(), d3.timer(function (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.
var 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.
var 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(){
var svg = d3.select("svg"),
width = svg.attr("width"),
height = svg.attr("height");
// Initialize the app to be "loading".
svg.call(app, {
width: width,
height: height,
loading: true
});
// Invoke the data fetching logic.
fetchData(function (data){
svg.call(app, {
width: width,
height: height,
loading: false,
data: data
});
});
}
main();
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment