Built with blockbuilder.org
forked from cdagli's block: fresh block
forked from cdagli's block: Scroll Bar Chart (Using Zoom)
license: mit |
Built with blockbuilder.org
forked from cdagli's block: fresh block
forked from cdagli's block: Scroll Bar Chart (Using Zoom)
<!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 = 66; | |
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", function(d) { | |
div.style("opacity", .9); | |
div.html('<div class="tooltip-name">' + d.name + '</div><div class="tooltip-value">Value:' + d.value + '</div>') | |
.style("left", (d3.event.pageX + 25) + "px") | |
.style("top", (d3.event.pageY - 25) + "px"); | |
}) | |
.on("mouseleave", function(d) { | |
div.style("opacity", 0); | |
}); | |
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 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> |