Built with blockbuilder.org
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="d3.v3.min.js"></script>
<script src="d3.slider.js"></script>
<link rel="stylesheet" href="d3.slider.css" />
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0;background:black;color:white;font: 10px sans-serif; }
path#spiral1fixed,
path#spiral2fixed,
path#spiral1,
path#spiral2 {
fill: none;
stroke: #222;
//stroke: none;
stroke-width: 3px;
}
path#spiral1fixed,
path#spiral2fixed {
display: none;
}
svg {
float: left;
}
</style>
</head>
<body>
<script>
var width = 500,
height = 300
start = 0 // for the spiral
end = 2; // for the spiral
var center = { x: width/2, y: height/2};
var dotWidth = d3.min([width,height])/50; // size of dots relative to view
var step = dotWidth * 3; // spacing relative to dot size
var offset = 42; //initial step
var numDots = 7;
var numDotsMax = 50;
var decay = 1; // to make the dots smaller after each step
var centralRatio = 2; // how big the centralDot is relative to other dots
var tightness = 1; // how tight the spiral is
var rotate = 30; // Initial rotation
// for rotating the view in "3d" space
var spinAngle = 50; // between -180 and 180
var tiltAngle = 40;//45; // between 0 and 90
var params = {
numDots: {
value: 7,
min: 0,
max: numDotsMax,
step: 1
},
dotWidth: { // width of dots
value: 6,
min: 0,
max: 100,
step: null
},
step: { // spacing of dots along arms
value: 18,
min: 0,
max: 100,
step: 1
},
offset: { // initial step away from the galactic center
value: 42,
min: 0,
max: 300,
step: 1
},
rotate: { // initial rotation of the galaxy on its axis
value: 30,
min: 0,
max: 1800,
step: 1
},
spinAngle: { // for rotating the view in "3d" space
value: 50,
min: 0,
max: 90,
step: 1
},
tiltAngle: { // for rotating the view in "3d" space
value: 40,
min: 0,
max: 90,
step: 1
},
centralRatio: {// how big the centralDot is relative to other dots
value: 2,
min: 0,
max: 5,
step: null
},
tightness: { // how tight the spiral is
value: 1,
min: 0,
max: 5,
step: null
},
decay: { // to make the dots smaller after each step
value: 1,
min: 0,
max: 5,
step: null
}
};
// create sliders
d3.keys(params).forEach(function(key) {
params[key].slider = d3.slider()
.axis(d3.svg.axis().ticks(0))
.min(params[key].min)
.max(params[key].max)
.value(params[key].value)
.on("slide", function(evt, value) {
console.log("slide",key,value);
params[key].value = value;
redraw();
});
if (params[key].step)
params[key].slider.step(params[key].step);
});
var numDotsSlider = d3.slider().axis(d3.svg.axis().ticks(0)).min(0).max(30).step(1).value(numDots);
var dotWidthSlider = d3.slider().axis(d3.svg.axis().ticks(0)).min(0).max(100).step(1).value(dotWidth);
var stepSlider = d3.slider().axis(d3.svg.axis().ticks(0)).min(0).max(100).step(1).value(step);
var offsetSlider = d3.slider().axis(d3.svg.axis().ticks(0)).min(0).max(300).step(1).value(offset);
var rotateSlider = d3.slider().axis(d3.svg.axis().ticks(0)).min(0).max(1800).step(1).value(rotate);
var spinSlider = d3.slider().axis(d3.svg.axis().ticks(0)).min(0).max(90).step(1).value(spinAngle);
var tiltSlider = d3.slider().axis(d3.svg.axis().ticks(0)).min(0).max(90).step(1).value(tiltAngle);
var centralSlider = d3.slider().axis(d3.svg.axis().ticks(0)).min(0).max(5).step(null).value(centralRatio);
var tightnessSlider = d3.slider().axis(d3.svg.axis().ticks(0)).min(0).max(5).step(null).value(tightness);
var decaySlider = d3.slider().axis(d3.svg.axis().ticks(0)).min(0).max(2).step(null).value(decay);
var theta = function(r) {
return -2*Math.PI*r*params.tightness.value;
};
var radius = d3.scale.linear()
.domain([start, end])
.range([0, d3.min([width,height])]);
var spiralLine1 = d3.svg.line()
.x(function(d) {
return getGalacticX(d)
*(90-spinAngle)/90 // should include cos or sin
+(getGalacticY(d)*(tiltAngle)/90); // should include cos or sin
})
.y(function(d) {
return getGalacticY(d)
*(90-tiltAngle)/90; // should include cos or sin
})
var spiralLine2 = d3.svg.line()
.x(function(d) {
return getGalacticX(d, 'left')
*(90-spinAngle)/90 // should include cos or sin
+(getGalacticY(d, 'left')*(tiltAngle)/90); // should include cos or sin
})
.y(function(d) {
return getGalacticY(d, 'left')
*(90-tiltAngle)/90; // should include cos or sin
})
var spiralLineFixed1 = d3.svg.line()
.x(function(d) { return getGalacticX(d); })
.y(function(d) { return getGalacticY(d); })
var spiralLineFixed2 = d3.svg.line()
.x(function(d) { return getGalacticX(d, 'left'); })
.y(function(d) { return getGalacticY(d, 'left'); })
// GalacticX and GalacticY are relative to a 0,0 at the center of the galaxy
getGalacticX = function(d, arm = 'right') {
var extraRotation = (arm == 'left') ? 0 : Math.PI;
return radius(d)*Math.cos((rotate/180)+extraRotation+theta(d));
}
getGalacticY = function(d, arm = 'right') {
var extraRotation = (arm == 'left') ? 0 : Math.PI;
return radius(d)*Math.sin((rotate/180)+extraRotation+theta(d));
}
var values = [...Array(numDotsMax).keys()];
var pieces = d3.range(start, end+0.001, (end-start)/1000);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var spiral1 = svg.append("g").selectAll("#spiral1")
.data([pieces])
.enter().append("path")
.attr("id", "spiral1")
.attr("d", spiralLine1)
.attr("transform",
"translate(" + center.x + "," + center.y +") "
);
var spiral2 = svg.append("g").selectAll("#spiral2")
.data([pieces])
.enter().append("path")
.attr("id", "spiral2")
.attr("d", spiralLine2)
.attr("transform",
"translate(" + center.x + "," + center.y +") "
);
var spiral1fixed = svg.append("g").selectAll("#spiral1fixed")
.data([pieces])
.enter().append("path")
.attr("id", "spiral1fixed")
.attr("d", spiralLineFixed1)
.attr("transform",
"translate(" + center.x + "," + center.y +") "
);
var spiral2fixed = svg.append("g").selectAll("#spiral2")
.data([pieces])
.enter().append("path")
.attr("id", "spiral2fixed")
.attr("d", spiralLineFixed2)
.attr("transform",
"translate(" + center.x + "," + center.y +") "
);
// Center dot:
var centralDot = svg.append("g")
.append("circle")
.attr("fill","#ccc")
//.attr("fill","none")
//.attr("stroke","white")
.attr("cx", center.x)
.attr("cy", center.y)
.attr("r", centralRatio*dotWidth);
/*
var spiral1Circles = svg.append("g")
.selectAll("circle")
.attr("class", "circles1")
.data(values)
.enter()
.append("circle")
.attr("fill","none")
.attr("stroke", function(d,i) { return i < numDots ? "white" : "none"; })
.attr("cx", function(d,i) {return spiral1.node().getPointAtLength(offset+step*i).x;})
.attr("cy", function(d,i) {return spiral1.node().getPointAtLength(offset+step*i).y;})
.attr("r", function(d,i) {
var radius = dotWidth - i*decay;
return radius > 0 ? radius : 0;
})
.attr("transform",
"translate(" + center.x + "," + center.y +") "
);
var spiral2Circles = svg.append("g")
.selectAll("circle")
.data(values)
.enter()
.append("circle")
.attr("class", "circles2")
.attr("fill","none")
.attr("stroke", function(d,i) { return i < numDots ? "white" : "none"; })
.attr("cx", function(d,i) {return spiral2.node().getPointAtLength(offset+step*i).x;})
.attr("cy", function(d,i) {return spiral2.node().getPointAtLength(offset+step*i).y;})
.attr("r", function(d,i) {
var radius = dotWidth - i*decay;
return radius > 0 ? radius : 0;
})
.attr("transform",
"translate(" + center.x + "," + center.y +") "
);
*/
var spiral1CirclesFixed = svg.append("g")
.selectAll("circle")
.attr("class", "circles1")
.data(values)
.enter()
.append("circle")
.attr("fill","none")
.attr("stroke", function(d,i) { return i < numDots ? "white" : "none"; })
.attr("cx", function(d,i) {
return spiral1fixed.node().getPointAtLength(offset+step*i).x
*(90-spinAngle)/90
+(spiral1fixed.node().getPointAtLength(offset+step*i).y*(tiltAngle)/90);
})
.attr("cy", function(d,i) {
return spiral1fixed.node().getPointAtLength(offset+step*i).y
*(90-tiltAngle)/90;
})
.attr("r", function(d,i) {
var radius = dotWidth - i*decay;
return radius > 0 ? radius : 0;
})
.attr("transform",
"translate(" + center.x + "," + center.y +") "
);
var spiral2CirclesFixed = svg.append("g")
.selectAll("circle")
.data(values)
.enter()
.append("circle")
.attr("class", "circles2")
.attr("fill","none")
.attr("stroke", function(d,i) { return i < numDots ? "white" : "none"; })
.attr("cx", function(d,i) {
return spiral2fixed.node().getPointAtLength(offset+step*i).x
*(90-spinAngle)/90
+(spiral2fixed.node().getPointAtLength(offset+step*i).y*(tiltAngle)/90);
})
.attr("cy", function(d,i) {
return spiral2fixed.node().getPointAtLength(offset+step*i).y
*(90-tiltAngle)/90;
})
.attr("r", function(d,i) {
var radius = dotWidth - i*decay;
return radius > 0 ? radius : 0;
})
.attr("transform",
"translate(" + center.x + "," + center.y +") "
);
function redraw() {
centralDot.attr("r", centralRatio*dotWidth);
spiral1.attr("d", spiralLine1);
spiral2.attr("d", spiralLine2);
spiral1fixed.attr("d", spiralLineFixed1);
spiral2fixed.attr("d", spiralLineFixed2);
/*
spiral1Circles
.attr("stroke", function(d,i) { return i < numDots ? "white" : "none"; })
.attr("r", function(d,i) {
var radius = dotWidth - i*decay;
return radius > 0 ? radius : 0;
})
.attr("cx", function(d,i) {return spiral1.node().getPointAtLength(offset+step*i).x;})
.attr("cy", function(d,i) {return spiral1.node().getPointAtLength(offset+step*i).y;});
spiral2Circles
.attr("stroke", function(d,i) { return i < numDots ? "white" : "none"; })
.attr("r", function(d,i) {
var radius = dotWidth - i*decay;
return radius > 0 ? radius : 0;
})
.attr("cx", function(d,i) {return spiral2.node().getPointAtLength(offset+step*i).x;})
.attr("cy", function(d,i) {return spiral2.node().getPointAtLength(offset+step*i).y;});
*/
spiral1CirclesFixed
.attr("stroke", function(d,i) { return i < numDots ? "white" : "none"; })
.attr("cx", function(d,i) {
return spiral1fixed.node().getPointAtLength(offset+step*i).x
*(90-spinAngle)/90
+(spiral1fixed.node().getPointAtLength(offset+step*i).y*(tiltAngle)/90);
})
.attr("cy", function(d,i) {
return spiral1fixed.node().getPointAtLength(offset+step*i).y
*(90-tiltAngle)/90;
})
.attr("r", function(d,i) {
var radius = dotWidth - i*decay;
return radius > 0 ? radius : 0;
});
spiral2CirclesFixed
.attr("stroke", function(d,i) { return i < numDots ? "white" : "none"; })
.attr("cx", function(d,i) {
return spiral2fixed.node().getPointAtLength(offset+step*i).x
*(90-spinAngle)/90
+(spiral2fixed.node().getPointAtLength(offset+step*i).y*(tiltAngle)/90);
})
.attr("cy", function(d,i) {
return spiral2fixed.node().getPointAtLength(offset+step*i).y
*(90-tiltAngle)/90;
})
.attr("r", function(d,i) {
var radius = dotWidth - i*decay;
return radius > 0 ? radius : 0;
});
}
numDotsSlider.on("slide", function(evt, value) { numDots = value; redraw(); });
dotWidthSlider.on("slide", function(evt, value) { dotWidth = value; redraw(); });
stepSlider.on("slide", function(evt, value) { step = value; redraw(); });
offsetSlider.on("slide", function(evt, value) { offset = value; redraw(); });
rotateSlider.on("slide", function(evt, value) { rotate = value; redraw(); });
spinSlider.on("slide", function(evt, value) { spinAngle = value; redraw(); });
tiltSlider.on("slide", function(evt, value) { tiltAngle = value; redraw(); });
centralSlider.on("slide", function(evt, value) { centralRatio = value; redraw(); });
tightnessSlider.on("slide", function(evt, value) { tightness = value; redraw(); });
decaySlider.on("slide", function(evt, value) { decay = value; redraw(); });
d3.select("body").append("text").text("numDots").style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(numDotsSlider).style("width","50%").style("float","left");
d3.select("body").append("text").text("dotWidth").style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(dotWidthSlider).style("width","50%").style("float","left");
d3.select("body").append("text").text("centerRatio").style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(centralSlider).style("width","50%").style("float","left");
d3.select("body").append("text").text("step").style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(stepSlider).style("width","50%").style("float","left");
d3.select("body").append("text").text("decay").style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(decaySlider).style("width","50%").style("float","left");
d3.select("body").append("text").text("offset").style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(offsetSlider).style("width","50%").style("float","left");
d3.select("body").append("text").text("tightness").style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(tightnessSlider).style("width","50%").style("float","left");
d3.select("body").append("text").text("rotate").style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(rotateSlider).style("width","50%").style("float","left");
d3.select("body").append("text").text("spin").style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(spinSlider).style("width","50%").style("float","left");
d3.select("body").append("text").text("tilt").style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(tiltSlider).style("width","50%").style("float","left");
//When I finish with the parameterization:
/*
d3.keys(params).forEach(function(key) {
d3.select("body").append("text").text(key).style("width","10%").style("float","left").style("clear","both");
d3.select("body").append("div").call(params[key].slider).style("width","50%").style("float","left");
});
*/
</script>
</body>
.d3-slider {
position: relative;
font-family: Verdana,Arial,sans-serif;
font-size: 1.1em;
border: 1px solid #aaaaaa;
z-index: 2;
}
.d3-slider-horizontal {
height: .8em;
}
.d3-slider-range {
background:#2980b9;
left:0px;
right:0px;
height: 0.8em;
position: absolute;
}
.d3-slider-range-vertical {
background:#2980b9;
left:0px;
right:0px;
position: absolute;
top:0;
}
.d3-slider-vertical {
width: .8em;
height: 100px;
}
.d3-slider-handle {
position: absolute;
width: 1.2em;
height: 1.2em;
border: 1px solid #d3d3d3;
border-radius: 4px;
background: #eee;
background: linear-gradient(to bottom, #eee 0%, #ddd 100%);
z-index: 3;
}
.d3-slider-handle:hover {
border: 1px solid #999999;
}
.d3-slider-horizontal .d3-slider-handle {
top: -.3em;
margin-left: -.6em;
}
.d3-slider-axis {
position: relative;
z-index: 1;
}
.d3-slider-axis-bottom {
top: .8em;
}
.d3-slider-axis-right {
left: .8em;
}
.d3-slider-axis path {
stroke-width: 0;
fill: none;
}
.d3-slider-axis line {
fill: none;
stroke: #aaa;
shape-rendering: crispEdges;
}
.d3-slider-axis text {
font-size: 11px;
}
.d3-slider-vertical .d3-slider-handle {
left: -.25em;
margin-left: 0;
margin-bottom: -.6em;
}
/*
D3.js Slider
Inspired by jQuery UI Slider
Copyright (c) 2013, Bjorn Sandvik - http://blog.thematicmapping.org
BSD license: http://opensource.org/licenses/BSD-3-Clause
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['d3'], factory);
} else if (typeof exports === 'object') {
if (process.browser) {
// Browserify. Import css too using cssify.
require('./d3.slider.css');
}
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require('d3'));
} else {
// Browser globals (root is window)
root.d3.slider = factory(root.d3);
}
}(this, function (d3) {
return function module() {
"use strict";
// Public variables width default settings
var min = 0,
max = 100,
step = 0.01,
animate = true,
orientation = "horizontal",
axis = false,
margin = 50,
value,
active = 1,
snap = false,
scale;
// Private variables
var axisScale,
dispatch = d3.dispatch("slide", "slideend"),
formatPercent = d3.format(".2%"),
tickFormat = d3.format(".0"),
handle1,
handle2 = null,
divRange,
sliderLength;
function slider(selection) {
selection.each(function() {
// Create scale if not defined by user
if (!scale) {
scale = d3.scale.linear().domain([min, max]);
}
// Start value
value = value || scale.domain()[0];
// DIV container
var div = d3.select(this).classed("d3-slider d3-slider-" + orientation, true);
var drag = d3.behavior.drag();
drag.on('dragend', function () {
dispatch.slideend(d3.event, value);
})
// Slider handle
//if range slider, create two
// var divRange;
if (toType(value) == "array" && value.length == 2) {
handle1 = div.append("a")
.classed("d3-slider-handle", true)
.attr("xlink:href", "#")
.attr('id', "handle-one")
.on("click", stopPropagation)
.call(drag);
handle2 = div.append("a")
.classed("d3-slider-handle", true)
.attr('id', "handle-two")
.attr("xlink:href", "#")
.on("click", stopPropagation)
.call(drag);
} else {
handle1 = div.append("a")
.classed("d3-slider-handle", true)
.attr("xlink:href", "#")
.attr('id', "handle-one")
.on("click", stopPropagation)
.call(drag);
}
// Horizontal slider
if (orientation === "horizontal") {
div.on("click", onClickHorizontal);
if (toType(value) == "array" && value.length == 2) {
divRange = d3.select(this).append('div').classed("d3-slider-range", true);
handle1.style("left", formatPercent(scale(value[ 0 ])));
divRange.style("left", formatPercent(scale(value[ 0 ])));
drag.on("drag", onDragHorizontal);
var width = 100 - parseFloat(formatPercent(scale(value[ 1 ])));
handle2.style("left", formatPercent(scale(value[ 1 ])));
divRange.style("right", width+"%");
drag.on("drag", onDragHorizontal);
} else {
handle1.style("left", formatPercent(scale(value)));
drag.on("drag", onDragHorizontal);
}
sliderLength = parseInt(div.style("width"), 10);
} else { // Vertical
div.on("click", onClickVertical);
drag.on("drag", onDragVertical);
if (toType(value) == "array" && value.length == 2) {
divRange = d3.select(this).append('div').classed("d3-slider-range-vertical", true);
handle1.style("bottom", formatPercent(scale(value[ 0 ])));
divRange.style("bottom", formatPercent(scale(value[ 0 ])));
drag.on("drag", onDragVertical);
var top = 100 - parseFloat(formatPercent(scale(value[ 1 ])));
handle2.style("bottom", formatPercent(scale(value[ 1 ])));
divRange.style("top", top+"%");
drag.on("drag", onDragVertical);
} else {
handle1.style("bottom", formatPercent(scale(value)));
drag.on("drag", onDragVertical);
}
sliderLength = parseInt(div.style("height"), 10);
}
if (axis) {
createAxis(div);
}
function createAxis(dom) {
// Create axis if not defined by user
if (typeof axis === "boolean") {
axis = d3.svg.axis()
.ticks(Math.round(sliderLength / 100))
.tickFormat(tickFormat)
.orient((orientation === "horizontal") ? "bottom" : "right");
}
// Copy slider scale to move from percentages to pixels
axisScale = scale.ticks ? scale.copy().range([0, sliderLength]) : scale.copy().rangePoints([0, sliderLength], 0.5);
axis.scale(axisScale);
// Create SVG axis container
var svg = dom.append("svg")
.classed("d3-slider-axis d3-slider-axis-" + axis.orient(), true)
.on("click", stopPropagation);
var g = svg.append("g");
// Horizontal axis
if (orientation === "horizontal") {
svg.style("margin-left", -margin + "px");
svg.attr({
width: sliderLength + margin * 2,
height: margin
});
if (axis.orient() === "top") {
svg.style("top", -margin + "px");
g.attr("transform", "translate(" + margin + "," + margin + ")");
} else { // bottom
g.attr("transform", "translate(" + margin + ",0)");
}
} else { // Vertical
svg.style("top", -margin + "px");
svg.attr({
width: margin,
height: sliderLength + margin * 2
});
if (axis.orient() === "left") {
svg.style("left", -margin + "px");
g.attr("transform", "translate(" + margin + "," + margin + ")");
} else { // right
g.attr("transform", "translate(" + 0 + "," + margin + ")");
}
}
g.call(axis);
}
function onClickHorizontal() {
if (toType(value) != "array") {
var pos = Math.max(0, Math.min(sliderLength, d3.event.offsetX || d3.event.layerX));
moveHandle(scale.invert ?
stepValue(scale.invert(pos / sliderLength))
: nearestTick(pos / sliderLength));
}
}
function onClickVertical() {
if (toType(value) != "array") {
var pos = sliderLength - Math.max(0, Math.min(sliderLength, d3.event.offsetY || d3.event.layerY));
moveHandle(scale.invert ?
stepValue(scale.invert(pos / sliderLength))
: nearestTick(pos / sliderLength));
}
}
function onDragHorizontal() {
if ( d3.event.sourceEvent.target.id === "handle-one") {
active = 1;
} else if ( d3.event.sourceEvent.target.id == "handle-two" ) {
active = 2;
}
var pos = Math.max(0, Math.min(sliderLength, d3.event.x));
moveHandle(scale.invert ?
stepValue(scale.invert(pos / sliderLength))
: nearestTick(pos / sliderLength));
}
function onDragVertical() {
if ( d3.event.sourceEvent.target.id === "handle-one") {
active = 1;
} else if ( d3.event.sourceEvent.target.id == "handle-two" ) {
active = 2;
}
var pos = sliderLength - Math.max(0, Math.min(sliderLength, d3.event.y))
moveHandle(scale.invert ?
stepValue(scale.invert(pos / sliderLength))
: nearestTick(pos / sliderLength));
}
function stopPropagation() {
d3.event.stopPropagation();
}
});
}
// Move slider handle on click/drag
function moveHandle(newValue) {
var currentValue = toType(value) == "array" && value.length == 2 ? value[active - 1]: value,
oldPos = formatPercent(scale(stepValue(currentValue))),
newPos = formatPercent(scale(stepValue(newValue))),
position = (orientation === "horizontal") ? "left" : "bottom";
if (oldPos !== newPos) {
if (toType(value) == "array" && value.length == 2) {
value[ active - 1 ] = newValue;
if (d3.event) {
dispatch.slide(d3.event, value );
};
} else {
if (d3.event) {
dispatch.slide(d3.event.sourceEvent || d3.event, value = newValue);
};
}
if ( value[ 0 ] >= value[ 1 ] ) return;
if ( active === 1 ) {
if (toType(value) == "array" && value.length == 2) {
(position === "left") ? divRange.style("left", newPos) : divRange.style("bottom", newPos);
}
if (animate) {
handle1.transition()
.styleTween(position, function() { return d3.interpolate(oldPos, newPos); })
.duration((typeof animate === "number") ? animate : 250);
} else {
handle1.style(position, newPos);
}
} else {
var width = 100 - parseFloat(newPos);
var top = 100 - parseFloat(newPos);
(position === "left") ? divRange.style("right", width + "%") : divRange.style("top", top + "%");
if (animate) {
handle2.transition()
.styleTween(position, function() { return d3.interpolate(oldPos, newPos); })
.duration((typeof animate === "number") ? animate : 250);
} else {
handle2.style(position, newPos);
}
}
}
}
// Calculate nearest step value
function stepValue(val) {
if (val === scale.domain()[0] || val === scale.domain()[1]) {
return val;
}
var alignValue = val;
if (snap) {
alignValue = nearestTick(scale(val));
} else{
var valModStep = (val - scale.domain()[0]) % step;
alignValue = val - valModStep;
if (Math.abs(valModStep) * 2 >= step) {
alignValue += (valModStep > 0) ? step : -step;
}
};
return alignValue;
}
// Find the nearest tick
function nearestTick(pos) {
var ticks = scale.ticks ? scale.ticks() : scale.domain();
var dist = ticks.map(function(d) {return pos - scale(d);});
var i = -1,
index = 0,
r = scale.ticks ? scale.range()[1] : scale.rangeExtent()[1];
do {
i++;
if (Math.abs(dist[i]) < r) {
r = Math.abs(dist[i]);
index = i;
};
} while (dist[i] > 0 && i < dist.length - 1);
return ticks[index];
};
// Return the type of an object
function toType(v) {
return ({}).toString.call(v).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
};
// Getter/setter functions
slider.min = function(_) {
if (!arguments.length) return min;
min = _;
return slider;
};
slider.max = function(_) {
if (!arguments.length) return max;
max = _;
return slider;
};
slider.step = function(_) {
if (!arguments.length) return step;
step = _;
return slider;
};
slider.animate = function(_) {
if (!arguments.length) return animate;
animate = _;
return slider;
};
slider.orientation = function(_) {
if (!arguments.length) return orientation;
orientation = _;
return slider;
};
slider.axis = function(_) {
if (!arguments.length) return axis;
axis = _;
return slider;
};
slider.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return slider;
};
slider.value = function(_) {
if (!arguments.length) return value;
if (value) {
moveHandle(stepValue(_));
};
value = _;
return slider;
};
slider.snap = function(_) {
if (!arguments.length) return snap;
snap = _;
return slider;
};
slider.scale = function(_) {
if (!arguments.length) return scale;
scale = _;
return slider;
};
d3.rebind(slider, dispatch, "on");
return slider;
}
}));