Skip to content

Instantly share code, notes, and snippets.

@rleddy
Last active August 17, 2016 03:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rleddy/f06005e9b6b132ece4ac4d4d0efbc375 to your computer and use it in GitHub Desktop.
Save rleddy/f06005e9b6b132ece4ac4d4d0efbc375 to your computer and use it in GitHub Desktop.
This is a date range picker done totally in SVG. This is known to work in Google Chrome at this time.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
pointer-events: all;
fill: none;
stroke: #666;
stroke-opacity: 0.2;
}
.active square {
stroke: #000;
stroke-width: 2px;
}
html, body { margin:0; padding:0; overflow:hidden }
</style>
<style>
.ticks {
font: 10px sans-serif;
}
.track,
.track-inset,
.track-overlay {
stroke-linecap: round;
}
.track {
stroke: #000;
stroke-opacity: 0.3;
stroke-width: 10px;
}
.track-inset {
stroke: #ddd;
stroke-width: 8px;
}
.track-overlay {
pointer-events: stroke;
stroke-width: 50px;
cursor: crosshair;
}
.handle {
fill: #fff;
stroke: #000;
stroke-opacity: 0.5;
stroke-width: 1.25px;
}
</style>
<svg id="mainsvg" width="0" height="0" ></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var sliderHandleMap = {}; /// a quick fix
var sliderFunctionMap = {};
var gCurrentSlider = "";
var g_calRange = {
"IntervalStart" : {
"year" : "2016",
"month" : "August",
"day" : 1,
"CalHours" : 0,
"CalMinutes" : 0,
"CalSeconds" : 0,
"exclude" : "IntervalEnd",
"exclOp" : function (a,b) { return( a <= b ); },
"time" : null
},
"IntervalEnd" : {
"year" : "2016",
"month" : "August",
"day" : 1,
"CalHours" : 0,
"CalMinutes" : 0,
"CalSeconds" : 0,
"exclude" : "IntervalStart",
"exclOp" : function (a,b) { return( a >= b ); },
"time" : null
}
};
var g_numOfMonth = {
"January" : 0,
"February" : 1,
"March" : 2,
"April" : 3,
"May" : 4,
"June" : 5,
"July" : 6,
"August" : 7,
"September" : 8,
"October" : 9,
"November" : 10,
"December" : 11
};
function slide(h,x,handle) {
handle.attr("cx", x(h));
}
g_currentSelectedCalendar = "CalStart";
function update_dateTime(tFacet,txt,whichCal) {
var cal = g_currentSelectedCalendar;
if ( whichCal ) {
cal = whichCal;
}
var saveOld = g_calRange[cal][tFacet];
g_calRange[cal][tFacet] = txt;
var time = new Date( 1*(g_calRange[cal].year), g_numOfMonth[g_calRange[cal].month], g_calRange[cal].day,
g_calRange[cal].CalHours, g_calRange[cal].CalMinutes, g_calRange[cal].CalSeconds );
var excl = g_calRange[cal].exclude;
if ( g_calRange[excl].time == null ) {
g_calRange[cal].time = time;
return(true);
}
if ( g_calRange[cal].exclOp(time,g_calRange[excl].time) ) {
g_calRange[cal].time = time;
return(true);
}
g_calRange[cal][tFacet] = saveOld;
return(false);
}
function setCalDates(calContainer) {
//-------------------------------------------
var yr = calContainer.select("#tspan-year").text();
var mo = calContainer.select("#tspan-month").text();
var moTime = new Date(mo + " 1, " + yr);
var curMo = moTime.getMonth();
while ( moTime.getDay() > 0 ) {
moTime.setDate(moTime.getDate()-1);
}
for ( var i = 0; i < 6; i++ ) {
for ( var j = 0; j < 7; j++ ) {
var tt = calContainer.select("#g_calBox_" + i + "_" + j).selectAll("tspan");
tt.text("" + moTime.getDate());
if ( curMo != moTime.getMonth() ) {
calContainer.select("#g_calBox_" + i + "_" + j).selectAll("rect")
.style("fill",function() {
var rct = d3.select(this);
this["preserve-color"] = rct.style("fill");
return("gray");
})
.style("fill-opacity","0.5");
} else {
calContainer.select("#g_calBox_" + i + "_" + j).selectAll("rect")
.style("fill",function() {
var rct = d3.select(this);
return( this["preserve-color"] ? this["preserve-color"] : rct.style("fill") );
})
.style("fill-opacity","1");
}
moTime.setDate(moTime.getDate()+1);
}
}
}
function toolMonth(monthList) {
monthList.style("visibility","hidden");
var mos = monthList.selectAll("g")
.on("mouseenter", function(){
var color = d3.select(this).select("rect").style("fill");
this["preserve_color"] = color;
d3.select(this).select("rect").style("fill","green");
})
.on("mouseleave", function(){
d3.select(this).select("rect").style("fill",this["preserve_color"]);
})
.on("click",function() {
var month_sel = d3.select(this).attr("id");
var calconP = d3.select(this.parentNode.parentNode);
var calcon = calconP.select("#calendar_widget");
var mo = calcon.select("#tspan-month").text(month_sel);
update_dateTime("month", month_sel );
setCalDates(calcon);
});
}
function addSlider(container,maxDomain,maxRange,sliderID) {
var whichCal = container.attr("id");
var margin = {right: 50, left: 550};
var width = maxRange - margin.left - margin.right;
var height = +container.attr("height");
var x = d3.scaleLinear()
.domain([0, maxDomain])
.range([0, maxRange])
.clamp(true);
var slider = container.append("g")
.attr("id",sliderID)
.attr("class", "slider")
.attr("visibility","hidden")
.attr("transform", "translate(" + margin.left + "," + height / 2 + ")");
slider.append("text").attr("x",x.range()[0]).attr("y",-10).text(sliderID);
slider.append("line")
.attr("class", "track")
.attr("x1", x.range()[0])
.attr("x2", x.range()[1])
.select( function() { return this.parentNode.appendChild(this.cloneNode(true)); } )
.attr("class", "track-inset")
.select( function() { return this.parentNode.appendChild(this.cloneNode(true)); } )
.attr("class", "track-overlay")
.call(d3.drag()
.on("start.interrupt", function() { slider.interrupt(); })
.on("start drag", function () {
sliderFunctionMap[gCurrentSlider]();
}
));
sliderFunctionMap[whichCal + sliderID] = function() {
var h = x.invert(d3.event.x-50);
if ( update_dateTime( sliderID, h, whichCal ) ) {
slide( h , x, sliderHandleMap[whichCal + sliderID] );
}
};
slider.insert("g", ".track-overlay")
.attr("class", "ticks")
.attr("transform", "translate(0," + 18 + ")")
.selectAll("text")
.data(x.ticks(10))
.enter().append("text")
.attr("x", x)
.attr("text-anchor", "middle")
.text(function(d) { return d + "°"; });
var handle = slider.insert("circle", ".track-overlay")
.attr("id","handle_" + sliderID)
.attr("class", "handle")
.attr("r", 9);
sliderHandleMap[whichCal + sliderID] = handle;
return(slider);
}
function hourSelection(hrsClock,svgCalBK) {
var vizState = svgCalBK.select("#CalHours").style("visibility");
hrsClock.raise();
hideAllSliders(svgCalBK);
if ( vizState == "hidden" ) {
var whichCal = svgCalBK.attr("id");
gCurrentSlider = whichCal + "CalHours";
svgCalBK.select("#CalHours").style("visibility","visible");
}
}
function minuteSelection(minsClock,svgCalBK) {
var vizState = svgCalBK.select("#CalMinutes").style("visibility");
minsClock.raise();
hideAllSliders(svgCalBK);
if ( vizState == "hidden" ) {
var whichCal = svgCalBK.attr("id");
gCurrentSlider = whichCal + "CalMinutes";
svgCalBK.select("#CalMinutes").style("visibility","visible");
}
}
function secondSelection(secsClock,svgCalBK) {
var vizState = svgCalBK.select("#CalSeconds").style("visibility");
secsClock.raise();
hideAllSliders(svgCalBK);
if ( vizState == "hidden" ) {
var whichCal = svgCalBK.attr("id");
gCurrentSlider = whichCal + "CalSeconds";
svgCalBK.select("#CalSeconds").style("visibility","visible");
}
}
function hideAllSliders(svgCalBK) {
svgCalBK.select("#CalHours").style("visibility","hidden");
svgCalBK.select("#CalMinutes").style("visibility","hidden");
svgCalBK.select("#CalSeconds").style("visibility","hidden");
}
function toolCalendar(svgCalBK,exclusionId) {
var calContainer = svgCalBK.selectAll("g");
calContainer.attr("exclusion",exclusionId);
calContainer.on("click", ( function(svgCalBKB,exclID) { return(function() {
var calContainer = d3.select(this.parentNode);
var exclusion = d3.select(this.parentNode.parentNode).select("#" + exclID);
var vbox = calContainer.attr("viewBox");
if ( vbox == "0 0 340 600" ) {
calContainer.attr("openstate","closed");
calContainer.lower();
var width = this.parentNode["saveW"];
var height = this.parentNode["saveH"];
calContainer
.attr("width", width)
.attr("height",height)
.attr("viewBox","0 0 500 490");
//var ccg = calContainer.select("#cal-clock-group");
//ccg.attr("transform", this.parentNode["saveCalClockOrigin"] );
for ( var i = 0; i < 6; i++ ) {
for ( var j = 0; j < 7; j++ ) {
calContainer.select("#g_calBox_" + i + "_" + j).selectAll("rect")
.on("mouseenter",null)
.on("mouseenter",null)
.on("click",null);
}
}
hideAllSliders(svgCalBKB);
} else {
if ( !exclusion.empty() ) {
var open = exclusion.attr("openstate");
if ( open === "open" ) {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent(
"click", /* type */
true, /* canBubble */
true, /* cancelable */
window, /* view */
0, /* detail */
0, /* screenX */
0, /* screenY */
0, /* clientX */
0, /* clientY */
false, /* ctrlKey */
false, /* altKey */
false, /* shiftKey */
false, /* metaKey */
0, /* button */
null); /* relatedTarget */
(exclusion.select("#calendar_widget").node()).dispatchEvent(evt);
}
}
calContainer.attr("openstate","open");
calContainer.raise();
this.parentNode["saveW"] = calContainer.attr("width");
this.parentNode["saveH"] = calContainer.attr("height");
var width = this.parentNode["saveW"];
var height = this.parentNode["saveH"];
calContainer
.attr("width", width*4)
.attr("height",height*8)
.attr("viewBox","0 0 340 600");
g_currentSelectedCalendar = svgCalBKB.attr("id");
var ccg = calContainer.select("#cal-clock-group");
ccg.raise();
//this.parentNode["saveCalClockOrigin"] = ccg.attr("transform");
for ( var i = 0; i < 6; i++ ) {
for ( var j = 0; j < 7; j++ ) {
calContainer.selectAll( "#g_calBox_" + i + "_" + j ).selectAll("rect")
.on("mouseenter",function() {
var boxC = d3.select(this);
if ( boxC.style("fill") == "rgb(255, 255, 255)" ) {
boxC.style("fill","green");
}
})
.on("mouseleave",function() {
var boxC = d3.select(this);
if ( boxC.style("fill") == "rgb(0, 128, 0)" ) {
boxC.style("fill","#ffffff");
}
})
.on("click",(function(calCtr) { return(
function() {
d3.event.stopPropagation();
var boxC = d3.select(this);
var bX = 1*boxC.attr("x");
var bY = 1*boxC.attr("y");
var boxGroup = d3.select(this.parentNode);
var dayTxt = boxGroup.select("tspan").text();
if ( !update_dateTime("day",dayTxt) ) return;
var ccg = calCtr.select("#cal-clock-group");
var groupRow = d3.select(this.parentNode.parentNode);
var t = groupRow.attr("transform");
var y = 0;
if ( t != null ) {
var numberPattern = /[+-]?\d+(\.\d+)?/g;
var els = t.match( numberPattern );
y = 1*els[1];
}
var h = 1*boxC.attr("height");
ccg.attr("transform","translate(" + [ bX - 500, y + h/3 ] + ")");
ccg.raise();
}
);})(calContainer) );
}
}
}
}); })(svgCalBK,exclusionId) );
var calclocks = calContainer.select("#cal-clock-group-background");
calclocks.on("click",function() { d3.event.stopPropagation(); } );
var calclockHour = calContainer.selectAll("#cal-clock-H");
var calclockMin = calContainer.selectAll("#cal-clock-M");
var calclockSecs = calContainer.selectAll("#cal-clock-S");
calclockHour.on("click", (function(svgCalBKB){ return(
function() {
d3.event.stopPropagation();
hourSelection(d3.select(this),svgCalBKB);
} ); })(svgCalBK) );
calclockMin.on("click", (function(svgCalBKB){ return(
function() {
d3.event.stopPropagation();
minuteSelection(d3.select(this),svgCalBKB);
} ); })(svgCalBK) );
calclockSecs.on("click", (function(svgCalBKB){ return(
function() {
d3.event.stopPropagation();
secondSelection(d3.select(this),svgCalBKB);
} ); })(svgCalBK) );
setCalDates(calContainer);
var month_button = calContainer.select("#month_button").on("click", (function(svgCalBKB) {
return(function() {
d3.event.stopPropagation();
var mlist = svgCalBKB.select("#cal-month_list");
mlist.raise();
var vis = mlist.style("visibility") == "hidden" ? "visible" : "hidden";
mlist.style("visibility",vis);
});
})(svgCalBK) );
}
var daysOfWeek = null;
function nestCalendar(selParent,calId,x,y,width,height,exclusivePair) {
selParent.append("svg")
.attr("id",calId)
.attr("x", x )
.attr("y", y )
.attr("width", width)
.attr("height",height)
.attr("viewBox","0 0 500 490")
.attr("preserveAspectRatio","xMinYMin meet");
var svgCal = selParent.select("svg");
d3.request("xcalendar-group.svg",
function(resp) {
var svgCalBK = selParent.select("#" + calId);
svgCalBK.html(resp.response);
toolCalendar(svgCalBK,exclusivePair);
d3.request("month-list.svg",
(function(svgCalB) { return(
function(resp) {
var monthList = svgCalB.append("g").attr("id","cal-month_list").html(resp.response);
var hoursPerDay = addSlider(svgCalB,24,300,"CalHours");
var minutePerHour = addSlider(svgCalB,60,300,"CalMinutes");
var secondsPerMinut = addSlider(svgCalB,60,300,"CalSeconds");
toolMonth(monthList);
}); })(svgCalBK) );
});
}
/* -- // d3 example by Richard Leddy (c) 2016 */
function updateRemove(pSvg,d) {
var ms = pSvg.selectAll("#win-" + d.iam);
ms.remove();
}
function update_control_context(ctxtCtrl,d) {
ctxtCtrl.text(d.iam);
}
var topSvg = d3.select("#mainsvg");
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
wx = w.innerWidth || e.clientWidth || g.clientWidth,
wy = w.innerHeight|| e.clientHeight|| g.clientHeight;
topSvg.attr("width",window.screen.width);
topSvg.attr("height",window.screen.height);
var winCount = 0;
var width = wx,
height = wy,
radius = 200;
var rectsAll = d3.range(6).map(function() {
return {
iam: winCount++,
x: Math.round(Math.random() * (width/2 - radius * 2) + radius),
y: Math.round(Math.random() * (height/2 - radius * 2) + radius),
w : radius,
h : radius/2
};
});
rectsAll.push({
iam: "context",
x: 40,
y: 40,
w : 2*width/3,
h : radius/2
});
var color = d3.scaleOrdinal()
.range(d3.schemeCategory20);
function addWindows(pSvg,defRegions) {
var rectangle = pSvg.selectAll("g.windowBox")
.data(defRegions, function(d) { return(d.iam); } )
.enter().append("g")
.attr("id", function(d){ return("win-" + d.iam); } )
.attr("class","windowBox")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
rectangle.attr("deltaX",function(d) { return(0); })
.attr("deltaY",function(d) { return(0); });
// This is the containing box window
rectangle.append("rect")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("height", function(d) { return d.h; })
.attr("width", function(d) { return d.w; })
.attr("cursor", "move")
.style("fill", function(d, i) { return color(i); });
var noDrag = d3.drag()
.on('start', function(d){
d3.select(this.parentNode).raise().classed("active", true);
var ctxtCtrl = d3.select("#data-context-control-text");
update_control_context(ctxtCtrl,d);
})
.on('drag', function(d,i){
})
.on("end", function(d,i){
d3.select(this.parentNode).classed("active", false);
});
// This is the interior box window
rectangle.append("rect")
.attr("x", function(d) { return d.x+1; })
.attr("y", function(d) { return d.y+20; })
.attr("height", function(d) { return d.h-22; })
.attr("width", function(d) { return d.w-2; })
.style("fill", function(d, i) { return "#FAFACC"; })
.call(noDrag);
var dragBox = d3.drag()
.on('start', function(d){
d3.event.sourceEvent.stopPropagation();
var ctxtCtrl = d3.select("#data-context-control-text");
update_control_context(ctxtCtrl,d);
})
.on('drag', function(d,i){
var resizeBox = d3.select(this);
var mx = +resizeBox.attr("x");
var my = +resizeBox.attr("y");
var topLimit = d.y + 20;
var leftLimit = d.x + 20;
mx += d3.event.dx;
if ( d.iam != "context" ) {
my += d3.event.dy;
}
if ( (mx > leftLimit) && (my > topLimit) ) {
resizeBox.attr('x', mx).attr('y', my);
var height = my - d.y;
var width = mx - d.x;
d3.select(this.previousElementSibling)
.attr("x", mx);
d3.select(this.previousElementSibling.previousElementSibling)
.attr("width", width + 10 - 2)
.attr("height", height + 10 - 22);
d3.select(this.previousElementSibling.previousElementSibling.previousElementSibling)
.attr("width", width + 10)
.attr("height", height + 10);
var subView = d3.select(this.parentNode).select("svg");
if ( !subView.empty()) {
var resF = subView.attr("custom-resize");
if ( resF && d.resize ) {
d.resize(subView,d,width,height);
}
}
}
});
rectangle.append("rect")
.attr("x", function(d) { return d.x + d.w - 10; })
.attr("y", function(d) { return d.y; })
.attr("height", 10)
.attr("width", 10)
.attr("fill", "navy" )
.attr("cursor","pointer")
.on("click", function(d,i) {
if ( d.iam == "context" ) return;
var j = rectsAll.findIndex(function(obj){ var res = (obj.iam === d.iam); return res; });
rectsAll.splice(j,1);
updateRemove(pSvg,d);
var rect = d3.select(this);
var cc = rect.attr("fill");
cc = (cc == "navy" )? "green" : "navy";
rect.attr("fill",cc);
});
rectangle.append("rect")
.attr("x", function(d) { return d.x + d.w - 10; })
.attr("y", function(d) { return d.y + d.h - 10; })
.attr("height", 10)
.attr("width", 10)
.style("fill", function(d, i) { return "#993300"; })
.attr("cursor", "nw-resize")
.call(dragBox);
}
function dragstarted(d) {
var dltX = 1*(d3.select(this).attr("deltaX"));
var dltY = 1*(d3.select(this).attr("deltaY"));
d3.select(this).attr("deltaX",d3.event.x - dltX);
d3.select(this).attr("deltaY",d3.event.y - dltY);
d3.select(this).raise().classed("active", true);
var ctxtCtrl = d3.select("#data-context-control-text");
update_control_context(ctxtCtrl,d);
}
function dragged(d) {
var dltX = 1*(d3.select(this).attr("deltaX"));
var dltY = 1*(d3.select(this).attr("deltaY"));
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d3.event.x - dltX, d3.event.y - dltY] + ")"
});
}
function dragended(d, i) {
var dltX = 1*(d3.select(this).attr("deltaX"));
var dltY = 1*(d3.select(this).attr("deltaY"));
d3.select(this).attr("deltaX",d3.event.x - dltX);
d3.select(this).attr("deltaY",d3.event.y - dltY);
d3.select(this).classed("active", false);
}
function renderCell(d) {
return d == null ? null : "M" + d.join("L") + "Z";
}
topSvg.append("rect")
.attr("x",5)
.attr("y",5)
.attr("height",20)
.attr("width",20).attr("fill","orange")
.on("click", function() {
var rect = d3.select(this);
var cc = rect.attr("fill");
cc = cc == "orange" ? "blue" : "orange";
rect.attr("fill",cc);
var newDatum = {
iam: winCount++,
x: Math.round(Math.random() * (width/2 - radius * 2) + radius),
y: Math.round(Math.random() * (height/2 - radius * 2) + radius),
w : radius,
h : radius/2
};
console.log(newDatum);
rectsAll.push(newDatum);
addWindows(topSvg,rectsAll);
});
addWindows(topSvg,rectsAll);
var context_window = topSvg.select("#win-context")
.append("svg")
.attr("id","data-context-control")
.attr("x", function(d){return d.x})
.attr("y", function(d){return d.y})
.attr("width", function(d){return width;})
.attr("height", function(d){return height;})
.attr("custom-resize",function(d) { // clip the content of this window in a particular way.
d.resize = function(subsvg,data,w,h) {
subsvg.attr("width",w);
}
return("true");
})
.append("text")
.attr("id","data-context-control-text")
.attr("stroke","black")
.attr("x", function(d){return 10})
.attr("y", function(d){return 15})
.text(function(d){ return "context"; });
nestCalendar( topSvg.select("#win-context").select("svg"), "IntervalStart", 10, 22, 45*7, 72, "IntervalEnd" );
nestCalendar( topSvg.select("#win-context").select("svg"), "IntervalEnd", 90, 22, 45*7, 72, "IntervalStart" );
</script>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment