Renders a simple interactive slider.
Inspired by The New York Times Is It Better to Rent or Buy?
Source code and documentation available on Github.
| license: bsd-3-clause | |
| scrolling: yes | |
| border: no |
Renders a simple interactive slider.
Inspired by The New York Times Is It Better to Rent or Buy?
Source code and documentation available on Github.
| <!DOCTYPE html> | |
| <meta charset="utf-8" /> | |
| <meta | |
| name="viewport" | |
| content="width=device-width, initial-scale=1, shrink-to-fit=no" | |
| /> | |
| <title>d3-simple-slider</title> | |
| <script src="https://d3js.org/d3.v6.min.js"></script> | |
| <script src="https://unpkg.com/d3-simple-slider"></script> | |
| <link | |
| rel="stylesheet" | |
| href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" | |
| integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" | |
| crossorigin="anonymous" | |
| /> | |
| <div class="container"> | |
| <h1>Basic functionality</h1> | |
| <h2>Simple</h2> | |
| <div class="row align-items-center"> | |
| <div class="col-sm-2"><p id="value-simple"></p></div> | |
| <div class="col-sm"><div id="slider-simple"></div></div> | |
| </div> | |
| <h2>Step</h2> | |
| <div class="row align-items-center"> | |
| <div class="col-sm-2"><p id="value-step"></p></div> | |
| <div class="col-sm"><div id="slider-step"></div></div> | |
| </div> | |
| <h2>Time</h2> | |
| <div class="row align-items-center"> | |
| <div class="col-sm-2"><p id="value-time"></p></div> | |
| <div class="col-sm"><div id="slider-time"></div></div> | |
| </div> | |
| <h2>Fill</h2> | |
| <div class="row align-items-center"> | |
| <div class="col-sm-2"><p id="value-fill"></p></div> | |
| <div class="col-sm"><div id="slider-fill"></div></div> | |
| </div> | |
| <h2>Range</h2> | |
| <div class="row align-items-center"> | |
| <div class="col-sm-2"><p id="value-range"></p></div> | |
| <div class="col-sm"><div id="slider-range"></div></div> | |
| </div> | |
| <h2>Vertical</h2> | |
| <div class="row align-items-center"> | |
| <div class="col-sm-2"><p id="value-vertical"></p></div> | |
| <div class="col-sm"><div id="slider-vertical"></div></div> | |
| </div> | |
| <h1>Extended functionality</h1> | |
| <h2>Alternative handle</h2> | |
| <div class="row align-items-center"> | |
| <div class="col-sm-2"><p id="value-alternative-handle"></p></div> | |
| <div class="col-sm"><div id="slider-alternative-handle"></div></div> | |
| </div> | |
| <h2>Transition</h2> | |
| <div class="row align-items-center"> | |
| <div class="col-sm-2"><p id="value-transition"></p></div> | |
| <div class="col-sm"><div id="slider-transition"></div></div> | |
| </div> | |
| <h1>Examples</h1> | |
| <h2>New York Times</h2> | |
| <div class="row align-items-center"> | |
| <div class="col-sm-2"><p id="value-new-york-times"></p></div> | |
| <div class="col-sm"><div id="slider-new-york-times"></div></div> | |
| </div> | |
| <h2>Color picker</h2> | |
| <div class="row align-items-center"> | |
| <div class="col-sm-2"><p id="value-color-picker"></p></div> | |
| <div class="col-sm"><div id="slider-color-picker"></div></div> | |
| </div> | |
| </div> | |
| <script> | |
| // Simple | |
| var data = [0, 0.005, 0.01, 0.015, 0.02, 0.025]; | |
| var sliderSimple = d3 | |
| .sliderBottom() | |
| .min(d3.min(data)) | |
| .max(d3.max(data)) | |
| .width(300) | |
| .tickFormat(d3.format('.2%')) | |
| .ticks(5) | |
| .default(0.015) | |
| .on('onchange', val => { | |
| d3.select('p#value-simple').text(d3.format('.2%')(val)); | |
| }); | |
| var gSimple = d3 | |
| .select('div#slider-simple') | |
| .append('svg') | |
| .attr('width', 500) | |
| .attr('height', 100) | |
| .append('g') | |
| .attr('transform', 'translate(30,30)'); | |
| gSimple.call(sliderSimple); | |
| d3.select('p#value-simple').text(d3.format('.2%')(sliderSimple.value())); | |
| // Step | |
| var sliderStep = d3 | |
| .sliderBottom() | |
| .min(d3.min(data)) | |
| .max(d3.max(data)) | |
| .width(300) | |
| .tickFormat(d3.format('.2%')) | |
| .ticks(5) | |
| .step(0.005) | |
| .default(0.015) | |
| .on('onchange', val => { | |
| d3.select('p#value-step').text(d3.format('.2%')(val)); | |
| }); | |
| var gStep = d3 | |
| .select('div#slider-step') | |
| .append('svg') | |
| .attr('width', 500) | |
| .attr('height', 100) | |
| .append('g') | |
| .attr('transform', 'translate(30,30)'); | |
| gStep.call(sliderStep); | |
| d3.select('p#value-step').text(d3.format('.2%')(sliderStep.value())); | |
| // Time | |
| var dataTime = d3.range(0, 10).map(function(d) { | |
| return new Date(1995 + d, 10, 3); | |
| }); | |
| var sliderTime = d3 | |
| .sliderBottom() | |
| .min(d3.min(dataTime)) | |
| .max(d3.max(dataTime)) | |
| .step(1000 * 60 * 60 * 24 * 365) | |
| .width(300) | |
| .tickFormat(d3.timeFormat('%Y')) | |
| .tickValues(dataTime) | |
| .default(new Date(1998, 10, 3)) | |
| .on('onchange', val => { | |
| d3.select('p#value-time').text(d3.timeFormat('%Y')(val)); | |
| }); | |
| var gTime = d3 | |
| .select('div#slider-time') | |
| .append('svg') | |
| .attr('width', 500) | |
| .attr('height', 100) | |
| .append('g') | |
| .attr('transform', 'translate(30,30)'); | |
| gTime.call(sliderTime); | |
| d3.select('p#value-time').text(d3.timeFormat('%Y')(sliderTime.value())); | |
| // Fill | |
| var sliderFill = d3 | |
| .sliderBottom() | |
| .min(d3.min(data)) | |
| .max(d3.max(data)) | |
| .width(300) | |
| .tickFormat(d3.format('.2%')) | |
| .ticks(5) | |
| .default(0.015) | |
| .fill('#2196f3') | |
| .on('onchange', val => { | |
| d3.select('p#value-fill').text(d3.format('.2%')(val)); | |
| }); | |
| var gFill = d3 | |
| .select('div#slider-fill') | |
| .append('svg') | |
| .attr('width', 500) | |
| .attr('height', 100) | |
| .append('g') | |
| .attr('transform', 'translate(30,30)'); | |
| gFill.call(sliderFill); | |
| d3.select('p#value-fill').text(d3.format('.2%')(sliderFill.value())); | |
| // Range | |
| var sliderRange = d3 | |
| .sliderBottom() | |
| .min(d3.min(data)) | |
| .max(d3.max(data)) | |
| .width(300) | |
| .tickFormat(d3.format('.2%')) | |
| .ticks(5) | |
| .default([0.015, 0.02]) | |
| .fill('#2196f3') | |
| .on('onchange', val => { | |
| d3.select('p#value-range').text(val.map(d3.format('.2%')).join('-')); | |
| }); | |
| var gRange = d3 | |
| .select('div#slider-range') | |
| .append('svg') | |
| .attr('width', 500) | |
| .attr('height', 100) | |
| .append('g') | |
| .attr('transform', 'translate(30,30)'); | |
| gRange.call(sliderRange); | |
| d3.select('p#value-range').text( | |
| sliderRange | |
| .value() | |
| .map(d3.format('.2%')) | |
| .join('-') | |
| ); | |
| // Vertical | |
| var sliderVertical = d3 | |
| .sliderLeft() | |
| .min(d3.min(data)) | |
| .max(d3.max(data)) | |
| .height(300) | |
| .tickFormat(d3.format('.2%')) | |
| .ticks(5) | |
| .default(0.015) | |
| .on('onchange', val => { | |
| d3.select('p#value-vertical').text(d3.format('.2%')(val)); | |
| }); | |
| var gVertical = d3 | |
| .select('div#slider-vertical') | |
| .append('svg') | |
| .attr('width', 100) | |
| .attr('height', 400) | |
| .append('g') | |
| .attr('transform', 'translate(60,30)'); | |
| gVertical.call(sliderVertical); | |
| d3.select('p#value-vertical').text(d3.format('.2%')(sliderVertical.value())); | |
| // Alternative handle | |
| var sliderAlternativeHandle = d3 | |
| .sliderBottom() | |
| .min(d3.min(data)) | |
| .max(d3.max(data)) | |
| .width(300) | |
| .tickFormat(d3.format('.2%')) | |
| .ticks(5) | |
| .default(0.015) | |
| .handle( | |
| d3 | |
| .symbol() | |
| .type(d3.symbolCircle) | |
| .size(200)() | |
| ) | |
| .on('onchange', val => { | |
| d3.select('p#value-alternative-handle').text(d3.format('.2%')(val)); | |
| }); | |
| var g2 = d3 | |
| .select('div#slider-alternative-handle') | |
| .append('svg') | |
| .attr('width', 500) | |
| .attr('height', 100) | |
| .append('g') | |
| .attr('transform', 'translate(30,30)'); | |
| g2.call(sliderAlternativeHandle); | |
| d3.select('p#value-alternative-handle').text(sliderAlternativeHandle.value()); | |
| // Transition | |
| var sliderTransition = d3 | |
| .sliderBottom() | |
| .min(d3.min(data)) | |
| .max(d3.max(data)) | |
| .width(300) | |
| .tickFormat(d3.format('.2%')) | |
| .ticks(5) | |
| .default(0.015) | |
| .on('onchange', val => { | |
| d3.select('p#value-transition').text(d3.format('.2%')(val)); | |
| }); | |
| var gTransition = d3 | |
| .select('div#slider-transition') | |
| .append('svg') | |
| .attr('width', 500) | |
| .attr('height', 100) | |
| .append('g') | |
| .attr('transform', 'translate(30,30)'); | |
| gTransition.call(sliderTransition); | |
| setInterval(() => { | |
| sliderTransition.width(Math.random() * 100 + 200); | |
| gTransition | |
| .transition() | |
| .duration(200) | |
| .call(sliderTransition); | |
| }, 1000); | |
| d3.select('p#value-transition').text( | |
| d3.format('.2%')(sliderTransition.value()) | |
| ); | |
| // New York Times | |
| var width = 565; | |
| var height = 120; | |
| var margin = { top: 20, right: 50, bottom: 50, left: 40 }; | |
| var dataNewYorkTimes = d3.range(1, 41).map(d => ({ | |
| year: d, | |
| value: 10000 * Math.exp(-(d - 1) / 40), | |
| })); | |
| var svg = d3 | |
| .select('div#slider-new-york-times') | |
| .append('svg') | |
| .attr('width', width) | |
| .attr('height', height); | |
| var padding = 0.1; | |
| var xBand = d3 | |
| .scaleBand() | |
| .domain(dataNewYorkTimes.map(d => d.year)) | |
| .range([margin.left, width - margin.right]) | |
| .padding(padding); | |
| var xLinear = d3 | |
| .scaleLinear() | |
| .domain([ | |
| d3.min(dataNewYorkTimes, d => d.year), | |
| d3.max(dataNewYorkTimes, d => d.year), | |
| ]) | |
| .range([ | |
| margin.left + xBand.bandwidth() / 2 + xBand.step() * padding - 0.5, | |
| width - | |
| margin.right - | |
| xBand.bandwidth() / 2 - | |
| xBand.step() * padding - | |
| 0.5, | |
| ]); | |
| var y = d3 | |
| .scaleLinear() | |
| .domain([0, d3.max(dataNewYorkTimes, d => d.value)]) | |
| .nice() | |
| .range([height - margin.bottom, margin.top]); | |
| var yAxis = g => | |
| g | |
| .attr('transform', `translate(${width - margin.right},0)`) | |
| .call( | |
| d3 | |
| .axisRight(y) | |
| .tickValues([1e4]) | |
| .tickFormat(d3.format('($.2s')) | |
| ) | |
| .call(g => g.select('.domain').remove()); | |
| var slider = g => | |
| g.attr('transform', `translate(0,${height - margin.bottom})`).call( | |
| d3 | |
| .sliderBottom(xLinear) | |
| .step(1) | |
| .ticks(4) | |
| .default(9) | |
| .on('onchange', value => draw(value)) | |
| ); | |
| var bars = svg | |
| .append('g') | |
| .selectAll('rect') | |
| .data(dataNewYorkTimes); | |
| var barsEnter = bars | |
| .enter() | |
| .append('rect') | |
| .attr('x', d => xBand(d.year)) | |
| .attr('y', d => y(d.value)) | |
| .attr('height', d => y(0) - y(d.value)) | |
| .attr('width', xBand.bandwidth()); | |
| svg.append('g').call(yAxis); | |
| svg.append('g').call(slider); | |
| svg.select('.track-overlay').attr('stroke-width', 120); // Ensure drag zone covers everything | |
| var draw = selected => { | |
| barsEnter | |
| .merge(bars) | |
| .attr('fill', d => (d.year === selected ? '#bad80a' : '#e0e0e0')); | |
| d3.select('p#value-new-york-times').text( | |
| d3.format('$,.2r')(dataNewYorkTimes[selected - 1].value) | |
| ); | |
| }; | |
| draw(9); | |
| // Color picker | |
| var num2hex = rgb => { | |
| return rgb | |
| .map(color => { | |
| let str = color.toString(16); | |
| if (str.length === 1) { | |
| str = '0' + str; | |
| } | |
| return str; | |
| }) | |
| .join(''); | |
| }; | |
| var rgb = [100, 0, 0]; | |
| var colors = ['red', 'green', 'blue']; | |
| var gColorPicker = d3 | |
| .select('div#slider-color-picker') | |
| .append('svg') | |
| .attr('width', 600) | |
| .attr('height', 400) | |
| .append('g') | |
| .attr('transform', 'translate(30,30)'); | |
| var box = gColorPicker | |
| .append('rect') | |
| .attr('width', 100) | |
| .attr('height', 100) | |
| .attr('transform', 'translate(400,0)') | |
| .attr('fill', `#${num2hex(rgb)}`); | |
| rgb.forEach((color, i) => { | |
| var slider = d3 | |
| .sliderBottom() | |
| .min(0) | |
| .max(255) | |
| .step(1) | |
| .width(300) | |
| .default(rgb[i]) | |
| .displayValue(false) | |
| .fill(colors[i]) | |
| .on('onchange', num => { | |
| rgb[i] = num; | |
| box.attr('fill', `#${num2hex(rgb)}`); | |
| d3.select('p#value-color-picker').text(`#${num2hex(rgb)}`); | |
| }); | |
| gColorPicker | |
| .append('g') | |
| .attr('transform', `translate(30,${60 * i})`) | |
| .call(slider); | |
| }); | |
| d3.select('p#value-color-picker').text(`#${num2hex(rgb)}`); | |
| </script> |