A Pen by David O'Brien

Thumbnail
This awesome code was written by iamdavidobrien, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright iamdavidobrien ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>A Pen by  David O'Brien</title>
  <link href='https://fonts.googleapis.com/css?family=Cabin:500' rel='stylesheet' type='text/css'>

<meta name="viewport" content="width=device-width, initial-scale=1">
  
  <link rel='stylesheet prefetch' href='https://meyerweb.com/eric/tools/css/reset/reset.css'>

      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  
		<h1>Nightingale's Rose</h1>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

		<script type="text/javascript">

		</script>
  
  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/iamdavidobrien/a-pen-by-david-oandaposbrien-PzvJpr */
/* reset.css now called from settings */
/* Apply a natural box layout model to all elements */
* { 
	-moz-box-sizing: border-box;
	-webkit-box-sizing: border-box;
	box-sizing: border-box; 
}

body {
	font-family: 'Cabin', sans-serif;
	font-size: 14px;
	font-weight: normal;
	color: #474747;
	width: 100%;
	min-height: 100%;
}

figure {
	position: relative;
	float: right;
	width: 50%;
	min-width: 400px;
	margin-top: -120px;
}

h1 {
	width: 70%;
	margin: 0 auto;
	margin-top: 20px;
	margin-bottom: 20px;
	text-align: center;
	font-size: 24px;
}

.title, .subtitle, .caption, .label, .legend, figure {
	font-family: baskerville, serif;
}

.title, .subtitle, .label, .legend {
	text-transform: uppercase;
}

.title {
	font-size: 18px;
	width: 400px;
	text-align: center;
	display: block;
	margin: 0 auto;
	margin-top: 50px;
}

	.title .small {
		font-size: 12px;
	}

.subtitle {
	font-size: 16px;
	width: 50%;
	min-width: 400px;
	margin-top: 16px;
	text-align: center;
}

	.subtitle.left {
		float: left;
	}

	.subtitle.right {
		float: right;
	}


.caption {
	position: relative;
	top: -170px;
	width: 320px;
	display: block;
	margin: 0 auto;
	font-style: oblique;
	font-size: 12px;
}

	.caption p {
		margin: 0;
		text-indent: 12px;
	}

.canvas {
	font-family: 'Cabin', sans-serif;
 	font-size: 14px;
 	fill: #474747;
 	font-weight: normal;
}

.wedge {
	stroke: #aaa;
	stroke-width: 1px;
	/*shape-rendering: crispEdges;*/
}

.label {
	font-size: 8px;
}

.disease {
	fill: rgb(198, 226, 255);
}

.other {
	fill: rgb(50,50,50);
}

.wounds {
	fill: rgb(252,112,147);
}

.legend {
	width: 400px;
	height: 50px;
	padding: 0 20px;
	display: block;
	position: relative;
	margin: 0 auto;
	top: -150px;
	text-align: center;
}

.slider {
	width: 400px;
	display: block;
	position: relative;
	margin: 0 auto;
	left: -30px; /* hacky */
	top: -100px;
}

/*Downloaded from https://www.codeseek.co/iamdavidobrien/a-pen-by-david-oandaposbrien-PzvJpr */
/**
*
*
*
*	NOTES:
*		[1] Slider could be improved. No displayed limits. Values are hidden. Etc.
*		[2] This chart's API is restricted and makes assumptions. E.g., legend entries provided to 'rose' and 'legend' are the same.
*
*
*
*	@author Kristofer Gryte. http://www.kgryte.com
*
*
*/




var Chart = {};

Chart.rose = function() {

	var margin = {'top': 20, 'right': 20, 'bottom': 20, 'left': 20},
		height = 500,
		width = 500,
		color = 'rgb(0,0,0)',
		area = function(d) { return [d.y]; },
		angle = function(d) { return d.x; },
		radiusScale = d3.scale.linear(),
		angleScale = d3.scale.linear().range( [Math.PI, 3*Math.PI ] ),
		domain = [0, 1],
		legend = [''],
		label = function(d) { return d.label; },
		delay = 1000,
		duration = 100,
		canvas, graph, centerX, centerY, numWedges, wedgeGroups, wedges, legendGroup;

	// Arc Generator:
	var arc = d3.svg.arc()
		.innerRadius( 0 )
		.outerRadius( function(d,i) { return radiusScale( d.radius ); } )
		.startAngle( function(d,i) { return angleScale( d.angle ); } );

	function chart( selection ) {

		selection.each( function( data ) {

			// Determine the number of wedges:
			numWedges = data.length;

			// Standardize the data:
			data = formatData( data );

			// Update the chart parameters:
			updateParams();

			// Create the chart base:
			createBase( this );

			// Create the wedges:
			createWedges( data );

		});

	}; // end FUNCTION chart()

	//
	function formatData( data ) {
		// Convert data to standard representation; needed for non-deterministic accessors:
		data = data.map( function(d, i) {
			return {
				'angle': angle.call(data, d, i),
				'area': area.call(data, d, i),
				'label': label.call(data, d, i)			
			};
		});

		// Now convert the area values to radii:
		// http://understandinguncertainty.org/node/214 
		return data.map( function(d, i) {
			return {
				'angle': d.angle,
				'label': d.label,
				'radius': d.area.map( function(area) {
					return Math.sqrt( area*numWedges / Math.PI );
				})
			}
		})
	}; // end FUNCTION formatData()

	//
	function updateParams() {
		// Update the arc generator:
		arc.endAngle( function(d,i) { return angleScale( d.angle ) + (Math.PI / (numWedges/2)); } );

		// Determine the chart center:
		centerX = (width - margin.left - margin.right) / 2;
		centerY = (height - margin.top - margin.bottom) / 2;

		// Update the radius scale:
		radiusScale.domain( domain )
			.range( [0, d3.min( [centerX, centerY] ) ] );

		// Update the angle scale:
		angleScale.domain( [0, numWedges] );		
	}; // end FUNCTION updateParams()

	// 
	function createBase( selection ) {

		// Create the SVG element:
		canvas = d3.select( selection ).append('svg:svg')
			.attr('width', width)
			.attr('height', height)
			.attr('class', 'canvas');

		// Create the graph element:
		graph = canvas.append('svg:g')
			.attr('class', 'graph')
			.attr('transform', 'translate(' + (centerX + margin.left) + ',' + (centerY + margin.top) + ')');

	}; // end FUNCTION createBase()


	function createWedges( data ) {

		// Create the wedge groups:
		wedgeGroups = graph.selectAll('.wedgeGroup')
			.data( data )
		  .enter().append('svg:g')
		  	.attr('class', 'wedgeGroup')
		  	.attr('transform', 'scale(0,0)');

		// Create the wedges:
		wedges = wedgeGroups.selectAll('.wedge')
		  	.data( function(d) { 
		  		var ids = d3.range(0, legend.length);

		  		ids.sort( function(a,b) { 
			  		var val2 = d.radius[b],
			  			val1 = d.radius[a]
			  		return  val2 - val1; 
			  	});
			  	return ids.map( function(i) {
			  		return {
			  			'legend': legend[i],
			  			'radius': d.radius[i],
			  			'angle': d.angle
			  		};
			  	});
		  	})
		  .enter().append('svg:path')
		  	.attr('class', function(d) { return 'wedge ' + d.legend; })
		  	.attr('d', arc );

		// Append title tooltips:
		wedges.append('svg:title')
			.text( function(d) { return d.legend + ': ' + Math.floor(Math.pow(d.radius,2) * Math.PI / numWedges); });

		// Transition the wedges to view:
		wedgeGroups.transition()
			.delay( delay )
			.duration( function(d,i) { 
				return duration*i;
			})
			.attr('transform', 'scale(1,1)');

		// Append labels to the wedgeGroups:
		var numLabels = d3.selectAll('.label-path')[0].length;
		
		wedgeGroups.selectAll('.label-path')
			.data( function(d,i) { 
				return [
					{
						'index': i,
						'angle': d.angle,
						'radius': d3.max( d.radius.concat( [23] ) )
					}
				];
			} )
		  .enter().append('svg:path')
		  	.attr('class', 'label-path')
		  	.attr('id', function(d) {
		  		return 'label-path' + (d.index + numLabels);
		  	})
			.attr('d', arc)
		  	.attr('fill', 'none')
		  	.attr('stroke', 'none');

		wedgeGroups.selectAll('.label')
			.data( function(d,i) { 
				return [
					{
						'index': i,
						'label': d.label
					}
				];
			} )
		  .enter().append('svg:text')
	   		.attr('class', 'label')
	   		.attr('text-anchor', 'start')
	   		.attr('x', 5)
	   		.attr('dy', '-.71em')
	   		.attr('text-align', 'center')
	  		.append('textPath')
	  			.attr('xlink:href', function(d,i) { 
	  				return '#label-path' + (d.index + numLabels);
	  			})
	  			.text( function(d) { return d.label; } );

	}; // end FUNCTION createWedges()	

	// Set/Get: margin
	chart.margin = function( _ ) {
		if (!arguments.length) return margin;
		margin = _;
		return chart;
	};

	// Set/Get: width
	chart.width = function( _ ) {
		if (!arguments.length) return width;
		width = _;
		return chart;
	};

	// Set/Get: height
	chart.height = function( _ ) {
		if (!arguments.length) return height;
		height = _;
		return chart;
	};

	// Set/Get: area
	chart.area = function( _ ) {
		if (!arguments.length) return area;
		area = _;
		return chart;
	};

	// Set/Get: angle
	chart.angle = function( _ ) {
		if (!arguments.length) return angle;
		angle = _;
		return chart;
	};

	// Set/Get: label
	chart.label = function( _ ) {
		if (!arguments.length) return label;
		label = _;
		return chart;
	};

	// Set/Get: domain
	chart.domain = function( _ ) {
		if (!arguments.length) return domain;
		domain = _;
		return chart;
	};

	// Set/Get: legend
	chart.legend = function( _ ) {
		if (!arguments.length) return legend;
		legend = _;
		return chart;
	};

	// Set/Get: delay
	chart.delay = function( _ ) {
		if (!arguments.length) return delay;
		delay = _;
		return chart;
	};

	// Set/Get: duration
	chart.duration = function( _ ) {
		if (!arguments.length) return duration;
		duration = _;
		return chart;
	};

	return chart;

}; // end FUNCTION rose()





Chart.legend = function( entries ) {
	// NOTE: positioning handled by CSS.

	// Add a legend:
	var legend = {}, 
		height,
		symbolRadius = 5;

	legend.container = d3.select('body').append('div')
		.attr('class', 'legend');

	height = parseInt( d3.select('.legend').style('height'), 10);
	legend.canvas = legend.container.append('svg:svg')
			.attr('class', 'legend-canvas');

	legend.entries = legend.canvas.selectAll('.legend-entry')
		.data( entries )
	  .enter().append('svg:g')
	  	.attr('class', 'legend-entry')
	  	.attr('transform', function(d,i) { return 'translate('+ (symbolRadius + i*120) +', ' + (height/2) + ')'; });

	// Append circles to each entry with appropriate class:
	legend.entries.append('svg:circle')
		.attr('class', function(d) { return 'legend-symbol ' + d;} )
		.attr('r', symbolRadius )
		.attr('cy', 0 )
		.attr('cx', 0 );

	// Append text to each entry:
	legend.entries.append('svg:text')
		.attr('class', 'legend-text' )
		.attr('text-anchor', 'start')
		.attr('dy', '.35em')
		.attr('transform', 'translate(' + (symbolRadius*2) + ',0)')
		.text( function(d) { return d; } );

	// Add interactivity:
	legend.entries.on('mouseover.focus', mouseover)
		.on('mouseout.focus', mouseout);

	//
	function mouseover() {

		// Select the current element and get the symbol child class:
		var _class = d3.select( this ).select('.legend-symbol')
			.attr('class')
			.replace('legend-symbol ', ''); // left with legend class.

		d3.selectAll('.wedge')
			.filter( function(d,i) {
				// Select those elements not belonging to the same symbol class:
				return !d3.select( this ).classed( _class );
			})
			.transition()
				.duration( 1000 )
				.attr('opacity', 0.05 );

	}; // end FUNCTION mouseover()

	function mouseout() {

		d3.selectAll('.wedge')
			.transition()
				.duration( 500 )
				.attr('opacity', 1 );

	}; // end FUNCTION mouseout()

}; // end FUNCTION legend()


