Add Gantt chart for projects
BIN
assets/css/images/ui-bg_flat_0_aaaaaa_40x100.png
Executable file → Normal file
|
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
BIN
assets/css/images/ui-bg_flat_75_ffffff_40x100.png
Executable file → Normal file
|
Before Width: | Height: | Size: 208 B After Width: | Height: | Size: 208 B |
BIN
assets/css/images/ui-bg_glass_55_fbf9ee_1x400.png
Executable file → Normal file
|
Before Width: | Height: | Size: 335 B After Width: | Height: | Size: 335 B |
BIN
assets/css/images/ui-bg_glass_65_ffffff_1x400.png
Executable file → Normal file
|
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 207 B |
BIN
assets/css/images/ui-bg_glass_75_dadada_1x400.png
Executable file → Normal file
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 262 B |
BIN
assets/css/images/ui-bg_glass_75_e6e6e6_1x400.png
Executable file → Normal file
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 262 B |
BIN
assets/css/images/ui-bg_glass_95_fef1ec_1x400.png
Executable file → Normal file
|
Before Width: | Height: | Size: 332 B After Width: | Height: | Size: 332 B |
BIN
assets/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png
Executable file → Normal file
|
Before Width: | Height: | Size: 280 B After Width: | Height: | Size: 280 B |
BIN
assets/css/images/ui-icons_222222_256x240.png
Executable file → Normal file
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
BIN
assets/css/images/ui-icons_2e83ff_256x240.png
Executable file → Normal file
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
BIN
assets/css/images/ui-icons_454545_256x240.png
Executable file → Normal file
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
BIN
assets/css/images/ui-icons_888888_256x240.png
Executable file → Normal file
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
BIN
assets/css/images/ui-icons_cd0a0a_256x240.png
Executable file → Normal file
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -1,3 +1,8 @@
|
||||
.toolbar {
|
||||
font-size: 0.9em;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.views {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
@@ -9,6 +14,7 @@
|
||||
padding-right: 12px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.views li.active a {
|
||||
|
||||
130
assets/css/src/gantt.css
Normal file
@@ -0,0 +1,130 @@
|
||||
/* Based on jQuery.ganttView v.0.8.0 Copyright (c) 2010 JC Grubbs - jc.grubbs@devmynd.com - MIT License */
|
||||
div.ganttview-hzheader-month,
|
||||
div.ganttview-hzheader-day,
|
||||
div.ganttview-vtheader,
|
||||
div.ganttview-vtheader-item-name,
|
||||
div.ganttview-vtheader-series,
|
||||
div.ganttview-grid,
|
||||
div.ganttview-grid-row-cell {
|
||||
float: left;
|
||||
}
|
||||
|
||||
div.ganttview-hzheader-month,
|
||||
div.ganttview-hzheader-day {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.ganttview-grid-row-cell.last,
|
||||
div.ganttview-hzheader-day.last,
|
||||
div.ganttview-hzheader-month.last {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
div.ganttview {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
/* Horizontal Header */
|
||||
div.ganttview-hzheader-month {
|
||||
width: 60px;
|
||||
height: 20px;
|
||||
border-right: 1px solid #d0d0d0;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
div.ganttview-hzheader-day {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
border-top: 1px solid #d0d0d0;
|
||||
line-height: 20px;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
/* Vertical Header */
|
||||
div.ganttview-vtheader {
|
||||
margin-top: 41px;
|
||||
width: 400px;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
div.ganttview-vtheader-item {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
div.ganttview-vtheader-series-name {
|
||||
width: 400px;
|
||||
height: 31px;
|
||||
line-height: 31px;
|
||||
padding-left: 3px;
|
||||
border-top: 1px solid #d0d0d0;
|
||||
font-size: 0.9em;
|
||||
text-overflow: ellipsis;
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div.ganttview-vtheader-series-name a {
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div.ganttview-vtheader-series-name a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Slider */
|
||||
div.ganttview-slide-container {
|
||||
overflow: auto;
|
||||
border-left: 1px solid #999;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
div.ganttview-grid-row-cell {
|
||||
width: 20px;
|
||||
height: 31px;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
div.ganttview-grid-row-cell.ganttview-weekend {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
/* Blocks */
|
||||
div.ganttview-blocks {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
div.ganttview-block-container {
|
||||
height: 28px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
div.ganttview-block {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
background-color: #E5ECF9;
|
||||
border: 1px solid #c0c0c0;
|
||||
border-radius: 3px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
div.ganttview-block-not-defined {
|
||||
border-color: #000;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
div.ganttview-block-text {
|
||||
position: absolute;
|
||||
height: 12px;
|
||||
font-size: 0.7em;
|
||||
color: #999;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
/* Adjustments for jQuery UI Styling */
|
||||
div.ganttview-block div.ui-resizable-handle.ui-resizable-s {
|
||||
bottom: -0;
|
||||
}
|
||||
@@ -89,6 +89,7 @@ nav .active a {
|
||||
.page-header ul {
|
||||
text-align: left;
|
||||
margin-top: 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.page-header li {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
div.ui-tooltip {
|
||||
min-width: 200px;
|
||||
max-width: 600px;
|
||||
font-size: 0.95em;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.tooltip-arrow {
|
||||
|
||||
6
assets/css/vendor/jquery-ui.min.css
vendored
2
assets/css/vendor/jquery-ui.theme.min.css
vendored
@@ -1,4 +1,4 @@
|
||||
/*! jQuery UI - v1.11.3 - 2015-02-14
|
||||
/*! jQuery UI - v1.11.4 - 2015-08-09
|
||||
* http://jqueryui.com
|
||||
* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
|
||||
106
assets/js/app.js
@@ -8,7 +8,6 @@ function App() {
|
||||
this.popover = new Popover(this);
|
||||
this.keyboardShortcuts();
|
||||
this.boardSelector();
|
||||
this.listen();
|
||||
this.poll();
|
||||
|
||||
// Alert box fadeout
|
||||
@@ -28,8 +27,6 @@ function App() {
|
||||
}
|
||||
|
||||
App.prototype.listen = function() {
|
||||
$(document).off();
|
||||
|
||||
this.popover.listen();
|
||||
this.markdown.listen();
|
||||
this.sidebar.listen();
|
||||
@@ -42,6 +39,11 @@ App.prototype.listen = function() {
|
||||
this.focus();
|
||||
};
|
||||
|
||||
App.prototype.refresh = function() {
|
||||
$(document).off();
|
||||
this.listen();
|
||||
};
|
||||
|
||||
App.prototype.focus = function() {
|
||||
|
||||
// Autofocus fields (html5 autofocus works only with page onload)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
function AvgTimeColumnChart() {
|
||||
function AvgTimeColumnChart(app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
AvgTimeColumnChart.prototype.execute = function(app) {
|
||||
AvgTimeColumnChart.prototype.execute = function() {
|
||||
var metrics = $("#chart").data("metrics");
|
||||
var plots = [$("#chart").data("label")];
|
||||
var categories = [];
|
||||
@@ -28,7 +29,7 @@ AvgTimeColumnChart.prototype.execute = function(app) {
|
||||
},
|
||||
y: {
|
||||
tick: {
|
||||
format: app.formatDuration
|
||||
format: this.app.formatDuration
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
function Board() {
|
||||
this.app = null;
|
||||
function Board(app) {
|
||||
this.app = app;
|
||||
this.checkInterval = null;
|
||||
}
|
||||
|
||||
Board.prototype.execute = function(app) {
|
||||
this.app = app;
|
||||
Board.prototype.execute = function() {
|
||||
this.app.swimlane.refresh();
|
||||
this.app.swimlane.listen();
|
||||
this.poll();
|
||||
@@ -33,7 +32,7 @@ Board.prototype.check = function() {
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
url: $("#board").attr("data-check-url"),
|
||||
url: $("#board").data("check-url"),
|
||||
statusCode: {
|
||||
200: function(data) { self.refresh(data); },
|
||||
304: function () { self.app.hideLoadingIcon(); }
|
||||
@@ -47,7 +46,7 @@ Board.prototype.save = function(taskId, columnId, position, swimlaneId) {
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
url: $("#board").attr("data-save-url"),
|
||||
url: $("#board").data("save-url"),
|
||||
contentType: "application/json",
|
||||
type: "POST",
|
||||
processData: false,
|
||||
@@ -65,7 +64,7 @@ Board.prototype.save = function(taskId, columnId, position, swimlaneId) {
|
||||
Board.prototype.refresh = function(data) {
|
||||
$("#board-container").replaceWith(data);
|
||||
|
||||
this.app.listen();
|
||||
this.app.refresh();
|
||||
this.app.swimlane.refresh();
|
||||
this.app.swimlane.listen();
|
||||
this.resizeColumnHeight();
|
||||
|
||||
402
assets/js/src/Gantt.js
Normal file
@@ -0,0 +1,402 @@
|
||||
// Based on jQuery.ganttView v.0.8.8 Copyright (c) 2010 JC Grubbs - jc.grubbs@devmynd.com - MIT License
|
||||
function Gantt(app) {
|
||||
this.app = app;
|
||||
this.data = [];
|
||||
|
||||
this.options = {
|
||||
container: "#gantt-chart",
|
||||
showWeekends: true,
|
||||
cellWidth: 21,
|
||||
cellHeight: 31,
|
||||
slideWidth: 1000,
|
||||
vHeaderWidth: 200
|
||||
};
|
||||
}
|
||||
|
||||
// Save task after a resize or move
|
||||
Gantt.prototype.saveTask = function(task) {
|
||||
this.app.showLoadingIcon();
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
url: $(this.options.container).data("save-url"),
|
||||
contentType: "application/json",
|
||||
type: "POST",
|
||||
processData: false,
|
||||
data: JSON.stringify(task),
|
||||
complete: this.app.hideLoadingIcon.bind(this)
|
||||
});
|
||||
};
|
||||
|
||||
// Build the Gantt chart
|
||||
Gantt.prototype.execute = function() {
|
||||
this.data = this.prepareData($(this.options.container).data('tasks'));
|
||||
|
||||
var minDays = Math.floor((this.options.slideWidth / this.options.cellWidth) + 5);
|
||||
var range = this.getDateRange(minDays);
|
||||
var startDate = range[0];
|
||||
var endDate = range[1];
|
||||
var container = $(this.options.container);
|
||||
var chart = jQuery("<div>", { "class": "ganttview" });
|
||||
|
||||
chart.append(this.renderVerticalHeader());
|
||||
chart.append(this.renderSlider(startDate, endDate));
|
||||
container.append(chart);
|
||||
|
||||
jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child", container).addClass("last");
|
||||
jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child", container).addClass("last");
|
||||
jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child", container).addClass("last");
|
||||
|
||||
this.listenForBlockResize(startDate);
|
||||
this.listenForBlockDrag(startDate);
|
||||
};
|
||||
|
||||
// Render task list on the left
|
||||
Gantt.prototype.renderVerticalHeader = function() {
|
||||
var headerDiv = jQuery("<div>", { "class": "ganttview-vtheader" });
|
||||
var itemDiv = jQuery("<div>", { "class": "ganttview-vtheader-item" });
|
||||
var seriesDiv = jQuery("<div>", { "class": "ganttview-vtheader-series" });
|
||||
|
||||
for (var i = 0; i < this.data.length; i++) {
|
||||
seriesDiv.append(jQuery("<div>", {
|
||||
"class": "ganttview-vtheader-series-name tooltip",
|
||||
"title": "<strong>" + this.data[i].column_title + "</strong> (" + this.data[i].progress + ")<br/>" + this.data[i].title
|
||||
}).append(jQuery("<a>", {"href": this.data[i].link, "target": "_blank"}).append(this.data[i].title)));
|
||||
}
|
||||
|
||||
itemDiv.append(seriesDiv);
|
||||
headerDiv.append(itemDiv);
|
||||
|
||||
return headerDiv;
|
||||
};
|
||||
|
||||
// Render right part of the chart (top header + grid + bars)
|
||||
Gantt.prototype.renderSlider = function(startDate, endDate) {
|
||||
var slideDiv = jQuery("<div>", {"class": "ganttview-slide-container"});
|
||||
var dates = this.getDates(startDate, endDate);
|
||||
|
||||
slideDiv.append(this.renderHorizontalHeader(dates));
|
||||
slideDiv.append(this.renderGrid(dates));
|
||||
slideDiv.append(this.addBlockContainers());
|
||||
this.addBlocks(slideDiv, startDate);
|
||||
|
||||
return slideDiv;
|
||||
};
|
||||
|
||||
// Render top header (days)
|
||||
Gantt.prototype.renderHorizontalHeader = function(dates) {
|
||||
var headerDiv = jQuery("<div>", { "class": "ganttview-hzheader" });
|
||||
var monthsDiv = jQuery("<div>", { "class": "ganttview-hzheader-months" });
|
||||
var daysDiv = jQuery("<div>", { "class": "ganttview-hzheader-days" });
|
||||
var totalW = 0;
|
||||
|
||||
for (var y in dates) {
|
||||
for (var m in dates[y]) {
|
||||
var w = dates[y][m].length * this.options.cellWidth;
|
||||
totalW = totalW + w;
|
||||
|
||||
monthsDiv.append(jQuery("<div>", {
|
||||
"class": "ganttview-hzheader-month",
|
||||
"css": { "width": (w - 1) + "px" }
|
||||
}).append($.datepicker.regional[$("body").data('js-lang')].monthNames[m] + " " + y));
|
||||
|
||||
for (var d in dates[y][m]) {
|
||||
daysDiv.append(jQuery("<div>", { "class": "ganttview-hzheader-day" }).append(dates[y][m][d].getDate()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
monthsDiv.css("width", totalW + "px");
|
||||
daysDiv.css("width", totalW + "px");
|
||||
headerDiv.append(monthsDiv).append(daysDiv);
|
||||
|
||||
return headerDiv;
|
||||
};
|
||||
|
||||
// Render grid
|
||||
Gantt.prototype.renderGrid = function(dates) {
|
||||
var gridDiv = jQuery("<div>", { "class": "ganttview-grid" });
|
||||
var rowDiv = jQuery("<div>", { "class": "ganttview-grid-row" });
|
||||
|
||||
for (var y in dates) {
|
||||
for (var m in dates[y]) {
|
||||
for (var d in dates[y][m]) {
|
||||
var cellDiv = jQuery("<div>", { "class": "ganttview-grid-row-cell" });
|
||||
if (this.options.showWeekends && this.isWeekend(dates[y][m][d])) {
|
||||
cellDiv.addClass("ganttview-weekend");
|
||||
}
|
||||
rowDiv.append(cellDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
var w = jQuery("div.ganttview-grid-row-cell", rowDiv).length * this.options.cellWidth;
|
||||
rowDiv.css("width", w + "px");
|
||||
gridDiv.css("width", w + "px");
|
||||
|
||||
for (var i = 0; i < this.data.length; i++) {
|
||||
gridDiv.append(rowDiv.clone());
|
||||
}
|
||||
|
||||
return gridDiv;
|
||||
};
|
||||
|
||||
// Render bar containers
|
||||
Gantt.prototype.addBlockContainers = function() {
|
||||
var blocksDiv = jQuery("<div>", { "class": "ganttview-blocks" });
|
||||
|
||||
for (var i = 0; i < this.data.length; i++) {
|
||||
blocksDiv.append(jQuery("<div>", { "class": "ganttview-block-container" }));
|
||||
}
|
||||
|
||||
return blocksDiv;
|
||||
};
|
||||
|
||||
// Render bars
|
||||
Gantt.prototype.addBlocks = function(slider, start) {
|
||||
var rows = jQuery("div.ganttview-blocks div.ganttview-block-container", slider);
|
||||
var rowIdx = 0;
|
||||
|
||||
for (var i = 0; i < this.data.length; i++) {
|
||||
var series = this.data[i];
|
||||
var size = this.daysBetween(series.start, series.end) + 1;
|
||||
var offset = this.daysBetween(start, series.start);
|
||||
var text = jQuery("<div>", {"class": "ganttview-block-text"});
|
||||
|
||||
var block = jQuery("<div>", {
|
||||
"class": "ganttview-block tooltip",
|
||||
"title": this.getBarTooltip(this.data[i]),
|
||||
"css": {
|
||||
"width": ((size * this.options.cellWidth) - 9) + "px",
|
||||
"margin-left": (offset * this.options.cellWidth) + "px"
|
||||
}
|
||||
}).append(text);
|
||||
|
||||
if (size >= 2) {
|
||||
text.append(this.data[i].progress);
|
||||
}
|
||||
|
||||
block.data("task", this.data[i]);
|
||||
this.setTaskColor(block, this.data[i]);
|
||||
jQuery(rows[rowIdx]).append(block);
|
||||
rowIdx = rowIdx + 1;
|
||||
}
|
||||
};
|
||||
|
||||
// Get tooltip for task bars
|
||||
Gantt.prototype.getBarTooltip = function(task) {
|
||||
|
||||
if (task.not_defined) {
|
||||
return $(this.options.container).data("label-not-defined");
|
||||
}
|
||||
|
||||
return "<strong>" + task.progress + "</strong><br/>" +
|
||||
$(this.options.container).data("label-assignee") + " " + (task.assignee ? task.assignee : '') + "<br/>" +
|
||||
$(this.options.container).data("label-start-date") + " " + $.datepicker.formatDate('yy-mm-dd', task.start) + "<br/>" +
|
||||
$(this.options.container).data("label-end-date") + " " + $.datepicker.formatDate('yy-mm-dd', task.end);
|
||||
};
|
||||
|
||||
// Set task color
|
||||
Gantt.prototype.setTaskColor = function(block, task) {
|
||||
if (task.not_defined) {
|
||||
block.addClass("ganttview-block-not-defined");
|
||||
}
|
||||
else {
|
||||
block.css("background-color", task.color.background);
|
||||
block.css("border-color", task.color.border);
|
||||
}
|
||||
};
|
||||
|
||||
// Setup jquery-ui resizable
|
||||
Gantt.prototype.listenForBlockResize = function(startDate) {
|
||||
var self = this;
|
||||
|
||||
jQuery("div.ganttview-block", this.options.container).resizable({
|
||||
grid: this.options.cellWidth,
|
||||
handles: "e,w",
|
||||
stop: function() {
|
||||
var block = jQuery(this);
|
||||
self.updateDataAndPosition(block, startDate);
|
||||
self.saveTask(block.data("task"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Setup jquery-ui drag and drop
|
||||
Gantt.prototype.listenForBlockDrag = function(startDate) {
|
||||
var self = this;
|
||||
|
||||
jQuery("div.ganttview-block", this.options.container).draggable({
|
||||
axis: "x",
|
||||
grid: [this.options.cellWidth, this.options.cellWidth],
|
||||
stop: function() {
|
||||
var block = jQuery(this);
|
||||
self.updateDataAndPosition(block, startDate);
|
||||
self.saveTask(block.data("task"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Update the task data and the position on the chart
|
||||
Gantt.prototype.updateDataAndPosition = function(block, startDate) {
|
||||
var container = jQuery("div.ganttview-slide-container", this.options.container);
|
||||
var scroll = container.scrollLeft();
|
||||
var offset = block.offset().left - container.offset().left - 1 + scroll;
|
||||
var task = block.data("task");
|
||||
|
||||
// Restore color for defined block
|
||||
task.not_defined = false;
|
||||
this.setTaskColor(block, task);
|
||||
|
||||
// Set new start date
|
||||
var daysFromStart = Math.round(offset / this.options.cellWidth);
|
||||
var newStart = this.addDays(this.cloneDate(startDate), daysFromStart);
|
||||
task.start = newStart;
|
||||
|
||||
// Set new end date
|
||||
var width = block.outerWidth();
|
||||
var numberOfDays = Math.round(width / this.options.cellWidth) - 1;
|
||||
task.end = this.addDays(this.cloneDate(newStart), numberOfDays);
|
||||
|
||||
if (numberOfDays > 0) {
|
||||
jQuery("div.ganttview-block-text", block).text(task.progress);
|
||||
}
|
||||
|
||||
// Update tooltip
|
||||
block.attr("title", this.getBarTooltip(task));
|
||||
|
||||
block.data("task", task);
|
||||
|
||||
// Remove top and left properties to avoid incorrect block positioning,
|
||||
// set position to relative to keep blocks relative to scrollbar when scrolling
|
||||
block
|
||||
.css("top", "")
|
||||
.css("left", "")
|
||||
.css("position", "relative")
|
||||
.css("margin-left", offset + "px");
|
||||
};
|
||||
|
||||
// Creates a 3 dimensional array [year][month][day] of every day
|
||||
// between the given start and end dates
|
||||
Gantt.prototype.getDates = function(start, end) {
|
||||
var dates = [];
|
||||
dates[start.getFullYear()] = [];
|
||||
dates[start.getFullYear()][start.getMonth()] = [start];
|
||||
var last = start;
|
||||
|
||||
while (this.compareDate(last, end) == -1) {
|
||||
var next = this.addDays(this.cloneDate(last), 1);
|
||||
|
||||
if (! dates[next.getFullYear()]) {
|
||||
dates[next.getFullYear()] = [];
|
||||
}
|
||||
|
||||
if (! dates[next.getFullYear()][next.getMonth()]) {
|
||||
dates[next.getFullYear()][next.getMonth()] = [];
|
||||
}
|
||||
|
||||
dates[next.getFullYear()][next.getMonth()].push(next);
|
||||
last = next;
|
||||
}
|
||||
|
||||
return dates;
|
||||
};
|
||||
|
||||
// Convert data to Date object
|
||||
Gantt.prototype.prepareData = function(data) {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var start = new Date(data[i].start[0], data[i].start[1] - 1, data[i].start[2], 0, 0, 0, 0);
|
||||
data[i].start = start;
|
||||
|
||||
var end = new Date(data[i].end[0], data[i].end[1] - 1, data[i].end[2], 0, 0, 0, 0);
|
||||
data[i].end = end;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
// Get the start and end date from the data provided
|
||||
Gantt.prototype.getDateRange = function(minDays) {
|
||||
var minStart = new Date();
|
||||
var maxEnd = new Date();
|
||||
|
||||
for (var i = 0; i < this.data.length; i++) {
|
||||
var start = new Date();
|
||||
start.setTime(Date.parse(this.data[i].start));
|
||||
|
||||
var end = new Date();
|
||||
end.setTime(Date.parse(this.data[i].end));
|
||||
|
||||
if (i == 0) {
|
||||
minStart = start;
|
||||
maxEnd = end;
|
||||
}
|
||||
|
||||
if (this.compareDate(minStart, start) == 1) {
|
||||
minStart = start;
|
||||
}
|
||||
|
||||
if (this.compareDate(maxEnd, end) == -1) {
|
||||
maxEnd = end;
|
||||
}
|
||||
}
|
||||
|
||||
// Insure that the width of the chart is at least the slide width to avoid empty
|
||||
// whitespace to the right of the grid
|
||||
if (this.daysBetween(minStart, maxEnd) < minDays) {
|
||||
maxEnd = this.addDays(this.cloneDate(minStart), minDays);
|
||||
}
|
||||
|
||||
// Always start one day before the minStart
|
||||
minStart.setDate(minStart.getDate() - 1);
|
||||
|
||||
return [minStart, maxEnd];
|
||||
};
|
||||
|
||||
// Returns the number of day between 2 dates
|
||||
Gantt.prototype.daysBetween = function(start, end) {
|
||||
if (! start || ! end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var count = 0, date = this.cloneDate(start);
|
||||
|
||||
while (this.compareDate(date, end) == -1) {
|
||||
count = count + 1;
|
||||
this.addDays(date, 1);
|
||||
}
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
// Return true if it's the weekend
|
||||
Gantt.prototype.isWeekend = function(date) {
|
||||
return date.getDay() % 6 == 0;
|
||||
};
|
||||
|
||||
// Clone Date object
|
||||
Gantt.prototype.cloneDate = function(date) {
|
||||
return new Date(date.getTime());
|
||||
};
|
||||
|
||||
// Add days to a Date object
|
||||
Gantt.prototype.addDays = function(date, value) {
|
||||
date.setDate(date.getDate() + value * 1);
|
||||
return date;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compares the first date to the second date and returns an number indication of their relative values.
|
||||
*
|
||||
* -1 = date1 is lessthan date2
|
||||
* 0 = values are equal
|
||||
* 1 = date1 is greaterthan date2.
|
||||
*/
|
||||
Gantt.prototype.compareDate = function(date1, date2) {
|
||||
if (isNaN(date1) || isNaN(date2)) {
|
||||
throw new Error(date1 + " - " + date2);
|
||||
} else if (date1 instanceof Date && date2 instanceof Date) {
|
||||
return (date1 < date2) ? -1 : (date1 > date2) ? 1 : 0;
|
||||
} else {
|
||||
throw new TypeError(date1 + " - " + date2);
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
function LeadCycleTimeChart() {
|
||||
function LeadCycleTimeChart(app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
LeadCycleTimeChart.prototype.execute = function(app) {
|
||||
LeadCycleTimeChart.prototype.execute = function() {
|
||||
var metrics = $("#chart").data("metrics");
|
||||
var cycle = [$("#chart").data("label-cycle")];
|
||||
var lead = [$("#chart").data("label-lead")];
|
||||
@@ -37,7 +38,7 @@ LeadCycleTimeChart.prototype.execute = function(app) {
|
||||
},
|
||||
y: {
|
||||
tick: {
|
||||
format: app.formatDuration
|
||||
format: this.app.formatDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ Router.prototype.dispatch = function(app) {
|
||||
for (var id in this.routes) {
|
||||
if (document.getElementById(id)) {
|
||||
var controller = Object.create(this.routes[id].prototype);
|
||||
controller.execute(app);
|
||||
this.routes[id].apply(controller, [app]);
|
||||
controller.execute();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -30,5 +31,7 @@ jQuery(document).ready(function() {
|
||||
router.addRoute('analytic-avg-time-column', AvgTimeColumnChart);
|
||||
router.addRoute('analytic-task-time-column', TaskTimeColumnChart);
|
||||
router.addRoute('analytic-lead-cycle-time', LeadCycleTimeChart);
|
||||
router.addRoute('gantt-chart', Gantt);
|
||||
router.dispatch(app);
|
||||
app.listen();
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
function TaskTimeColumnChart() {
|
||||
function TaskTimeColumnChart(app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
TaskTimeColumnChart.prototype.execute = function(app) {
|
||||
TaskTimeColumnChart.prototype.execute = function() {
|
||||
var metrics = $("#chart").data("metrics");
|
||||
var plots = [$("#chart").data("label")];
|
||||
var categories = [];
|
||||
@@ -28,7 +29,7 @@ TaskTimeColumnChart.prototype.execute = function(app) {
|
||||
},
|
||||
y: {
|
||||
tick: {
|
||||
format: app.formatDuration
|
||||
format: this.app.formatDuration
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ Tooltip.prototype.listen = function() {
|
||||
$(".tooltip").tooltip({
|
||||
track: false,
|
||||
show: false,
|
||||
hide: false,
|
||||
position: {
|
||||
my: 'left-20 top',
|
||||
at: 'center bottom+9',
|
||||
|
||||