Skip to content

Instantly share code, notes, and snippets.

@espetro
Last active May 4, 2017 21:30
Show Gist options
  • Save espetro/a692ad5dc941ed5381e4f137083a08cd to your computer and use it in GitHub Desktop.
Save espetro/a692ad5dc941ed5381e4f137083a08cd to your computer and use it in GitHub Desktop.
D3 challenge month: week 1, day 2 - Reusable charts + ES6

Graphs following the reusable chart pattern have two characteristics:

  1. Configurability. We want to modify the appearance and behavior of the graph without having to modify the code itself.
  2. Ability to be built in an independent way. We want every graph element associated with a data point of our dataset independently. This has to do with the way D3 associates data instances with DOM elements. This will become more clear in a minute.

You can also preview this gist.


This D3 block is based on the @dmesquita block.

const bubbleChart = () => {
let margin = { top: 40, right: 30, bottom: 40, left: 30},
width = 960 - margin.right - margin.left,
height = 960 - margin.top - margin.bottom,
colorCategory = "category",
colRadius = "views";
// # MODIFIES THE selection GIVEN BY .call()
const chart = selection => {
let data = selection.enter().data();
let chart = d3.select("svg")
.attr("class", "chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
// # CREATE CATEGORICAL AND QUANTITATIVE EXTRA ENCODINGS
let colorCircles = d3.scaleOrdinal(d3.schemeCategory10);
let scaleRadius = d3.scaleLinear()
.domain([
d3.min(data, dp => +dp[colRadius]),
d3.max(data, dp => +dp[colRadius])
])
.range([5,21]);
// # DUE TO REUSABLE CHARTS, ALL 'STYLE' IS DONE WITH JS
let tooltip = selection.append("div")
.style("position", "absolute")
.style("visibility", "visible")
.style("color", "white")
.style("padding", "8px")
.style("background-color", "#626D71")
.style("border-radius", "6px")
.style("text-align", "center")
.style("font-family", "monospace")
.style("width", "400px")
.text("");
// # A FORCE ANIMATION FOR EVERY DP
let sim = d3.forceSimulation(data)
.force("charge", d3.forceManyBody().strength([-80]))
.force("x", d3.forceX())
.force("y", d3.forceY())
.on("tick", e => {
node.attr("cx", dp => dp.x)
.attr("cy", dp => dp.y);
});
// # TELLS D3 TO CREATE 'CIRCLES' BINDED TO DATA IF NOT BINDED YET, ADD ATTRS
let node = chart.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", dp => scaleRadius(dp[colRadius]))
.attr("fill", dp => colorCircles(dp[colorCategory]))
.attr("transform", `translate(${ width /2}, ${ height /2})`)
.on("mouseover", dp => {
tooltip.html(`${ dp[colorCategory] }<br>${ dp["title"] }<br>${ dp[colorCategory] } hearts`);
return tooltip.style("visibility", "visible");
})
.on("mousemove", () => {
return tooltip.style("top", `${ d3.event.pageY -10 }px`)
.style("left", `${ d3.event.pageX +10 }px`);
})
.on("mouseout", () => {
return tooltip.style("visibility", "hidden");
});
// SOMEHOW THESE 'MOUSE' ACTIONS DON'T WORK
}
// APPLIES D3 CONCEPT OF CHAINING METHODS
chart.width = val => {
width = val - margin.right - margin.left;
return chart;
}
chart.height = val => {
height = val - margin.top - margin.bottom;
return chart;
}
chart.color = column => {
colorCategory = column;
return chart;
}
chart.radius = column => {
colRadius = column;
return chart;
}
return chart;
}
d3.csv('medium_january.csv', (error, data) => {
if (error) {
console.error('Error getting or parsing the data.');
throw error;
}
// PRE-PROCESS DATA
data.map(dp => dp["views"] = +dp["views"]);
// CHOOSE THE CHART PARAMETERS, THEN CALL chart TO DRAW IT WITH data
let chart = bubbleChart()
.width(720)
.height(500);
d3.select('svg')
.data(data)
.call(chart);
});
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Reusable Bubble Chart - inspired by @dmesquita</title>
</head>
<body>
<svg></svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.min.js"></script>
<script type="text/javascript">
"use strict";
var bubbleChart = function bubbleChart() {
var margin = { top: 40, right: 30, bottom: 40, left: 30 },
width = 960 - margin.right - margin.left,
height = 960 - margin.top - margin.bottom,
colorCategory = "category",
colRadius = "views";
// # MODIFIES THE selection GIVEN BY .call()
var chart = function chart(selection) {
var data = selection.enter().data();
var chart = d3.select("svg")
.attr("class", "chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + ", " + margin.top + ")");
// # CREATE CATEGORICAL AND QUANTITATIVE EXTRA ENCODINGS
var colorCircles = d3.scaleOrdinal(d3.schemeCategory10);
var scaleRadius = d3.scaleLinear().domain([d3.min(data, function (dp) {
return +dp[colRadius];
}), d3.max(data, function (dp) {
return +dp[colRadius];
})]).range([5, 21]);
// # DUE TO REUSABLE CHARTS, ALL 'STYLE' IS DONE WITH JS
var tooltip = selection.append("div")
.style("position", "absolute")
.style("visibility", "visible")
.style("color", "white")
.style("padding", "8px")
.style("background-color", "#626D71")
.style("border-radius", "6px")
.style("text-align", "center")
.style("font-family", "monospace")
.style("width", "400px")
.text("");
// # A FORCE ANIMATION FOR EVERY DP
var sim = d3.forceSimulation(data)
.force("charge", d3.forceManyBody().strength([-80]))
.force("x", d3.forceX())
.force("y", d3.forceY())
.on("tick", function (e) {
node.attr("cx", function (dp) {
return dp.x;
}).attr("cy", function (dp) {
return dp.y;
});
});
// # TELLS D3 TO CREATE 'CIRCLES' BINDED TO DATA IF NOT BINDED YET, ADD ATTRS
var node = chart.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", function (dp) {
return scaleRadius(dp[colRadius]);
}).attr("fill", function (dp) {
return colorCircles(dp[colorCategory]);
}).attr("transform", "translate(" + width / 2 + ", " + height / 2 + ")")
.on("mouseover", function (dp) {
tooltip.html(dp[colorCategory] + "<br>" + dp["title"] + "<br>" + dp[colorCategory] + " hearts");
return tooltip.style("visibility", "visible");
}).on("mousemove", function () {
return tooltip.style("top", d3.event.pageY - 10 + "px")
.style("left", d3.event.pageX + 10 + "px");
}).on("mouseout", function () {
return tooltip.style("visibility", "hidden");
});
// SOMEHOW THESE 'MOUSE' ACTIONS DON'T WORK
};
// APPLIES D3 CONCEPT OF CHAINING METHODS
chart.width = function (val) {
width = val - margin.right - margin.left;
return chart;
};
chart.height = function (val) {
height = val - margin.top - margin.bottom;
return chart;
};
chart.color = function (column) {
colorCategory = column;
return chart;
};
chart.radius = function (column) {
colRadius = column;
return chart;
};
return chart;
};
d3.csv('medium_january.csv', function (error, data) {
if (error) {
console.error('Error getting or parsing the data.');
throw error;
}
// PRE-PROCESS DATA
data.map(function (dp) {
return dp["views"] = +dp["views"];
});
// CHOOSE THE CHART PARAMETERS, THEN CALL chart TO DRAW IT WITH data
var chart = bubbleChart().width(720).height(500);
d3.select('svg').data(data).call(chart);
});
</script>
</body>
</html>
title category views
How Flexbox works — explained with big, colorful, animated gifs Design 5700
How I went from zero experience to landing a 6-figure San Francisco design job in less than 12 months Design 3700
I ranked every Intro to Data Science course on the internet, based on thousands of data points Data Science 1600
Nobody wants to use software Development 2700
A million requests per second with Python Development 1100
Material Design and the Mystery Meat Navigation Problem Design 1100
Lossless Web Navigation with Trails Design 688
How to use spaced repetition with Anki to learn to code faster Development 756
How to commit entire directories to GitHub directly from your browser using GitHub.js Development 82
Every time you build a to-do list app, a puppy dies Development 1500
How to build your own Uber-for-X application part 2 Development 650
How we got our 2-year-old repo trending on GitHub in just 48 hours Data Science 718
How making hundreds of hip hop beats helped me understand HTML and CSS Development 506
Scaling your Redux App with ducks Development 315
A Beginner’s JavaScript Study Plan Development 672
JavaScript’s Prototypal Inheritance Explained Using CSS Development 519
Tree-shaking ES6 Modules in webpack 2 Development 86
3 JavaScript questions to watch out for during coding interviews Development 1100
Build a Node.js API in Under 30 Minutes Development 343
npm cache: the unsung hero Development 122
Code That Doesn't Exist Is The Code You Don't Need To Debug Development 204
A 5-minute Intro to Styled Components Design 760
How to land a top-notch tech internship — and a tech job — while you’re still in school Development 1600
Firebase: the great, the meh, and the ugly Development 407
Which programming languages got the most GitHub stars in 2016? Data Science 100
ElasticSearch with Django the easy way Development 115
Git Please: how to force push without being a jerk Development 66
React Interview Questions Development 389
React’s Five Fingers of Death. Master these five concepts, then master React Development 1700
How to set up ESLint in Atom so you can contribute to Open Source Development 94
If you want a developer job, be fearless and dream big Development 695
Understanding Flexbox: Everything you need to know Design 2200
How I designed an algorithm that mixes playlists of bands coming to your town Design 47
How making hundreds of hip hop beats helped me understand HTML and CSS Design 506
WebSlides: a new open source way to build beautiful presentations that run in your browser Design 747
What I’ve learned from 18 weeks of vlogging my coding journey Design 276
How I used machine learning to explore the differences between British and American literature Data Science 74
The Rise of the Data Engineer Data Science 862
How to bootstrap your analytics in 1 hour Data Science 90
Rolling Stone’s 500 Greatest Albums Visualized Using Pandas and Bokeh Data Science 48
Recognizing Traffic Lights With Deep Learning Data Science 1700
Women only said 27% of the words in 2016’s biggest movies Data Science 1000
What I learned from analyzing the top 252 Medium stories of 2016 Data Science 2200
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment