A simple upset chart implementation, a chart type useful for visualising set intersections, applied to flatmate-purchase assignment data from my onlineshop project.
See it live on bl.ocks
A simple upset chart implementation, a chart type useful for visualising set intersections, applied to flatmate-purchase assignment data from my onlineshop project.
See it live on bl.ocks
| id | purchase_id | flatmate_id | |
|---|---|---|---|
| 1 | 1 | 2 | |
| 2 | 2 | 1 | |
| 3 | 3 | 2 | |
| 4 | 3 | 1 | |
| 5 | 4 | 2 | |
| 6 | 5 | 2 | |
| 7 | 5 | 1 |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Upset Viz</title> | |
| <link rel='stylesheet' type='text/css' href='style.css'> | |
| <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js' charset='utf-8'></script> | |
| <script src='./UpsetChart.js'></script> | |
| <script src='./main.js'></script> | |
| </head> | |
| <body> | |
| <div id='vis'></div> | |
| </body> | |
| </html> | |
| 'use strict' | |
| var dataFilepath = 'assignment.csv' | |
| d3.csv(dataFilepath, function(loadedData) { | |
| // remodel data for chart's requirements | |
| var numFlatmates = d3.nest() | |
| .key(function(a) { return a.flatmate_id }) | |
| .entries(loadedData) | |
| .length | |
| var purchaseAssignments = d3.nest() | |
| .key(function(a) { return a.purchase_id }) | |
| .rollup(function(assignments) { | |
| var assignment = new Array(numFlatmates).fill(0) | |
| var assignedFlatmates = assignments.map(function(a) { return a.flatmate_id }) | |
| assignedFlatmates.forEach(function(f) { | |
| // flatmate ids start at 1 in the csv | |
| assignment[f-1] = 1 | |
| }) | |
| return assignment | |
| }) | |
| .entries(loadedData) | |
| var dataset = { | |
| numFlatmates: numFlatmates | |
| , values: purchaseAssignments | |
| } | |
| // the chart | |
| var svg = d3.select('div#vis') | |
| .append('svg') | |
| .attr('width', 960) | |
| .attr('height', 500) | |
| function draw(data) { | |
| var rows = svg.selectAll('g.upsetchart') | |
| .data(data, function(d) { return d.key }) | |
| rows.enter() | |
| .append('g') | |
| .classed('upsetchart purchase', true) | |
| .attr('transform', function(d, i) { | |
| return 'translate(' + 10 + ',' + (10 + i*30) + ')' | |
| }) | |
| rows | |
| .call(UpsetChart()) | |
| rows.exit() | |
| .remove() | |
| } | |
| draw(dataset.values) | |
| }) |
| div#vis { | |
| width: 960px; | |
| margin: 0px auto; | |
| } | |
| .innerchart line { | |
| stroke: gray; | |
| stroke-width: 4px; | |
| } | |
| circle { | |
| stroke: gray; | |
| stroke-width: 3px; | |
| } | |
| circle.assigned { | |
| fill: gray; | |
| } | |
| circle.not-assigned { | |
| fill: white; | |
| } |
| function UpsetChart() { | |
| // assumptions about the data passed to this chart: | |
| // i.e. dataset of each selection | |
| // Object with the following keys: | |
| // - key: purchase id | |
| // - values: flatmates assigned to this purchase | |
| // chart config DEFAULTS | |
| var margin = {top: 10, right: 10, bottom: 10, left: 10} | |
| var width = 960 - margin.left - margin.right | |
| var height = 30 - margin.top - margin.bottom | |
| function drawChart(selection) { | |
| selection.each(function(dataset, i) { | |
| var wholeChart = this | |
| // | |
| // THE CHART | |
| // | |
| // select chart element(s) (eg. innerchart) if they exist already | |
| var innerchart = d3.select(wholeChart).selectAll('g.innerchart') | |
| .data(function(dataset) { return [dataset.values] }) | |
| // otherwise create them and the elements they contain | |
| var innerchartEnter = innerchart.enter() | |
| .append('g') | |
| .classed('innerchart', true) | |
| .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') | |
| innerchartEnter | |
| .append('line') | |
| .attr('x1', 10) | |
| .attr('y1', 10) | |
| .attr('x2', 60-10-10) | |
| .attr('y2', 10) | |
| // update data | |
| var flatmates = innerchart.selectAll('circle') | |
| .data(function(d) { return d }) | |
| flatmates.enter() | |
| .append('circle') | |
| .attr('class', function(d, i) { | |
| var assigned | |
| if (d) { assigned = 'assigned' } | |
| else { assigned = 'not-assigned' } | |
| var flatmate_id = i+1 | |
| return assigned + ' ' + flatmate_id | |
| }) | |
| .attr('cx', function(d, i) { return 10+i*(10+20) }) | |
| .attr('cy', 10) | |
| .attr('r', 10) | |
| }) | |
| } | |
| // | |
| // Chart CONFIG getter setter methods | |
| // | |
| // TODO figure out which side effects are actually necessary to recalculate | |
| drawChart.width = function(value) { | |
| if (!arguments.length) return width | |
| width = value | |
| return drawChart | |
| } | |
| drawChart.height = function(value) { | |
| if (!arguments.length) return height | |
| height = value | |
| return drawChart | |
| } | |
| return drawChart | |
| } |