Chart.slider = function( minVal, maxVal, step ) {

	d3.select('body').append('input')
		.attr('class', 'slider')
		.attr('type', 'range')
		.attr('name', 'slider')
		.attr('min', minVal)
		.attr('max', maxVal)
		.attr('step', 0.001)
		.attr('value', maxVal);

	d3.select("input").on("change", function() {
	  var value = Math.round(this.value);

	  d3.selectAll('.wedgeGroup')
	  	.filter( function(d,i) { return i < value; } )
	  	.transition()
	  		.duration( 500 )
	  		.attr( 'transform', 'scale(1,1)');
	  
	  d3.selectAll('.wedgeGroup')
	  	.filter( function(d,i) { return i >= value; } )
	  	.transition()
	  		.duration( 500 )
	  		.attr( 'transform', 'scale(0,0)' );

	});


}; // end FUNCTION slider()

			var rose = Chart.rose(),
				height = 600,
				format = d3.time.format('%m/%Y'),
				causes = ['disease', 'wounds', 'other'],
				labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];

			// Add a title:
			d3.select('body').append('h2')
				.attr('class', 'title')
				.html( 'Diagram <span class="small">of the</span> Causes <span class="small">of</span> Mortality <span class="small">in the</span> Army <span class="small">of the</span> East');

			// Add sub-titles:
			d3.select('body').append('h3')
				.attr('class', 'subtitle left')
				.html('April 1855 - March 1856');

			d3.select('body').append('h3')
				.attr('class', 'subtitle right')
				.html('April 1854 - March 1855');

			// Load the JSON data:
      var theData = {"data": [
  {
  	"date": "4/1854",
  	"army_size": 8571, 
  	"disease": 1, 
  	"wounds": 0, 
  	"other": 5 
  },
  {
  	"date": "5/1854", 
  	"army_size": 23333, 
  	"disease": 12, 
  	"wounds": 0, 
  	"other": 9 
  },
  {
  	"date": "6/1854", 
  	"army_size": 28333, 
  	"disease": 11, 
  	"wounds": 0, 
  	"other": 6 
  },
  {
  	"date": "7/1854", 
  	"army_size": 28722, 
  	"disease": 359, 
  	"wounds": 0, 
  	"other": 23 
  },
  {
  	"date": "8/1854", 
  	"army_size": 30246, 
  	"disease": 828, 
  	"wounds": 1, 
  	"other": 30 
  },
  {
  	"date": "9/1854", 
  	"army_size": 30290, 
  	"disease": 788, 
  	"wounds": 81, 
  	"other": 70 
  },
  {
  	"date": "10/1854", 
  	"army_size": 30643, 
  	"disease": 503, 
  	"wounds": 132, 
  	"other": 128 
  },
  {
  	"date": "11/1854", 
  	"army_size": 29736, 
  	"disease": 844, 
  	"wounds": 287, 
  	"other": 106 
  },
  {
  	"date": "12/1854", 
  	"army_size": 32779, 
  	"disease": 1725, 
  	"wounds": 114, 
  	"other": 131 
  },
  {
  	"date": "1/1855", 
  	"army_size": 32393, 
  	"disease": 2761, 
  	"wounds": 83, 
  	"other": 324 
  },
  {
  	"date": "2/1855", 
  	"army_size": 30919, 
  	"disease": 2120, 
  	"wounds": 42, 
  	"other": 361 
  },
  {
  	"date": "3/1855", 
  	"army_size": 30107, 
  	"disease": 1205, 
  	"wounds": 32, 
  	"other": 172 
  },
  {
  	"date": "4/1855", 
  	"army_size": 32252, 
  	"disease": 477, 
  	"wounds": 48, 
  	"other": 57 
  },
  {
  	"date": "5/1855", 
  	"army_size": 35473, 
  	"disease": 508, 
  	"wounds": 49, 
  	"other": 37 
  },
  {
  	"date": "6/1855", 
  	"army_size": 38863, 
  	"disease": 802, 
  	"wounds": 209, 
  	"other": 31 
  },
  {
  	"date": "7/1855", 
  	"army_size": 42647, 
  	"disease": 382, 
  	"wounds": 134, 
  	"other": 33 
  },
  {
  	"date": "8/1855", 
  	"army_size": 44614, 
  	"disease": 483, 
  	"wounds": 164, 
  	"other": 25 
  },
  {
  	"date": "9/1855", 
  	"army_size": 47751, 
  	"disease": 189, 
  	"wounds": 276, 
  	"other": 20 
  },
  {
  	"date": "10/1855", 
  	"army_size": 46852, 
  	"disease": 128, 
  	"wounds": 53, 
  	"other": 18 
  },
  {
  	"date": "11/1855", 
  	"army_size": 37853, 
  	"disease": 178, 
  	"wounds": 33, 
  	"other": 32 
  },
  {
  	"date": "12/1855", 
  	"army_size": 43217, 
  	"disease": 91, 
  	"wounds": 18, 
  	"other": 28 
  },
  {
  	"date": "1/1856", 
  	"army_size": 44212, 
  	"disease": 42, 
  	"wounds": 2, 
  	"other": 48 
  },
  {
  	"date": "2/1856", 
  	"army_size": 43485, 
  	"disease": 24, 
  	"wounds": 0, 
  	"other": 19 
  },
  {
  	"date": "3/1856", 
  	"army_size": 46140, 
  	"disease": 15, 
  	"wounds": 0, 
  	"other": 35 
  }
]
};
      
			d3.json( theData, function( data ) {
				// Data from: http://ocp.hul.harvard.edu/dl/contagion/010164675

				// Format the date and rework the data:
				var scalar;
				data.forEach( function(d) { 
					d.date = format.parse(d.date);
					d.label = labels[d.date.getMonth()];
					
					// Calculate the average annual mortality, as done by Nightingale:
					// http://understandinguncertainty.org/node/214 
					scalar = 1000*12 / d.army_size;
					d.disease = d.disease * scalar;
					d.wounds  = d.wounds  * scalar;
					d.other   = d.other   * scalar;
				} );

				// Get the maximum value:
				var maxVal = d3.max( data, function(d) {
					return d3.max( [d.disease, d.wounds, d.other] );
				});

				// Where the maximum value gives us the maximum radius:
				var maxRadius = Math.sqrt(maxVal*12 / Math.PI);

				// Divide the dataset in two:
				var dataset2 = data.slice(12,24),
					dataset1 = data.slice(0,12);
				
				// Append a new figure to the DOM:
				figure = d3.select( 'body' )
					.append( 'figure' );

				// Get the figure width:
				width = parseInt( figure.style( 'width' ), 10 );

				// Update the chart generator settings:
				rose.legend( causes )
					.width( width )
					.height( height )
					.delay( 0 )
					.duration( 500 )
					.domain( [0, maxRadius] )
					.angle( function(d) { return d.date.getMonth(); } )
					.area( function(d, i) { return [d.disease, d.wounds, d.other]; } );							

				// Bind the data and generate a new chart:
				figure.datum( dataset1 )
					.attr('class', 'chart figure1')
					.call( rose );	

				// Append a new figure to the DOM:
				figure = d3.select( 'body' )
					.append( 'figure' );

				// Get the figure width:
				width = parseInt( figure.style( 'width' ), 10 );

				// Update the chart generator settings:
				rose.width( width )
					.delay( 3000 );

				// Bind the second dataset and generate a new chart:
				figure.datum( dataset2 )
					.attr('class', 'chart figure2')
					.call( rose );	

				// Append a caption:
				d3.select('.figure2').append('figcaption')
					.attr('class', 'caption')
					.html('The Areas of the blue, red, &amp; black wedges are each measured from the centre as the common vertex <p> The blue wedges measured from the centre of the circle represent area for area the deaths from Preventible or Mitigable Zymotic Diseases, the red wedges measured from the center the deaths from wounds, &amp; the black wedges measured from the center the deaths from all other causes </p><p> In October 1844, &amp; April 1855, the black area coincides with the red, in January &amp; February 1856, the blue coincides with the black </p><p> The entire areas may be compared by following the blue, the red &amp; the black lines enclosing them.</p>');

				// Create a legend:
				Chart.legend( causes );

				// Create a slider:
				Chart.slider( 0, data.length, 1 ); // minVal, maxVal, step
				
			});

Comments