This example comes from my blog on Using gradients for abrupt color changes in data visualizations.
Each line in this data visualization is filled with a linear gradient (there is one unique SVG gradient) that abruptly changes color to showcase the different categories within the BMI scale.
The chart shows the growing BMI of practically all these 25 countries (a random sample of the 200 available) over the past 40 years. Use the buttons below to switch between Men and Women
The data is from the NCD RisC
Two other examples of using these abrupt color changes can be found here
///////////////////////////////////////////////////////////////////////////
/////////////////////////////// Initiate SVG //////////////////////////////
///////////////////////////////////////////////////////////////////////////
var margin = {
top: 160,
right: 140,
bottom: 50,
left: 140
};
var width = Math.max(window.innerWidth - margin.left - margin.right, 500) - 30,
height = Math.min(window.innerHeight - margin.top - margin.bottom - 100, width*2/3);
//Reset the overall font size
var newFontSize = Math.min(width * 62.5 / 1400, 62.5);
d3.select("html").style("font-size", newFontSize + "%");
//SVG container
var svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
///////////////////////////////////////////////////////////////////////////
/////////////////////////////// Append titles /////////////////////////////
///////////////////////////////////////////////////////////////////////////
var titleWrapper = svg.append("g").attr("class", "titleWrapper");
//Append title to the top
titleWrapper.append("text")
.attr("class", "title")
.attr("x", width/2)
.attr("y", -100)
.style("text-anchor", "middle")
.text("Mean Body Mass Index (BMI) per Country");
titleWrapper.append("text")
.attr("class", "subtitle")
.attr("x", width/2)
.attr("y", -50)
.style("text-anchor", "middle")
.text("Men");
///////////////////////////////////////////////////////////////////////////
//////////////////////////////// Create axes //////////////////////////////
///////////////////////////////////////////////////////////////////////////
var axisWrapper = svg.append("g").attr("class", "axisWrapper");
var xScale = d3.scale.linear()
.range([0, width])
.domain(d3.extent(bmi, function(d) { return d.year; }))
.nice();
var yScale = d3.scale.linear()
.range([height, 0])
.domain(d3.extent(bmi, function(d) { return d.mean_bmi; }))
.nice();
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(d3.format("d"));
axisWrapper.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
axisWrapper.append("g")
.attr("class", "y axis")
.call(yAxis);
///////////////////////////////////////////////////////////////////////////
////////////////////////////// Create legend //////////////////////////////
///////////////////////////////////////////////////////////////////////////
var legendWrapper = svg.append("g").attr("class", "legendWrapper");
//Legend
legendWrapper.append("text")
.attr("class", "axisLegend")
.attr("transform", "rotate(-90)")
.attr("y", width)
.attr("x", -yScale(32))
.style("fill", "#D62C2B")
.text("Obese");
legendWrapper.append("text")
.attr("class", "axisLegend")
.attr("transform", "rotate(-90)")
.attr("y", width)
.attr("x", -yScale(27.5))
.style("fill", "#F7804B")
.text("Overweight");
legendWrapper.append("text")
.attr("class", "axisLegend")
.attr("transform", "rotate(-90)")
.attr("y", width)
.attr("x", -yScale(22.5))
.style("fill", "#9C9C9C")
.text("Normal weight");
//////////////////////////////////////////////////////////////
//////////////////////// Gradients ///////////////////////////
//////////////////////////////////////////////////////////////
var defs = svg.append("defs");
linearGradient = defs.append("linearGradient")
.attr("id", "gradient-bmi")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", height);
linearGradient.append("stop")
.attr("class", "left")
.attr("offset", yScale(30)/yScale.range()[0])
.attr("stop-color", "#D62C2B");
linearGradient.append("stop")
.attr("class", "left")
.attr("offset", yScale(30)/yScale.range()[0])
.attr("stop-color", "#F7804B");
linearGradient.append("stop")
.attr("class", "left")
.attr("offset", yScale(25)/yScale.range()[0])
.attr("stop-color", "#F7804B");
linearGradient.append("stop")
.attr("class", "left")
.attr("offset", yScale(25)/yScale.range()[0])
.attr("stop-color", "#BABABA");
///////////////////////////////////////////////////////////////////////////
////////////////////////////////// Voronoi ////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//Initiate the voronoi function
var voronoi = d3.geom.voronoi()
.x(function(d) { return xScale(d.year); })
.y(function(d) { return yScale(d.mean_bmi); })
.clipExtent([[0, yScale(35)], [width, yScale(17)]]);
//Prepare the data
var gender = d3.nest()
.key(function(d) { return d.sex; })
.entries(bmi);
//Initiate the voronoi group element
var voronoiGroup = svg.append("g").attr("class", "voronoiWrapper");
//Create a new voronoi map including only the visible points
voronoiGroup.selectAll("path")
.data(voronoi(gender[0].values))
.enter().append("path")
.attr("class", function(d,i) {
return "voronoi " + d.point.iso;
})
//.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; })
.attr("class", "voronoiCells")
.on("mouseover", mouseoverVor)
.on("mouseout", mouseoutVor);
///////////////////////////////////////////////////////////////////////////
//////////////////////////////// Plot Data ////////////////////////////////
///////////////////////////////////////////////////////////////////////////
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return xScale(d.year); })
.y(function(d) { return yScale(d.mean_bmi); });
//Wrapper for all the lines
var countriesWrapper = svg.append("g").attr("class","countryWrapper");
//Prepare the data
var countries = d3.nest()
.key(function(d) { return d.iso; })
.key(function(d) { return d.sex; })
.entries(bmi);
//Create a group for each country
var countryGroup = countriesWrapper.selectAll(".countryGroup")
.data(countries, function(d) { return d.key; })
.enter().append("g")
.attr("class", function(d) { return "country " + d.key; });
//Draw a line for each country
countryGroup.append("path")
.attr("class", "country line")
//.attr("d", function(d) { return line(d.values[0].values); })
.style("stroke", "url(#gradient-bmi)");
//Append the actual text to the right of the last value for each country
countryGroup.append("text")
.attr("class", "countryName")
.datum(function(d) { return {country: d.values[0].values[0].country, valueMen: d.values[0].values[d.values[0].values.length - 1], valueWomen: d.values[1].values[d.values[1].values.length - 1]}; })
//.attr("transform", function(d) { return "translate(" + xScale(d.valueMen.year) + "," + yScale(d.valueMen.mean_bmi) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.country; });
///////////////////////////////////////////////////////////////////////////
///////////////////////////// Threshold lines /////////////////////////////
///////////////////////////////////////////////////////////////////////////
countriesWrapper.append("path")
.attr("class", "threshold")
.attr("d", "M" + 0 + "," + yScale(25) + " L" + width + "," + yScale(25))
.style("stroke", "#F7804B");
countriesWrapper.append("path")
.attr("class", "threshold")
.attr("d", "M" + 0 + "," + yScale(30) + " L" + width + "," + yScale(30))
.style("stroke", "#D62C2B");
///////////////////////////////////////////////////////////////////////////
//////////////////////// Change between datasets //////////////////////////
///////////////////////////////////////////////////////////////////////////
function changeToMen() {
//Change subtitle
d3.select(".subtitle")
.text("Men");
//Change to females
d3.selectAll(".country")
.transition().duration(1000)
.attr("d", function(d) { return line(d.values[0].values); });
//Move labels to men
d3.selectAll(".countryName")
.attr("transform", function(d) { return "translate(" + xScale(d.valueMen.year) + "," + yScale(d.valueMen.mean_bmi) + ")"; });
//Create voronois
setTimeout(function() {
d3.selectAll(".voronoiWrapper").selectAll("path")
.data(voronoi(gender[0].values))
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; });
},1000);
}//men
function changeToWomen() {
//Change subtitle
d3.select(".subtitle")
.text("Women");
//Change to females
d3.selectAll(".country")
.transition().duration(1000)
.attr("d", function(d) { return line(d.values[1].values); });
//Move labels to women
d3.selectAll(".countryName")
.attr("transform", function(d) { return "translate(" + xScale(d.valueWomen.year) + "," + yScale(d.valueWomen.mean_bmi) + ")"; });
//Create voronois
setTimeout(function() {
d3.selectAll(".voronoiWrapper").selectAll("path")
.data(voronoi(gender[1].values))
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; });
},1000);
}//women
///////////////////////////////////////////////////////////////////////////
//////////////////////////// Button Activity //////////////////////////////
///////////////////////////////////////////////////////////////////////////
d3.select("#menButton").on("click", changeToMen);
d3.select("#womenButton").on("click", changeToWomen);
///////////////////////////////////////////////////////////////////////////
/////////////////////////// Mouseover functions ///////////////////////////
///////////////////////////////////////////////////////////////////////////
function mouseoverVor(d) {
//Dim all lines
d3.selectAll(".line")
.style("opacity", 0.1);
//Highlight selected line
d3.select(".country." + d.iso + " .line")
.style("stroke-width", 5)
.style("opacity", 1);
//Show selected country
d3.select(".country." + d.iso + " .countryName")
.style("opacity", 1);
//Hide legends
d3.selectAll(".axisLegend")
.style("opacity", 0.1);
}//mouseover
function mouseoutVor(d) {
//Remove stylings
d3.selectAll(".line, .countryName, .axisLegend")
.style("opacity", null);
d3.select(".country." + d.iso + " .line")
.style("stroke-width", null)
.style("opacity", null);
}//mouseoutVor
changeToMen();
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>The growth of BMI over the last 40 years</title>
<!-- D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<!-- Google Fonts -->
<link href='//fonts.googleapis.com/css?family=Open+Sans:300,400' rel='stylesheet' type='text/css'>
<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- Compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<style>
html { font-size: 62.5%; }
body {
font-size: 1.2rem;
font-family: 'Open Sans', sans-serif;
font-weight: 400;
text-align: center;
fill: #2B2B2B;
}
.title {
font-size: 3.6rem;
fill: #4F4F4F;
font-weight: 300;
}
.subtitle {
font-size: 2.2rem;
fill: #AAAAAA;
font-weight: 300;
}
.axis path,
.axis line {
fill: none;
stroke: #949494;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.axis text {
fill: #383838;
font-size: 1.6rem;
font-weight: 300;
}
.line {
fill: none;
stroke-width: 1.5px;
opacity: 0.8;
pointer-events: none;
}
.axisLegend {
text-anchor: middle;
font-size: 2.2rem;
font-weight: 300;
opacity: 1;
}
.countryName {
text-anchor: start;
font-size: 1.6rem;
font-weight: 300;
fill: #383838;
opacity: 0;
}
.threshold {
fill: none;
shape-rendering: crispEdges;
opacity: 0.3;
stroke-dasharray: 8, 5;
/*stroke-width: 1.5px;*/
pointer-events: none;
}
.voronoiWrapper path {
fill: none;
pointer-events: all;
}
</style>
</head>
<body>
<div id = "chart"></div>
<!-- The buttons -->
<div id="button" class="btn-group" data-toggle="buttons">
<label id="menButton" class="btn btn-default active"><input type="radio" class="btn-options"> Men </label>
<label id="womenButton" class="btn btn-default"><input type="radio" class="btn-options"> Women </label>
</div>
<script src = "bmiSmall.js"></script>
<script src = "script.js"></script>
</body>
</html>