block by nbremer eb0d1fd4118b731d069e2ff98dfadc47

Data based gradients - HR Diagram

Full Screen

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

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

index.html

<!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>