Skip to content

Instantly share code, notes, and snippets.

@philgyford
Last active December 12, 2017 04:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save philgyford/7288e5271469a45db34b9cc0fea64164 to your computer and use it in GitHub Desktop.
Save philgyford/7288e5271469a45db34b9cc0fea64164 to your computer and use it in GitHub Desktop.
D3.js v4 Ladder Graph
license: cc-by-sa-4.0
height: 570
scrolling: no
border: yes

This is a ladder graph created in D3.js v4 that:

  • is responsive,
  • is reusable,
  • and can have two different left-axes which can be switched between.

The data shows the fuel consumption and CO₂ emissions of cars, matched up with the "Eco rating" given when they were reviewed in the Guardian's 'On The Road' column, between July 2015 and July 2017.

You can view the data in a Google Sheet. Only conventional-fuel and hybrid cars are included.

Read more about the graph in my blog post.

For the difference between a slopegraph and a ladder graph, see Charlie Park's blog post.

View this as a gist or on bl.ocks.org.

[
{
"Date": "2017-07-01",
"Manufacturer": "BMW",
"Name": "740Le xDrive",
"Combined fuel consumption (mpg)": 113,
"CO₂ emissions (g/km)": 56,
"Eco rating": 9
},
{
"Date": "2017-06-24",
"Manufacturer": "Toyota",
"Name": "CH-R",
"Combined fuel consumption (mpg)": 47.1,
"CO₂ emissions (g/km)": 136,
"Eco rating": 7
},
{
"Date": "2017-06-10",
"Manufacturer": "Ssangyong",
"Name": "Tivoli XLV",
"Combined fuel consumption (mpg)": 57.6,
"CO₂ emissions (g/km)": 127,
"Eco rating": 7
},
{
"Date": "2017-06-03",
"Manufacturer": "Mini",
"Name": "Clubman Cooper S All 4",
"Combined fuel consumption (mpg)": 38.2,
"CO₂ emissions (g/km)": 168,
"Eco rating": 7
},
{
"Date": "2017-05-20",
"Manufacturer": "Alfa Romeo",
"Name": "Giulietta",
"Combined fuel consumption (mpg)": 74.3,
"CO₂ emissions (g/km)": 99,
"Eco rating": 9
},
{
"Date": "2017-05-06",
"Manufacturer": "Kia",
"Name": "Niro",
"Combined fuel consumption (mpg)": 74.3,
"CO₂ emissions (g/km)": 88,
"Eco rating": 9
},
{
"Date": "2017-04-22",
"Manufacturer": "Citroën",
"Name": "C3",
"Combined fuel consumption (mpg)": 76,
"CO₂ emissions (g/km)": 95,
"Eco rating": 9
},
{
"Date": "2017-04-15",
"Manufacturer": "VW",
"Name": "Amarok Aventura",
"Combined fuel consumption (mpg)": 36,
"CO₂ emissions (g/km)": 204,
"Eco rating": 3
},
{
"Date": "2017-04-08",
"Manufacturer": "Mazda",
"Name": "3 2.0",
"Combined fuel consumption (mpg)": 55.4,
"CO₂ emissions (g/km)": 119,
"Eco rating": 6
},
{
"Date": "2017-03-25",
"Manufacturer": "Fiat",
"Name": "Tipo",
"Combined fuel consumption (mpg)": 76,
"CO₂ emissions (g/km)": 98,
"Eco rating": 9
},
{
"Date": "2017-03-18",
"Manufacturer": "Mercedes",
"Name": "GLC 250 d 4Matic AMG",
"Combined fuel consumption (mpg)": 56,
"CO₂ emissions (g/km)": 143,
"Eco rating": 5
},
{
"Date": "2017-02-18",
"Manufacturer": "Jaguar",
"Name": "F-Pace R-Sport",
"Combined fuel consumption (mpg)": 53.3,
"CO₂ emissions (g/km)": 139,
"Eco rating": 7
},
{
"Date": "2017-02-04",
"Manufacturer": "Range Rover",
"Name": "Evoque",
"Combined fuel consumption (mpg)": 49.6,
"CO₂ emissions (g/km)": 149,
"Eco rating": 6
},
{
"Date": "2017-01-28",
"Manufacturer": "Peugeot",
"Name": 3008,
"Combined fuel consumption (mpg)": 52.3,
"CO₂ emissions (g/km)": 129,
"Eco rating": 7
},
{
"Date": "2017-01-21",
"Manufacturer": "Vauxhall",
"Name": "Mokka",
"Combined fuel consumption (mpg)": 43.5,
"CO₂ emissions (g/km)": 150,
"Eco rating": 7
},
{
"Date": "2017-01-14",
"Manufacturer": "Ferrari",
"Name": "California T",
"Combined fuel consumption (mpg)": 26.9,
"CO₂ emissions (g/km)": 250,
"Eco rating": 1
},
{
"Date": "2016-12-17",
"Manufacturer": "Lexus",
"Name": "RX 450h",
"Combined fuel consumption (mpg)": 51.4,
"CO₂ emissions (g/km)": 127,
"Eco rating": 7
},
{
"Date": "2016-10-12",
"Manufacturer": "Citroën",
"Name": "DS 3",
"Combined fuel consumption (mpg)": 52,
"CO₂ emissions (g/km)": 125,
"Eco rating": 7
},
{
"Date": "2016-12-03",
"Manufacturer": "Mercedes",
"Name": "E-class",
"Combined fuel consumption (mpg)": 72,
"CO₂ emissions (g/km)": 112,
"Eco rating": 7
},
{
"Date": "2016-11-12",
"Manufacturer": "Honda",
"Name": "Civic Tourer",
"Combined fuel consumption (mpg)": 72.4,
"CO₂ emissions (g/km)": 103,
"Eco rating": 8
},
{
"Date": "2016-10-29",
"Manufacturer": "Toyota",
"Name": "Rav4 hybrid",
"Combined fuel consumption (mpg)": 55.4,
"CO₂ emissions (g/km)": 118,
"Eco rating": 7
},
{
"Date": "2016-10-22",
"Manufacturer": "Citroën",
"Name": "Cactus",
"Combined fuel consumption (mpg)": 80.7,
"CO₂ emissions (g/km)": 95,
"Eco rating": 8
},
{
"Date": "2016-10-08",
"Manufacturer": "VW",
"Name": "California Ocean",
"Combined fuel consumption (mpg)": 44,
"CO₂ emissions (g/km)": 169,
"Eco rating": 5
},
{
"Date": "2016-10-01",
"Manufacturer": "Honda",
"Name": "Jazz",
"Combined fuel consumption (mpg)": 57,
"CO₂ emissions (g/km)": 114,
"Eco rating": 8
},
{
"Date": "2016-09-17",
"Manufacturer": "Audi",
"Name": "RS6",
"Combined fuel consumption (mpg)": 29,
"CO₂ emissions (g/km)": 223,
"Eco rating": 2
},
{
"Date": "2016-09-03",
"Manufacturer": "Renault",
"Name": "Mégane",
"Combined fuel consumption (mpg)": 76.4,
"CO₂ emissions (g/km)": 96,
"Eco rating": 9
},
{
"Date": "2016-08-13",
"Manufacturer": "Peugeot",
"Name": 2008,
"Combined fuel consumption (mpg)": 76.3,
"CO₂ emissions (g/km)": 96,
"Eco rating": 9
},
{
"Date": "2016-08-06",
"Manufacturer": "Ford",
"Name": "Focus",
"Combined fuel consumption (mpg)": 51.4,
"CO₂ emissions (g/km)": 127,
"Eco rating": 7
},
{
"Date": "2016-07-30",
"Manufacturer": "Citroën",
"Name": "DS3 Cabrio",
"Combined fuel consumption (mpg)": 78.5,
"CO₂ emissions (g/km)": 94,
"Eco rating": 8
},
{
"Date": "2016-07-16",
"Manufacturer": "Kia",
"Name": "Sportage",
"Combined fuel consumption (mpg)": 47.9,
"CO₂ emissions (g/km)": 154,
"Eco rating": 6
},
{
"Date": "2016-06-25",
"Manufacturer": "Toyota",
"Name": "Prius",
"Combined fuel consumption (mpg)": 94,
"CO₂ emissions (g/km)": 70,
"Eco rating": 9
},
{
"Date": "2016-07-18",
"Manufacturer": "Vauxhall",
"Name": "Astra Sports Tourer 1.6CDTi",
"Combined fuel consumption (mpg)": 67.3,
"CO₂ emissions (g/km)": 112,
"Eco rating": 7
},
{
"Date": "2016-06-11",
"Manufacturer": "Peugeot",
"Name": "208 GTI",
"Combined fuel consumption (mpg)": 52.3,
"CO₂ emissions (g/km)": 125,
"Eco rating": 7
},
{
"Date": "2016-06-04",
"Manufacturer": "Mazda",
"Name": "MX-5",
"Combined fuel consumption (mpg)": 41,
"CO₂ emissions (g/km)": 161,
"Eco rating": 5
},
{
"Date": "2016-05-14",
"Manufacturer": "VW",
"Name": "Sharan SE Nav 2.0 TDI 150PS DSG",
"Combined fuel consumption (mpg)": 57,
"CO₂ emissions (g/km)": 130,
"Eco rating": 6
},
{
"Date": "2016-05-07",
"Manufacturer": "Volvo",
"Name": "XC90",
"Combined fuel consumption (mpg)": 49.6,
"CO₂ emissions (g/km)": 149,
"Eco rating": 6
},
{
"Date": "2016-04-30",
"Manufacturer": "Skoda",
"Name": "Superb SE Business estate 2.0 TDI 150PS",
"Combined fuel consumption (mpg)": 68.9,
"CO₂ emissions (g/km)": 108,
"Eco rating": 7
},
{
"Date": "2016-04-16",
"Manufacturer": "Mazda",
"Name": "3 1.5D",
"Combined fuel consumption (mpg)": 74.3,
"CO₂ emissions (g/km)": 99,
"Eco rating": 9
},
{
"Date": "2016-04-09",
"Manufacturer": "Ssangyong",
"Name": "Korando",
"Combined fuel consumption (mpg)": 41.5,
"CO₂ emissions (g/km)": 177,
"Eco rating": 5
},
{
"Date": "2016-04-02",
"Manufacturer": "Citroën",
"Name": "C4 Cactus",
"Combined fuel consumption (mpg)": 80.8,
"CO₂ emissions (g/km)": 95,
"Eco rating": 9
},
{
"Date": "2016-03-26",
"Manufacturer": "Nissan",
"Name": "X-Trail",
"Combined fuel consumption (mpg)": 44,
"CO₂ emissions (g/km)": 149,
"Eco rating": 7
},
{
"Date": "2016-03-12",
"Manufacturer": "Volvo",
"Name": "V60 Cross Country",
"Combined fuel consumption (mpg)": 67.3,
"CO₂ emissions (g/km)": 111,
"Eco rating": 8
},
{
"Date": "2016-03-05",
"Manufacturer": "Séat",
"Name": "Alhambra",
"Combined fuel consumption (mpg)": 55.4,
"CO₂ emissions (g/km)": 132,
"Eco rating": 4
},
{
"Date": "2016-02-20",
"Manufacturer": "Fiat",
"Name": 500,
"Combined fuel consumption (mpg)": 67.3,
"CO₂ emissions (g/km)": 99,
"Eco rating": 9
},
{
"Date": "2016-02-13",
"Manufacturer": "Citroën",
"Name": "DS",
"Combined fuel consumption (mpg)": 72.4,
"CO₂ emissions (g/km)": 103,
"Eco rating": 9
},
{
"Date": "2016-02-06",
"Manufacturer": "Peugeot",
"Name": "308 GTi",
"Combined fuel consumption (mpg)": 47.1,
"CO₂ emissions (g/km)": 139,
"Eco rating": 7
},
{
"Date": "2016-01-30",
"Manufacturer": "Audi",
"Name": "Q7",
"Combined fuel consumption (mpg)": 48,
"CO₂ emissions (g/km)": 163,
"Eco rating": 6
},
{
"Date": "2016-01-23",
"Manufacturer": "Hyundai",
"Name": "Tucson",
"Combined fuel consumption (mpg)": 42.5,
"CO₂ emissions (g/km)": 170,
"Eco rating": 5
},
{
"Date": "2016-01-16",
"Manufacturer": "BMW",
"Name": "X1",
"Combined fuel consumption (mpg)": 57.6,
"CO₂ emissions (g/km)": 128,
"Eco rating": 7
},
{
"Date": "2016-01-02",
"Manufacturer": "Smart Car",
"Name": "Forfour",
"Combined fuel consumption (mpg)": 65.7,
"CO₂ emissions (g/km)": 99,
"Eco rating": 9
},
{
"Date": "2015-12-19",
"Manufacturer": "Vauxhall",
"Name": "Viva Se 1.0",
"Combined fuel consumption (mpg)": 62.8,
"CO₂ emissions (g/km)": 104,
"Eco rating": 8
},
{
"Date": "2015-12-12",
"Manufacturer": "Mini",
"Name": "JCW",
"Combined fuel consumption (mpg)": 42,
"CO₂ emissions (g/km)": 155,
"Eco rating": 6
},
{
"Date": "2015-12-05",
"Manufacturer": "Audi",
"Name": "RS3 Sportback",
"Combined fuel consumption (mpg)": 34.9,
"CO₂ emissions (g/km)": 194,
"Eco rating": 4
},
{
"Date": "2015-11-28",
"Manufacturer": "Ssangyong",
"Name": "Tivoli",
"Combined fuel consumption (mpg)": 65.7,
"CO₂ emissions (g/km)": 113,
"Eco rating": 8
},
{
"Date": "2015-11-21",
"Manufacturer": "Renault",
"Name": "Kadjar",
"Combined fuel consumption (mpg)": 62.8,
"CO₂ emissions (g/km)": 117,
"Eco rating": 8
},
{
"Date": "2015-11-14",
"Manufacturer": "Suzuki",
"Name": "Vitara",
"Combined fuel consumption (mpg)": 50.4,
"CO₂ emissions (g/km)": 130,
"Eco rating": 7
},
{
"Date": "2015-11-07",
"Manufacturer": "Vauxhall",
"Name": "Adam S",
"Combined fuel consumption (mpg)": 47.8,
"CO₂ emissions (g/km)": 139,
"Eco rating": 7
},
{
"Date": "2015-10-31",
"Manufacturer": "Fiat",
"Name": "500X",
"Combined fuel consumption (mpg)": 68.9,
"CO₂ emissions (g/km)": 109,
"Eco rating": 8
},
{
"Date": "2015-10-24",
"Manufacturer": "Séat",
"Name": "Ibiza 1.2 TSI 90PS Connect",
"Combined fuel consumption (mpg)": 57.6,
"CO₂ emissions (g/km)": 116,
"Eco rating": 8
},
{
"Date": "2015-10-10",
"Manufacturer": "Honda",
"Name": "Civic Type R",
"Combined fuel consumption (mpg)": 38.7,
"CO₂ emissions (g/km)": 170,
"Eco rating": 6
},
{
"Date": "2015-10-03",
"Manufacturer": "Mitsubishi",
"Name": "L200 Barbarian",
"Combined fuel consumption (mpg)": 42.8,
"CO₂ emissions (g/km)": 173,
"Eco rating": 5
},
{
"Date": "2015-09-26",
"Manufacturer": "Kia",
"Name": "Sorento",
"Combined fuel consumption (mpg)": 46.3,
"CO₂ emissions (g/km)": 161,
"Eco rating": 7
},
{
"Date": "2015-09-19",
"Manufacturer": "Mazda",
"Name": "CX-3",
"Combined fuel consumption (mpg)": 70.6,
"CO₂ emissions (g/km)": 105,
"Eco rating": 8
},
{
"Date": "2015-09-12",
"Manufacturer": "Nissan",
"Name": "Pulsar",
"Combined fuel consumption (mpg)": 48,
"CO₂ emissions (g/km)": 138,
"Eco rating": 7
},
{
"Date": "2015-09-05",
"Manufacturer": "Citroën",
"Name": "DS5",
"Combined fuel consumption (mpg)": 68.9,
"CO₂ emissions (g/km)": 105,
"Eco rating": 7
},
{
"Date": "2015-08-29",
"Manufacturer": "Volvo",
"Name": "XC90",
"Combined fuel consumption (mpg)": 48.7,
"CO₂ emissions (g/km)": 152,
"Eco rating": 6
},
{
"Date": "2015-08-22",
"Manufacturer": "Séat",
"Name": "Leon Cupra",
"Combined fuel consumption (mpg)": 42.2,
"CO₂ emissions (g/km)": 157,
"Eco rating": 6
},
{
"Date": "2015-08-15",
"Manufacturer": "VW",
"Name": "Polo GTi",
"Combined fuel consumption (mpg)": 47.1,
"CO₂ emissions (g/km)": 139,
"Eco rating": 7
},
{
"Date": "2015-08-08",
"Manufacturer": "Hyundai",
"Name": "i30 Turbo",
"Combined fuel consumption (mpg)": 44,
"CO₂ emissions (g/km)": 169,
"Eco rating": 7
},
{
"Date": "2015-08-01",
"Manufacturer": "Skoda",
"Name": "Fabia",
"Combined fuel consumption (mpg)": 60.1,
"CO₂ emissions (g/km)": 107,
"Eco rating": 8
},
{
"Date": "2015-07-25",
"Manufacturer": "Honda",
"Name": "CR-V",
"Combined fuel consumption (mpg)": 55.4,
"CO₂ emissions (g/km)": 139,
"Eco rating": 7
},
{
"Date": "2015-07-04",
"Manufacturer": "Subaru",
"Name": "Outback",
"Combined fuel consumption (mpg)": 50.4,
"CO₂ emissions (g/km)": 159,
"Eco rating": 6
}
]
<!DOCTYPE html>
<html lang="en-gb">
<head>
<meta charset="UTF-8">
<title>Ladder graph of cars' Eco Ratings</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="js-laddergraph laddergraph"></div>
<h1 class="chart-title">
Cars reviewed in the Guardian’s <a href="https://www.theguardian.com/technology/series/ontheroad">‘On&nbsp;The&nbsp;Road’</a>&nbsp;column
</h1>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="laddergraph.js"></script>
<script>
'use strict';
(function() {
d3.json('data.json', function(error, data) {
if (error) {
throw error;
};
var laddergraph = charts.laddergraph()
.leftKey1('Combined fuel consumption (mpg)')
.leftKey2('CO₂ emissions (g/km)')
.leftKey2Orientation('inverted')
.rightKey('Eco rating')
.tooltipFormat(function(d) {
var text = '<strong>' + d.Manufacturer + ' ' + d.Name + '</strong>';
text += '<dl>';
text += '<dt>Date reviewed</dt><dd>' + d.Date + '</dd>';
text += '<dt>Combined fuel consumption</dt><dd>' + d['Combined fuel consumption (mpg)'] + 'mpg</dd>';
text += '<dt>CO₂ emissions</dt><dd>' + d['CO₂ emissions (g/km)'] + 'g/km</dd>';
text += '<dt>Eco rating</dt><dd>' + d['Eco rating'] + '/10</dd>';
text += '</dl>';
return text;
});
d3.select('.js-laddergraph')
.datum(data)
.call(laddergraph);
});
})();
</script>
</body>
</html>
/**
* Reusable, responsive ladder chart, that can have two switchable left axes.
* For D3.js v4.
*
* In use:
*
* <div class="js-chart" style="max-width: 400px; height: 500px;"></div>
*
* <script>
* var data = [
* {
* 'Service': 5,
* 'Speed': 7,
* 'Overall': 88,
* 'Name': 'Bob'
* 'Age': 34
* },
* ...
* ];
*
* var laddergraph = charts.laddergraph()
* .leftKey1('Service')
* .leftKey2('Speed')
* .rightKey('Overall')
* .tooltipFormat(function(d) {
* return '<b>' + d['Name'] + '</b> ' + d['Age'];
* });
*
* d3.select('js-chart')
* .datum(data)
* .call(laddergraph);
*
* </script>
*
*
* If you want one of the left-hand axes to be the other way up (i.e. have
* lower values at the top) then change the default orientation to 'inverted'.
* e.g.:
*
* var laddergraph = charts.laddergraph()
* .leftKey1('Service')
* .leftKey1Orientation('inverted')
* .leftKey2('Speed')
* .rightKey('Overall');
*/
;(function() {
'use strict';
window.charts = window.charts || {};
charts.laddergraph = function module() {
// Default values that can be overridden:
var leftKey1 = '',
leftKey1Orientation = 'normal',
leftKey2 = '',
leftKey2Orientation = 'normal',
rightKey = '',
tooltipFormat = function(d, i) {
return i;
};
function chart(selection) {
var margin = {top: 45, bottom: 10, left: 0, right: 0};
var tooltip = d3.select('body')
.append('div')
.classed('tooltip', true)
.style('position', 'absolute')
.style('visibility', 'hidden');
selection.each(function(data) {
var container = d3.select(this);
var svg = container.append('svg');
// Initial defaults for left y-axis:
var leftKey = leftKey1;
var leftKeyOrientation = leftKey1Orientation;
var yScaleLeft = d3.scaleLinear();
setLeftAxisDomain();
var yScaleRight = d3.scaleLinear()
.domain([0, 10]);
var yAxisLeft = svg.append('g')
.classed('axis axis-l', true);
var yAxisRight = svg.append('g')
.classed('axis axis-r', true);
var yAxisLeftLabel = svg.append('text')
.classed('axis-label axis-label-l', true);
var yAxisRightLabel = svg.append('text')
.classed('axis-label axis-label-r', true);
var yAxisLeftSwitcher = svg.append('text')
.classed('axis-switcher', true)
.text('Change')
.on('click', switchLeftAxis);
// Need to be in a scope the functions below can access:
var totalW;
var totalH;
var chartW;
var chartH;
render();
window.addEventListener('resize', render);
function render() {
setOuterDimensions();
renderStructure();
renderAxes();
setInnerDimensions();
renderLines();
};
/**
* Work out how big the entire chart area is.
*/
function setOuterDimensions() {
var rect = container.node().getBoundingClientRect();
// Total width and height:
totalW = parseInt(rect.width, 10);
totalH = parseInt(rect.height, 10);
};
/**
* Draw chart to correct dimensions.
*/
function renderStructure() {
svg.transition().attr('width', totalW)
.attr('height', totalH);
};
/**
* Draw the two y axes and ticks.
* AND set margin.left and margin.right to allow for how wide the tick
* labels are.
*/
function renderAxes() {
renderLeftAxis();
renderRightAxis();
};
function renderLeftAxis() {
yScaleLeft.range([totalH - margin.bottom, margin.top]);
yAxisLeft
.call(d3.axisLeft(yScaleLeft));
// Find widest left axis tick label and base margin.left on that.
// Because we have two left axes we need to do this for both.
var maxw = 0;
// First, for the left axis that is currently displayed.
yAxisLeft.selectAll('text').each(function() {
if(this.getBBox().width > maxw) maxw = this.getBBox().width;
});
// Now to get it for whatever isn't the current left axis.
// Make a temporary left axis using the other key:
var otherLeftKey = leftKey == leftKey1 ? leftKey2 : leftKey1;
var otherYScaleLeft = d3.scaleLinear();
// Set its domain like setLeftAxisDomain() does:
otherYScaleLeft.domain(
[0, d3.max(data, function(d) { return d[otherLeftKey]; })]
);
// Create the temporary left axis element:
var otherYAxisLeft = svg.append('g')
.classed('axis axis-l', true).
style('visibility', 'hidden');
otherYAxisLeft.call(d3.axisLeft(otherYScaleLeft));
// See if any of its tick values are bigger than what we have already:
otherYAxisLeft.selectAll('text').each(function() {
if(this.getBBox().width > maxw) maxw = this.getBBox().width;
});
margin.left = maxw + 10;
yAxisLeft.attr("transform", "translate(" + margin.left + ",0)");
yAxisLeftLabel
.attr('dx', 0)
.attr('dy', 15)
.text(leftKey);
// The 'Change' link.
yAxisLeftSwitcher
.attr('dx', 0)
.attr('dy', 30);
};
function renderRightAxis() {
yScaleRight.range([totalH - margin.bottom, margin.top])
yAxisRight
.call(d3.axisRight(yScaleRight).ticks(10));
// Find widest right axis tick label and base margin.right on that.
var maxw = 0;
yAxisRight.selectAll('text').each(function() {
if(this.getBBox().width > maxw) maxw = this.getBBox().width;
});
margin.right = maxw + 10;
yAxisRight.attr(
"transform", "translate(" + (totalW - margin.right) + ",0)");
yAxisRightLabel
.attr('dx', totalW)
.attr('dy', 30)
.attr('text-anchor', 'end')
.text(rightKey);
};
/**
* Set dimensions of inner chart area, between y axes.
* Needs the left and right margins to have been set.
*/
function setInnerDimensions() {
// Area of the chart itself:
chartW = totalW - margin.left - margin.right;
chartH = totalH - margin.top - margin.bottom;
};
/**
* Draw the lines between the axes.
*/
function renderLines() {
var lines = svg.selectAll('.js-line')
.data(data);
// Update
lines.transition()
.attr('x2', chartW + margin.left)
.attr('y1', function(d) { return yScaleLeft(d[leftKey]); })
.attr('y2', function(d) { return yScaleRight(d[rightKey]); });
// Enter
lines.enter()
.append('line')
.attr('x1', margin.left)
.attr('x2', chartW + margin.left)
.attr('y1', function(d) { return yScaleLeft(d[leftKey]); })
.attr('y2', function(d) { return yScaleRight(d[rightKey]); })
.style('stroke', '#000')
.classed('js-line line', true)
.on('mouseover', function(d) {
tooltip.html( tooltipFormat(d) );
tooltip.style('visibility', 'visible');
})
.on('mousemove', function(d) {
tooltip
.style('top', (event.pageY-10)+'px')
.style('left',(event.pageX+15)+'px');
})
.on('mouseout', function(d) {
tooltip.style('visibility', 'hidden');
})
.on('click', function(d) {
// When clicked, toggle the .line-selected class off
// or on.
d3.select(this)
.classed(
'line-selected',
d3.select(this)
.classed('line-selected') ? false : true);
});
// Remove
lines.exit().remove();
};
/**
* When the 'change' link is clicked, change the left y-axis,
* and re-render the chart.
*/
function switchLeftAxis() {
if (leftKey === leftKey1) {
leftKey = leftKey2;
leftKeyOrientation = leftKey2Orientation;
} else {
leftKey = leftKey1;
leftKeyOrientation = leftKey1Orientation;
};
setLeftAxisDomain();
render();
};
/**
* Set the domain of whatever the current left y-axis is.
*/
function setLeftAxisDomain() {
var leftMax = d3.max(data, function(d) { return d[leftKey]; });
if (leftKeyOrientation === 'inverted') {
yScaleLeft.domain([leftMax, 0]);
} else {
yScaleLeft.domain([0, leftMax]);
};
};
});
}; // end chart()
chart.leftKey1 = function(value) {
if (!arguments.length) {
return leftKey1;
};
leftKey1 = value;
return this;
};
chart.leftKey2 = function(value) {
if (!arguments.length) {
return leftKey2;
};
leftKey2 = value;
return this;
};
chart.leftKey1Orientation = function(value) {
if (!arguments.length) {
return leftKey1Orientation;
};
leftKey1Orientation = value;
return this;
};
chart.leftKey2Orientation = function(value) {
if (!arguments.length) {
return leftKey2Orientation;
};
leftKey2Orientation = value;
return this;
};
chart.rightKey = function(value) {
if (!arguments.length) {
return rightKey;
};
rightKey = value;
return this;
};
chart.tooltipFormat = function(value) {
if (!arguments.length) {
return tooltipFormat;
};
tooltipFormat = value;
return this;
};
return chart;
};
}());
* {
box-sizing: border-box;
}
body {
background: #fff;
color: #000;
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
margin: 15px 8px;
}
.laddergraph {
width: 100%;
max-width: 400px;
height: 500px;
margin: 0 auto;
}
.line {
stroke: #333 !important;
stroke-width: 1px;
}
.line:hover {
stroke: #000 !important;
stroke-width: 2px;
cursor: pointer;
}
.line-selected,
.line-selected:hover {
stroke: #f00 !important;
}
.domain,
.tick line {
stroke: #000;
}
.tick text {
fill: #000;
}
.axis-switcher {
font-size: 12px;
fill: #00F;
text-decoration: underline;
cursor: pointer;
}
.chart-title {
font-size: 14px;
text-align: center;
}
.tooltip {
background: #fff;
line-height: 1.5em;
padding: 0.5em 1em 0.6em 1em;
border: 1px solid #999;
width: 21em;
}
.tooltip dl {
display: flex;
flex-wrap: wrap;
width: 20em;
margin: 0;
}
.tooltip dt {
width: 14em;
font-weight: normal;
margin: 0;
}
.tooltip dd {
width: 6em;
font-weight: bold;
margin: 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment