Browse Source

svg timeline experiment

css_transitions
Alex de Mulder 10 years ago
parent
commit
b9406e72f6
11 changed files with 1455 additions and 11 deletions
  1. +2
    -0
      Jakefile.js
  2. +254
    -1
      dist/vis.js
  3. +9
    -9
      dist/vis.min.js
  4. +41
    -0
      examples/svgTimeline/01_basic.html
  5. +0
    -1
      examples/timeline/03_much_data.html
  6. +63
    -0
      src/svgTimeline/Item.js
  7. +5
    -0
      src/svgTimeline/d3.v3.js
  8. +7
    -0
      src/svgTimeline/hammer.min.js
  9. +9
    -0
      src/svgTimeline/moment-with-langs.min.js
  10. +253
    -0
      src/svgTimeline/svgTimeline.js
  11. +812
    -0
      src/svgTimeline/util.js

+ 2
- 0
Jakefile.js View File

@ -62,6 +62,8 @@ task('build', {async: true}, function () {
'./src/DataSet.js', './src/DataSet.js',
'./src/DataView.js', './src/DataView.js',
'./src/svgTimeline/svgTimeline.js',
'./src/timeline/TimeStep.js', './src/timeline/TimeStep.js',
'./src/timeline/Stack.js', './src/timeline/Stack.js',
'./src/timeline/Range.js', './src/timeline/Range.js',

+ 254
- 1
dist/vis.js View File

@ -5,7 +5,7 @@
* A dynamic, browser-based visualization library. * A dynamic, browser-based visualization library.
* *
* @version 0.6.0-SNAPSHOT * @version 0.6.0-SNAPSHOT
* @date 2014-02-26
* @date 2014-02-27
* *
* @license * @license
* Copyright (C) 2011-2014 Almende B.V, http://almende.com * Copyright (C) 2011-2014 Almende B.V, http://almende.com
@ -2350,6 +2350,259 @@ DataView.prototype._trigger = DataSet.prototype._trigger;
DataView.prototype.subscribe = DataView.prototype.on; DataView.prototype.subscribe = DataView.prototype.on;
DataView.prototype.unsubscribe = DataView.prototype.off; DataView.prototype.unsubscribe = DataView.prototype.off;
/**
* Created by Alex on 2/27/14.
*/
function SvgAxis (range,mainId, constants) {
this.svgId = mainId;
this.range = range;
this.constants = constants;
this.duration = this.range.end - this.range.start; // in milliseconds
this.minColumnWidth = 100;
this._drawElements();
this._update();
}
SvgAxis.prototype._drawElements = function() {
d3.select(this.svgId)
.append("rect")
.attr("id","bars")
.attr("x",0)
.attr("y",0)
.attr("width", this.constants.width)
.attr("height",this.constants.barHeight)
.style("stroke", "rgb(6,120,155)");
this.leftText = d3.select(this.svgId)
.append("text")
.attr("x", 5)
.attr("y", 20)
.attr("font-size", 14)
.text(moment(this.range.start));
this.rightText = d3.select(this.svgId)
.append("text")
.attr("y", 20)
.attr("font-size", 14)
.text(moment(this.range.end))
this.rightText.attr("x", this.constants.width - 5 - this.rightText.node().getBBox().width);
this.dateLabels = {};
this.markerLines = {};
}
SvgAxis.prototype._createMarkerLine = function(index) {
this.markerLines[index] = {svg:d3.select("svg#main").append("line")
.attr('y1',0)
.attr('y2',this.constants.height)
.style("stroke", "rgb(220,220,220)")
}
}
SvgAxis.prototype._createDateLabel = function(index) {
this.dateLabels[index] = {svg:d3.select(this.svgId)
.append("text")
.attr("font-size",12)
, active:false};
}
SvgAxis.prototype._update = function() {
this.duration = this.range.end - this.range.start; // in milliseconds
this.leftText.text(moment(this.range.start).format("DD-MM-YYYY HH:mm:ss"))
this.rightText.text(moment(this.range.end).format("DD-MM-YYYY"))
this.rightText.attr("x", this.constants.width - 5 - this.rightText.node().getBBox().width);
this.msPerPixel = this.duration / this.constants.width;
this.columnDuration = this.minColumnWidth * this.msPerPixel;
var milliSecondScale = [1,10,50,100,250,500];
var secondScale = [1,5,15,30];
var minuteScale = [1,5,15,30];
var hourScale = [1,3,6,12];
var dayScale = [1,2,3,5,10,15];
var monthScale = [1,2,3,4,5,6];
var yearScale = [1,2,3,4,5,6,7,8,9,10,15,20,25,50,75,100,150,250,500,1000];
var multipliers = [1,1000,60000,3600000,24*3600000,30*24*3600000,365*24*3600000];
var scales = [milliSecondScale,secondScale,minuteScale,hourScale,dayScale,monthScale,yearScale]
var formats = ["SSS","mm:ss","hh:mm:ss","DD HH:mm","MM-DD","MM","YYYY"]
var indices = this._getAppropriateScale(scales,multipliers);
var scale = scales[indices[0]][indices[1]] * multipliers[indices[0]];
var dateCorrection = (this.range.start.valueOf() % scale) +3600000;
for (var i = 0; i < 30; i++) {
var date = this.range.start + i*scale - dateCorrection;
if (((i+1)*scale - dateCorrection)/this.msPerPixel > this.constants.width + 200) {
if (this.dateLabels.hasOwnProperty(i)) {
this.dateLabels[i].svg.remove();
delete this.dateLabels[i]
}
if (this.markerLines.hasOwnProperty(i)) {
this.markerLines[i].svg.remove();
delete this.markerLines[i]
}
}
else {
if (!this.dateLabels.hasOwnProperty(i)) {
this._createDateLabel(i);
}
if (!this.markerLines.hasOwnProperty(i)) {
this._createMarkerLine(i);
}
this.dateLabels[i].svg.text(moment(date).format(formats[indices[0]]))
.attr("x",(i*scale - dateCorrection)/this.msPerPixel)
.attr("y",50)
this.markerLines[i].svg.attr("x1",(i*scale - dateCorrection)/this.msPerPixel)
.attr("x2",(i*scale - dateCorrection)/this.msPerPixel)
}
}
}
SvgAxis.prototype._getAppropriateScale = function(scales,multipliers) {
for (var i = 0; i < scales.length; i++) {
for (var j = 0; j < scales[i].length; j++) {
if (scales[i][j] * multipliers[i] > this.columnDuration) {
return [i,j]
}
}
}
}
/**
* @constructor SvgTimeline
* Create a graph visualization, displaying nodes and edges.
*
* @param {Element} container The DOM element in which the Graph will
* be created. Normally a div element.
* @param {Object} items An object containing parameters
* {Array} nodes
* {Array} edges
* @param {Object} options Options
*/
function SvgTimeline (container, items, options) {
var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
this.range = {start:now.clone().add('days', -3).valueOf(),
end: now.clone().add('days', 4).valueOf()}
this.constants = {
width:1400,
height:400,
barHeight: 60
}
this.container = container;
this._createSVG();
this.axis = new SvgAxis(this.range,"svg#main",this.constants);
var me = this;
this.hammer = Hammer(document.getElementById("main"), {
prevent_default: true
});
this.hammer.on('tap', me._onTap.bind(me) );
this.hammer.on('doubletap', me._onDoubleTap.bind(me) );
this.hammer.on('hold', me._onHold.bind(me) );
this.hammer.on('pinch', me._onPinch.bind(me) );
this.hammer.on('touch', me._onTouch.bind(me) );
this.hammer.on('dragstart', me._onDragStart.bind(me) );
this.hammer.on('drag', me._onDrag.bind(me) );
this.hammer.on('dragend', me._onDragEnd.bind(me) );
this.hammer.on('release', me._onRelease.bind(me) );
this.hammer.on('mousewheel',me._onMouseWheel.bind(me) );
this.hammer.on('DOMMouseScroll',me._onMouseWheel.bind(me) ); // for FF
this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
//this._drawLines();
}
SvgTimeline.prototype._createSVG = function() {
d3.select("div#visualization")
.append("svg").attr("id","main")
.attr("width",this.constants.width)
.attr("height",this.constants.height)
.attr("style","border:1px solid black")
};
/**
* Get the pointer location from a touch location
* @param {{pageX: Number, pageY: Number}} touch
* @return {{x: Number, y: Number}} pointer
* @private
*/
SvgTimeline.prototype._getPointer = function (touch) {
return {
x: touch.pageX,
y: touch.pageY
};
};
SvgTimeline.prototype._onTap = function() {};
SvgTimeline.prototype._onDoubleTap = function() {};
SvgTimeline.prototype._onHold = function() {};
SvgTimeline.prototype._onPinch = function() {};
SvgTimeline.prototype._onTouch = function(event) {};
SvgTimeline.prototype._onDragStart = function(event) {
this.initialDragPos = this._getPointer(event.gesture.center);
};
SvgTimeline.prototype._onDrag = function(event) {
var pointer = this._getPointer(event.gesture.center);
var diffX = pointer.x - this.initialDragPos.x;
// var diffY = pointer.y - this.initialDragPos.y;
this.initialDragPos = pointer;
this.range.start -= diffX * this.axis.msPerPixel;
this.range.end -= diffX * this.axis.msPerPixel;
this._update();
};
SvgTimeline.prototype._onDragEnd = function() {};
SvgTimeline.prototype._onRelease = function() {};
SvgTimeline.prototype._onMouseWheel = function(event) {
var delta = 0;
if (event.wheelDelta) { /* IE/Opera. */
delta = event.wheelDelta/120;
}
else if (event.detail) { /* Mozilla case. */
// In Mozilla, sign of delta is different than in IE.
// Also, delta is multiple of 3.
delta = -event.detail/3;
}
if (delta) {
var pointer = {x:event.x, y:event.y}
var center = this.range.start + this.axis.duration * 0.5;
var zoomSpeed = 0.1;
var scrollSpeed = 0.1;
this.range.start = center - 0.5*(this.axis.duration * (1 - delta*zoomSpeed));
this.range.end = this.range.start + (this.axis.duration * (1 - delta*zoomSpeed));
var diffX = delta*(pointer.x - 0.5*this.constants.width);
// var diffY = pointer.y - this.initialDragPos.y;
this.range.start -= diffX * this.axis.msPerPixel * scrollSpeed;
this.range.end -= diffX * this.axis.msPerPixel * scrollSpeed;
this._update();
}
};
SvgTimeline.prototype._onMouseMoveTitle = function() {};
SvgTimeline.prototype._update = function() {
this.axis._update();
}
/** /**
* @constructor TimeStep * @constructor TimeStep
* The class TimeStep is an iterator for dates. You provide a start date and an * The class TimeStep is an iterator for dates. You provide a start date and an

+ 9
- 9
dist/vis.min.js
File diff suppressed because it is too large
View File


+ 41
- 0
examples/svgTimeline/01_basic.html View File

@ -0,0 +1,41 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Timeline | Basic demo</title>
<style type="text/css">
body, html {
font-family: sans-serif;
}
#bars {
fill:#FF0000;
}
</style>
<script src="../../src/svgTimeline/util.js" charset="utf-8"></script>
<script src="../../src/svgTimeline/d3.v3.js" charset="utf-8"></script>
<script src="../../src/svgTimeline/hammer.min.js" charset="utf-8"></script>
<script src="../../src/svgTimeline/moment-with-langs.min.js"></script>
<script src="../../src/svgTimeline/svgTimeline.js"></script>
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="visualization"></div>
<script type="text/javascript">
var container = document.getElementById('visualization');
var items = [
{id: 1, content: 'item 1', start: '2013-04-20'},
{id: 2, content: 'item 2', start: '2013-04-14'},
{id: 3, content: 'item 3', start: '2013-04-18'},
{id: 4, content: 'item 4', start: '2013-04-16', end: '2013-04-19'},
{id: 5, content: 'item 5', start: '2013-04-25'},
{id: 6, content: 'item 6', start: '2013-04-27'}
];
var options = {};
var timeline = new SvgTimeline(container, items, options);
</script>
</body>
</html>

+ 0
- 1
examples/timeline/03_much_data.html View File

@ -63,7 +63,6 @@
}; };
var timeline = new vis.Timeline(container, items, options); var timeline = new vis.Timeline(container, items, options);
</script> </script>
</body> </body>
</html> </html>

+ 63
- 0
src/svgTimeline/Item.js View File

@ -0,0 +1,63 @@
/**
* Created by Alex on 2/27/14.
*/
/**
* @class Item
* A node. A node can be connected to other nodes via one or multiple edges.
* @param {object} properties An object containing properties for the node. All
* properties are optional, except for the id.
* {number} id Id of the node. Required
* {string} label Text label for the node
* {number} x Horizontal position of the node
* {number} y Vertical position of the node
* {string} shape Node shape, available:
* "database", "circle", "ellipse",
* "box", "image", "text", "dot",
* "star", "triangle", "triangleDown",
* "square"
* {string} image An image url
* {string} title An title text, can be HTML
* {anytype} group A group name or number
* @param {Graph.Images} imagelist A list with images. Only needed
* when the node has an image
* @param {Graph.Groups} grouplist A list with groups. Needed for
* retrieving group properties
* @param {Object} constants An object with default values for
* example for the color
*
*/
function Item(properties, constants) {
this.id = null;
this.start = null;
this.end = null;
this.content = null;
this.duration = null;
this.setProperties(properties, constants);
}
/**
* Set or overwrite properties for the node
* @param {Object} properties an object with properties
* @param {Object} constants and object with default, global properties
*/
Item.prototype.setProperties = function(properties, constants) {
if (!properties) {
return;
}
this.originalLabel = undefined;
// basic properties
if (properties.id !== undefined) {this.id = properties.id;}
if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
if (properties.title !== undefined) {this.title = properties.title;}
if (properties.group !== undefined) {this.group = properties.group;}
if (properties.x !== undefined) {this.x = properties.x;}
if (properties.y !== undefined) {this.y = properties.y;}
if (properties.value !== undefined) {this.value = properties.value;}
if (properties.level !== undefined) {this.level = properties.level;}
};

+ 5
- 0
src/svgTimeline/d3.v3.js
File diff suppressed because it is too large
View File


+ 7
- 0
src/svgTimeline/hammer.min.js
File diff suppressed because it is too large
View File


+ 9
- 0
src/svgTimeline/moment-with-langs.min.js
File diff suppressed because it is too large
View File


+ 253
- 0
src/svgTimeline/svgTimeline.js View File

@ -0,0 +1,253 @@
/**
* Created by Alex on 2/27/14.
*/
function SvgAxis (range,mainId, constants) {
this.svgId = mainId;
this.range = range;
this.constants = constants;
this.duration = this.range.end - this.range.start; // in milliseconds
this.minColumnWidth = 100;
this._drawElements();
this._update();
}
SvgAxis.prototype._drawElements = function() {
d3.select(this.svgId)
.append("rect")
.attr("id","bars")
.attr("x",0)
.attr("y",0)
.attr("width", this.constants.width)
.attr("height",this.constants.barHeight)
.style("stroke", "rgb(6,120,155)");
this.leftText = d3.select(this.svgId)
.append("text")
.attr("x", 5)
.attr("y", 20)
.attr("font-size", 14)
.text(moment(this.range.start));
this.rightText = d3.select(this.svgId)
.append("text")
.attr("y", 20)
.attr("font-size", 14)
.text(moment(this.range.end))
this.rightText.attr("x", this.constants.width - 5 - this.rightText.node().getBBox().width);
this.dateLabels = {};
this.markerLines = {};
}
SvgAxis.prototype._createMarkerLine = function(index) {
this.markerLines[index] = {svg:d3.select("svg#main").append("line")
.attr('y1',0)
.attr('y2',this.constants.height)
.style("stroke", "rgb(220,220,220)")
}
}
SvgAxis.prototype._createDateLabel = function(index) {
this.dateLabels[index] = {svg:d3.select(this.svgId)
.append("text")
.attr("font-size",12)
, active:false};
}
SvgAxis.prototype._update = function() {
this.duration = this.range.end - this.range.start; // in milliseconds
this.leftText.text(moment(this.range.start).format("DD-MM-YYYY HH:mm:ss"))
this.rightText.text(moment(this.range.end).format("DD-MM-YYYY"))
this.rightText.attr("x", this.constants.width - 5 - this.rightText.node().getBBox().width);
this.msPerPixel = this.duration / this.constants.width;
this.columnDuration = this.minColumnWidth * this.msPerPixel;
var milliSecondScale = [1,10,50,100,250,500];
var secondScale = [1,5,15,30];
var minuteScale = [1,5,15,30];
var hourScale = [1,3,6,12];
var dayScale = [1,2,3,5,10,15];
var monthScale = [1,2,3,4,5,6];
var yearScale = [1,2,3,4,5,6,7,8,9,10,15,20,25,50,75,100,150,250,500,1000];
var multipliers = [1,1000,60000,3600000,24*3600000,30*24*3600000,365*24*3600000];
var scales = [milliSecondScale,secondScale,minuteScale,hourScale,dayScale,monthScale,yearScale]
var formats = ["SSS","mm:ss","hh:mm:ss","DD HH:mm","MM-DD","MM","YYYY"]
var indices = this._getAppropriateScale(scales,multipliers);
var scale = scales[indices[0]][indices[1]] * multipliers[indices[0]];
var dateCorrection = (this.range.start.valueOf() % scale) +3600000;
for (var i = 0; i < 30; i++) {
var date = this.range.start + i*scale - dateCorrection;
if (((i+1)*scale - dateCorrection)/this.msPerPixel > this.constants.width + 200) {
if (this.dateLabels.hasOwnProperty(i)) {
this.dateLabels[i].svg.remove();
delete this.dateLabels[i]
}
if (this.markerLines.hasOwnProperty(i)) {
this.markerLines[i].svg.remove();
delete this.markerLines[i]
}
}
else {
if (!this.dateLabels.hasOwnProperty(i)) {
this._createDateLabel(i);
}
if (!this.markerLines.hasOwnProperty(i)) {
this._createMarkerLine(i);
}
this.dateLabels[i].svg.text(moment(date).format(formats[indices[0]]))
.attr("x",(i*scale - dateCorrection)/this.msPerPixel)
.attr("y",50)
this.markerLines[i].svg.attr("x1",(i*scale - dateCorrection)/this.msPerPixel)
.attr("x2",(i*scale - dateCorrection)/this.msPerPixel)
}
}
}
SvgAxis.prototype._getAppropriateScale = function(scales,multipliers) {
for (var i = 0; i < scales.length; i++) {
for (var j = 0; j < scales[i].length; j++) {
if (scales[i][j] * multipliers[i] > this.columnDuration) {
return [i,j]
}
}
}
}
/**
* @constructor SvgTimeline
* Create a graph visualization, displaying nodes and edges.
*
* @param {Element} container The DOM element in which the Graph will
* be created. Normally a div element.
* @param {Object} items An object containing parameters
* {Array} nodes
* {Array} edges
* @param {Object} options Options
*/
function SvgTimeline (container, items, options) {
var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
this.range = {start:now.clone().add('days', -3).valueOf(),
end: now.clone().add('days', 4).valueOf()}
this.constants = {
width:1400,
height:400,
barHeight: 60
}
this.container = container;
this._createSVG();
this.axis = new SvgAxis(this.range,"svg#main",this.constants);
var me = this;
this.hammer = Hammer(document.getElementById("main"), {
prevent_default: true
});
this.hammer.on('tap', me._onTap.bind(me) );
this.hammer.on('doubletap', me._onDoubleTap.bind(me) );
this.hammer.on('hold', me._onHold.bind(me) );
this.hammer.on('pinch', me._onPinch.bind(me) );
this.hammer.on('touch', me._onTouch.bind(me) );
this.hammer.on('dragstart', me._onDragStart.bind(me) );
this.hammer.on('drag', me._onDrag.bind(me) );
this.hammer.on('dragend', me._onDragEnd.bind(me) );
this.hammer.on('release', me._onRelease.bind(me) );
this.hammer.on('mousewheel',me._onMouseWheel.bind(me) );
this.hammer.on('DOMMouseScroll',me._onMouseWheel.bind(me) ); // for FF
this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
//this._drawLines();
}
SvgTimeline.prototype._createSVG = function() {
d3.select("div#visualization")
.append("svg").attr("id","main")
.attr("width",this.constants.width)
.attr("height",this.constants.height)
.attr("style","border:1px solid black")
};
/**
* Get the pointer location from a touch location
* @param {{pageX: Number, pageY: Number}} touch
* @return {{x: Number, y: Number}} pointer
* @private
*/
SvgTimeline.prototype._getPointer = function (touch) {
return {
x: touch.pageX,
y: touch.pageY
};
};
SvgTimeline.prototype._onTap = function() {};
SvgTimeline.prototype._onDoubleTap = function() {};
SvgTimeline.prototype._onHold = function() {};
SvgTimeline.prototype._onPinch = function() {};
SvgTimeline.prototype._onTouch = function(event) {};
SvgTimeline.prototype._onDragStart = function(event) {
this.initialDragPos = this._getPointer(event.gesture.center);
};
SvgTimeline.prototype._onDrag = function(event) {
var pointer = this._getPointer(event.gesture.center);
var diffX = pointer.x - this.initialDragPos.x;
// var diffY = pointer.y - this.initialDragPos.y;
this.initialDragPos = pointer;
this.range.start -= diffX * this.axis.msPerPixel;
this.range.end -= diffX * this.axis.msPerPixel;
this._update();
};
SvgTimeline.prototype._onDragEnd = function() {};
SvgTimeline.prototype._onRelease = function() {};
SvgTimeline.prototype._onMouseWheel = function(event) {
var delta = 0;
if (event.wheelDelta) { /* IE/Opera. */
delta = event.wheelDelta/120;
}
else if (event.detail) { /* Mozilla case. */
// In Mozilla, sign of delta is different than in IE.
// Also, delta is multiple of 3.
delta = -event.detail/3;
}
if (delta) {
var pointer = {x:event.x, y:event.y}
var center = this.range.start + this.axis.duration * 0.5;
var zoomSpeed = 0.1;
var scrollSpeed = 0.1;
this.range.start = center - 0.5*(this.axis.duration * (1 - delta*zoomSpeed));
this.range.end = this.range.start + (this.axis.duration * (1 - delta*zoomSpeed));
var diffX = delta*(pointer.x - 0.5*this.constants.width);
// var diffY = pointer.y - this.initialDragPos.y;
this.range.start -= diffX * this.axis.msPerPixel * scrollSpeed;
this.range.end -= diffX * this.axis.msPerPixel * scrollSpeed;
this._update();
}
};
SvgTimeline.prototype._onMouseMoveTitle = function() {};
SvgTimeline.prototype._update = function() {
this.axis._update();
}

+ 812
- 0
src/svgTimeline/util.js View File

@ -0,0 +1,812 @@
/**
* utility functions
*/
var util = {};
/**
* Test whether given object is a number
* @param {*} object
* @return {Boolean} isNumber
*/
util.isNumber = function isNumber(object) {
return (object instanceof Number || typeof object == 'number');
};
/**
* Test whether given object is a string
* @param {*} object
* @return {Boolean} isString
*/
util.isString = function isString(object) {
return (object instanceof String || typeof object == 'string');
};
/**
* Test whether given object is a Date, or a String containing a Date
* @param {Date | String} object
* @return {Boolean} isDate
*/
util.isDate = function isDate(object) {
if (object instanceof Date) {
return true;
}
else if (util.isString(object)) {
// test whether this string contains a date
var match = ASPDateRegex.exec(object);
if (match) {
return true;
}
else if (!isNaN(Date.parse(object))) {
return true;
}
}
return false;
};
/**
* Test whether given object is an instance of google.visualization.DataTable
* @param {*} object
* @return {Boolean} isDataTable
*/
util.isDataTable = function isDataTable(object) {
return (typeof (google) !== 'undefined') &&
(google.visualization) &&
(google.visualization.DataTable) &&
(object instanceof google.visualization.DataTable);
};
/**
* Create a semi UUID
* source: http://stackoverflow.com/a/105074/1262753
* @return {String} uuid
*/
util.randomUUID = function randomUUID () {
var S4 = function () {
return Math.floor(
Math.random() * 0x10000 /* 65536 */
).toString(16);
};
return (
S4() + S4() + '-' +
S4() + '-' +
S4() + '-' +
S4() + '-' +
S4() + S4() + S4()
);
};
/**
* Extend object a with the properties of object b or a series of objects
* Only properties with defined values are copied
* @param {Object} a
* @param {... Object} b
* @return {Object} a
*/
util.extend = function (a, b) {
for (var i = 1, len = arguments.length; i < len; i++) {
var other = arguments[i];
for (var prop in other) {
if (other.hasOwnProperty(prop) && other[prop] !== undefined) {
a[prop] = other[prop];
}
}
}
return a;
};
/**
* Convert an object to another type
* @param {Boolean | Number | String | Date | Moment | Null | undefined} object
* @param {String | undefined} type Name of the type. Available types:
* 'Boolean', 'Number', 'String',
* 'Date', 'Moment', ISODate', 'ASPDate'.
* @return {*} object
* @throws Error
*/
util.convert = function convert(object, type) {
var match;
if (object === undefined) {
return undefined;
}
if (object === null) {
return null;
}
if (!type) {
return object;
}
if (!(typeof type === 'string') && !(type instanceof String)) {
throw new Error('Type must be a string');
}
//noinspection FallthroughInSwitchStatementJS
switch (type) {
case 'boolean':
case 'Boolean':
return Boolean(object);
case 'number':
case 'Number':
return Number(object.valueOf());
case 'string':
case 'String':
return String(object);
case 'Date':
if (util.isNumber(object)) {
return new Date(object);
}
if (object instanceof Date) {
return new Date(object.valueOf());
}
else if (moment.isMoment(object)) {
return new Date(object.valueOf());
}
if (util.isString(object)) {
match = ASPDateRegex.exec(object);
if (match) {
// object is an ASP date
return new Date(Number(match[1])); // parse number
}
else {
return moment(object).toDate(); // parse string
}
}
else {
throw new Error(
'Cannot convert object of type ' + util.getType(object) +
' to type Date');
}
case 'Moment':
if (util.isNumber(object)) {
return moment(object);
}
if (object instanceof Date) {
return moment(object.valueOf());
}
else if (moment.isMoment(object)) {
return moment(object);
}
if (util.isString(object)) {
match = ASPDateRegex.exec(object);
if (match) {
// object is an ASP date
return moment(Number(match[1])); // parse number
}
else {
return moment(object); // parse string
}
}
else {
throw new Error(
'Cannot convert object of type ' + util.getType(object) +
' to type Date');
}
case 'ISODate':
if (util.isNumber(object)) {
return new Date(object);
}
else if (object instanceof Date) {
return object.toISOString();
}
else if (moment.isMoment(object)) {
return object.toDate().toISOString();
}
else if (util.isString(object)) {
match = ASPDateRegex.exec(object);
if (match) {
// object is an ASP date
return new Date(Number(match[1])).toISOString(); // parse number
}
else {
return new Date(object).toISOString(); // parse string
}
}
else {
throw new Error(
'Cannot convert object of type ' + util.getType(object) +
' to type ISODate');
}
case 'ASPDate':
if (util.isNumber(object)) {
return '/Date(' + object + ')/';
}
else if (object instanceof Date) {
return '/Date(' + object.valueOf() + ')/';
}
else if (util.isString(object)) {
match = ASPDateRegex.exec(object);
var value;
if (match) {
// object is an ASP date
value = new Date(Number(match[1])).valueOf(); // parse number
}
else {
value = new Date(object).valueOf(); // parse string
}
return '/Date(' + value + ')/';
}
else {
throw new Error(
'Cannot convert object of type ' + util.getType(object) +
' to type ASPDate');
}
default:
throw new Error('Cannot convert object of type ' + util.getType(object) +
' to type "' + type + '"');
}
};
// parse ASP.Net Date pattern,
// for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
// code from http://momentjs.com/
var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
/**
* Get the type of an object, for example util.getType([]) returns 'Array'
* @param {*} object
* @return {String} type
*/
util.getType = function getType(object) {
var type = typeof object;
if (type == 'object') {
if (object == null) {
return 'null';
}
if (object instanceof Boolean) {
return 'Boolean';
}
if (object instanceof Number) {
return 'Number';
}
if (object instanceof String) {
return 'String';
}
if (object instanceof Array) {
return 'Array';
}
if (object instanceof Date) {
return 'Date';
}
return 'Object';
}
else if (type == 'number') {
return 'Number';
}
else if (type == 'boolean') {
return 'Boolean';
}
else if (type == 'string') {
return 'String';
}
return type;
};
/**
* Retrieve the absolute left value of a DOM element
* @param {Element} elem A dom element, for example a div
* @return {number} left The absolute left position of this element
* in the browser page.
*/
util.getAbsoluteLeft = function getAbsoluteLeft (elem) {
var doc = document.documentElement;
var body = document.body;
var left = elem.offsetLeft;
var e = elem.offsetParent;
while (e != null && e != body && e != doc) {
left += e.offsetLeft;
left -= e.scrollLeft;
e = e.offsetParent;
}
return left;
};
/**
* Retrieve the absolute top value of a DOM element
* @param {Element} elem A dom element, for example a div
* @return {number} top The absolute top position of this element
* in the browser page.
*/
util.getAbsoluteTop = function getAbsoluteTop (elem) {
var doc = document.documentElement;
var body = document.body;
var top = elem.offsetTop;
var e = elem.offsetParent;
while (e != null && e != body && e != doc) {
top += e.offsetTop;
top -= e.scrollTop;
e = e.offsetParent;
}
return top;
};
/**
* Get the absolute, vertical mouse position from an event.
* @param {Event} event
* @return {Number} pageY
*/
util.getPageY = function getPageY (event) {
if ('pageY' in event) {
return event.pageY;
}
else {
var clientY;
if (('targetTouches' in event) && event.targetTouches.length) {
clientY = event.targetTouches[0].clientY;
}
else {
clientY = event.clientY;
}
var doc = document.documentElement;
var body = document.body;
return clientY +
( doc && doc.scrollTop || body && body.scrollTop || 0 ) -
( doc && doc.clientTop || body && body.clientTop || 0 );
}
};
/**
* Get the absolute, horizontal mouse position from an event.
* @param {Event} event
* @return {Number} pageX
*/
util.getPageX = function getPageX (event) {
if ('pageY' in event) {
return event.pageX;
}
else {
var clientX;
if (('targetTouches' in event) && event.targetTouches.length) {
clientX = event.targetTouches[0].clientX;
}
else {
clientX = event.clientX;
}
var doc = document.documentElement;
var body = document.body;
return clientX +
( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
( doc && doc.clientLeft || body && body.clientLeft || 0 );
}
};
/**
* add a className to the given elements style
* @param {Element} elem
* @param {String} className
*/
util.addClassName = function addClassName(elem, className) {
var classes = elem.className.split(' ');
if (classes.indexOf(className) == -1) {
classes.push(className); // add the class to the array
elem.className = classes.join(' ');
}
};
/**
* add a className to the given elements style
* @param {Element} elem
* @param {String} className
*/
util.removeClassName = function removeClassname(elem, className) {
var classes = elem.className.split(' ');
var index = classes.indexOf(className);
if (index != -1) {
classes.splice(index, 1); // remove the class from the array
elem.className = classes.join(' ');
}
};
/**
* For each method for both arrays and objects.
* In case of an array, the built-in Array.forEach() is applied.
* In case of an Object, the method loops over all properties of the object.
* @param {Object | Array} object An Object or Array
* @param {function} callback Callback method, called for each item in
* the object or array with three parameters:
* callback(value, index, object)
*/
util.forEach = function forEach (object, callback) {
var i,
len;
if (object instanceof Array) {
// array
for (i = 0, len = object.length; i < len; i++) {
callback(object[i], i, object);
}
}
else {
// object
for (i in object) {
if (object.hasOwnProperty(i)) {
callback(object[i], i, object);
}
}
}
};
/**
* Update a property in an object
* @param {Object} object
* @param {String} key
* @param {*} value
* @return {Boolean} changed
*/
util.updateProperty = function updateProp (object, key, value) {
if (object[key] !== value) {
object[key] = value;
return true;
}
else {
return false;
}
};
/**
* Add and event listener. Works for all browsers
* @param {Element} element An html element
* @param {string} action The action, for example "click",
* without the prefix "on"
* @param {function} listener The callback function to be executed
* @param {boolean} [useCapture]
*/
util.addEventListener = function addEventListener(element, action, listener, useCapture) {
if (element.addEventListener) {
if (useCapture === undefined)
useCapture = false;
if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
action = "DOMMouseScroll"; // For Firefox
}
element.addEventListener(action, listener, useCapture);
} else {
element.attachEvent("on" + action, listener); // IE browsers
}
};
/**
* Remove an event listener from an element
* @param {Element} element An html dom element
* @param {string} action The name of the event, for example "mousedown"
* @param {function} listener The listener function
* @param {boolean} [useCapture]
*/
util.removeEventListener = function removeEventListener(element, action, listener, useCapture) {
if (element.removeEventListener) {
// non-IE browsers
if (useCapture === undefined)
useCapture = false;
if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
action = "DOMMouseScroll"; // For Firefox
}
element.removeEventListener(action, listener, useCapture);
} else {
// IE browsers
element.detachEvent("on" + action, listener);
}
};
/**
* Get HTML element which is the target of the event
* @param {Event} event
* @return {Element} target element
*/
util.getTarget = function getTarget(event) {
// code from http://www.quirksmode.org/js/events_properties.html
if (!event) {
event = window.event;
}
var target;
if (event.target) {
target = event.target;
}
else if (event.srcElement) {
target = event.srcElement;
}
if (target.nodeType != undefined && target.nodeType == 3) {
// defeat Safari bug
target = target.parentNode;
}
return target;
};
/**
* Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
* @param {Element} element
* @param {Event} event
*/
util.fakeGesture = function fakeGesture (element, event) {
var eventType = null;
// for hammer.js 1.0.5
var gesture = Hammer.event.collectEventData(this, eventType, event);
// for hammer.js 1.0.6
//var touches = Hammer.event.getTouchList(event, eventType);
// var gesture = Hammer.event.collectEventData(this, eventType, touches, event);
// on IE in standards mode, no touches are recognized by hammer.js,
// resulting in NaN values for center.pageX and center.pageY
if (isNaN(gesture.center.pageX)) {
gesture.center.pageX = event.pageX;
}
if (isNaN(gesture.center.pageY)) {
gesture.center.pageY = event.pageY;
}
return gesture;
};
util.option = {};
/**
* Convert a value into a boolean
* @param {Boolean | function | undefined} value
* @param {Boolean} [defaultValue]
* @returns {Boolean} bool
*/
util.option.asBoolean = function (value, defaultValue) {
if (typeof value == 'function') {
value = value();
}
if (value != null) {
return (value != false);
}
return defaultValue || null;
};
/**
* Convert a value into a number
* @param {Boolean | function | undefined} value
* @param {Number} [defaultValue]
* @returns {Number} number
*/
util.option.asNumber = function (value, defaultValue) {
if (typeof value == 'function') {
value = value();
}
if (value != null) {
return Number(value) || defaultValue || null;
}
return defaultValue || null;
};
/**
* Convert a value into a string
* @param {String | function | undefined} value
* @param {String} [defaultValue]
* @returns {String} str
*/
util.option.asString = function (value, defaultValue) {
if (typeof value == 'function') {
value = value();
}
if (value != null) {
return String(value);
}
return defaultValue || null;
};
/**
* Convert a size or location into a string with pixels or a percentage
* @param {String | Number | function | undefined} value
* @param {String} [defaultValue]
* @returns {String} size
*/
util.option.asSize = function (value, defaultValue) {
if (typeof value == 'function') {
value = value();
}
if (util.isString(value)) {
return value;
}
else if (util.isNumber(value)) {
return value + 'px';
}
else {
return defaultValue || null;
}
};
/**
* Convert a value into a DOM element
* @param {HTMLElement | function | undefined} value
* @param {HTMLElement} [defaultValue]
* @returns {HTMLElement | null} dom
*/
util.option.asElement = function (value, defaultValue) {
if (typeof value == 'function') {
value = value();
}
return value || defaultValue || null;
};
util.GiveDec = function GiveDec(Hex)
{
if(Hex == "A")
Value = 10;
else
if(Hex == "B")
Value = 11;
else
if(Hex == "C")
Value = 12;
else
if(Hex == "D")
Value = 13;
else
if(Hex == "E")
Value = 14;
else
if(Hex == "F")
Value = 15;
else
Value = eval(Hex)
return Value;
}
util.GiveHex = function GiveHex(Dec)
{
if(Dec == 10)
Value = "A";
else
if(Dec == 11)
Value = "B";
else
if(Dec == 12)
Value = "C";
else
if(Dec == 13)
Value = "D";
else
if(Dec == 14)
Value = "E";
else
if(Dec == 15)
Value = "F";
else
Value = "" + Dec;
return Value;
}
/**
* http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php
*
* @param {String} hex
* @returns {{r: *, g: *, b: *}}
*/
util.hexToRGB = function hexToRGB(hex) {
hex = hex.replace("#","").toUpperCase();
var a = util.GiveDec(hex.substring(0, 1));
var b = util.GiveDec(hex.substring(1, 2));
var c = util.GiveDec(hex.substring(2, 3));
var d = util.GiveDec(hex.substring(3, 4));
var e = util.GiveDec(hex.substring(4, 5));
var f = util.GiveDec(hex.substring(5, 6));
var r = (a * 16) + b;
var g = (c * 16) + d;
var b = (e * 16) + f;
return {r:r,g:g,b:b};
};
util.RGBToHex = function RGBToHex(red,green,blue) {
var a = util.GiveHex(Math.floor(red / 16));
var b = util.GiveHex(red % 16);
var c = util.GiveHex(Math.floor(green / 16));
var d = util.GiveHex(green % 16);
var e = util.GiveHex(Math.floor(blue / 16));
var f = util.GiveHex(blue % 16);
var hex = a + b + c + d + e + f;
return "#" + hex;
};
/**
* http://www.javascripter.net/faq/rgb2hsv.htm
*
* @param red
* @param green
* @param blue
* @returns {*}
* @constructor
*/
util.RGBToHSV = function RGBToHSV (red,green,blue) {
red=red/255; green=green/255; blue=blue/255;
var minRGB = Math.min(red,Math.min(green,blue));
var maxRGB = Math.max(red,Math.max(green,blue));
// Black-gray-white
if (minRGB == maxRGB) {
return {h:0,s:0,v:minRGB};
}
// Colors other than black-gray-white:
var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
var hue = 60*(h - d/(maxRGB - minRGB))/360;
var saturation = (maxRGB - minRGB)/maxRGB;
var value = maxRGB;
return {h:hue,s:saturation,v:value};
};
/**
* https://gist.github.com/mjijackson/5311256
* @param hue
* @param saturation
* @param value
* @returns {{r: number, g: number, b: number}}
* @constructor
*/
util.HSVToRGB = function HSVToRGB(h, s, v) {
var r, g, b;
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
};
util.HSVToHex = function HSVToHex(h,s,v) {
var rgb = util.HSVToRGB(h,s,v);
return util.RGBToHex(rgb.r,rgb.g,rgb.b);
}
util.hexToHSV = function hexToHSV(hex) {
var rgb = util.hexToRGB(hex);
return util.RGBToHSV(rgb.r,rgb.g,rgb.b);
}
util.isValidHex = function isValidHex(hex) {
var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
return isOk;
}

Loading…
Cancel
Save