/** * @Orignal author Dimitry Kudrayvtsev * Rewritten for Hive Web by Jonas Lodewyckx * /* HeuristicLab * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ d3.gantt = function () { var FIT_TIME_DOMAIN_MODE = "fit"; var FIXED_TIME_DOMAIN_MODE = "fixed"; var margin = { top: 20, right: 40, bottom: 20, left: 70 }; var drawytitles = true; var selector = 'body'; var timeDomainStart = d3.time.day.offset(new Date(), -3); var zoomStart; var zoomEnd; var timeDomainEnd = d3.time.hour.offset(new Date(), +3); var timeDomainMode = FIT_TIME_DOMAIN_MODE;// fixed or fit var taskTypes = []; var taskStatus = []; var height = document.body.clientHeight - margin.top - margin.bottom - 5; var width = document.body.clientWidth - margin.right - margin.left - 5; var tickFormat = "%H:%M"; var keyFunction = function (d) { return d.startDate + d.taskName + d.endDate; }; //Locks graph to specific timezone //3600000 = +1 hour var timeCorrector = 3600000 * (+1); //Currently: + 1 var rectTransform = function (d) { return "translate(" + x(d.startDate - timeCorrector) + "," + y(d.taskName) + ")"; }; var x = d3.time.scale().domain([timeDomainStart, timeDomainEnd]).range([0, width]).clamp(true); var y = d3.scale.ordinal().domain(taskTypes).rangeRoundBands([0, height - margin.top - margin.bottom], .1); var xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(d3.time.format(tickFormat)).tickSubdivide(true) .tickSize(8).tickPadding(8); var yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0); var initTimeDomain = function (tasks) { if (timeDomainMode === FIT_TIME_DOMAIN_MODE) { if (tasks === undefined || tasks.length < 1) { timeDomainStart = d3.time.day.offset(new Date(), -3); timeDomainEnd = d3.time.hour.offset(new Date(), +3); return; } tasks.sort(function (a, b) { return a.endDate - b.endDate; }); timeDomainEnd = tasks[tasks.length - 1].endDate; tasks.sort(function (a, b) { return a.startDate - b.startDate; }); timeDomainStart = tasks[0].startDate; } }; var initAxis = function () { x = d3.time.scale().domain([timeDomainStart - timeCorrector, timeDomainEnd - timeCorrector]).range([0, width]).clamp(true); y = d3.scale.ordinal().domain(taskTypes).rangeRoundBands([0, height - margin.top - margin.bottom], .1); if (new Date(timeDomainStart).toDateString() != new Date(timeDomainEnd).toDateString()) { xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(null).tickSubdivide(true) .tickSize(8).tickPadding(8); } else xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(d3.time.format(tickFormat)).tickSubdivide(true) .tickSize(8).tickPadding(8); yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0); }; function gantt(tasks) { initTimeDomain(tasks); initAxis(); var tip = d3.tip() .attr('class', 'd3-tip') .html(function (d) { var start = new Date(d.startDate); var end = new Date(d.endDate); var stat = d.status; if (d.last) { return stat + "
" + start.toUTCString() + " to now"; } return stat + "
" + start.toUTCString() + " to " + end.toUTCString(); }); var svgitself = d3.select(selector) .append("svg") .attr("class", "chart") .attr("width", "100%") .attr("height", height + margin.top + margin.bottom) var svg = svgitself.append("g") .attr("class", "gantt-chart") .attr("width", "100%") .attr("height", height + margin.top + margin.bottom) .attr("transform", "translate(" + margin.left + ", " + margin.top + ")") .call(tip); svg.selectAll(".chart") .data(tasks, keyFunction).enter() .append("rect") .attr("rx", 5) .attr("ry", 5) .attr("class", function (d) { if (taskStatus[d.status] == null) { return "bar"; } return taskStatus[d.status]; }) .attr("y", 0) .attr("transform", rectTransform) .attr("height", function (d) { return y.rangeBand(); }) .attr("width", function (d) { return (x(d.endDate - timeCorrector) - x(d.startDate - timeCorrector)); }) .on('mouseover', tip.show) .on('mouseout', tip.hide); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0, " + (height - margin.top - margin.bottom) + ")") .transition() .call(xAxis); if (drawytitles) svg.append("g") .attr("class", "y axis") //.attr("transform", "translate(" + ((width / 2)) + "," + (-(height / 2) + margin.top) + " )") .transition() .call(yAxis); svgitself.on("mousedown", function () { var p = d3.mouse(this); var w = width; w = (p[0])/w; gantt.zoom(tasks, w, this); }) .on("contextmenu", function (d, i) { zoomStart = null; d3.event.preventDefault(); gantt.redraw(tasks, this); }); return gantt; }; gantt.zoom = function (tasks, xcor, el) { initTimeDomain(tasks); if (zoomStart == null) { zoomStart = timeDomainStart; zoomEnd = timeDomainEnd; } var diff = zoomEnd - zoomStart; var cent = (diff * xcor) + zoomStart; var piece = diff / 1.5; var tempstart = cent - (piece / 2); piece = piece /2; if (tempstart < zoomStart) { piece += zoomStart - tempstart; } else zoomStart = tempstart; var tempend = cent + piece; if (tempend > zoomEnd) zoomStart -= tempend - zoomEnd; else zoomEnd = tempend; timeDomainStart = zoomStart; timeDomainEnd = zoomEnd; gantt.redrawInner(tasks, el); } gantt.redraw = function(tasks, el){ initTimeDomain(tasks); gantt.redrawInner(tasks, el); } gantt.redrawInner= function (tasks, el) { initAxis(); var svg = d3.select(el); var ganttChartGroup = svg.select(".gantt-chart"); var rect = ganttChartGroup.selectAll("rect").data(tasks, keyFunction); rect.enter() .insert("rect", ":first-child") .attr("rx", 5) .attr("ry", 5) .attr("class", function (d) { if (taskStatus[d.status] == null) { return "bar"; } return taskStatus[d.status]; }) .transition() .attr("y", 0) .attr("transform", rectTransform) .attr("height", function (d) { return y.rangeBand(); }) .attr("width", function (d) { return (x(d.endDate - timeCorrector) - x(d.startDate - timeCorrector)); }); rect.transition() .attr("transform", rectTransform) .attr("height", function (d) { return y.rangeBand(); }) .attr("width", function (d) { return (x(d.endDate - timeCorrector) - x(d.startDate - timeCorrector)); }); rect.exit().remove(); svg.select(".x").transition().call(xAxis); svg.select(".y").transition().call(yAxis); return gantt; }; gantt.margin = function (value) { if (!arguments.length) return margin; margin = value; return gantt; }; gantt.drawytitles = function (value) { if (!arguments.length) return drawytitles; drawytitles = value; return gantt; } gantt.timeDomain = function (value) { if (!arguments.length) return [timeDomainStart, timeDomainEnd]; timeDomainStart = +value[0], timeDomainEnd = +value[1]; return gantt; }; /** * @param {string} * vale The value can be "fit" - the domain fits the data or * "fixed" - fixed domain. */ gantt.timeDomainMode = function (value) { if (!arguments.length) return timeDomainMode; timeDomainMode = value; return gantt; }; gantt.taskTypes = function (value) { if (!arguments.length) return taskTypes; taskTypes = value; return gantt; }; gantt.taskStatus = function (value) { if (!arguments.length) return taskStatus; taskStatus = value; return gantt; }; gantt.width = function (value) { if (!arguments.length) return width; width = +value; return gantt; }; gantt.height = function (value) { if (!arguments.length) return height; height = +value; return gantt; }; gantt.tickFormat = function (value) { if (!arguments.length) return tickFormat; tickFormat = value; return gantt; }; gantt.selector = function (value) { if (!arguments.length) return selector; selector = value; return gantt; }; return gantt; };