Skip to content

Instantly share code, notes, and snippets.

@AlainRo
Last active October 4, 2017 13:36
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 AlainRo/9264cd08e341f2c92f020c39642c34d1 to your computer and use it in GitHub Desktop.
Save AlainRo/9264cd08e341f2c92f020c39642c34d1 to your computer and use it in GitHub Desktop.
BarChart with draggable bars to input/adjust data values (Horizontal + Vertical)

Barchart as an input device

Brushes can be used to draw a barchart. This gives an easy way to input or adjust values with the mouse !!!

The d3 brush is a complex piece of logic taking care of events and user feed-backs. Among other characterics it exposes a nice selection rectangle which can be used as a bar !!!

2D-brushes come in two flavors see official API v4 doc

  • d3.brushX()
  • d3.brushY()

Let's try both of them on this example.

Code comments

Code is made of:

  • CSS
  • a data definition in the form of: [{index:2, value: 23.34}, ...]
  • two almost identical barchart generators (horizontal/vertical) allowing to drag a bar to a new value
  • a refresh function which propagates data updates into the two barcharts

Enjoy !!

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.selection {
fill: steelblue;
fill-opacity: 1;
}
body {
width: 80%;
margin:auto;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data = [{index: 0, value: 3},
{index: 1, value: 5},
{index: 2, value: 12},
{index: 3, value: 8},
{index: 4, value: 20},
{index: 5, value: 7},
{index: 6, value: 2},
{index: 7, value: 15}
];
//--------------- HORIZONTAL --------------------
var widthX = 300,
heightX = 250,
delim = 4;
var scaleX = d3.scaleLinear()
.domain([0, 21])
.rangeRound([0, widthX]);
var y = d3.scaleLinear()
.domain([0, data.length])
.rangeRound([0, heightX]);
var svgX = d3.select('body')
.append("svg")
.attr("width", widthX)
.attr("height", heightX)
.append('g');
svgX
.append('rect')
.attr('x', 0)
.attr('y', 0)
.style('stroke', 'black')
.style('fill', 'lightyellow')
.attr('width', widthX)
.attr('height', heightX);
// Moveable barChart
var brushX = d3.brushX()
.extent(function (d, i) {
return [[0,y(i)+delim/2 ],
[widthX, y(i)+ heightX/data.length -delim/2]];})
.on("brush", brushmoveX)
.on("end", brushendX);
var svgbrushX = svgX
.selectAll('.brush')
.data(data)
.enter()
.append('g')
.attr('class', 'brush')
.append('g')
.call(brushX)
.call(brushX.move, function (d){return [0, d.value].map(scaleX);});
svgbrushX
.append('text')
.attr('x', function (d){return scaleX(d.value)-10;})
.attr('y', function (d, i){return y(i) + y(0.5);})
.attr('dy', '.35em')
.attr('dx', -15)
.style('fill', 'white')
.text(function (d) {return d3.format('.2')(d.value);})
function brushendX(){
if (!d3.event.sourceEvent) return;
if (d3.event.sourceEvent.type === "brush") return;
if (!d3.event.selection) { // just in case of click with no move
svgbrushX
.call(brushX.move, function (d){
return [0, d.value].map(scaleX);});}
}
function brushmoveX() {
if (!d3.event.sourceEvent) return;
if (d3.event.sourceEvent.type === "brush") return;
if (!d3.event.selection) return;
var d0 = d3.event.selection.map(scaleX.invert);
var d = d3.select(this).select('.selection');
d.datum().value= d0[1]; // Change the value of the original data
update();
}
//--------------- VERTICAL --------------------
var widthY = 250,
heightY = 300;
var scaleY = d3.scaleLinear()
.domain([0, 21])
.rangeRound([heightY, 0]);
var x = d3.scaleLinear()
.domain([0, data.length])
.rangeRound([0, widthY]);
var svgY = d3.select('body')
.append("svg")
.attr("width", widthY)
.attr("height", heightY)
.append('g');
svgY
.append('rect')
.attr('x', 0)
.attr('y', 0)
.style('stroke', 'black')
.style('fill', 'lightyellow')
.attr('width', widthY)
.attr('height', heightY);
// Moveable barChart
var brushY = d3.brushY()
.extent(function (d, i) {
return [[x(i)+ delim/2, 0],
[x(i) + x(1) - delim/2, heightY]];})
.on("brush", brushmoveY)
.on("end", brushendY);
var svgbrushY = svgY
.selectAll('.brush')
.data(data)
.enter()
.append('g')
.attr('class', 'brush')
.append('g')
.call(brushY)
.call(brushY.move, function (d){return [d.value, 0].map(scaleY);});
svgbrushY
.append('text')
.attr('y', function (d){return scaleY(d.value) + 25;})
.attr('x', function (d, i){return x(i) + x(0.5);})
.attr('dx', '-.60em')
.attr('dy', -5)
.style('fill', 'white')
.text(function (d) {return d3.format('.2')(d.value);});
function brushendY(){
if (!d3.event.sourceEvent) return;
if (d3.event.sourceEvent.type === "brush") return;
if (!d3.event.selection) { // just in case of click with no move
svgbrushY
.call(brushY.move, function (d){
return [d.value, 0].map(scaleY);})
};
}
function brushmoveY() {
if (!d3.event.sourceEvent) return;
if (d3.event.sourceEvent.type === "brush") return;
if (!d3.event.selection) return;
var d0 = d3.event.selection.map(scaleY.invert);
var d = d3.select(this).select('.selection');
d.datum().value= d0[0]; // Change the value of the original data
update();
}
//---------UPDATE VERTICAL and HORIZONTAL
function update(){
svgbrushX
.call(brushX.move, function (d){
return [0, d.value].map(scaleX);})
.selectAll('text')
.attr('x', function (d){return scaleX(d.value)-10;})
.text(function (d) {return d3.format('.2')(d.value);});
svgbrushY
.call(brushY.move, function (d){
return [d.value, 0].map(scaleY);})
.selectAll('text')
.attr('y', function (d){return scaleY(d.value) + 25;})
.text(function (d) {return d3.format('.2')(d.value);});
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment