Skip to content

Instantly share code, notes, and snippets.

@sgpinkus
Created July 25, 2017 02:59
Show Gist options
  • Save sgpinkus/810fecbedc6cad45f05e3b283aa215bd to your computer and use it in GitHub Desktop.
Save sgpinkus/810fecbedc6cad45f05e3b283aa215bd to your computer and use it in GitHub Desktop.
/**
* Presents a bar graph with mutatable bars. The use can click on one bar and drag to change its
* height and corresponding value. The user can click and drag to select >1 and simultaneously
* change thier values.
*/
class EditableBarChart
{
/**
* Init. We need to explicitly deal with margins ~because axes.
*/
constructor(container, data, options = {}) {
var defaults = {
margin: { top: 20, right: 20, bottom: 40, left: 40},
}
options = Object.assign(defaults, options)
this.svg = d3.select(container)
this.width = +this.svg.attr('width') - options.margin.left - options.margin.right;
this.height = +this.svg.attr('height') - options.margin.top - options.margin.bottom;
this.g = this.svg.append('g')
.attr('transform', 'translate(' + options.margin.left + ',' + options.margin.top + ')');
this.init(data, options)
this.draw()
}
/**
* Draw data and set up graph based on data. data must be an array of numbers.
* If range use that for y-axis range.
*/
init(data, options = {}) {
var defaults = {
range: null,
xFormatter: null,
}
options = Object.assign(defaults, options)
this.selected = [-1,-1]
this.data = data
if(options.range == null) {
options.range = [Math.min.apply(null, this.data), Math.max.apply(null, this.data)]
}
this.x = d3.scaleBand().rangeRound([0, this.width]).padding(0.1).domain(Object.keys(data))
this.y = d3.scaleLinear().rangeRound([this.height,0]).domain(options.range);
this.g.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + this.height + ')')
.call(d3.axisBottom(this.x).tickFormat(options.xFormatter))
this.g.selectAll('.axis--x g.tick text')
.attr('transform', 'rotate(-90) translate(-20,-14)')
this.g.append('g')
.attr('class', 'axis axis--y')
.call(d3.axisLeft(this.y).ticks(10, ''))
console.log(this.xi)
}
/**
* Draw / redraw
*/
draw() {
var dg = d3.drag()
var circles = this.g.selectAll('.bar').data(this.data).enter()
.append('g')
.attr('transform', (d,i) => { return 'translate(' + this.x(i) + ',0)'; })
circles.append('rect')
.attr('class', 'bar-bar')
.attr('width', this.x.bandwidth())
.attr('transform', (d) => { return 'translate(0,' + this.y(d) + ')'; })
.attr('height', (d) => { return this.height - this.y(d); })
.call(dg.on('start', (d, i, n) => { this.started(d, i, n) }))
.call(dg.on('drag', (d, i, n) => { this.dragged(d, i, n) }))
.call(dg.on('end', (d, i, n) => { this.stopped(d, i, n) }))
circles.exit()
}
started(d, i, n) {
console.log('Drag started', d, i);
this.selected[0] = this.selected[1] = i
d3.select(n[i]).classed('bar-active', true)
}
dragged(d, i, n) {
console.log('Drag', d, i);
var mouse = d3.mouse(this.g.node())
this.selected[1] = this.xi(mouse[0])
var target = d3.selectAll(n.slice.apply(n, this.selection()))
d3.selectAll(n).classed('bar-active', false)
target.attr('transform', 'translate(0,' + mouse[1] + ')')
.attr('height', this.height - mouse[1])
.classed('bar-active', true)
}
stopped(d, i, n) {
console.log('Drag stopped');
d3.selectAll(n).classed('bar-active', false)
this.selected = [-1,-1]
}
/**
* Return current drag selection.
*/
selection() {
return [Math.min(this.selected[0], this.selected[1]), 1+Math.max(this.selected[0], this.selected[1])]
}
/**
* Implements equiv of scaleBand().inverse() to map mouse pos to column.
*/
xi(x) {
if(x <= this.x(1)) {
return 0
}
for(var i = 2; i < this.x.domain().length; i++) {
if(x <= this.x(i)) {
return i-1
}
}
return this.x.domain().length-1
}
get() {
return this.data
}
set(data) {
this.init(data)
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title></title>
<style>
.bar-bar, .bar, .bar-circle {
fill: steelblue;
}
.bar-active {
fill: brown;
}
.axis--x path {
display: none;
}
</style>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='editable-bar-chart.js'></script>
<script>
data = [0.57, 0.14, 0.17, 0.49, 0.29, 0.22, 0.01, 0.27, 0.57, 0.2, 0.19, 0.05, 0.8, 0.49, 0.31, 0.56, 0.75, 0.66, 0.67, 0.37, 0.62, 0.5, 0.82, 0.31, 0.57, 0.25, 0.87, 0.24, 0.89, 0.73, 0.76, 0.53, 0.79, 0.95, 0.31, 0.18, 0.56, 0.11, 0.87, 0.26, 0.22, 0.65, 0.13, 0.21, 0.96, 0.92, 0.61, 0.45];
function init(){
x = new EditableBarChart('svg', data, {
range: [0,1],
xFormatter: (n) => { return d3.format('02d')(Math.floor(n/2)) + ':' + d3.format('02d')((n % 2)*30); }
})
}
</script>
</head>
<body onload='init()'>
<svg width='960' height='500'></svg>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment