How to make a SVG Date & Time Picker
HOME
-
Making a stunning SVG date time picker
-
1 Feb 2017
-
This article will explain the core techniques on how to make a SVG date time picker. Before proceeding, we will consider you have experience with jQuery and D3. We will be using D3 library to apply many functions to SVG component.
Let's look at the example first :
From below, we will draw all controls one by one and assign events to handle user clicks.
-
Making a SVG and initialising variables
-
First thing you need to do is to create a SVG and a group which translate its position to the center of SVG width & height. And you need to initiate all local variables especially for the radius of circle.
// Clock radius. T.tickness = thickness of month & date circle. ie) T.tickness = 40;
T.radius = (_width > _height ? (height / 2) - (T.tickness * 2) : (_width / 2) - (T.tickness * 2));
T.handRadius = T.radius - 15;
T.radians = 0.0174532925;
// Set the hour scale
T.hourScale = d3.scaleLinear().range([0,330]).domain([0,11]);
// Set the minute scale
T.minuteScale = d3.scaleLinear().range([0,354]).domain([0,59]);
// Create a SVG and set possible attributes
T.svg = d3.select(_svgContainer[0]).append('svg');
T.svg.attr('width', _width).attr('height', _height);
T.svg.attr('viewBox', '0 0 ' + _width + ' ' + _height);
T.svg.attr('preserveAspectRatio', 'none');
// Set the parent group for drawing and translate its position to the center of width & height
T.baseGroup = T.svg.append('g').attr('transform','translate(' + (_width / 2) + ',' + (_height / 2) + ')');
// Add a clock rim
T.baseGroup.append('circle').attr('cx', 0).attr('cy', 0).attr('r', T.radius).attr('class', 'some class');
Below is the code to prepare an array to keep the number of days in a month. This is changed whenever month is selected and changed.
// Form days array. For _angleIncrease, it's up to you. You can calculate it's value whenever month is changed.
// In our case, we thought it is more beautiful to have space at the end of angle.
var _angleIncrease = 11;
for(var i = 0; i <= 30; i++){
T.dateData.push({
date: i+1,
startangle: i * _angleIncrease,
endangle: (i + 1) * _angleIncrease,
backcolor:'#' + // Any color range you want...
isItSaturday: false,
isItSunday: false,
isAvailableDate: true
});
}
-
Drawing clock
-
The next thing is shaping the clock. Below is to add a rim for the clock and fill the circles for hour and minute. Click event is assigned.
// Add a clock rim
T.baseGroup.append('circle').attr('cx', 0).attr('cy', 0).attr('r', T.radius).attr('class', 'class name');
// Add enter group
var _textGroup = T.baseGroup.selectAll('.fakeClockSelector').data(d3.range(1, 13, 1)).enter()
.append('g'). attr('class', 'class name')
.on('click', function(_data){
d3.event.preventDefault();
d3.event.stopPropagation();
T.changeTime(
T.turnMinuteClick ? null : _data,
T.turnMinuteClick ? (_data * 5 == 60 ? 0 : (_data * 5)) : null
);
// toggle clicking
T.turnMinuteClick = !T.turnMinuteClick;
d3.select(this).select('circle').style('fill', (T.turnMinuteClick ? 'minute fill color' : 'hour fill color'));
})
.on('mouseover', function(){
// Mouse hover effect here
})
.on('mouseout', function(){
// Mouse out effect here
})
;
// Add a circle to the text group
_textGroup.append('circle').attr('class', 'some class')
.attr('cx',function(d){ return T.handRadius * Math.sin(T.hourScale(d) * T.radians);})
.attr('cy',function(d){ return -(T.handRadius) * Math.cos(T.hourScale(d) * T.radians); })
.attr('r', 10)
.style('fill', 'come color code')
;
// Add hour text
_textGroup.append('text').attr('class', 'some class')
.attr('text-anchor','middle')
.attr('dominant-baseline', 'middle')
.attr('x',function(d){return T.handRadius * Math.sin(T.hourScale(d) * T.radians);})
.attr('y',function(d){return -(T.handRadius) * Math.cos(T.hourScale(d) * T.radians);})
.text(function(d){return d;})
;
-
Month & Date
-
It's time to draw month components. You need to set the array for months and their angles. Angle is up to you. You cal fill the month circle without space by assigning 30 degree (360 / 12) to the angle variable.
var _monthAngleIncrease = 27; // Angle for month group. the value is up to you.
for(var i = 0; i < 12; i++) {
T.monthData[i].startangle = i * _monthAngleIncrease;
T.monthData[i].endangle = (i + 1) * _monthAngleIncrease;
}
T.monthGroup = T.baseGroup.append('g').attr('id', 'monthGroup');
var _monthArc = d3.arc().innerRadius(T.radius).outerRadius(T.radius + T.tickness);
// Append background into month group
var background = T.monthGroup.append('path')
.datum({ startAngle: 0 * (Math.PI/180), endAngle: 360 * (Math.PI/180) })
.attr('d', _monthArc)
.attr('fill', 'Any color you want. You can use an array for colors')
;
var _monthLabelGroup = T.monthGroup.selectAll('.fakeMonthSelector').data(T.monthData).enter()
.append('g'). attr('class', 'some class')
.on('click', function(_data){
d3.event.preventDefault();
d3.event.stopPropagation();
T.changeMonth(_data); // Call a function to change month value
})
;
// Add path & background and set the ID for path for later call
var _monthTextPath = _monthLabelGroup.append('path')
.attr('id', function(_data){ return 'monthTextPath' + _data.month.toString(); })
.attr('fill', function(_data){ return _data.backcolor; })
.datum(function(_data){
return { startAngle: _data.startangle * (Math.PI/180), endAngle: _data.endangle * (Math.PI/180) }
})
.attr('d', _monthArc)
;
// Add month text to arc
_monthLabelGroup.append('text')
.attr('class', 'some class')
.attr('dx', 15)
.attr('dy', 20)
.append('textPath')
.attr('xlink:href',function(_data){return '#monthTextPath' + _data.month.toString();})
.text(function(d){return d.name;});
Now we need to implement the case month is changed. Below shows how it works when month is changed.
// Change background color
if(T.selectedMonth != null){
// If the changed month is the same then exit
if(T.selectedMonth.month == _monthObject.month) return;
}
// _monthObject is from an array of { month:1, name: 'Jan', startangle:0, endangle:0 }
// start and end angle are already set previously in the month array
T.selectedMonth = _monthObject;
var _angle = -(_monthObject.startangle + _monthObject.endangle) / 2;
T.monthGroup.transition().duration(800).attr('transform', 'rotate(' + _angle + ')');
// Trigger 'data changed' event
_svgContainer.trigger('dateTimeChanged', T.returnTimeStamp());
Below is to draw date.
T.dateGroup = T.baseGroup.append('g').attr('id', 'datesGroup');
var _daysArc = d3.arc().innerRadius(T.radius + T.tickness).outerRadius(T.radius + (T.tickness*2));
// Append background into month group
var background = T.dateGroup.append('path')
.datum({ startAngle: 0 * (Math.PI/180), endAngle: 360 * (Math.PI/180) })
.attr('d', _daysArc)
.attr('fill', '#eeeeee')
;
// Define a group for label
var _daysLabelGroup = T.dateGroup.selectAll('class for date').data(T.dateData).enter()
.append('g'). attr('class', 'some class')
.attr('id', function(_data){ return 'datesOneElementGroup' + _data.date.toString(); })
.on('click', function(_data){
d3.event.preventDefault();
d3.event.stopPropagation();
if(!_data.isAvailableDate) return;
T.changeDate(_data);
})
// Define hover effect
.on('mouseover', function(_data){ })
.on('mouseout', function(_data){ })
;
// Add path & background - set ID for later call
var _datesTextPath = _daysLabelGroup.append('path')
.attr('id', function(_data){ return 'datesTextPath' + _data.date.toString(); })
.attr('fill', function(_data){ return _data.backcolor; })
.datum(function(_data){ return { startAngle: _data.startangle * (Math.PI/180), endAngle: _data.endangle * (Math.PI/180) }})
.attr('d', _daysArc)
;
// Add dates text to arc
_daysLabelGroup.append('text')
.attr('class', 'some class')
.attr('dx', 7)
.attr('dy', 20)
.append('textPath')
.attr('xlink:href',function(_data){return '#datesTextPath' + _data.date.toString();})
.text(function(_data){return _data.date;})
;
// Draw polygon
T.baseGroup.append('polygon').attr('fill', '#ff5500').attr('points',
'-4, ' + (0 - (T.radius + T.tickness)) + ', 0, ' + (0 - (T.radius + T.tickness) - 5) + ', 4, ' + (0 - (T.radius + T.tickness)));
You have to prepare a function in case month is changed. This will be the most expensive module to be called. Let's look inside. We will use moment.js for this part to get date & time and other values.
// Should get the number of days based on the first date(1)
var _tempDate = new Date(T.year, T.month - 1, 1);
// Using moment.js for date & time functions
var _daysInMonth = moment(_tempDate.getTime()).daysInMonth();
for(var i = 0; i < Object.keys(T.dateData).length; i++){
var _tempGroup = T.dateGroup.select('#datesOneElementGroup' + T.dateData[i].date.toString());
if(i >= _daysInMonth) {
_tempGroup.select('textPath').attr('class', 'some class');
T.dateData[i].isAvailableDate = false;
}
else{
var _textPath = _tempGroup.select('textPath');
_textPath.attr('class', 'some class');
var _tempBlur = Date.parse(T.year + '-' + T.month + '-' + T.dateData[i].date);
var _tempDate = new Date(_tempBlur);
if(_tempDate.getDay() == 6) _textPath.attr('class', 'class for saturday');
if(_tempDate.getDay() == 0) _textPath.attr('class', 'class for sunday');
}
}
// If the selected date is over the max date in the selected month, we set the selected date with the last date of month.
if(T.selectedDate != null && T.selectedDate.date > _daysInMonth){
setTimeout(function(){
T.changeDate(T.dateData[_daysInMonth - 1])
}, 500);
}
Almost there. We need to prepare a function to be called when date is selected. When date is changed, new date will rotate to the top of circle and trigger an event for other operations.
// Change background color
if(T.selectedDate != null){
T.dateGroup.select('#datesTextPath' + T.selectedDate.date.toString()).attr('fill', 'some color');
}
T.selectedDate = _dateObject;
// Calculate the angle of rotation
var _angle = -(_dateObject.startangle + _dateObject.endangle) / 2;
T.dateGroup.transition().duration(800).attr('transform', 'rotate(' + _angle + ')');
T.dateGroup.select('#datesTextPath' + _dateObject.date.toString()).attr('fill', ''some color for chosen date);
T.date = _dateObject.date;
// Trigger an event for date changed
_svgContainer.trigger('dateTimeChanged', T.returnTimeStamp());
We have briefly looked the code for SVG date time picker. This provides just the starting point for you to make more beautiful SVG controls. Thanks for visiting our website. Have a good day!!