Built with blockbuilder.org
Last active
December 22, 2015 12:05
-
-
Save mmmatthew/3d53a3fbeb1df4b1588e to your computer and use it in GitHub Desktop.
SuperSlider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<title>#########</title> | |
<!-- <link rel="stylesheet" href="d3.slider.css" /> --> | |
<link rel="stylesheet" href="style.css" /> | |
</head> | |
<body> | |
<div class="wrapper"> | |
<h1>Slider DEV</h1> | |
<h2>Reference date: <span class='showDate ref'></span></h2> | |
<h3>Start date: <span class='showDate left'></span></h3> | |
<h3>End date: <span class='showDate right'></span></h3> | |
<input type="radio" name="mode" value="obs" onchange="updateApp(this.value)">OBS<br> | |
<input type="radio" name="mode" value="prev" onchange="updateApp(this.value)">PREV | |
<div id="slider"></div> | |
<div id="sliderState"></div> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<!-- // <script src="d3.slider.js"></script> --> | |
<script src="script.js"></script> | |
<script> | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var opts = { | |
handle:{ | |
height: 12, | |
width:14 | |
}, | |
upper:{ | |
height:35 | |
}, | |
lower:{ | |
height:60 | |
}, | |
margin:{ | |
side:20, | |
top:20, | |
bottom:10 | |
}, | |
axis:{ | |
height:30 | |
}, | |
dataBar:{ | |
height:10, | |
hydro:{ | |
}, | |
meteo:{ | |
} | |
}, | |
workingPeriod:{ | |
height:5 | |
} | |
}; | |
var dFormat = d3.time.format('%a %d %b @ %H:%M:%S'); | |
opts.main={ | |
width: d3.select('#slider')[0][0].offsetWidth-opts.margin.side*2, | |
height:opts.lower.height + opts.upper.height | |
}; | |
opts.holder = { | |
width: opts.main.width+2*opts.margin.side, | |
height: opts.main.height + opts.margin.top + opts.margin.bottom | |
}; | |
var appMode = 'prev'; | |
var slider = {}; | |
var prevs = {}; | |
// init dates | |
var dates = { | |
now: new Date(), | |
ref: new Date(), | |
left: new Date(), | |
right: d3.time.day.offset(new Date(), 1), | |
max: d3.time.day.floor(d3.time.day.offset(new Date(), 3)), | |
min: d3.time.day.floor(d3.time.day.offset(new Date(),-6)), | |
minHydro: d3.time.day.floor(d3.time.day.offset(new Date(),-3)) | |
}; | |
var bounds ={ | |
obs:{ | |
left:{ | |
min:function(){return Math.max(+dates.min,+d3.time.day.offset(dates.ref,-3));}, | |
max:function(){return +d3.time.hour.offset(dates.ref,-1);} | |
}, | |
right:{ | |
min:function(){return Math.max(+dates.minHydro,+d3.time.hour.offset(dates.left,1));}, | |
max:function(){return +dates.now;} | |
}, | |
ref:{ | |
min:function(){return Math.max(+dates.right,+dates.minHydro);}, | |
max:function(){return Math.min(+dates.right,+dates.now);} | |
} | |
}, | |
prev:{ | |
left:{ | |
min:function(){return Math.max(+dates.minHydro,Math.min(+prevs.hydro[0].refTime,+prevs.meteo[0].refTime));}, | |
max:function(){return +d3.time.hour.offset(dates.right,-1);} | |
}, | |
right:{ | |
min:function(){return +d3.time.hour.offset(Math.max(dates.left,Math.min(prevs.hydro[0].refTime,prevs.meteo[0].refTime)) ,1);}, | |
max:function(){return Math.min(+dates.max, Math.max(+prevs.hydro[0].endTime,+prevs.meteo[0].endTime));} | |
}, | |
ref:{ | |
min:function(){return +dates.minHydro;}, | |
max:function(){return +dates.now;} | |
} | |
} | |
} | |
var sliderState = {} | |
// date functions | |
dates.startDate = function() { | |
return new Date(Math.min(dates.right, dates.left)); | |
} | |
dates.endDate = function() { | |
return new Date(Math.max(dates.right, dates.left)); | |
} | |
// Generate dummy data | |
var data = generateTestData(); | |
// Define scale | |
var scale = d3.time.scale() | |
.domain([dates.min, dates.max]) | |
.range([0,opts.main.width]); | |
// create slider | |
slider.holder = d3.select('#slider') | |
.append('svg') | |
.attr('width', opts.main.width+2*opts.margin.side) | |
.attr('height', opts.main.height + opts.margin.top + opts.margin.bottom) | |
slider.main = slider.holder | |
.append('g') | |
.attr('transform', 'translate(' + opts.margin.side + ',' + opts.margin.top + ')') | |
// background | |
var background = slider.holder.append('rect') | |
.attr('class', 'background') | |
.attr('y', opts.margin.top+opts.upper.height) | |
.attr('width', opts.holder.width)//+2*opts.margin.side) | |
.attr('height', opts.main.height)// + opts.margin.top + opts.margin.bottom) | |
// workingPeriod | |
var workingPeriod = slider.main.append('rect') | |
.attr('class', 'workingPeriod') | |
.attr('width', scale(dates.now)-scale(dates.minHydro))// | |
.attr('height', opts.workingPeriod.height)// | |
.attr('x', scale(dates.minHydro)) | |
.attr('y', opts.upper.height-opts.workingPeriod.height); | |
slider.upper = slider.main | |
.append('g') | |
.attr('transform', 'translate(' + 0 + ',' + 0 + ')') | |
slider.lower = slider.main | |
.append('g') | |
.attr('transform', 'translate(' + 0 + ',' + opts.upper.height + ')') | |
// axis | |
var xDateAxis = d3.svg.axis() | |
.scale(scale) | |
.orient('top') | |
.ticks(d3.time.day) | |
.tickSize(15, 10,0) | |
.tickFormat(d3.time.format('%a %d')); | |
var xDateAxisMinor = d3.svg.axis() | |
.scale(scale) | |
.orient('bottom') | |
.ticks(d3.time.hour) | |
.tickSize(5, 10,0) | |
.tickFormat(function(){return '';}); | |
slider.upper.append('g') | |
.attr('transform', 'translate(0,' + opts.upper.height + ')') | |
.attr('class', 'axis date minor') | |
.call(xDateAxisMinor); | |
slider.upper.selectAll('.axis.date.minor line') | |
.attr('y2',function(d){ | |
return -Math.sin(Math.PI*(d.getHours()-6)/12)*6; | |
}); | |
slider.upper.append('g') | |
.attr('transform', 'translate(0,' + opts.upper.height + ')') | |
.attr('class', 'axis date major') | |
.call(xDateAxis); | |
// reference Period | |
var refPeriod = slider.lower.append('rect').attr('class', 'refPeriod'); | |
// group to hold bars | |
var barGroup = slider.lower.append('g'); | |
// hydro text | |
barGroup.append('text').attr('class', 'hydro label'); | |
// meteo text | |
barGroup.append('text').attr('class', 'meteo label'); | |
// reference date text | |
slider.holder.append('text').attr('class', 'refTime label') | |
.attr('y', opts.margin.top-5) | |
.attr('text-anchor','center'); | |
// NOW LINE | |
var nowline = slider.main.selectAll('.nowline').data([dates.now]); | |
nowline.enter() | |
.append('line') | |
.attr('y2',opts.main.height) | |
.attr('x2',0) | |
.attr('transform', function(d){return 'translate(' + scale(d) + ', 0)'}) | |
.attr('class','nowline'); | |
// triangles | |
slider.lower.append('path').attr('class', 'handle left'); | |
slider.lower.append('path').attr('class', 'handle right'); | |
slider.upper.append('path').attr('class', 'handle ref'); | |
// brushing | |
var brushes = {}; | |
slider.brushes = {}; | |
slider.brush = slider.holder.append('rect') | |
.attr('x', 0) | |
.attr('y', 0) | |
.attr('opacity', 0.0) | |
.attr('fill', 'red') | |
.attr('class', 'brush') | |
.attr('width', opts.holder.width) | |
.attr('height', opts.holder.height) | |
.on('mousedown',brushInit) | |
// .on('mouseout',brushEnd) | |
.on('mousemove',brush); | |
brushes.fine = d3.svg.brush() | |
.x(scale) | |
.extent([0, 0]) | |
.on("brush", brushed); | |
slider.brush.call(brushes.fine); | |
updateSlider(); | |
function brush(){ | |
sliderState.mouseX = d3.mouse(this)[0]; | |
sliderState.mouseY = d3.mouse(this)[1]; | |
if (sliderState.mouseY<opts.upper.height+opts.margin.top) { | |
sliderState.mode = 'ref'; | |
}else if(sliderState.mouseX<= scale(dates.left)+opts.margin.side){ | |
sliderState.mode = 'left'; | |
}else if(sliderState.mouseX<= scale(dates.right)+opts.margin.side){ | |
sliderState.mode = 'center'; | |
}else if (sliderState.mouseX > scale(dates.right)+opts.margin.side){ | |
sliderState.mode = 'right'; | |
} | |
updateHandles(); | |
} | |
function brushed() { | |
var value = d3.mouse(this)[0]; | |
var mouseYrel = d3.mouse(this)[1]/opts.main.height; | |
var downGear = Math.max( Math.pow(mouseYrel,2), 1) | |
// if (mouseYrel >1){ | |
// downGear = Math.pow(mouseYrel,2) | |
// } | |
// console.log('pos: '+mouseYrel+' | downGear: '+downGear) | |
if (sliderState.mode=='ref') { | |
if (appMode=='obs') { | |
dates.right = d3.time.hour.round(scale.invert(sliderState.right +(value-sliderState.mouseX)/downGear)); | |
dates.left = d3.time.hour.round(scale.invert(sliderState.left +(value-sliderState.mouseX)/downGear)); | |
} else{ | |
dates.ref = d3.time.hour.round(scale.invert(sliderState.ref +(value-sliderState.mouseX)/downGear)); | |
}; | |
checkDates(['right','left','ref']); | |
}else if(sliderState.mode == 'left'){ | |
dates.left = d3.time.hour.round(scale.invert(sliderState.left +(value-sliderState.mouseX)/downGear)); | |
checkDates(['left','ref','right']); | |
}else if(sliderState.mode == 'center'){ | |
dates.left = d3.time.hour.round(scale.invert(sliderState.left +(value-sliderState.mouseX)/downGear)); | |
checkDates(['left','ref','right']); | |
dates.right = d3.time.hour.round(scale.invert(sliderState.right +(value-sliderState.mouseX)/downGear)); | |
checkDates(['right','ref','left']); | |
}else if (sliderState.mode == 'right'){ | |
dates.right = d3.time.hour.round(scale.invert(sliderState.right +(value-sliderState.mouseX)/downGear)); | |
checkDates(['right','ref','left']); | |
}; | |
// console.log(dFormat(new Date(dates.left)) + ' comp'+ dFormat(new Date(bounds.prev.left.min()))+' - '+dFormat(new Date(bounds.prev.left.max()))); | |
// console.log(dates.left + ' comp'+ bounds.prev.left.min()+' - '+bounds.prev.left.max()); | |
// TODO: Check for chaanges before updating | |
updateSlider(); | |
updateLabels(); | |
} | |
function checkDates (order) { | |
order.forEach(function(sliderName){ | |
// todo: use unupdates values to avoid impossible situations | |
dates[sliderName] = Math.min(Math.max(dates[sliderName], bounds[appMode][sliderName].min()), bounds[appMode][sliderName].max()); | |
}) | |
} | |
function brushInit () { | |
sliderState.left = scale(dates.left); | |
sliderState.right = scale(dates.right); | |
sliderState.ref = scale(dates.ref); | |
} | |
function brushEnd () { | |
sliderState.mode = 'none'; | |
updateHandles(); | |
} | |
function updateSlider () { | |
// filter data | |
if(appMode=='prev'){ | |
prevs.hydro = data.hydro.prev.filter(function(f){ | |
return f.refTime < dates.ref | |
}).slice(-1); | |
var hydro = prevs.hydro; | |
prevs.meteo = data.meteo.prev.filter(function(f){ | |
return f.refTime < dates.ref | |
}).slice(-1); | |
var meteo = prevs.meteo; | |
}else{ | |
var hydro = data.hydro.obs; | |
var meteo = data.meteo.obs; | |
} | |
// Bars | |
var meteoBar = barGroup.selectAll('.meteo.bar').data(meteo, function(d){return d.refTime}); | |
var hydroBar = barGroup.selectAll('.hydro.bar').data(hydro, function(d){return d.refTime}); | |
meteoBar.exit().remove(); | |
hydroBar.exit().remove(); | |
meteoBar.enter() | |
.append('rect') | |
.attr('height',opts.dataBar.height) | |
.attr('y', 30) | |
.attr('class','meteo bar'); | |
meteoBar | |
.attr('width',function(d){return scale(d.endTime)-scale(d.refTime)}) | |
.attr('x', function(d){return scale(d.refTime)}); | |
hydroBar.enter() | |
.append('rect') | |
.attr('height',opts.dataBar.height) | |
.attr('y', 10) | |
.attr('class','hydro bar'); | |
hydroBar | |
.attr('width',function(d){return scale(d.endTime)-scale(d.refTime)}) | |
.attr('x', function(d){return scale(d.refTime)}); | |
// forecast Bar text | |
barGroup.selectAll('.label.meteo').data(meteo.slice(0,1)) | |
.text(function(d){return (appMode=='obs'?'COMBIPRECIP':(d.type.toUpperCase() + ' - ' + d3.time.format('%d %b %H:%M')(d.refTime)))}) | |
.attr('x', function(d){return (appMode=='obs'?scale(dates.now):scale(d.refTime))}) | |
.attr('y', 30+opts.dataBar.height) | |
.attr('text-anchor',function(){return (appMode=='obs'?'start':'end')}); | |
barGroup.selectAll('.label.hydro').data(hydro.slice(0,1)) | |
.text(function(d){return (appMode=='obs'?'MESURES HYDRO':(d.type.toUpperCase() + ' - ' + d3.time.format('%d %b %H:%M')(d.refTime)))}) | |
.attr('x', function(d){return (appMode=='obs'?scale(dates.now):scale(d.refTime))}) | |
.attr('y', 10+opts.dataBar.height) | |
.attr('text-anchor',function(){return (appMode=='obs'?'start':'end')}); | |
// Date labels | |
slider.holder.selectAll('.label.refTime') | |
.text(function(){return d3.time.format('%d %b %H:%M')(new Date(dates.ref))}) | |
.attr('x', function(d){return scale(dates.ref)}); | |
// range indicator | |
slider.lower.selectAll('.refPeriod') | |
.attr('height',opts.lower.height) | |
.attr('width',function(d){return scale(dates.endDate())-scale(dates.startDate())}) | |
.attr('x', function(d){return scale(dates.startDate())}) | |
.attr('y', 0); | |
// triangles | |
var triangle = 'm 0 0 l '+opts.handle.width/2+' 0 l -'+opts.handle.width/2+' -'+opts.handle.height+' l -'+opts.handle.width/2+' '+opts.handle.height+' z' | |
slider.lower.selectAll('.handle.left') | |
.attr('transform', function(){ | |
return 'translate(' + scale(dates.left) +', '+ opts.lower.height+')' | |
}) | |
.attr('d', function(d) { | |
return triangle; | |
}); | |
slider.lower.selectAll('.handle.right') | |
.attr('d', function(d) { | |
return triangle; | |
}) | |
.attr('transform', function(){ | |
return 'translate(' + scale(dates.right) +', '+ opts.lower.height+')' | |
}); | |
slider.upper.selectAll('.handle.ref') | |
.attr('d', function(d) { | |
return triangle; | |
}) | |
.attr('transform', function(){ | |
return 'translate(' + scale(dates.ref) +', 0) rotate(180)' | |
}); | |
} | |
function updateApp (val) { | |
appMode = val; | |
checkDates(['left','right','ref']) | |
updateSlider(); | |
} | |
function updateLabels () { | |
elements = document.getElementsByClassName('showDate'); | |
Array.prototype.forEach.call(elements,function(el){ | |
var type = el.className.split(' ')[1] | |
el.innerHTML = d3.time.format('%a %d %b @ %H:%M:%S')(new Date(dates[type])) | |
}) | |
} | |
function updateHandles(){ | |
var mode = sliderState.mode.replace('center','left,.handle.right') | |
slider.main.selectAll('.handle') | |
.classed('active', false); | |
slider.main.selectAll('.handle.'+mode) | |
.classed('active', true); | |
} | |
function generateTestData () { | |
// dummy forecasts | |
var forecasts = d3.time.scale() | |
.domain([dates.min, dates.now]) | |
.ticks(d3.time.hour, 6) | |
.filter(function(time){ | |
return time.getHours()<=12 | |
}) | |
.map(function (time) { | |
return { | |
refTime: time, | |
endTime: d3.time.hour.offset(time,72), | |
durationHours: 72, | |
type: 'cosmo7', | |
mode:'prev' | |
} | |
}); | |
// dummy combiprecip | |
var cpc = d3.time.scale() | |
.domain([dates.min, dates.now]) | |
.ticks(d3.time.hour, 1) | |
.filter(function (time){ | |
return Math.random()>0.1 && time<d3.time.hour.offset(dates.now,-1); | |
}) | |
.map(function (time) { | |
return { | |
refTime: time, | |
endTime: d3.time.hour.offset(time,1), | |
durationHours: 1, | |
type: 'combiprecip', | |
mode:'obs' | |
} | |
}) | |
// dummy hydroObs | |
var hydro = [{ | |
refTime: dates.minHydro, | |
endTime: dates.now, | |
durationHours: 1, | |
type: 'mesures hydro', | |
mode:'obs' | |
}]; | |
// merge data | |
return { | |
hydro: { | |
obs:hydro, | |
prev:forecasts.slice(0,-1) | |
}, | |
meteo: { | |
prev:forecasts, | |
obs:cpc | |
}, | |
} | |
} | |
function generateMetaData () { | |
return [ | |
{ | |
type: 'hydro', | |
mode: 'obs', | |
name: 'mesures hydro', | |
sourceId: '' | |
},{ | |
type: 'meteo', | |
mode: 'obs', | |
name: 'COMBIPRECIP', | |
sourceId: '' | |
},{ | |
type: 'hydro', | |
mode: 'prev', | |
name: 'RS MINERVE', | |
sourceId: '' | |
},{ | |
type: 'hydro', | |
mode: 'prev', | |
name: 'SwissRivers', | |
sourceId: '' | |
},{ | |
type: 'hydro', | |
mode: 'prev', | |
name: 'WASIM', | |
sourceId: '' | |
},{ | |
type: 'meteo', | |
mode: 'prev', | |
name: 'COSMO-7', | |
sourceId: '' | |
},{ | |
type: 'meteo', | |
mode: 'prev', | |
name: 'COSMO-2', | |
sourceId: '' | |
},{ | |
type: 'meteo', | |
mode: 'obs', | |
name: 'MODIS', | |
sourceId: '' | |
}, | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
font-family: Verdana,Arial,sans-serif; | |
} | |
h2 { | |
font-size: 1.2em; | |
margin: 60px 0 5px 0; | |
} | |
.wrapper { | |
width: 80%; | |
margin-left: auto; | |
margin-right: auto; | |
} | |
.wrapper > div { | |
margin: 35px 0; | |
} | |
#slider { | |
width: 100%; | |
} | |
/* SLIDER PREFERENCES */ | |
.chart { | |
shape-rendering: crispEdges; | |
} | |
.mini text { | |
font: 9px sans-serif; | |
} | |
.main text { | |
font: 12px sans-serif; | |
} | |
.month text { | |
text-anchor: start; | |
} | |
.todayLine { | |
stroke: blue; | |
stroke-width: 1.5; | |
} | |
.axis line, .axis path { | |
stroke: black; | |
} | |
.miniItem { | |
stroke-width: 6; | |
} | |
.future { | |
stroke: gray; | |
fill: #ddd; | |
} | |
.past { | |
stroke: green; | |
fill: lightgreen; | |
} | |
.brush .extent { | |
stroke: gray; | |
fill: blue; | |
fill-opacity: .165; | |
} | |
.date .tick { | |
font-size: 9px; | |
} | |
.nowline { | |
stroke: red; | |
stroke-width: 0.5; | |
} | |
.refPeriod{ | |
fill: grey; | |
opacity: 0.3; | |
} | |
.workingPeriod{ | |
fill: grey; | |
opacity: 0.3; | |
} | |
.background { | |
fill: grey; | |
opacity: 0.1; | |
} | |
.label { | |
font-size: 9px; | |
font-style: italic; | |
} | |
.label.hydro { | |
} | |
.meteo.bar { | |
fill:#6F9768; | |
stroke:#1D4219; | |
stroke-width: 0.5; | |
} | |
.hydro.bar { | |
fill: #618CBD; | |
stroke:#191F42; | |
stroke-width: 0.5; | |
} | |
path.handle { | |
fill: #9A9A9A; | |
stroke: #6A6A6A; | |
stroke-width: 2; | |
stroke-linejoin: round; | |
} | |
path.handle.active { | |
stroke-width: 3.2 ; | |
fill:#E7E7E7; | |
stroke:#DD2727; | |
} | |
rect.brush { | |
cursor:ew-resize; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment