var gantt = require('./parser/gantt').parser; gantt.yy = require('./ganttDb'); var d3 = require('../../d3'); var moment = require('moment'); var log = require('../../logger').create(); var daysInChart; var conf = { titleTopMargin: 25, barHeight: 20, barGap: 4, topPadding: 50, sidePadding: 75, gridLineStartPadding: 35, fontSize: 11, fontFamily: '"Open-Sans", "sans-serif"' }; module.exports.setConf = function (cnf) { var keys = Object.keys(cnf); keys.forEach(function (key) { conf[key] = cnf[key]; }); }; var w; module.exports.draw = function (text, id) { gantt.yy.clear(); gantt.parse(text); var elem = document.getElementById(id); w = elem.parentElement.offsetWidth; if (typeof w === 'undefined') { w = 1200; } if(typeof conf.useWidth !== 'undefined'){ w = conf.useWidth; } var taskArray = gantt.yy.getTasks(); // Set height based on number of tasks var h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding; elem.setAttribute('height', '100%'); // Set viewBox elem.setAttribute('viewBox','0 0 '+w+' '+h); var svg = d3.select('#' + id); var dateFormat = d3.time.format('%Y-%m-%d'); var startDate = d3.min(taskArray, function (d) { return d.startTime; }); var endDate = d3.max(taskArray, function (d) { return d.endTime; }); // Set timescale var timeScale = d3.time.scale() .domain([d3.min(taskArray, function (d) { return d.startTime; }), d3.max(taskArray, function (d) { return d.endTime; })]) .rangeRound([0, w - 150]); //.nice(d3.time.monday); var categories = []; daysInChart = moment.duration(endDate-startDate).asDays(); for (var i = 0; i < taskArray.length; i++) { categories.push(taskArray[i].type); } var catsUnfiltered = categories; //for vert labels categories = checkUnique(categories); makeGant(taskArray, w, h); if(typeof conf.useWidth !== 'undefined'){ elem.setAttribute('width', w); } var title = svg.append('text') .text(gantt.yy.getTitle()) .attr('x', w / 2) .attr('y', conf.titleTopMargin) .attr('class', 'titleText'); function makeGant(tasks, pageWidth, pageHeight) { var barHeight = conf.barHeight; var gap = barHeight + conf.barGap; var topPadding = conf.topPadding; var sidePadding = conf.sidePadding; var colorScale = d3.scale.linear() .domain([0, categories.length]) .range(['#00B9FA', '#F95002']) .interpolate(d3.interpolateHcl); makeGrid(sidePadding, topPadding, pageWidth, pageHeight); drawRects(tasks, gap, topPadding, sidePadding, barHeight, colorScale, pageWidth, pageHeight); vertLabels(gap, topPadding, sidePadding, barHeight, colorScale); drawToday(sidePadding, topPadding, pageWidth, pageHeight); } 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) .attr('class', function (d) { for (var i = 0; i < categories.length; i++) { if (d.type === categories[i]) { return 'section section' + (i % conf.numberSectionStyles); } } return 'section section0'; }); var rectangles = svg.append('g') .selectAll('rect') .data(theArray) .enter(); var innerRects = rectangles.append('rect') .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); } } 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; }) ; var rectText = rectangles.append('text') .text(function (d) { return d.task; }) .attr('font-size',conf.fontSize) //.attr('font-family',conf.fontFamily) .attr('x', function (d) { var startX = timeScale(d.startTime), endX = timeScale(d.endTime), textWidth = this.getBBox().width; // Check id text width > width of rectangle if (textWidth > (endX - startX)) { if (endX + textWidth + 1.5*conf.sidePadding> w) { return startX + theSidePad - 5; } else { return endX + theSidePad + 5; } } else { return (endX - startX) / 2 + startX + theSidePad; } }) .attr('y', function (d, i) { return i * theGap + (conf.barHeight / 2) + (conf.fontSize / 2 - 2) + theTopPad; }) //.attr('text-anchor', 'middle') .attr('text-height', theBarHeight) .attr('class', function (d) { var startX = timeScale(d.startTime), endX = timeScale(d.endTime), textWidth = this.getBBox().width; var secNum = 0; for (var i = 0; i < categories.length; i++) { if (d.type === categories[i]) { secNum = (i % conf.numberSectionStyles); } } var taskType = ''; if(d.active){ if (d.crit) { taskType = 'activeCritText'+secNum; }else{ taskType = 'activeText'+secNum; } } if (d.done) { if (d.crit) { taskType = taskType + ' doneCritText'+secNum; }else{ taskType = taskType + ' doneText'+secNum; } }else{ if (d.crit) { taskType = taskType + ' critText'+secNum; } } // Check id text width > width of rectangle if (textWidth > (endX - startX)) { if (endX + textWidth + 1.5*conf.sidePadding > w) { return 'taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType; } else { return 'taskTextOutsideRight taskTextOutside' + secNum+ ' ' + taskType; } } else { return 'taskText taskText' + secNum+ ' ' + taskType; } }); } function makeGrid(theSidePad, theTopPad, w, h) { var pre = [ ['.%L', function (d) { return d.getMilliseconds(); }], [':%S', function (d) { return d.getSeconds(); }], // Within a hour ['h1 %I:%M', function (d) { return d.getMinutes(); }]]; var post = [ ['%Y', function () { return true; }]]; var mid = [ // Within a day ['%I:%M', function (d) { return d.getHours(); }], // Day within a week (not monday) ['%a %d', function (d) { //return d.getDay() ==1; return d.getDay() && d.getDate() != 1; }], // within a month ['%b %d', function (d) { return d.getDate() != 1; }], // Month ['%B', function (d) { return d.getMonth(); }] ]; var formatter; if(typeof conf.axisFormatter !== 'undefined'){ mid = []; conf.axisFormatter.forEach(function(item){ var n = []; n[0] = item[0]; n[1] = item[1]; mid.push(n); }); } formatter = pre.concat(mid).concat(post); var xAxis = d3.svg.axis() .scale(timeScale) .orient('bottom') .tickSize(-h + theTopPad + conf.gridLineStartPadding, 0, 0) .tickFormat(d3.time.format.multi(formatter)) ; if(daysInChart >7 && daysInChart<230){ xAxis = xAxis.ticks(d3.time.monday.range); } 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]; // log.debug(prevGap); return d[1] * theGap / 2 + prevGap * theGap + theTopPad; } } else { return d[1] * theGap / 2 + theTopPad; } }) .attr('class', function (d) { for (var i = 0; i < categories.length; i++) { if (d[0] === categories[i]) { return 'sectionTitle sectionTitle' + (i % conf.numberSectionStyles); } } return 'sectionTitle'; }); } function drawToday(theSidePad, theTopPad, w, h) { var todayG = svg.append('g') .attr('class', 'today'); var today = new Date(); var todayLine = todayG.append('line') .attr('x1', timeScale(today) + theSidePad) .attr('x2', timeScale(today) + theSidePad) .attr('y1', conf.titleTopMargin) .attr('y2', h-conf.titleTopMargin) .attr('class', 'today') ; } //from this stackexchange question: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript 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]); } } return result; } //from this stackexchange question: http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array function getCounts(arr) { var i = arr.length, // var to loop over obj = {}; // obj to store results while (i) { obj[arr[--i]] = (obj[arr[i]] || 0) + 1; // count occurrences } return obj; } // get specific from everything function getCount(word, arr) { return getCounts(arr)[word] || 0; } };