This is a d3 step slider inspired by Mike Bostock's d3 Slider (https://bl.ocks.org/mbostock/6452972). This instance has an addition of a "STEP" value that makes it work just like a jQuery slider initiated with a step value. Change the step to either null or 0 to get the drag value as is!
forked from shashank2104's block: d3 Step Slider
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="UTF-8">
<title>d3 Slider</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
#chart {
border: 1px solid #ccc;
}
#chart svg line.track {
stroke-opacity: 0.4;
stroke: #000;
stroke-width: 10px;
}
#chart svg line.track-inset {
stroke: steelblue;
stroke-width: 5px;
}
#chart svg line.track-overlay {
stroke: #e73a4e;
stroke-width: 15px;
stroke-opacity: 0.3;
cursor: crosshair;
}
#chart svg .handle {
fill: red;
stroke: #000;
}
#chart svg line.track-overlay, #chart svg line.track, #chart svg line.track-inset {
stroke-linecap: round;
}
</style>
</head>
<body>
<div id="chart">
</div>
<script type="text/javascript">
var margin = {left: 30, right: 30},
width = 260,
height = 200,
range = [70, 130],
step = 10; // change the step and if null, it'll switch back to a normal slider
// append svg
var svg = d3.select('div#chart').append('svg')
.attr('width', width)
.attr('height', height);
var slider = svg.append('g')
.classed('slider', true)
.attr('transform', 'translate(' + margin.left +', '+ (height/2) + ')');
// using clamp here to avoid slider exceeding the range limits
var xScale = d3.scaleLinear()
.domain(range)
.range([0, width - margin.left - margin.right])
.clamp(true);
// array useful for step sliders
var rangeValues = d3.range(range[0], range[1], step || 1).concat(range[1]);
var xAxis = d3.axisBottom(xScale).tickValues(rangeValues).tickFormat(function (d) {
return d;
});
xScale.clamp(true);
// drag behavior initialization
var drag = d3.drag()
.on('start.interrupt', function () {
slider.interrupt();
}).on('start drag', function () {
dragged(d3.event.x);
});
// this is the main bar with a stroke (applied through CSS)
var track = slider.append('line').attr('class', 'track')
.attr('x1', xScale.range()[0])
.attr('x2', xScale.range()[1]);
// this is a bar (steelblue) that's inside the main "track" to make it look like a rect with a border
var trackInset = d3.select(slider.node().appendChild(track.node().cloneNode())).attr('class', 'track-inset');
var ticks = slider.append('g').attr('class', 'ticks').attr('transform', 'translate(0, 4)')
.call(xAxis);
// drag handle
var handle = slider.append('circle').classed('handle', true)
.attr('r', 8);
// this is the bar on top of above tracks with stroke = transparent and on which the drag behaviour is actually called
// try removing above 2 tracks and play around with the CSS for this track overlay, you'll see the difference
var trackOverlay = d3.select(slider.node().appendChild(track.node().cloneNode())).attr('class', 'track-overlay')
.call(drag);
// text to display
var text = svg.append('text').attr('transform', 'translate(' + (width/2) + ', ' + height/3 + ')')
.text('Value: 0');
// initial transition
slider.transition().duration(1050)
.tween("drag", function () {
var i = d3.interpolate(0, 14);
return function (t) {
dragged(xScale(i(t)));
}
});
function dragged(value) {
var x = xScale.invert(value), index = null, midPoint, cx, xVal;
if(step) {
// if step has a value, compute the midpoint based on range values and reposition the slider based on the mouse position
for (var i = 0; i < rangeValues.length - 1; i++) {
if (x >= rangeValues[i] && x <= rangeValues[i + 1]) {
index = i;
break;
}
}
midPoint = (rangeValues[index] + rangeValues[index + 1]) / 2;
if (x < midPoint) {
cx = xScale(rangeValues[index]);
xVal = rangeValues[index];
} else {
cx = xScale(rangeValues[index + 1]);
xVal = rangeValues[index + 1];
}
} else {
// if step is null or 0, return the drag value as is
cx = xScale(x);
xVal = x.toFixed(3);
}
// use xVal as drag value
handle.attr('cx', cx);
text.text('Value: ' + xVal);
}
</script>
</body>
</html>
https://d3js.org/d3.v4.min.js