Skip to content

Instantly share code, notes, and snippets.

@mell0kat
Created March 26, 2018 15:16
Show Gist options
  • Save mell0kat/f78f4dbef7fd75116ef33f2c06e392b3 to your computer and use it in GitHub Desktop.
Save mell0kat/f78f4dbef7fd75116ef33f2c06e392b3 to your computer and use it in GitHub Desktop.
Animated Bar Chart
license: mit

Built with blockbuilder.org

Credit goes to Ben Clinkinbeard's "Learn D3 in 5 Days" course

This is the finished product of the course.

And here are the notes I took throughout the course:

Day 1

d3 Overview

  • Can build d3 visualizations in HTML and / or SVG
  • SVG graphics provide resolution independence - looks good anywhere
  • SVG graphics do not participate in normal browser layout flow

Core Concepts

  • D3: Data Driven Documents
  • declarative = what, not how
  • style, attributes, properties can be defined as functions of data

Selections

  • returns array of elements

Scales

  • provid domain and range
  • scaleLinear(), e.g.

Day 2

Selections

  • Can select by tag name, id, class name
  • can be scoped d3.select('#foo').selectAll('p')

Element CRUD

selection.attr

  • can be used as getter or setter
  • setter version: .attr('name', value)
  • value can be a function whose form looks like:
function(d, i, nodes){

}
  • d is datum, i is index, nodes is array of nodes in selection
  • this is bound to actual DOM element (not true if you use arrow function)

selection.style

selection.property

selection.classed

  • .classed(names[, value]) - can specify space-separated list of class names

selection.text

  • similar to selection.html (used to set innerHtml)

selection.append('type')

  • Will create a new element and append it to last child of each node in selection

selection.insert

  • Can be used to specify placement of where element is appended

selection.remove

Handling Events

selection.on('type', listener)

Day 3

Joining Data and Selections

  • The enter() selection represennts data items in need of DOM elements

Dynamic Charts

  • key function
    • second argument to selection.data
    • 'key' to reusing elements instead of deleting them
    • without a key function, data is assigned by index
  • Data points joined to existing elements produce the UPDATE selection
  • Leftover data produce the ENTER selection
  • Leftover elements produce the EXIT seletcion

Day 4

D3 Scales

Fundamentals

  • Have a domain and a range
  • d3.scaleX returns a scale function
  • REVERSIBLE
    • Can swap domain and range OR
    • Use the invert method
const decimalToPercent = d3.scaleLinear()
    .domain([0, 1])
    .range([0, 100])

decimalToPercent.invert(50) // 0.5

Outliers

  • There is nothing stopping someone from inputted a value greater than the defined domain
  • If you need to limit it, you can call the clamp method
const percentToDecimal = d3.scaleLinear()
    .domain([0, 100])
    .range([0, 1])
    .clamp(true)

percentToDecimal(-10) // 0
percentToDecimal(150) // 1
percentToDecimal(200) // 1

Scales in practice

  • Making your charts fill available space
  • bandScale is a good way to calculate width (or height in this case) of bars
    • domain is set of values that can be coerced to string
    • range would be the space you want the chart to take up

Day 5

Axes

Axes come from scales

  • D3 has built-in methods for axes
  • Axis consistents of path element with g elements for "ticks"
  • selection.call is used to invoke a method and pass the selection to and then return the selection

Moving axes into view

  • SVG's default position is top left
  • Need to use transform attribute to position axes

d3 Margin Convention

  • Reserve margins on sides of chart for axes
  • Subtract margins when defining width and height
<!DOCTYPE html>
<html>
<head>
<title>d3 in 5 Days - Aniamated Bar Chart</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<div id="chart">
</div>
<button onclick="render('math')">Math</button>
<button onclick="render('science')">Science</button>
<script src="index.js"></script>
</body>
<style type="text/css">
#chart {
padding: 10px 10px 20px 50px;
position: relative;
margin-top: 20px;
}
.bar {
background-color: teal;
height: 20px;
margin-top: 2px;
}
</style>
</html>
const data = [
{ name: 'Alice', math: 93, science: 84 },
{ name: 'Bobby', math: 81, science: 97 },
{ name: 'Carol', math: 74, science: 88 },
{ name: 'David', math: 64, science: 76 },
{ name: 'Emily', math: 80, science: 94 }
]
const margin = { top: 10, right: 10, bottom: 20, left: 50}
const width = 600 - margin.left - margin.right
const height = 400 - margin.top - margin.bottom
const xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, width])
const yScale = d3.scaleBand()
.domain(data.map(d => d.name))
.range([0, height])
const svg = d3.select('#chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.style('position', 'absolute')
.style('top', 0)
.style('left', 0)
const axisContainer = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
axisContainer.append('g')
.attr('transform', `translate(0, ${height})`)
.call(d3.axisBottom(xScale))
axisContainer.append('g')
.call(d3.axisLeft(yScale)) // we don't have to move this at all now
const render = (subject) => {
const bars = d3.select('#chart')
.selectAll('div')
.data(data, d => d.name)
const newBars = bars
.enter() // returns enter select for data that need DOM elements
.append('div')
.attr('class', 'bar')
.style('width', 0)
// combine the selections so you can act on them together
newBars.merge(bars)
.transition()
.style('width', d => `${xScale(d[subject])}px`)
.style('height', d => `${yScale.bandwidth() - 2}px`)
}
render('math')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment