2015-02-20 16:22:37 +01:00
|
|
|
var gantt = require('./parser/gantt').parser;
|
|
|
|
gantt.yy = require('./ganttDb');
|
|
|
|
var d3 = require('./d3');
|
2015-02-08 20:07:15 +01:00
|
|
|
|
2015-02-20 19:06:15 +01:00
|
|
|
var conf = {
|
2015-02-22 20:18:44 +01:00
|
|
|
titleTopMargin: 25,
|
|
|
|
barHeight: 20,
|
|
|
|
barGap: 4,
|
|
|
|
topPadding: 50,
|
|
|
|
sidePadding: 75,
|
|
|
|
gridLineStartPadding: 35,
|
|
|
|
fontSize: 11
|
2015-02-20 19:06:15 +01:00
|
|
|
};
|
2015-02-22 20:18:44 +01:00
|
|
|
module.exports.setConf = function (cnf) {
|
2015-02-20 19:06:15 +01:00
|
|
|
var keys = Object.keys(cnf);
|
|
|
|
|
2015-02-22 20:18:44 +01:00
|
|
|
keys.forEach(function (key) {
|
2015-02-20 19:06:15 +01:00
|
|
|
conf[key] = cnf[key];
|
|
|
|
});
|
|
|
|
};
|
|
|
|
var w;
|
2015-02-20 16:22:37 +01:00
|
|
|
module.exports.draw = function (text, id) {
|
|
|
|
gantt.yy.clear();
|
|
|
|
gantt.parse(text);
|
2015-02-20 19:06:15 +01:00
|
|
|
var elem = document.getElementById(id);
|
|
|
|
w = elem.offsetWidth;
|
2015-02-22 20:18:44 +01:00
|
|
|
|
|
|
|
if (typeof w === 'undefined') {
|
2015-02-20 19:06:15 +01:00
|
|
|
w = 800;
|
|
|
|
}
|
2015-02-22 20:18:44 +01:00
|
|
|
|
2015-02-20 19:06:15 +01:00
|
|
|
var taskArray = gantt.yy.getTasks();
|
2015-02-22 20:18:44 +01:00
|
|
|
|
2015-02-20 19:06:15 +01:00
|
|
|
// Set height based on number of tasks
|
2015-02-22 20:18:44 +01:00
|
|
|
var h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding;
|
2015-02-20 19:06:15 +01:00
|
|
|
|
2015-02-22 20:18:44 +01:00
|
|
|
elem.style.height = h + 'px';
|
|
|
|
var svg = d3.select('#' + id);
|
2015-02-08 20:07:15 +01:00
|
|
|
|
|
|
|
// http://codepen.io/anon/pen/azLvWR
|
|
|
|
|
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
var dateFormat = d3.time.format("%Y-%m-%d");
|
2015-02-08 20:07:15 +01:00
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
// Set timescale
|
|
|
|
var timeScale = d3.time.scale()
|
|
|
|
.domain([d3.min(taskArray, function (d) {
|
2015-02-20 19:06:15 +01:00
|
|
|
return d.startTime;
|
2015-02-20 16:22:37 +01:00
|
|
|
}),
|
|
|
|
d3.max(taskArray, function (d) {
|
2015-02-20 19:06:15 +01:00
|
|
|
return d.endTime;
|
2015-02-20 16:22:37 +01:00
|
|
|
})])
|
|
|
|
.range([0, w - 150]);
|
2015-02-08 20:07:15 +01:00
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
var categories = [];
|
2015-02-08 20:07:15 +01:00
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
for (var i = 0; i < taskArray.length; i++) {
|
|
|
|
categories.push(taskArray[i].type);
|
|
|
|
}
|
|
|
|
|
|
|
|
var catsUnfiltered = categories; //for vert labels
|
2015-02-08 20:07:15 +01:00
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
categories = checkUnique(categories);
|
2015-02-08 20:07:15 +01:00
|
|
|
|
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
makeGant(taskArray, w, h);
|
2015-02-08 20:07:15 +01:00
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
var title = svg.append("text")
|
|
|
|
.text(gantt.yy.getTitle())
|
|
|
|
.attr("x", w / 2)
|
2015-02-20 19:06:15 +01:00
|
|
|
.attr("y", conf.titleTopMargin)
|
2015-02-22 20:18:44 +01:00
|
|
|
.attr('class', 'titleText');
|
2015-02-08 20:07:15 +01:00
|
|
|
|
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
function makeGant(tasks, pageWidth, pageHeight) {
|
2015-02-08 20:07:15 +01:00
|
|
|
|
2015-02-20 19:06:15 +01:00
|
|
|
var barHeight = conf.barHeight;
|
|
|
|
var gap = barHeight + conf.barGap;
|
|
|
|
var topPadding = conf.topPadding;
|
|
|
|
var sidePadding = conf.sidePadding;
|
2015-02-08 20:07:15 +01:00
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
var colorScale = d3.scale.linear()
|
|
|
|
.domain([0, categories.length])
|
|
|
|
.range(["#00B9FA", "#F95002"])
|
|
|
|
.interpolate(d3.interpolateHcl);
|
2015-02-08 20:07:15 +01:00
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
makeGrid(sidePadding, topPadding, pageWidth, pageHeight);
|
|
|
|
drawRects(tasks, gap, topPadding, sidePadding, barHeight, colorScale, pageWidth, pageHeight);
|
|
|
|
vertLabels(gap, topPadding, sidePadding, barHeight, colorScale);
|
2015-02-08 20:07:15 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-02-20 16:22:37 +01:00
|
|
|
|
|
|
|
function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h) {
|
|
|
|
|
|
|
|
var bigRects = svg.append("g")
|
|
|
|
.selectAll("rect")
|
|
|
|
.data(theArray)
|
|
|
|
.enter()
|
|
|
|
.append("rect")
|
|
|
|
.attr("x", 0)
|
|
|
|
.attr("y", function (d, i) {
|
|
|
|
return i * theGap + theTopPad - 2;
|
|
|
|
})
|
|
|
|
.attr("width", function (d) {
|
|
|
|
return w - theSidePad / 2;
|
|
|
|
})
|
|
|
|
.attr("height", theGap)
|
2015-02-22 20:18:44 +01:00
|
|
|
.attr('class', function (d) {
|
2015-02-20 16:22:37 +01:00
|
|
|
for (var i = 0; i < categories.length; i++) {
|
2015-02-20 19:06:15 +01:00
|
|
|
if (d.type === categories[i]) {
|
2015-02-22 20:18:44 +01:00
|
|
|
return 'section section' + (i % conf.numberSectionStyles);
|
2015-02-20 16:22:37 +01:00
|
|
|
}
|
2015-02-08 20:07:15 +01:00
|
|
|
}
|
2015-02-22 20:18:44 +01:00
|
|
|
return 'section section0';
|
|
|
|
});
|
2015-02-20 16:22:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
var rectangles = svg.append('g')
|
|
|
|
.selectAll("rect")
|
|
|
|
.data(theArray)
|
|
|
|
.enter();
|
|
|
|
|
|
|
|
|
|
|
|
var innerRects = rectangles.append("rect")
|
2015-02-22 20:18:44 +01:00
|
|
|
.attr("rx", 3)
|
|
|
|
.attr("ry", 3)
|
|
|
|
.attr("x", function (d) {
|
|
|
|
return timeScale(d.startTime) + theSidePad;
|
|
|
|
})
|
|
|
|
.attr("y", function (d, i) {
|
|
|
|
return i * theGap + theTopPad;
|
|
|
|
})
|
|
|
|
.attr("width", function (d) {
|
|
|
|
return (timeScale(d.endTime) - timeScale(d.startTime));
|
|
|
|
})
|
|
|
|
.attr("height", theBarHeight)
|
|
|
|
.attr('class', function (d) {
|
|
|
|
var res = 'task ';
|
|
|
|
|
|
|
|
|
|
|
|
var secNum = 0;
|
|
|
|
for (var i = 0; i < categories.length; i++) {
|
|
|
|
if (d.type === categories[i]) {
|
|
|
|
secNum = (i % conf.numberSectionStyles);
|
|
|
|
}
|
2015-02-20 16:22:37 +01:00
|
|
|
}
|
2015-02-22 20:18:44 +01:00
|
|
|
|
|
|
|
if(d.active){
|
|
|
|
if (d.crit) {
|
|
|
|
return res + ' activeCrit'+secNum;
|
|
|
|
}else{
|
|
|
|
return res + ' active'+secNum;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d.done) {
|
|
|
|
if (d.crit) {
|
|
|
|
return res + ' doneCrit'+secNum;
|
|
|
|
}else{
|
|
|
|
return res + ' done'+secNum;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (d.crit) {
|
|
|
|
return res + ' crit'+secNum;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return res + ' task'+secNum;
|
|
|
|
})
|
|
|
|
;
|
2015-02-20 16:22:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
var rectText = rectangles.append("text")
|
|
|
|
.text(function (d) {
|
|
|
|
return d.task;
|
|
|
|
})
|
|
|
|
.attr("x", function (d) {
|
2015-02-20 19:06:15 +01:00
|
|
|
var startX = timeScale(d.startTime),
|
|
|
|
endX = timeScale(d.endTime),
|
|
|
|
textWidth = this.getBBox().width;
|
2015-02-22 20:18:44 +01:00
|
|
|
|
2015-02-20 19:06:15 +01:00
|
|
|
// Check id text width > width of rectangle
|
2015-02-22 20:18:44 +01:00
|
|
|
if (textWidth > (endX - startX)) {
|
|
|
|
if (endX + textWidth > w) {
|
2015-02-20 19:06:15 +01:00
|
|
|
return startX + theSidePad;
|
2015-02-22 20:18:44 +01:00
|
|
|
} else {
|
2015-02-20 19:06:15 +01:00
|
|
|
return endX + theSidePad;
|
|
|
|
}
|
2015-02-22 20:18:44 +01:00
|
|
|
} else {
|
2015-02-20 19:06:15 +01:00
|
|
|
return (endX - startX) / 2 + startX + theSidePad;
|
|
|
|
}
|
2015-02-20 16:22:37 +01:00
|
|
|
})
|
|
|
|
.attr("y", function (d, i) {
|
2015-02-22 20:18:44 +01:00
|
|
|
return i * theGap + (conf.barHeight / 2) + (conf.fontSize / 2 - 2) + theTopPad;
|
2015-02-20 16:22:37 +01:00
|
|
|
})
|
2015-02-20 19:06:15 +01:00
|
|
|
//.attr("text-anchor", "middle")
|
2015-02-20 16:22:37 +01:00
|
|
|
.attr("text-height", theBarHeight)
|
2015-02-22 20:18:44 +01:00
|
|
|
.attr("class", function (d) {
|
2015-02-20 19:06:15 +01:00
|
|
|
var startX = timeScale(d.startTime),
|
|
|
|
endX = timeScale(d.endTime),
|
|
|
|
textWidth = this.getBBox().width;
|
2015-02-22 20:18:44 +01:00
|
|
|
var secNum = 0;
|
|
|
|
for (var i = 0; i < categories.length; i++) {
|
|
|
|
if (d.type === categories[i]) {
|
|
|
|
secNum = (i % conf.numberSectionStyles);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-20 19:06:15 +01:00
|
|
|
// Check id text width > width of rectangle
|
2015-02-22 20:18:44 +01:00
|
|
|
if (textWidth > (endX - startX)) {
|
|
|
|
if (endX + textWidth > w) {
|
|
|
|
return 'taskTextOutsideLeft taskTextOutside' + secNum;
|
|
|
|
} else {
|
|
|
|
return 'taskTextOutsideRight taskTextOutsideRight' + secNum;
|
2015-02-20 19:06:15 +01:00
|
|
|
}
|
2015-02-22 20:18:44 +01:00
|
|
|
} else {
|
|
|
|
return 'taskText taskText' + secNum;
|
2015-02-20 19:06:15 +01:00
|
|
|
}
|
|
|
|
});
|
2015-02-20 16:22:37 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function makeGrid(theSidePad, theTopPad, w, h) {
|
|
|
|
|
|
|
|
var xAxis = d3.svg.axis()
|
|
|
|
.scale(timeScale)
|
|
|
|
.orient('bottom')
|
2015-02-20 19:06:15 +01:00
|
|
|
//.ticks(d3.time.days, 5)
|
2015-02-22 20:18:44 +01:00
|
|
|
.tickSize(-h + theTopPad + conf.gridLineStartPadding, 0, 0);
|
|
|
|
//.tickFormat(d3.time.format('%d %b'));
|
2015-02-20 16:22:37 +01:00
|
|
|
|
|
|
|
var grid = svg.append('g')
|
|
|
|
.attr('class', 'grid')
|
|
|
|
.attr('transform', 'translate(' + theSidePad + ', ' + (h - 50) + ')')
|
|
|
|
.call(xAxis)
|
|
|
|
.selectAll("text")
|
|
|
|
.style("text-anchor", "middle")
|
|
|
|
.attr("fill", "#000")
|
|
|
|
.attr("stroke", "none")
|
|
|
|
.attr("font-size", 10)
|
|
|
|
.attr("dy", "1em");
|
|
|
|
}
|
|
|
|
|
|
|
|
function vertLabels(theGap, theTopPad, theSidePad, theBarHeight, theColorScale) {
|
|
|
|
var numOccurances = [];
|
|
|
|
var prevGap = 0;
|
|
|
|
|
|
|
|
for (var i = 0; i < categories.length; i++) {
|
|
|
|
numOccurances[i] = [categories[i], getCount(categories[i], catsUnfiltered)];
|
|
|
|
}
|
|
|
|
|
|
|
|
var axisText = svg.append("g") //without doing this, impossible to put grid lines behind text
|
|
|
|
.selectAll("text")
|
|
|
|
.data(numOccurances)
|
|
|
|
.enter()
|
|
|
|
.append("text")
|
|
|
|
.text(function (d) {
|
|
|
|
return d[0];
|
|
|
|
})
|
|
|
|
.attr("x", 10)
|
|
|
|
.attr("y", function (d, i) {
|
|
|
|
if (i > 0) {
|
|
|
|
for (var j = 0; j < i; j++) {
|
|
|
|
prevGap += numOccurances[i - 1][1];
|
|
|
|
// console.log(prevGap);
|
|
|
|
return d[1] * theGap / 2 + prevGap * theGap + theTopPad;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return d[1] * theGap / 2 + theTopPad;
|
|
|
|
}
|
|
|
|
})
|
2015-02-22 20:18:44 +01:00
|
|
|
.attr('class', function (d) {
|
2015-02-20 16:22:37 +01:00
|
|
|
for (var i = 0; i < categories.length; i++) {
|
2015-02-20 19:06:15 +01:00
|
|
|
if (d[0] === categories[i]) {
|
2015-02-22 20:18:44 +01:00
|
|
|
return 'sectionTitle sectionTitle' + (i % conf.numberSectionStyles);
|
2015-02-20 16:22:37 +01:00
|
|
|
}
|
|
|
|
}
|
2015-02-22 20:18:44 +01:00
|
|
|
return 'sectionTitle';
|
2015-02-20 16:22:37 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
}
|
2015-02-08 20:07:15 +01:00
|
|
|
|
|
|
|
//from this stackexchange question: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript
|
2015-02-20 16:22:37 +01:00
|
|
|
function checkUnique(arr) {
|
|
|
|
var hash = {}, result = [];
|
|
|
|
for (var i = 0, l = arr.length; i < l; ++i) {
|
|
|
|
if (!hash.hasOwnProperty(arr[i])) { //it works with objects! in FF, at least
|
|
|
|
hash[arr[i]] = true;
|
|
|
|
result.push(arr[i]);
|
|
|
|
}
|
2015-02-08 20:07:15 +01:00
|
|
|
}
|
2015-02-20 16:22:37 +01:00
|
|
|
return result;
|
2015-02-08 20:07:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//from this stackexchange question: http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array
|
2015-02-20 16:22:37 +01:00
|
|
|
function getCounts(arr) {
|
|
|
|
var i = arr.length, // var to loop over
|
|
|
|
obj = {}; // obj to store results
|
2015-02-22 20:18:44 +01:00
|
|
|
while (i) {
|
2015-02-20 19:06:15 +01:00
|
|
|
obj[arr[--i]] = (obj[arr[i]] || 0) + 1; // count occurrences
|
2015-02-22 20:18:44 +01:00
|
|
|
}
|
2015-02-20 16:22:37 +01:00
|
|
|
return obj;
|
|
|
|
}
|
2015-02-08 20:07:15 +01:00
|
|
|
|
|
|
|
// get specific from everything
|
2015-02-20 16:22:37 +01:00
|
|
|
function getCount(word, arr) {
|
|
|
|
return getCounts(arr)[word] || 0;
|
|
|
|
}
|
|
|
|
};
|