Skip to content

Instantly share code, notes, and snippets.

@esshka
Created March 29, 2015 06:26
Show Gist options
  • Save esshka/a98a521b554b0cf48ce6 to your computer and use it in GitHub Desktop.
Save esshka/a98a521b554b0cf48ce6 to your computer and use it in GitHub Desktop.
(function() {
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Zoomator.directive("timeline", [
"$filter", "$document", "$window", "FactorComputate", function($filter, $document, $window, FactorComputate) {
return {
restrict: "EA",
replace: true,
template: "<div id='timeline' style='height:90px;'><svg style='height:100%;width:100%;'></svg></div>",
scope: {
selectedPeriodBegin: "=",
selectedPeriodEnd: "=",
fullPeriodBegin: "=",
fullPeriodEnd: "=",
iconsPath: "@",
markerDatetime: "=",
markerPosition: "="
},
link: function(scope, element) {
/**
* Snap svg plugin
*/
var BottomAxis, Label, Marker, Selector, Timeline, TopAxis, timeline;
Snap.plugin(function(Snap, Element) {
return Element.prototype.moveTo = function(x, y) {
var diffx, diffy;
diffx = this.getBBox().cx - x;
diffy = this.getBBox().cy - y;
return this.attr("transform", this.transform().local + ("" + (this.transform().local ? 'T' : 't') + (-diffx) + "," + (-diffy)));
};
});
/**
* timeline main class
*/
Timeline = (function() {
function Timeline() {
this.paper = Snap(angular.element('#timeline').find('svg')[0]);
this.paper.selectAll('*').remove();
/**
* detect timeline container width and height
*/
this.containerWidth = angular.element("#timeline").width();
this.containerHeight = angular.element("#timeline").height();
/**
* initialize timeline components
*/
Snap.load(scope.iconsPath, (function(_this) {
return function(icons) {
_this.svgIcons = icons;
_this.topAxis = new TopAxis(_this);
_this.bottomAxis = new BottomAxis(_this);
_this.selector = new Selector(_this);
scope.markerDatetime = scope.selectedPeriodBegin + (scope.selectedPeriodEnd - scope.selectedPeriodBegin) / 2;
scope.markerPosition = _this.bottomAxis.timestampToXPosition(scope.markerDatetime);
_this.marker = new Marker(_this);
return scope.$emit('timeline:ready');
};
})(this));
}
/**
* public
*/
Timeline.prototype.update = function() {
var newx1, newx2;
if (this.topAxis) {
newx1 = this.topAxis.timestampToXPosition(scope.selectedPeriodBegin);
newx2 = this.topAxis.timestampToXPosition(scope.selectedPeriodEnd);
this.selector.update(newx1, newx2);
scope.$emit('timeline:ready');
}
return console.log("timeline updated");
};
return Timeline;
})();
TopAxis = (function() {
function TopAxis(timeline) {
this.timeline = timeline;
this.paper = this.timeline.paper;
this.duration = scope.fullPeriodEnd - scope.fullPeriodBegin;
this.nodeClass = "t_axis";
this.strokeWidth = 2;
this.margin = {
left: 25,
right: 25,
top: 20
};
this.labels = [];
this.minLabelsSpacing = 4;
this.bigLabelsNumber = 10;
this.length = this.timeline.containerWidth - this.margin.left - this.margin.right;
this.labelsNumber = Math.ceil(this.length / this.minLabelsSpacing);
this.bigLabelsSpacing = Math.ceil(this.labelsNumber / this.bigLabelsNumber);
this.pixelMilliseconds = this.duration / this.length;
this._fillLabels();
this._draw();
this._drawSideControls();
}
TopAxis.prototype._drawSideControls = function() {
var moversGroup;
moversGroup = this.paper.group().attr({
"class": 'movers'
});
this.lm = this.timeline.svgIcons.select("#to_left");
this.paper.append(this.lm);
moversGroup.g(this.lm).attr({
"class": 'move-left'
}).moveTo(0 + this.lm.getBBox().width / 2, this.margin.top * 3 / 4).click(this._leftShiftPeriod);
this.rm = this.timeline.svgIcons.select("#to_right");
this.paper.append(this.rm);
return moversGroup.g(this.rm).attr({
"class": 'move-right'
}).moveTo(this.length - this.rm.getBBox().width / 2 + this.margin.right * 2, this.margin.top * 3 / 4).click(this._rightShiftPeriod);
};
TopAxis.prototype._leftShiftPeriod = function() {
var duration;
duration = scope.selectedPeriodEnd - scope.selectedPeriodBegin;
if (scope.selectedPeriodBegin - duration > scope.fullPeriodBegin) {
scope.selectedPeriodEnd -= duration;
return scope.selectedPeriodBegin -= duration;
}
};
TopAxis.prototype._rightShiftPeriod = function() {
var duration;
duration = scope.selectedPeriodEnd - scope.selectedPeriodBegin;
if (scope.selectedPeriodEnd + duration < scope.fullPeriodEnd) {
scope.selectedPeriodEnd += duration;
return scope.selectedPeriodBegin += duration;
}
};
TopAxis.prototype._fillLabels = function() {
_.times(this.labelsNumber, (function(_this) {
return function(n) {
var label, type, x;
type = (function() {
switch (false) {
case !(n % this.bigLabelsSpacing === 0 && n !== 0 && n !== this.labelsNumber):
return "big";
case n % 2 !== 0:
return "medium";
default:
return "small";
}
}).call(_this);
x = n * _this.minLabelsSpacing + _this.margin.left;
label = new Label(_this, x, type);
if (label !== void 0) {
return _this.labels.push(label);
}
};
})(this));
return this.labels = _.compact(this.labels);
};
TopAxis.prototype._draw = function() {
this.tAxis = this.paper.g(this.paper.line(this.margin.left, this.margin.top, this.timeline.containerWidth - this.margin.right, this.margin.top).attr({
"class": "" + this.nodeClass
})).attr({
"class": 'topaxis-group'
});
return _.each(this.labels, (function(_this) {
return function(l) {
return l.draw(_this.tAxis);
};
})(this));
};
/**
* Public
*/
TopAxis.prototype.xPositionToTimestamp = function(x) {
return scope.fullPeriodBegin + this.pixelMilliseconds * (x - this.margin.left);
};
TopAxis.prototype.timestampToXPosition = function(ts) {
return this.margin.left + (ts - scope.fullPeriodBegin) / this.pixelMilliseconds;
};
return TopAxis;
})();
BottomAxis = (function() {
function BottomAxis(timeline) {
this.timeline = timeline;
this.paper = this.timeline.paper;
this.duration = scope.selectedPeriodEnd - scope.selectedPeriodBegin;
this.nodeClass = "b_axis";
this.strokeWidth = 4;
this.margin = {
top: this.timeline.containerHeight,
left: 0,
right: 0
};
this.labels = [];
this.minLabelsSpacing = 4;
this.bigLabelsNumber = 10;
this.length = this.timeline.containerWidth;
this.labelsNumber = Math.ceil(this.length / this.minLabelsSpacing);
this.bigLabelsSpacing = Math.ceil(this.labelsNumber / this.bigLabelsNumber);
this.pixelMilliseconds = this.duration / this.length;
this._fillLabels();
this._draw();
}
BottomAxis.prototype._fillLabels = function() {
_.times(this.labelsNumber, (function(_this) {
return function(n) {
var label, type, x;
type = (function() {
switch (false) {
case !(n % this.bigLabelsSpacing === 0 && n !== 0 && n !== this.labelsNumber):
return "big";
case n % 2 !== 0:
return "medium";
default:
return "small";
}
}).call(_this);
x = n * _this.minLabelsSpacing;
label = new Label(_this, x, type);
if (label !== void 0) {
return _this.labels.push(label);
}
};
})(this));
return this.labels = _.compact(this.labels);
};
BottomAxis.prototype._draw = function() {
this.tAxis = this.paper.g(this.paper.line(this.margin.left, this.margin.top, this.timeline.containerWidth - this.margin.right, this.margin.top).attr({
"class": "" + this.nodeClass
})).attr({
"class": 'bottomaxis-group'
});
return _.each(this.labels, (function(_this) {
return function(l) {
return l.draw(_this.tAxis);
};
})(this));
};
/**
* Public
*/
BottomAxis.prototype.xPositionToTimestamp = function(x) {
return scope.selectedPeriodBegin + this.pixelMilliseconds * x;
};
BottomAxis.prototype.timestampToXPosition = function(ts) {
return (ts - scope.selectedPeriodBegin) / this.pixelMilliseconds;
};
return BottomAxis;
})();
Label = (function() {
function Label(axis, x, type) {
this.axis = axis;
this.x = x;
this.type = type;
this.nodeClass = (function() {
switch (false) {
case !(this.axis instanceof TopAxis):
return "t_mark";
case !(this.axis instanceof BottomAxis):
return "b_mark";
}
}).call(this);
this.bigLabelClass = (function() {
switch (false) {
case !(this.axis instanceof TopAxis):
return "axis_labels";
case !(this.axis instanceof BottomAxis):
return "bt_axis_labels";
}
}).call(this);
this.bigTopLabelTextBottomOffset = 2;
this.bigBottomLabelBottomDateOffset = 12;
this.bigBottomLabelBottomTimeOffset = 2;
this.labelHeight = (function() {
switch (this.type) {
case "big":
return 8;
case "medium":
return 4;
case "small":
return 2;
}
}).call(this);
this.text = this.axis.xPositionToTimestamp(this.x);
}
Label.prototype.draw = function(g) {
var bigLabelGroup;
if (this.type !== "big") {
return g.add(this.axis.paper.line(this.x, this.axis.margin.top - this.axis.strokeWidth / 2, this.x, this.axis.margin.top - this.labelHeight - this.axis.strokeWidth / 2).attr({
"class": "" + this.nodeClass + " " + this.type
}));
} else {
bigLabelGroup = this.axis.paper.g(this.axis.paper.line(this.x, this.axis.margin.top - this.axis.strokeWidth / 2, this.x, this.axis.margin.top - this.labelHeight - this.axis.strokeWidth / 2).attr({
"class": "" + this.nodeClass + " " + this.type
}));
if (this.axis instanceof TopAxis) {
bigLabelGroup.add(this.axis.paper.text(this.x, this.axis.margin.top - this.labelHeight - this.bigTopLabelTextBottomOffset - this.axis.strokeWidth, ["" + ($filter('date')(this.text, 'dd MMMM yyyy'))]).attr({
"class": "" + this.bigLabelClass,
textAnchor: 'middle'
}));
} else {
bigLabelGroup.add(this.axis.paper.text(this.x, this.axis.margin.top - this.labelHeight - this.bigBottomLabelBottomDateOffset - this.axis.strokeWidth, ["" + ($filter('date')(this.text, 'dd MMMM yyyy'))]).attr({
"class": "" + this.bigLabelClass,
textAnchor: 'middle'
}));
bigLabelGroup.add(this.axis.paper.text(this.x, this.axis.margin.top - this.labelHeight - this.bigBottomLabelBottomTimeOffset - this.axis.strokeWidth, ["" + ($filter('date')(this.text, 'HH:MM'))]).attr({
"class": "" + this.bigLabelClass,
textAnchor: 'middle'
}));
}
return g.add(bigLabelGroup);
}
};
return Label;
})();
Selector = (function() {
function Selector(timeline) {
this.timeline = timeline;
this.translateRunner = __bind(this.translateRunner, this);
this.updateMovement = __bind(this.updateMovement, this);
this.saveOrigData = __bind(this.saveOrigData, this);
this.rightRunnerMoveEnd = __bind(this.rightRunnerMoveEnd, this);
this.rightRunnerMoveStart = __bind(this.rightRunnerMoveStart, this);
this.rightRunnerMove = __bind(this.rightRunnerMove, this);
this.leftRunnerMoveEnd = __bind(this.leftRunnerMoveEnd, this);
this.leftRunnerMoveStart = __bind(this.leftRunnerMoveStart, this);
this.leftRunnerMove = __bind(this.leftRunnerMove, this);
this.endMove = __bind(this.endMove, this);
this.startMove = __bind(this.startMove, this);
this.move = __bind(this.move, this);
this.paper = this.timeline.paper;
this.begin = this.timeline.topAxis.timestampToXPosition(scope.selectedPeriodBegin);
this.end = this.timeline.topAxis.timestampToXPosition(scope.selectedPeriodEnd);
this.runnerWidth = 10;
this._draw();
}
Selector.prototype._draw = function() {
this.line = this.paper.line(this.begin, 20, this.end, 20).attr({
"class": "range_selector",
stroke: "#76b7e4",
strokeWidth: 10
}).drag(this.move, this.startMove, this.endMove);
this.tlBorder = this.paper.line(0, this.timeline.containerHeight - 60, this.begin, this.timeline.containerHeight - 60).attr({
"class": 'tl_border'
});
this.trBorder = this.paper.line(this.end, this.timeline.containerHeight - 60, this.timeline.containerWidth, this.timeline.containerHeight - 60).attr({
"class": 'tr_border'
});
this.paper.g(this.paper.line(0, this.timeline.containerHeight - 60, 0, this.timeline.containerHeight).attr({
"class": 'r_border'
}), this.paper.line(this.timeline.containerWidth, this.timeline.containerHeight - 60, this.timeline.containerWidth, this.timeline.containerHeight).attr({
"class": 'l_border'
}));
this.rightRunner = this.timeline.svgIcons.select("#drag_right");
this.paper.append(this.rightRunner);
this.rightRunner.moveTo(this.end + this.rightRunner.getBBox().width / 2, this.timeline.topAxis.margin.top + 2).drag(this.rightRunnerMove, this.rightRunnerMoveStart, this.rightRunnerMoveEnd);
this.leftRunner = this.timeline.svgIcons.select("#drag_left");
this.paper.append(this.leftRunner);
this.leftRunner.moveTo(this.begin - this.leftRunner.getBBox().width / 2, this.timeline.topAxis.margin.top + 2).drag(this.leftRunnerMove, this.leftRunnerMoveStart, this.leftRunnerMoveEnd);
return this.controlGroup = this.timeline.topAxis.tAxis.add(this.line, this.leftRunner, this.rightRunner).attr({
"class": 'control-group'
});
};
Selector.prototype.update = function(newx1, newx2) {
this.begin = newx1;
this.end = newx2;
this.leftRunner.moveTo(newx1 - this.leftRunner.getBBox().width / 2, this.leftRunner.getBBox().cy);
this.rightRunner.moveTo(newx2 + this.rightRunner.getBBox().width / 2, this.rightRunner.getBBox().cy);
this.line.attr('x1', newx1);
this.line.attr('x2', newx2);
this.tlBorder.attr('x2', newx1);
return this.trBorder.attr('x1', newx2);
};
Selector.prototype.move = function(dx) {
return this.updateMovement(this.line, dx);
};
Selector.prototype.startMove = function(x) {
return this.saveOrigData();
};
Selector.prototype.endMove = function(e) {
var ex1, ex2;
ex1 = this.line.attr("x1");
ex2 = this.line.attr("x2");
scope.selectedPeriodBegin = this.timeline.topAxis.xPositionToTimestamp(ex1);
return scope.selectedPeriodEnd = this.timeline.topAxis.xPositionToTimestamp(ex2);
};
Selector.prototype.leftRunnerMove = function(dx) {
return this.updateMovement(this.leftRunner, dx);
};
Selector.prototype.leftRunnerMoveStart = function(x) {
return this.saveOrigData();
};
Selector.prototype.leftRunnerMoveEnd = function(e) {
var ex1;
ex1 = this.line.attr("x1");
return scope.selectedPeriodBegin = this.timeline.topAxis.xPositionToTimestamp(ex1);
};
Selector.prototype.rightRunnerMove = function(dx) {
return this.updateMovement(this.rightRunner, dx);
};
Selector.prototype.rightRunnerMoveStart = function(x) {
return this.saveOrigData();
};
Selector.prototype.rightRunnerMoveEnd = function(e) {
var ex2;
ex2 = this.line.attr("x2");
return scope.selectedPeriodEnd = this.timeline.topAxis.xPositionToTimestamp(ex2);
};
Selector.prototype.saveOrigData = function() {
this.line.data('ox1', this.line.attr("x1"));
this.line.data('ox2', this.line.attr("x2"));
this.leftRunner.data('origTransform', this.leftRunner.transform().local);
this.rightRunner.data('origTransform', this.rightRunner.transform().local);
this.leftRunner.data("origBBoxCx", this.leftRunner.getBBox().cx);
this.rightRunner.data("origBBoxCx", this.rightRunner.getBBox().cx);
this.leftRunner.data('ibb', this.leftRunner.getBBox());
return this.rightRunner.data('ibb', this.rightRunner.getBBox());
};
Selector.prototype.updateMovement = function(obj, dx) {
var lim1, lim2, newx1, newx2;
if (obj !== this.line) {
this.translateRunner(obj, dx);
}
newx1 = +this.line.data("ox1") + dx;
newx2 = +this.line.data("ox2") + dx;
switch (false) {
case obj !== this.leftRunner:
lim1 = this.timeline.topAxis.margin.left;
lim2 = this.line.attr('x2');
if (newx1 < lim1) {
this.line.attr('x1', lim1);
return this.tlBorder.attr('x2', lim1);
} else if (newx1 > lim2) {
this.line.attr('x1', lim2);
return this.tlBorder.attr('x2', lim2);
} else {
this.line.attr('x1', newx1);
return this.tlBorder.attr('x2', newx1);
}
break;
case obj !== this.rightRunner:
lim1 = this.line.attr('x1');
lim2 = this.timeline.containerWidth - this.timeline.topAxis.margin.right;
if (newx2 < lim1) {
this.line.attr('x2', lim1);
return this.trBorder.attr('x1', lim1);
} else if (newx2 > lim2) {
this.line.attr('x2', lim2);
return this.trBorder.attr('x1', lim2);
} else {
this.line.attr('x2', newx2);
return this.trBorder.attr('x1', newx2);
}
break;
case obj !== this.line:
lim1 = 0 + this.timeline.topAxis.margin.left;
lim2 = this.timeline.containerWidth - this.timeline.topAxis.margin.right;
if (newx1 > lim1 && newx2 < lim2) {
newx1 = +this.line.data("ox1") + dx;
newx2 = +this.line.data("ox2") + dx;
this.line.attr('x1', newx1);
this.line.attr('x2', newx2);
this.trBorder.attr('x1', newx2);
this.tlBorder.attr('x2', newx1);
return _.each([this.leftRunner, this.rightRunner], (function(_this) {
return function(ele) {
return _this.translateRunner(ele, dx);
};
})(this));
}
}
};
Selector.prototype.translateRunner = function(ele, dx) {
var cur, lim1, lim2, transformString;
if (ele === this.leftRunner) {
lim1 = this.rightRunner.getBBox().x;
lim2 = this.timeline.topAxis.margin.left;
cur = this.leftRunner.data("ibb").x2 + dx;
if (cur > lim1) {
dx = lim1 - this.leftRunner.data("ibb").x2;
}
if (cur < lim2) {
dx = lim2 - this.leftRunner.data("ibb").x2;
}
} else if (ele === this.rightRunner) {
lim1 = this.leftRunner.getBBox().cx + this.leftRunner.getBBox().width / 2;
lim2 = this.timeline.containerWidth - this.timeline.topAxis.margin.right;
cur = this.rightRunner.data("ibb").x + dx;
if (cur < lim1) {
dx = lim1 - this.rightRunner.data("ibb").x;
}
if (cur > lim2) {
dx = lim2 - this.rightRunner.data("ibb").x;
}
}
transformString = ele.data('origTransform') + ("" + (ele.data('origTransform') ? 'T' : 't') + dx);
return ele.attr('transform', transformString);
};
return Selector;
})();
Marker = (function() {
function Marker(timeline) {
this.timeline = timeline;
this._onEndMove = __bind(this._onEndMove, this);
this._onMove = __bind(this._onMove, this);
this.paper = this.timeline.paper;
this.icon = this.timeline.svgIcons.select('#runner');
this.position = this.timeline.containerWidth / 2 || this.timeline.bottomAxis.timestampToXPosition(scope.markerDatetime);
this.lim1 = 0;
this.lim2 = this.timeline.containerWidth;
this._draw();
this._bindEvents();
}
Marker.prototype._draw = function() {
var shiftx;
this.paper.append(this.icon);
shiftx = this.icon.getBBox().height / 2;
return this.icon.moveTo(this.position, this.timeline.containerHeight - shiftx - 2);
};
Marker.prototype._bindEvents = function() {
return this.icon.drag(this._onMove, this._onStartMove, this._onEndMove);
};
Marker.prototype._onMove = function(dx) {
var cx;
cx = this.icon.data("startcx");
if (cx + dx < this.lim1) {
dx = this.lim1 - cx;
} else if (this.icon.data("startcx") + dx > this.lim2) {
dx = this.lim2 - cx;
}
this.icon.attr("transform", this.icon.data('origTransform') + ("" + (this.icon.data('origTransform') ? 'T' : 't') + dx));
scope.markerDatetime = Math.round(this.timeline.bottomAxis.xPositionToTimestamp(cx + dx));
scope.markerPosition = Math.round(cx + dx);
return scope.$apply();
};
Marker.prototype._onStartMove = function() {
this.data('origTransform', this.transform().local);
return this.data('startcx', this.getBBox().cx);
};
Marker.prototype._onEndMove = function() {
return console.log("end move");
};
return Marker;
})();
timeline = void 0;
scope.$watchGroup(["fullPeriodBegin", "fullPeriodEnd"], function(newvalues) {
if (newvalues[0] && newvalues[1]) {
return timeline = new Timeline;
}
});
return scope.$watchGroup(["selectedPeriodBegin", "selectedPeriodEnd"], function(newvalues) {
if (newvalues[0] && newvalues[1]) {
scope.$on("timeline:ready", function() {
return console.log(timeline.topAxis.timestampToXPosition(newvalues[0]));
});
timeline.update();
return console.log(newvalues);
}
});
}
};
}
]);
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment