Built with blockbuilder.org
xxxxxxxxxx
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://dimplejs.org/dist/dimple.v2.3.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/eligrey/canvas-toblob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toblob.js"></script>
<script src="https://cdn.jsdelivr.net/gh/eligrey/filesaver.js/e9d941381475b5df8b7d7691013401e171014e89/filesaver.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-jetpack@1.0.2/d3-jetpack.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,600,700" rel="stylesheet" type="text/css">
</head>
<body>
<div id="barContainer" style = 'width: 100%; height: 1030px' ></div>
<center><button id='saveButton'>Download this visualization!</button></center>
<script type="text/javascript">
var selectedBar = -1;
var tableTransition = 600;
initTable();
var barSvg = dimple.newSvg("#barContainer", "100%", "100%");
barSvg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "#f6f6f6")
.on('click', function() {chartClick();});
drawOscar(barSvg);
drawDataFaceLogo(barSvg);
renderTitle(barSvg, "How Old is the Average Oscar Nominee?");
renderSubtitle(barSvg, "We collected data on every Oscar nominee in a major acting category since 1928. Click on a bar for more details.");
addSource(barSvg, "Source: ages from Wikipedia, photos from IMDB");
// Set-up the export button
d3.select('#saveButton').on('click', function(){
var svgString = getSVGString(barSvg.node());
svgString2Image(svgString, 802, 1030, 'jpeg', save); // passes Blob and filesize String to the callback
function save(dataBlob,filesize){
saveAs(dataBlob,'Age-of-Oscar-Nominees.jpeg'); // FileSaver.js function
}
});
var charts = [];
d3.csv("Academy Awards - Acting Awards - Full Data Set1.csv", function (data) {
// Get a unique list of categories
var categories = dimple.getUniqueValues(data, "Category");
// Set the bounds for the charts
var row = 0,
top = 80,
left = '7.5%',
inMarg = 25,
width = '88%',
height = 200,
totalWidth = parseFloat(barSvg.attr("width"));
// Draw a chart for each of the 4 categories
categories.forEach(function (category) {
// Filter for the month in the iteration
var chartData = dimple.filterData(data, "Category", category);
var nestedData = d3.nest()
.key(function(d) { return d.Age; }).sortKeys(sortNumber)
.entries(chartData);
nestedData.forEach(function (c) {
c.count = c.values.length;
});
var condensedData = condense(nestedData);
renderCategoryName(barSvg, chartData[0].Category)
// Create a chart at the correct point in the trellis
var myChart = new dimple.chart(barSvg, condensedData);
myChart.setBounds(
left,
top + (row * (height + inMarg)),
width,
height);
var defaultColorMen = new dimple.color("#89cff0", "#89cff0", 1);
var defaultColorWomen = new dimple.color("#f4c2c2", "#f4c2c2", 1);
var indicatorColorMen = new dimple.color("#569cbd", "#569cbd", 1);
var indicatorColorWomen = new dimple.color("#e88282", "#e88282", 1);
myChart.defaultColors = [defaultColorMen]
if (category === 'Best Actress'
|| category === 'Best Supporting Actress') {
myChart.defaultColors = [defaultColorWomen]
}
// Continue to set up a standard chart
var x = myChart.addCategoryAxis("x", "Age");
var y = myChart.addMeasureAxis("y", "Count");
x.addOrderRule("Age");
x.fontSize = "auto";
x.fontFamily = "Roboto";
y.fontSize = "auto";
y.fontFamily = "Roboto";
y.title = "Total Nominees";
y.overrideMax = 30;
y.showGridlines = false;
var series = myChart.addSeries(null, dimple.plot.bar);
(function(hoverFunction) {
series.addEventHandler("mouseover", hoverFunction);
})(onMouseOver);
(function(hoverFunction) {
series.addEventHandler("mouseout", hoverFunction);
})(onMouseOut);
(function(clickFunction) {
series.addEventHandler("click", clickFunction);
})(onClick);
// Draw the chart
myChart.ease = "cubic";
myChart.staggerDraw = true;
myChart.draw(1000);
capitalizeAxisTitles(myChart);
x.shapes.selectAll("line").remove();
cleanAxis(x, 5);
//draw average lines
if (category === 'Best Actor') {
drawVerticalLine(barSvg, '46.8%');
renderAverageText(barSvg, 'Average: 43.3 years', '47.5%', 190, 'start');
} else if (category === 'Best Actress') {
drawVerticalLine(barSvg, '40.5%');
renderAverageText(barSvg, 'Average: 37.1 years', '40%', 190, 'end');
} else if (category === 'Best Supporting Actor') {
drawVerticalLine(barSvg, '50.1%');
renderAverageText(barSvg, 'Average: 46.6 years', '49.6%', 190, 'end');
} else {
drawVerticalLine(barSvg, '43.8%');
renderAverageText(barSvg, 'Average: 40.5 years', '43.3%', 190, 'end');
}
// Move to the next row
row += 1;
charts.push(myChart);
function onMouseOver(e) {
var width_pix = barSvg.style("width");
var width = parseFloat(width_pix.substring(0, width_pix.length-2));
myChart.series[0].shapes
.style("fill", function (d) {
if (category === 'Best Actress'
|| category === 'Best Supporting Actress') {
return (d.x === e.xValue
? indicatorColorWomen.fill : defaultColorWomen.fill)
} else {
return (d.x === e.xValue
? indicatorColorMen.fill : defaultColorMen.fill)
}
})
barSvg.selectAll(".dimple-hover-text-"
+ category.replace(/\s+/g, '-').toLowerCase())
.data(["Age: " + e.xValue, "Total Nominees: " + e.yValue])
.enter()
.append("text")
.attr("class", "dimple-hover-text-"
+ category.replace(/\s+/g, '-').toLowerCase())
.attr("x", '93%')
.attr("y", function (d, i) {
return myChart._yPixels() + 75 + i * 25; })
.style("font-family", "sans-serif")
.style("text-anchor", "end")
.style("font-size", function() {
if (width > 530) {
return "20px";
} else {
return "16px";
}
})
.style("opacity", 0.6)
.text(function (d) { return d; });
// Put a coloured bar next to the text for no good reason
barSvg.append("rect")
.attr("class", "dimple-hover-rect-"
+ category.replace(/\s+/g, '-').toLowerCase())
.attr("x", '94%')
.attr("y", myChart._yPixels()+50)
.attr("height", 60)
.attr("width", 10)
.style("fill", function () {
if (category === 'Best Actress'
|| category === 'Best Supporting Actress') {
return indicatorColorWomen.fill;
} else {
return indicatorColorMen.fill;
}
})
.style("opacity", 1);
}
function onMouseOut(e) {
myChart.series[0].shapes
.style("fill", function (d) {
if (category === 'Best Actress'
|| category === 'Best Supporting Actress') {
return defaultColorWomen.fill;
} else {
return defaultColorMen.fill;
}
})
d3.selectAll(".dimple-hover-text-"
+ category.replace(/\s+/g, '-').toLowerCase()).remove();
d3.selectAll(".dimple-hover-rect-"
+ category.replace(/\s+/g, '-').toLowerCase()).remove();
}
// On click of the toggles
function onClick(e) {
if (selectedBar === -1) {
selectedBar = e;
drawTable(e, nestedData);
showTable(0);
}
else if (selectedBar.xValue + selectedBar.category
=== e.xValue + e.category) {
hideTable();
selectedBar = -1;
} else {
selectedBar = e;
hideTable(e, nestedData);
showTable(tableTransition);
}
}
}, this);
function renderCategoryName(svg, category_name) {
var width_pix = svg.style("width");
var width = parseFloat(width_pix.substring(0, width_pix.length-2));
// Use d3 to draw a text label for the category
svg.append("text")
.attr("x", '95%')
.attr("y", top + 40 + (row * (height + inMarg)))
.style("font-family", "sans-serif")
.style("text-anchor", "end")
.style("font-size", function() {
if (width > 530) {
return "28px";
} else {
return "20px";
}
})
.style("opacity", 0.4)
.text(category_name);
}
function renderAverageText(svg, text, x_offset, y_offset, position) {
var width_pix = svg.style("width");
var width = parseFloat(width_pix.substring(0, width_pix.length-2));
// Use d3 to draw a text label for the category
svg.append("text")
.attr("x", x_offset)
.attr("y", top + y_offset + (row * (height + inMarg)))
.style("font-family", "sans-serif")
.style("text-anchor", position)
.style("font-size", function() {
if (width > 530) {
return "16px";
} else {
return "12px";
}
})
.style("opacity", 0.8)
.transition()
.delay(2000)
.text(text);
}
function drawVerticalLine(svg, x) {
svg.append("line")
.attr("x1", x)
.attr("y1", top + height + (row * height) + (row * inMarg))
.attr("x2", x)
.attr("y2", top + height + (row * height) + (row * inMarg))
.style('stroke', 'black')
.style('stroke-width', 1.5)
.style('opacity', 0.3)
.style("stroke-dasharray","5,5")//dashed array for line
.transition()
.delay(1500)
.duration(1500)
.attr("x2", x)
.attr("y2", top + height/10 + (row * height) + (row * inMarg))
}
});
function sortNumber(a,b) {
return +a - +b;
}
//function to take nested data and summarize it
function condense(nestedData) {
var lookup = {};
for (i=0; i < nestedData.length; i++) {
lookup[nestedData[i].key] = nestedData[i].count;
}
var condensedData = [];
for (i=5; i<=90; i++) {
condensedData.push({'Age':i.toString(),'Count':lookup[i.toString()]});
}
return condensedData;
}
function cleanAxis(axis, oneInEvery) {
// This should have been called after draw, otherwise do nothing
var del = 0;
// If there is an interval set
if (oneInEvery > 1) {
// Operate on all the axis text
axis.shapes.selectAll("text").each(function (d) {
d3.select(this)
.style("text-anchor", "middle")
.attr("opacity", 1)
.attr("dy", "0.5em")
.attr("transform", "rotate(0)" );
// Remove all but the nth label
if (del % oneInEvery !== 0) {
d3.select(this).attr("opacity", 0);
}
del += 1;
});
}
}
function capitalizeAxisTitles(chart) {
for (i = 0; i< chart.axes.length; i++) {
chart.axes[i].titleShape
.style("font-weight", "bold")
.style("font-size", "14px");
}
}
function drawTable(e, data) {
d3.select('#tablesvg').select('table').remove();
d3.select('#tablesvg').selectAll('.side-panel-text').remove();
var filtered_data;
for (i=0; i<data.length; i++) {
if (data[i].key === e.xValue) {
filtered_data = data[i].values;
break;
}
}
console.log(typeof(filtered_data[0].Category + ' Nominees'));
d3.select("#tablesvg")
.append("text")
.style("font-family", "sans-serif")
.style('font-weight', 500)
.style("font-size", "20px")
.style('color', 'white')
.text(filtered_data[0].Category + ' Nominees, Age ' + e.xValue + '. ')
.classed("side-panel-text", true)
d3.select("#tablesvg")
.append("text")
.style("font-family", "sans-serif")
.style('font-weight', 300)
.style("font-size", "15px")
.style('color', 'white')
.text('Winners are outlined in gold. Scroll down for more.')
.classed("side-panel-text", true)
var columns = [
{ head: 'Image', cl: 'actor-image', html: function(d) {
if (d['Winner?'] === 'X') {
return "<img src='"+d.Image+"'"
+"style='height:75px;border: 3px solid rgb(218, 165, 32);'</img>"
} else {
return "<img src='"+d.Image+"' style=height:75px;</img>"
}
}},
{ head: 'Name', cl: 'actor-name', html: ƒ('Nominee(s)')},
{ head: 'Movie', cl: 'movie-title', html: ƒ('Movie')},
{ head: 'Year', cl: 'movie-year', html: ƒ('Year')},
];
// create table
var table = d3.select("#tablesvg")
.append("table")
.style('height', '150px')
.style('display', 'block')
.style('margin-left', 'auto')
.style('margin-right', 'auto')
.style('overflow', 'scroll')
.style('font-family', 'Roboto, sans-serif')
.style('line-height', '100%')
.style('text-transform', 'none')
.style('letter-spacing', '0em');
table.append('tbody')
.selectAll('tr')
.data(filtered_data).enter()
.append('tr')
.each(function(d, i) {
d3.select(this).style('animation-delay', i*0.1+0.5 + 's');
d3.select(this).style('color', 'white');
})
.selectAll('td')
.data(function(row, i) {
var map = columns.map(function(c) {
// compute cell values for this specific row
var cell = {};
d3.keys(c).forEach(function(k) {
cell[k] = typeof c[k] == 'function' ? c[k](row,i) : c[k];
});
return cell;
});
return map;
}).enter()
.append('td')
.html(ƒ('html'))
.attr('class', ƒ('cl'))
}
function drawOscar(svg) {
var width_pix = svg.style("width");
var width = parseFloat(width_pix.substring(0, width_pix.length-2));
if (width > 530) {
// add Oscar logo in upper left
svg.append("svg:image")
.attr("xlink:href",
"https://thedataface.com/wp-content/uploads/2017/02/oscars.png")
.attr("x", '4%')
.attr("y", '1%')
.attr("height", width/20)
.classed("oscar-image",true);
}
}
function drawDataFaceLogo(svg) {
var width_pix = svg.style("width");
var width = parseFloat(width_pix.substring(0, width_pix.length-2));
if (width > 530) {
// add DataFace logo to upper right
svg.append("svg:image")
.attr("xlink:href",
"https://thedataface.com/wp-content/uploads/2015/10/Data-Face_logo-300x90.png")
.attr("x", '80%')
.attr("y", '1%')
.attr("width", width/6)
.classed("dataface-image",true);
}
}
//render chart titles
function renderTitle(svg, title) {
var width_pix = svg.style("width");
var width = parseFloat(width_pix.substring(0, width_pix.length-2));
svg.append("text")
.attr("x", width/2)
.attr("y", 28)
.attr("dy", 0)
.style("text-anchor", "middle")
.style("font-family", "Roboto")
.style("font-weight", "bold")
.text(title)
.classed("chart-titles", true)
.call(wrap, width*0.9);
}
//render chart subtitle
function renderSubtitle(svg, subtitle) {
var width_pix = svg.style("width");
var width = parseFloat(width_pix.substring(0, width_pix.length-2));
if (width > 338) {
var overflow_val;
if (width > 530) {
overflow_val = width*0.56;
} else {
overflow_val = width*0.9;
}
svg.append("text")
.attr("x", width/2)
.attr("y", 48)
.attr("dy", 0)
.style("text-anchor", "middle")
.style("font-family", "Roboto")
.style("font-size", "15px")
.style("opacity", 0.4)
.text(subtitle)
.classed("chart-titles", true)
.call(wrap, overflow_val);
}
}
function addSource(svg, title) {
var width_pix = svg.style("width");
var width = parseFloat(width_pix.substring(0, width_pix.length-2));
svg.append("text")
.attr("x", "2%")
.attr("y", "1020px")
.style("text-anchor", "start")
.style("font-family", "Roboto")
.style("font-size", "14px")
.style("opacity", 0.4)
.text(title)
.classed("data-source", true)
}
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy",
++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
// Below are the function that handle actual exporting:
// getSVGString (svgNode ) and svgString2Image( svgString, width, height,
// format, callback )
function getSVGString( svgNode ) {
svgNode.setAttribute('xlink','https://www.w3.org/1999/xlink');
var cssStyleText = getCSSStyles( svgNode );
appendCSS( cssStyleText, svgNode )
var serializer = new XMLSerializer();
var svgString = serializer.serializeToString(svgNode);
svgString = svgString.replace(
/(\w+)?:?xlink=/g, 'xmlns:xlink=') // Fix root xlink without namespace
svgString = svgString.replace(
/NS\d+:href/g, 'xlink:href') // Safari NS namespace fix
return svgString;
function getCSSStyles( parentElement ) {
var selectorTextArr = [];
// Add Parent element Id and Classes to the list
selectorTextArr.push( '#'+parentElement.id );
for (var c = 0; c < parentElement.classList.length; c++)
if ( !contains('.'+parentElement.classList[c], selectorTextArr) )
selectorTextArr.push( '.'+parentElement.classList[c] );
// Add Children element Ids and Classes to the list
var nodes = parentElement.getElementsByTagName("*");
for (var i = 0; i < nodes.length; i++) {
var id = nodes[i].id;
if ( !contains('#'+id, selectorTextArr) ) {
selectorTextArr.push( '#'+id );
}
var classes = nodes[i].classList;
for (var c = 0; c < classes.length; c++) {
if ( !contains('.'+classes[c], selectorTextArr) ) {
selectorTextArr.push( '.'+classes[c] );
}
}
}
// Extract CSS Rules
var extractedCSSText = "";
for (var i = 0; i < document.styleSheets.length; i++) {
var s = document.styleSheets[i];
try {
if(!s.cssRules) continue;
} catch( e ) {
if(e.name !== 'SecurityError') throw e; // for Firefox
continue;
}
var cssRules = s.cssRules;
for (var r = 0; r < cssRules.length; r++) {
if ( contains( cssRules[r].selectorText, selectorTextArr ) )
extractedCSSText += cssRules[r].cssText;
}
}
return extractedCSSText
function contains(str,arr) {
return arr.indexOf( str ) === -1 ? false : true;
}
}
function appendCSS( cssText, element ) {
var styleElement = document.createElement("style");
styleElement.setAttribute("type","text/css");
styleElement.innerHTML = cssText;
var refNode = element.hasChildNodes() ? element.children[0] : null;
element.insertBefore( styleElement, refNode );
}
}
function svgString2Image( svgString, width, height, format, callback ) {
var format = format ? format : 'png';
var imgsrc = 'data:image/svg+xml;base64,'
+ btoa( unescape( encodeURIComponent( svgString ) ) );
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
var image = new Image;
image.onload = function() {
context.clearRect ( 0, 0, width, height );
context.drawImage(image, 0, 0, width, height);
canvas.toBlob( function(blob) {
var filesize = Math.round( blob.length/1024 ) + ' KB';
if ( callback ) callback( blob, filesize );
});
};
image.src = imgsrc;
}
// Add a method to draw the chart on resize of the window
window.onresize = function () {
// clear chart area
d3.selectAll('.chart-titles').remove();
d3.selectAll(".oscar-image").remove();
d3.selectAll(".dataface-image").remove();
//redraw bar charts
charts[0].draw(0, true);
capitalizeAxisTitles(charts[0]);
cleanAxis(charts[0].axes[0], 5);
charts[1].draw(0, true);
capitalizeAxisTitles(charts[1]);
cleanAxis(charts[1].axes[0], 5);
charts[2].draw(0, true);
capitalizeAxisTitles(charts[2]);
cleanAxis(charts[2].axes[0], 5);
charts[3].draw(0, true);
capitalizeAxisTitles(charts[3]);
cleanAxis(charts[3].axes[0], 5);
renderTitle(barSvg, "How Old is the Average Oscar Nominee?");
renderSubtitle(barSvg, "We collected data on every Oscar nominee in a major acting category since 1928. Click on a bar for more details.");
drawOscar(barSvg);
drawDataFaceLogo(barSvg);
};
function hideTable() {
d3.select('#tablesvg')
.transition(tableTransition)
.style('bottom', '-180px');
}
function hideTable(e, nestedData) {
d3.select('#tablesvg')
.transition(tableTransition)
.style('bottom', '-180px')
.on('end', function() {drawTable(e, nestedData);});
}
function showTable(delay) {
d3.select('#tablesvg')
.transition(tableTransition)
.delay(delay)
.style('bottom', '0px')
}
function initTable() {
d3.select('body')
.append('div')
.attr('id', 'tablesvg')
.style('width', '100%')
.style('height','180px')
.style('position', 'fixed')
.style('background', 'rgba(0, 0, 0, 0.9)')
.style('left', '0px')
.style('bottom', '-180px');
}
function chartClick() {
if (selectedBar !== -1) {
selectedBar = -1;
hideTable();
}
}
</script>
</body>
</html>
Modified http://d3js.org/d3.v4.min.js to a secure url
Modified http://dimplejs.org/dist/dimple.v2.3.0.min.js to a secure url
Updated missing url https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js to https://cdn.jsdelivr.net/gh/eligrey/canvas-toblob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toblob.js
Updated missing url https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js to https://cdn.jsdelivr.net/gh/eligrey/filesaver.js/e9d941381475b5df8b7d7691013401e171014e89/filesaver.min.js
Modified http://thedataface.com/wp-content/uploads/2017/03/d3-jetpack.js to a secure url
https://d3js.org/d3.v4.min.js
https://dimplejs.org/dist/dimple.v2.3.0.min.js
https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js
https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js
https://thedataface.com/wp-content/uploads/2017/03/d3-jetpack.js