Skip to content

Instantly share code, notes, and snippets.

@cdagli
Last active January 23, 2018 14:07
Show Gist options
  • Save cdagli/fbfb5a8680acd8091f7bcd7abed0d8f0 to your computer and use it in GitHub Desktop.
Save cdagli/fbfb5a8680acd8091f7bcd7abed0d8f0 to your computer and use it in GitHub Desktop.
Smaller Version (Less Data)
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<style>
.bar {
fill: steelblue;
}
.subBar{
fill: steelblue;
}
.axis text {
font: 10px sans-serif;
user-select: none;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
rect.mover {
fill: lightSteelBlue;
fill-opacity: .5;
}
.brush .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
.tooltip {
position: absolute;
pointer-events: none;
padding: 12px;
background: white;
border: 1px solid gray;
border-radius: 4px;
}
.tooltip-name
{
text-align: center;
color: steelblue;
}
.tooltip-value
{
text-align: left;
margin-top: 5px;
}
</style>
</head>
<body>
<!DOCTYPE html>
<script>
var DATA_COUNT = 6;
var MAX_LABEL_LENGTH = 10;
var MIN_LABEL_LENGTH = 50;
var MAX_LABEL_LENGTH_ALLOWED = 27;
var MARGIN_MODIFIER_CONSTANT = 4;
var BAR_WIDTH = 20;
var BAR_PADDING = 5;
var data = [];
for (var i = 0; i < DATA_COUNT; i++) {
var datum = {};
var plusOrMinus = Math.random() < 0.5 ? -1 : 1;
datum.name = stringGen(MIN_LABEL_LENGTH, MAX_LABEL_LENGTH);
datum.value = Math.floor(Math.random() * 600 * plusOrMinus);
data.push(datum);
}
function stringGen(minLength, maxLength) {
var text = "";
var charset = "abcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < getRandomArbitrary(minLength, maxLength); i++) {
text += charset.charAt(Math.floor(Math.random() * charset.length));
}
return text;
}
function getRandomArbitrary(min, max) {
return Math.round(Math.random() * (max - min) + min);
}
var maxLabelLength =d3.max(data.map(function(d){ return d.name.length}));
var marginModifier = maxLabelLength < MAX_LABEL_LENGTH_ALLOWED ? maxLabelLength : MAX_LABEL_LENGTH_ALLOWED;
var margin = {
top: 50,
right: 30,
bottom: 40 + marginModifier * MARGIN_MODIFIER_CONSTANT,
left: 80
};
var marginOverview = {
top: 56 + marginModifier * MARGIN_MODIFIER_CONSTANT,
right: 30,
bottom: 0,
left: 30
};
var width = 400 - margin.left - margin.right;
var heightOverview = 50;
var height = 350 - margin.top - margin.bottom;
var barWidth = width / data.length;
var overviewVisible = true;
if (barWidth > BAR_WIDTH) {
BAR_WIDTH = barWidth * 90 / 100;
BAR_PADDING = barWidth * 10 / 100;
overviewVisible = false;
}
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.range(data.map(function(d, i) {
return i * (BAR_WIDTH + BAR_PADDING);
}));
var y = d3.scale.linear()
.domain([d3.min(data, function(d) {
return d.value;
}), Math.abs(d3.max(data, function(d) {
return d.value;
}))])
.range([height, 0]).nice();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom + heightOverview + marginOverview.top + marginOverview.bottom);
var chart = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var defs = chart.append("defs");
defs.append("clipPath").attr('id', 'chart-clip-path').append('rect')
.attr('width', width) //Set the width of the clipping area
.attr('height', height); // set the height of the clipping area
defs.append("clipPath").attr('id', 'x-axis-clip-path').append('rect')
.attr('width', width) //Set the width of the clipping area
.attr('height', height + margin.bottom); // set the height of the clipping area
var barsGroup = chart.append('g');
barsGroup.attr('clip-path', 'url(#chart-clip-path)');
var xAxisGroup = chart.append("g").attr('class', 'x-axis')
xAxisGroup.append('g')
.attr("class", "x axis")
.attr("transform", "translate(" + (BAR_WIDTH + BAR_PADDING) / 2 + "," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("y", 10)
.attr("x", -10)
.attr("dy", 0)
.attr("transform", "rotate(-60)")
.style("text-anchor", "end")
.text(function (d) {
if(d.length > MAX_LABEL_LENGTH_ALLOWED)
return d.substring(0,MAX_LABEL_LENGTH_ALLOWED)+'...';
else
return d;
});
xAxisGroup.attr('clip-path', 'url(#x-axis-clip-path)');
var yAxisGroup = chart.append("g").attr("class", "y axis")
yAxisGroup.call(yAxis);
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var bars = barsGroup.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.name);
})
.attr("y", function(d) {
return d.value > 0 ? y(d.value) : y(0);
})
.attr("height", function(d) {
return Math.abs(y(d.value) - y(0));
})
.attr("width", BAR_WIDTH)
.on("mouseenter", showTooltip)
.on("touchstart", showTooltip)
.on("mouseleave", hideTooltip)
.on("touchend", hideTooltip);
var xAxisLabel = chart.append("text")
.attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
.attr("transform", "translate("+ (width/2) +","+(height + marginOverview.top )+")") // centre below axis
.text("Name");
var yAxisLabel = chart.append("text")
.attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
.attr("transform", "translate("+ -margin.left / 2 +","+(height/2)+")rotate(-90)") // text is drawn off the screen top left, move down and out and rotate
.text("Value");
if (overviewVisible) {
var zoom = d3.behavior.zoom().scaleExtent([1, 1]);
// var zoomRect = chart.append("rect")
// .attr("width", width)
// .attr("height", height)
// .attr("fill", "none")
// .attr("pointer-events", "all")
// .call(zoom);
var xOverview = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeBands([0, width], BAR_PADDING / BAR_WIDTH, 0);
var yOverview = d3.scale.linear().range([heightOverview, 0]);
yOverview.domain(y.domain());
var overviewGroup = chart.append('g')
.attr('width', width)
.attr('height', heightOverview);
var subBars = overviewGroup.append('g').selectAll('.subBar')
.data(data)
subBars.enter().append("rect")
.classed('subBar', true)
.attr({
height: function(d) {
return Math.abs(yOverview(d.value) - yOverview(0));
},
width: function(d) {
return xOverview.rangeBand()
},
x: function(d) {
return xOverview(d.name);
},
y: function(d) {
return height + marginOverview.top + (d.value > 0 ? yOverview(d.value) : yOverview(0));
}
});
var overviewRect = overviewGroup.append('rect')
.attr('y', height + marginOverview.top)
.attr('width', width)
.attr('height', heightOverview)
.style("opacity", "0")
.style("cursor", "pointer").on("click", click);
var selectorWidth = (width / (BAR_WIDTH) * (xOverview.rangeBand()));
var selector = chart.append("rect")
.attr("class", "mover")
.attr("x", 0)
.attr("y", height + marginOverview.top)
.attr("height", heightOverview)
.attr("width", selectorWidth)
.attr("pointer-events", "all")
.attr("cursor", "ew-resize")
.call(d3.behavior.drag().on("drag", drag));
}
function showTooltip(data)
{
div.style("opacity", .9);
div.html('<div class="tooltip-name">' + data.name + '</div><div class="tooltip-value">Value:' + data.value + '</div>')
.style("left", (d3.event.pageX + 25) + "px")
.style("top", (d3.event.pageY - 25) + "px");
}
function hideTooltip(data)
{
div.style("opacity", 0);
}
function click() {
var newX = null;
var selectorX = null;
var customScale = d3.scale.linear().domain([0, width]).range([0, ((BAR_WIDTH + BAR_PADDING) * data.length)])
selectorX = (d3.event.x - marginOverview.left) - selectorWidth / 2;
newX = customScale(selectorX);
if (selectorX > width - selectorWidth) {
newX = customScale(width - selectorWidth);
selectorX = width - selectorWidth;
} else if (selectorX - (selectorWidth / 2) < 0) {
newX = 0;
selectorX = 0
}
selector.transition().attr("x", selectorX)
bars.transition().duration(300).attr("transform", "translate(" + (-newX) + ",0)");
chart.transition().duration(300).select(".x.axis").attr("transform", "translate(" + -(newX - (BAR_WIDTH + BAR_PADDING) / 2) + "," + (height) + ")");
chart.select(".y.axis").call(yAxis);
var transformX = (-(d3.event.x - selectorWidth) * ((BAR_WIDTH + BAR_PADDING) * data.length) / width);
zoom.translate([-newX, 0])
}
// function zoomed() {
// var t = zoom.translate(),
// tx = t[0],
// ty = t[1];
// var xEndValue = x(data[data.length - 1].name) - (width) + (BAR_WIDTH + BAR_PADDING) + BAR_WIDTH / 2;
// if (tx <= 0 && tx > -xEndValue) {
// bars.attr("transform", "translate(" + d3.event.translate[0] + ",0)");
// chart.select(".x.axis").attr("transform", "translate(" + (d3.event.translate[0] + (BAR_WIDTH + BAR_PADDING) / 2) + "," + (height) + ")");
// chart.select(".y.axis").call(yAxis);
// var selectorX = Math.abs(width * tx / ((BAR_WIDTH + BAR_PADDING) * data.length))
// selector.attr("x", selectorX)
// }
// if (tx < -xEndValue) {
// zoom.translate([-xEndValue, ty])
// }
// if (tx >= 0) {
// zoom.translate([0, ty])
// }
// }
function drag() {
var nx = d3.event.dx;
var t = zoom.translate(),
tx = t[0],
ty = t[1];
var selectorX = parseFloat(selector.attr("x")) + nx
var customScale = d3.scale.linear().domain([0, width]).range([0, ((BAR_WIDTH + BAR_PADDING) * data.length)])
var transformX = customScale(selectorX)
var xEndValue = customScale(xOverview(data[data.length - 1].name)) - customScale(selectorWidth) + BAR_WIDTH
if (transformX < xEndValue && transformX >= 0) {
selector.attr("x", selectorX)
bars.attr("transform", "translate(" + -transformX + ",0)");
chart.select(".x.axis").attr("transform", "translate(" + (-transformX + (BAR_WIDTH + BAR_PADDING) / 2) + "," + (height) + ")");
chart.select(".y.axis").call(yAxis);
zoom.translate([transformX, 0])
}
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment