An example of creating data based gradients used in my blog on Data-based and unique gradients for visualizations with d3.js . Here each star has a unique radial gradient where the color is based on the effective temperature of the star.
This is a Hertzsprung–Russell diagram. In this diagram stars are plotted according to their luminosities (or the related absolute magnitudes) versus their effective temperatures (or related spectral classifications). Many interesting discoveries around stellar evolution were speculated from this chart even before much was known about what happens in the interior of stars by looking at the positions of stars on the diagram.
The data comes from the HYG database. I took a subset of 400 stars that lie relatively close.
The orange circle is where our own Sun lies approximately
Watch for a few seconds and see the visual change between a 3 states
The first state in which all the stars have a 3D sphere like gradient. As if the stars were shined upon themselves. Although this might seem strange, the HR diagram is often visualized in this manner
The second state shows how the diagram looks when the stars are sized correctly relatively. As you can see, this creates a rather unreadable chart and that is why normally a transformation is used to the sizes of the stars
The third state shows a different kind of radial gradient. This time it tries to make the stars look like they are glowing from the inside
Other examples from this Astronomy-themed blog can be found here
You can find another version that I find visually a bit more appealing in the original SVG beyond mere shapes presentation slides - You do have to press refresh again after it loaded, otherwise the height offset is wrong, sorry
Built with blockbuilder.org
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<!-- Google Font -->
<link href='//fonts.googleapis.com/css?family=Open+Sans:300,400' rel='stylesheet' type='text/css'>
<style>
html { font-size: 62.5%; }
body {
font-size: 1rem;
font-family: 'Open Sans', sans-serif;
font-weight: 400;
fill: #8C8C8C;
text-align: center;
background: #03010C;
}
.axis path,
.axis line {
fill: none;
stroke: #CCCCCC;
shape-rendering: crispEdges;
}
.axisTitle {
text-anchor: middle;
fill: white;
font-size: 1.8rem;
font-weight: 300;
}
.axisSubtitle {
text-anchor: middle;
fill: #AAAAAA;
font-size: 1.2rem;
font-weight: 300;
}
.axis text {
fill: #CCCCCC;
font-size: 1.1rem;
font-weight: 300;
}
.title {
font-size: 2.4rem;
fill: white;
font-weight: 300;
}
.subtitle {
font-size: 1.2rem;
fill: #AAAAAA;
font-weight: 300;
}
.explanation {
font-size: 1.6rem;
fill: #AAAAAA;
font-weight: 300;
}
.credit {
font-size: 1rem;
fill: #AAAAAA;
font-weight: 300;
}
</style>
</head>
<body>
<div id="chart"></div>
<script src="starsSample.js"></script>
<script>
///////////////////////////////////////////////////////////////////////////
//////////////////// Set up and initiate svg containers ///////////////////
///////////////////////////////////////////////////////////////////////////
var margin = {
top: 100,
right: 60,
bottom: 90,
left: 180
};
var width = Math.min(Math.max(window.innerWidth - margin.left - margin.right - 30, 400), 600),
height = width*3/2;
// Changing iframe height - Thanks to Davo!
//I understand this approach is now deprecated by Mike Bostock - use a .block file instead: https://bl.ocks.org/-/about - Thanks to Curran
//d3.select(self.frameElement).style("height", (height + margin.top + margin.bottom + 20) + "px");
//SVG container
var svg = d3.select('#chart')
.append("svg")
.attr("width", width + margin.left + margin.right + 120)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + (margin.left) + "," + (margin.top) + ")");
//Reset the overall font size
var newFontSize = width * 62.5 / 600;
d3.select("html").style("font-size", newFontSize + "%");
///////////////////////////////////////////////////////////////////////////
////////////////////////// Create color scale /////////////////////////////
///////////////////////////////////////////////////////////////////////////
//Create color gradient for stars based on the temperature of the star
var colors = ["#FB1108","#FD150B","#FA7806","#FBE426","#FCFB8F","#F3F5E7","#C7E4EA","#ABD6E6","#9AD2E1","#42A1C1","#1C5FA5", "#172484"];
var temps = [2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 14000, 20000, 30000];
var colorScale = d3.scale.linear()
.domain(temps)
.range(colors);
///////////////////////////////////////////////////////////////////////////
///////////////////////// Create radius scales ////////////////////////////
///////////////////////////////////////////////////////////////////////////
var radiusScaleRange = [2*width/600, 40*width/600],
radiusScaleLinearRange = [0, 300*width/600]
//Set scale for radius of circles - downsize it by taking a sqrt scale
var rScale = d3.scale.sqrt()
.range(radiusScaleRange)
.domain(d3.extent(stars, function(d) { return d.radiusSun; }))
//.domain( [0.005, 300]);
//Make the radius of the circles to its actual relative size
var rScaleLinear = d3.scale.linear()
.range(radiusScaleLinearRange)
.domain([0, d3.max(stars, function(d) { return d.radiusSun; })] );
///////////////////////////////////////////////////////////////////////////
////////////////////////// Create axis scales /////////////////////////////
///////////////////////////////////////////////////////////////////////////
var absMagSun = 4.83,
tempSun = 5800;
var axisGroup = svg.append("g").attr("class", "axisWrapper");
//Go from BV to temperature
function BVtoTemp(d) {
return 4600*(1/(0.92*d + 1.7) + 1/(0.92*d + 0.62));
}
//Go from absolute magnitude to luminosity
function magToLum(d) {
return Math.pow(10,0.4*(absMagSun - d));
}
var BVmin = -0.3, BVmax = 2.2;
var tempScale = d3.scale.log()
.range([0, width])
.domain([BVtoTemp(BVmin), BVtoTemp(BVmax)]);
var absMagMin = -11, absMagMax = 16;
var lumScale = d3.scale.log()
.range([0, height])
.domain([ magToLum(absMagMin), magToLum(absMagMax) ]);
//Define the axes
var xAxisBottom = d3.svg.axis()
.scale(tempScale)
.orient("bottom")
.tickFormat(d3.format(".0f"))
.outerTickSize(0)
.ticks(3);
//Add the X bottom Axis
axisGroup.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxisBottom);
//Append x-axis bottom title
axisGroup.append("text")
.attr("class", "axisTitle")
.attr("x", width/2)
.attr("y", height + 48)
.text("Temperature");
axisGroup.append("text")
.attr("class", "axisSubtitle")
.attr("x", width/2)
.attr("y", height + 65)
.text("in Kelvin");
//Define the Y axis
var yAxis = d3.svg.axis()
.scale(lumScale)
.orient("left")
.outerTickSize(0);
function powerOfTen(d) {
return d / Math.pow(10, Math.ceil(Math.log(d) / Math.LN10 - 1e-12)) === 1;
}
//Add the Y Axis with very specific tick values
var axisTicks = [0.0001, 0.001, 0.01, 0.1, 1, 10, 100, 1000, 10000, 100000, 1000000],
axisTicksText = [0.0001, 0.001, 0.01, 0.1, 1, 10, 100, "1,000", "10,000", "100,000", "1,000,000"];
axisGroup.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll(".tick text")
.text(null)
.filter(powerOfTen)
.text(function(d,i) {
var location = axisTicks.indexOf(d);
return location >= 0 ? axisTicksText[location] : "";
});
//Hide all the sub tick marks
d3.selectAll(".y .tick line")
.style("opacity", 0)
.filter(powerOfTen)
.style("opacity", function(d,i) {
for (var j = 0; j < axisTicks.length; j++) {
if (axisTicks[j] == d) return 1;
}//for j
return 0;
});
//Append y-axis title
axisGroup.append("text")
.attr("class", "axisTitle")
.attr("x", -110)
.attr("y", height/2)
.text("Luminosity");
axisGroup.append("text")
.attr("class", "axisSubtitle")
.attr("x", -110)
.attr("y", height/2+17)
.text("compared to the Sun");
///////////////////////////////////////////////////////////////////////////
//////////////////////////// Create gradients /////////////////////////////
///////////////////////////////////////////////////////////////////////////
var defs = svg.append("defs");
//Filter for the outside glow of the stars
var filter = defs.append('filter').attr('id','glow'),
feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation','2').attr('result','coloredBlur'),
feMerge = filter.append('feMerge');
feMerge.append('feMergeNode').attr('in','coloredBlur');
feMerge.append('feMergeNode').attr('in','SourceGraphic');
//Calculate the variables for the temperature gradient
var numStops = 10;
tempRange = tempScale.domain(); //d3.extent(stars, function(d) { return d.BV; });
tempRange[2] = tempRange[0] - tempRange[1];
tempPoint = [];
for(var i = 0; i < numStops; i++) {
tempPoint.push(i * tempRange[2]/(numStops-1) + tempRange[1]);
}
tempPoint = tempPoint.reverse();
//Create the gradient for the colored temperature bar at the bottom x axis
defs.append("linearGradient")
.attr("id", "gradientTemp")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%")
.selectAll("stop")
.data(d3.range(numStops))
.enter().append("stop")
.attr("offset", function(d,i) { return tempScale(tempPoint[i])/width; })
.attr("stop-color", function(d,i) { return colorScale( tempPoint[i] ); });
//Create data based gradients for each star - based on 3d sphere
var gradientOffset = defs.selectAll(".gradientOffset")
.data(stars).enter()
.append("radialGradient")
.attr("class", "gradientOffset")
.attr("cx", "25%")
.attr("cy", "25%")
.attr("r", "65%")
.attr("id", function(d,i){ return "gradOffset-"+i; })
gradientOffset.append("stop")
.attr("offset", "0%")
.attr("stop-color", function(d) { return d3.rgb(colorScale(d.temp)).brighter(1); });
gradientOffset.append("stop")
.attr("offset", "40%")
.attr("stop-color", function(d) { return colorScale(d.temp); });
gradientOffset.append("stop")
.attr("offset", "100%")
.attr("stop-color", function(d) { return d3.rgb(colorScale(d.temp)).darker(1.5); });
//Create data based gradients for each star - based on center glow
var gradientCenter = defs.selectAll(".gradientCenter")
.data(stars).enter()
.append("radialGradient")
.attr("class", "gradientCenter")
.attr('id', function(d,i){ return "gradCenter-"+i; })
gradientCenter.append("stop")
.attr("offset", "0%")
.attr("stop-color", function(d) { return d3.rgb(colorScale(d.temp)).brighter(1.75); });
gradientCenter.append("stop")
.attr("offset", "60%")
.attr("stop-color", function(d) { return d3.rgb(colorScale(d.temp)).brighter(0.7); });
gradientCenter.append("stop")
.attr("offset", "90%")
.attr("stop-color", function(d) { return colorScale(d.temp); });
gradientCenter.append("stop")
.attr("offset", "110%")
.attr("stop-color", function(d) { return d3.rgb(colorScale(d.temp)).darker(0.5); });
///////////////////////////////////////////////////////////////////////////
////////////////////////////// Plot the stars /////////////////////////////
///////////////////////////////////////////////////////////////////////////
//Append temperature colored rect
axisGroup.append("rect")
.attr("width", width+1)
.attr("height", 5)
.attr("x", -1)
.attr("y", height - 4)
.style("fill", "url(#gradientTemp)");
//Wrapper for the stars
var starContainer = svg.append("g").attr("class","starContainer");
//Draw the stars
var stars = starContainer.selectAll(".star")
.data(stars).enter()
.append("circle")
.attr("class", "star")
.attr("r", function(d) {return rScale(d.radiusSun); })
.attr("cx", function(d) { return tempScale(d.temp); })
.attr("cy", function(d) {return lumScale(d.lum); })
.style("opacity", 0.9)
.style("fill", function(d,i){return "url(#gradOffset-" + i + ")"; });
//Place marker for the Sun
starContainer.append("circle")
.attr("class", "sunIndicator")
.attr("r", 15*width/600)
.attr("cx", function(d) { return tempScale(tempSun); })
.attr("cy", function(d) { return lumScale(1); })
.style("fill", "none")
.style("stroke", "#fa4f06")
.style("stroke-width", 3*width/600);
looping();
///////////////////////////////////////////////////////////////////////////
//////////////////////////// Create Titles ////////////////////////////////
///////////////////////////////////////////////////////////////////////////
var textWrapper = svg.append("g")
.attr("class", "textWrapper")
.attr("transform", "translate(" + (-margin.left + 15) + ",0)");
//Append title to the top
textWrapper.append("text")
.attr("class", "title")
.attr("y", -50)
.text("Our nearest Stars");
textWrapper.append("text")
.attr("class", "subtitle")
.attr("y", -35)
.text("In a Hertzsprung-Russell diagram");
//Append credit at bottom
textWrapper.append("text")
.attr("class", "credit")
.attr("x", 0)
.attr("y", height + 80)
.text("Data sampled from HYG database");
//Explanation
svg.append("text")
.attr("class", "explanation")
.attr("x", tempScale(15000))
.attr("y", lumScale(0.1))
.text("Sphere like gradient");
///////////////////////////////////////////////////////////////////////////
//////////////////////////// Loop though phases ///////////////////////////
///////////////////////////////////////////////////////////////////////////
setInterval(looping, 14000);
//Loop through different phases
function looping() {
setTimeout(trueSize, 5000);
setTimeout(glow, 9000);
setTimeout(sphere, 14000);
}//looping
function trueSize() {
//Change the radius to actual sizes
d3.selectAll(".star")
.transition().duration(1000)
.attr("r", function(d) {return rScaleLinear(d.radiusSun); });
//Adjust explanation
d3.select(".explanation").text("True relative sizes");
}//trueSize
function glow() {
//Give the stars a glow like radial gradient
d3.selectAll(".star")
.transition().duration(1000)
.attr("r", 0)
.call(endall, function() {
d3.selectAll(".star")
.style("filter", "url(#glow)")
.style("fill", function(d,i){return "url(#gradCenter-" + i + ")";})
.transition().duration(1000)
.attr("r", function(d) { return rScale(d.radiusSun); });
});
//Adjust explanation
d3.select(".explanation")
.transition().duration(0).delay(1000)
.text("Glow like gradient");
}//glow
function sphere() {
//Give the stars a 3D sphere like radial gradient
d3.selectAll(".star")
.transition().duration(1000)
.attr("r", 0)
.call(endall, function() {
d3.selectAll(".star")
.style("filter", "none")
.style("fill", function(d,i){return "url(#gradOffset-" + i + ")";})
.transition().duration(1000)
.attr("r", function(d) { return rScale(d.radiusSun); });
});
//Adjust explanation
d3.select(".explanation")
.transition().duration(0).delay(1000)
.text("Sphere like gradient");
}//sphere
///////////////////////////////////////////////////////////////////////////
//////////////////////////// Helper functions /////////////////////////////
///////////////////////////////////////////////////////////////////////////
//Function to only run once after the last transition ends
function endall(transition, callback) {
var n = 0;
transition
.each(function() { ++n; })
.each("end", function() { if (!--n) callback.apply(this, arguments); });
}//endall
</script>
</body>
</html>