Skip to content

Instantly share code, notes, and snippets.

@orrery
Last active January 16, 2017 17:16
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save orrery/4704709 to your computer and use it in GitHub Desktop.
Save orrery/4704709 to your computer and use it in GitHub Desktop.
D3 Gantt chart
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
<script src="https://raw.github.com/timrwood/moment/1.6.0/min/moment.min.js"></script>
<style type="text/css">
#chart-mini {
margin: 0 auto 0px auto;
box-shadow: 3px 2px 7px rgba(0, 0, 0, 0.6);
padding: 0px 0px;
}
#chart-headertop {
height: 45px;
margin: 0 auto 0px auto;
box-shadow: 3px -2px 7px rgba(0, 0, 0, 0.6);
padding: 0px 0px;
}
#chart-container {
height: 450px;
width: 1101px;
margin: 0 auto 0px auto;
box-shadow: 3px 2px 7px rgba(0, 0, 0, 0.6);
padding: 0px 0px;
overflow-y: visible;
overflow-x: hidden;
}
.axis path, .axis line {
fill: none;
stroke: #888888;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: gray;
fill: dodgerblue;
fill-opacity: .365;
}
path.link {
fill: none;
/* stroke: #3182bd; */
stroke: #888888;
stroke-width: 1px;
}
.header rect {
cursor: pointer;
fill-opacity: 1.0;
stroke: #888888;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.node rect {
cursor: pointer;
fill-opacity: 1.0;
stroke: #888888;
stroke-width: 1px;
}
.mini text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);
}
.main text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);
}
.axis text {
font: 10px sans-serif;
pointer-events: none;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);
}
#gantt {
padding: 2em 0 2em 0;
}
</style>
<!-- link rel="stylesheet" type="text/css" href="css/gantt.css"
/-->
<title>Gantt chart</title>
</head>
<body>
<div class="container">
<section class="main">
<p>
<div id="chart-headertop"></div>
<div id="chart-container"></div>
<script type="text/javascript">
function GanttChart(jsonData, dateFormat) {
/**
*the top gap in pixels
*/
var topGap = 40;
/**
*the right gap in pixels
*/
var rightGap = 40;
/**
*the bottom gap in pixels
*/
var bottomGap = 40;
/**
*the left gap in pixels
*/
var leftGap = 40;
/**
*the horizontal size of a cell in pixels
*/
var gridSizeX = 90;
/**
*the vertical size of a cell in pixels
*/
var gridSizeY = 18;
/**
*the vertical gap between cells in pixels
*/
var nodeGap = 3;
/**
* the gap between header svg and the bottom of the header canvas
*/
var headerGap = 3;
/**
*the number of pixels to offset text horizontally within a cell.
*/
var textOffsetX = 4;
/**
*the number of pixels to offset text vertically within a cell, around the vertical mid-point.
*/
var textOffsetY = 4;
/**
*offsets the chart canvas this number of pixels to the right
*/
var chartOffsetX = 5;
/**
* transition speed in ms
*/
var transitionSpeed = 600;
/**
* the relative location in the tree grid node where the connector is positioned vertically (between 0 and 1.0)
*/
var scaleFactorConnection = 0.6;
/**
*tree grid view node rounding on X-axis
*/
var roundX = 3;
/**
* tree grid view node rounding on Y-axis
*/
var roundY = 5;
var brushHeight = 80;
var miniGap = 25;
/**
*cell color at each depth (for depth exceeding colors.length, colors start from beginning again)
*/
//http://simple.be/web/color/codes is handy
var colors = ["#f99b0c", "#f9603a ", "#f98972", "#f9baaa ", "#afbffb"];
var local = this;
local.margin = {
top: topGap,
right: rightGap,
bottom: bottomGap,
left: leftGap
};
local.width = $("#chart-container").width() - local.margin.left - local.margin.right;
local.height = $("#chart-container").height() - local.margin.top - local.margin.bottom;
$("#chart-mini").width(local.width + local.margin.left + local.margin.top);
$("#chart-headertop").width(local.width + local.margin.left + local.margin.top);
local.gridsizex = gridSizeX; // width of cell
local.gridsizey = gridSizeY; // height of cell
local.textoffsety = local.gridsizey / 2 + textOffsetY;
local.textoffsetx = textOffsetX; //local.gridsizex/2-local.gridsizey;
local.chartoffsetx = chartOffsetX;
local.nodeGap = nodeGap; // the gap between nodes in the tree view
local.headerGap = headerGap; // the gap between the header boxes and the bottom of the header canvas
local.chartoffsety = local.gridsizey + local.gridsizey / 2 + local.headerGap;
local.root;
local.nodes;
local.tree;
local.treedepth;
local.idCount = 0;
local.duration = d3.event && d3.event.altKey ? 7000 : transitionSpeed;
local.diagonal = d3.svg.diagonal().projection(function (d) {
return [d.x, d.y + local.gridsizey * scaleFactorConnection];
});
local.visitedNodeMap = {}
local.chart = initialiseChartCanvas();
local.menu = initialiseHeaderCanvas();
local.root = jsonData;
// create tree layout
local.tree = d3.layout.tree();
local.nodes = local.tree.nodes(jsonData);
local.mini = initialiseminiCanvas();
// get depth of tree.
local.treedepth = getTreeDepth(local.nodes);
createHeader(getColHeader(local.nodes));
updateTimeStampsOnNodes(local.nodes);
local.xScale = createXScale(local.nodes);
local.xScaleBrush = createXScaleBrush(local.nodes);
local.xHeaderDateAxis = d3.svg.axis()
.scale(local.xScale)
.orient('top')
.ticks(d3.time.years, 1)
.tickFormat(d3.time.format('%Y'))
.tickSize(6, 3, 0);
local.menu.append('g') // append month axes (top)
.attr('transform', "translate(" + 0 + "," + -headerGap + ")")
.attr('class', 'main axis date')
.call(local.xHeaderDateAxis)
.selectAll('text')
var xMiniYearAxis = d3.svg.axis()
.scale(local.xScaleBrush)
.orient('top')
.ticks(d3.time.weeks, 26)
.tickFormat(d3.time.format('Wk.%U'))
.tickSize(6, 3, 0);
var xMiniMonthAxis = d3.svg.axis()
.scale(local.xScaleBrush)
.orient('bottom')
.ticks(d3.time.weeks, 26)
.tickFormat(d3.time.format('%d/%m/%Y'))
.tickSize(6, 3, 0);
local.visitedNodeMap = [];
// count the levels in the mini
local.countLevels = 0;
local.nodes.forEach(function (n, i) {
n.x = local.xScaleBrush(n._start);
// no mod for root node
if (!n.parent) {
local.treeIndexOffsetCounter = 0;
n.y = 50 + local.treeIndexOffsetCounter;
local.countLevels++;
} else {
// leaf node has not been seen under this parent before, therefore
// we add it to the visitedNodeMap for leaf nodes under the same parent,
// so the y coordinate can be reused.
if (!local.visitedNodeMap[n.parent.label + n.label]) {
local.treeIndexOffsetCounter += 1;
n.y = 50 + (local.treeIndexOffsetCounter - 1);
local.visitedNodeMap[n.parent.label + n.label] = {
y: n.y
};
local.countLevels++;
}
// this leaf node under this parent has been seen before, therefore
// the calculation for the y position should use the previous y position
// for this leaf node under this parent.
// This re-uses the y coordinate of the first occurence of this leaf node
// under the same parent.
else {
n.x = local.xScaleBrush(n._start);
properNodePosition = local.visitedNodeMap[n.parent.label + n.label];
n.y = properNodePosition.y;
}
}
});
local.mini.append('g')
.attr('transform', 'translate(0,' + miniGap + ')')
.attr('class', 'mini axis year')
.call(xMiniYearAxis)
.selectAll('text')
local.mini.append('g')
.attr('transform', 'translate(0,' + miniGap + ')')
.attr('class', 'mini axis month')
.call(xMiniMonthAxis)
.selectAll('text')
//mini item rects
local.mini.append("g")
.selectAll("miniItems")
.data(local.nodes)
.enter().append("rect")
.attr("fill", function (d, i) {
return getCellColor(d.depth)
})
.attr("class", function (d, i) {
return "miniItem" + i;
})
.attr("x", function (d) {
return local.xScaleBrush(d._start);
})
.attr("y", function (d, i) {
return d.y;
})
.attr("width", function (d) {
return (local.xScaleBrush(d._end) - local.xScaleBrush(d._start));
})
.attr("height", 1);
// now we know the node count, reset the brush height.
brushHeight = local.countLevels + 60;
$("#chart-mini").height(brushHeight);
//formatNodes(local.nodes)
// can copy here for display and modification, currently just reference.
local.root = jsonData;
//brush
local.brush = d3.svg.brush()
.x(local.xScaleBrush);
local.brush.on("brush", function () {
update(local.root);
}); //update(local.root= jsonData));
local.brush.extent([local.root._start, local.root._end]);
local.mini.append("g")
.attr("class", "x brush")
.call(local.brush)
.selectAll("rect")
.attr("y", 1)
.attr("height", brushHeight - 1);
update(local.root);
// updates time stamps on nodes.
// iterates over all sub-trees, and updates time stamps on each node level.
function updateTimeStampsOnNodes(nodes) {
//assignTimeStamps(nodes[0])
assignTimeStamps(nodes[0])
}
function createXScaleBrush(nodes) {
return d3.time.scale().domain([nodes[0]._start, nodes[0]._end]).range([0, local.width + local.margin.left + 24]);
}
function createXScale(nodes) {
return d3.time.scale().domain([nodes[0]._start, nodes[0]._end]).range([(local.treedepth + 1) * local.gridsizex, local.width + local.margin.left + 24]);
}
/**
* recursively assign start and end date times to nodes.
* @param {Object} node
*/
function assignTimeStamps(node) {
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
var child = node.children[i];
if (child == {}) {
continue;
}
var range = assignTimeStamps(child);
if (i == 0) {
node._start = range[0];
node._end = range[1];
} else {
if (range[0].getTime() < node._start.getTime()) {
node._start = range[0];
}
if (range[1].getTime() > node._end.getTime()) {
node._end = range[1];
}
}
}
return [node._start, node._end];
}
// no more children (leaf node expects a start time and duration)
else {
node._start = getDate(node);
node._end = addDurationAndGetDate(node);
return [node._start, node._end];
}
}
/**
* recursively assign start and end date times to nodes, where leaf nodes can have multiple activities.
* @param {Object} node
*/
function assignTimeStampsForMultiActivities(node) {
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
var child = node.children[i];
if (child == {}) {
continue;
}
var range = assignTimeStampsForMultiActivities(child);
if (i == 0) {
node._start = range[0];
node._end = range[1];
} else {
if (range[0].getTime() < node._start.getTime()) {
node._start = range[0];
}
if (range[1].getTime() > node._end.getTime()) {
node._end = range[1];
}
}
}
return [node._start, node._end];
}
// no more children (leaf node expects a start time and duration)
else {
node._start = getDate(node.activities[0]);
node._end = addDurationAndGetDate(node.activities[0]);
node.activities[0]._start = node._start;
node.activities[0]._end = node._end;
for (var j = 1; j < node.activities.length; j++) {
node.activities[j]._start = getDate(node.activities[j]);
node.activities[j]._end = addDurationAndGetDate(node.activities[j]);
if (node.activities[j]._start < node.activities[j - 1]._start) {
node._start = node.activities[j]._start;
}
if (node.activities[j]._end > node.activities[j - 1]._end) {
node._end = node.activities[j]._end;
}
}
//node._start = getDate(node);
//node._end = addDurationAndGetDate(node);
return [node._start, node._end];
}
}
function createHeader(colheader) {
var maxlength = 0;
for (var i = 0; i < colheader.length; i++) {
ch = colheader[i]
if (ch != null && ch.length > maxlength) {
maxlength = ch.length;
}
}
// resets grid size based on text length (note local should get the font size from the CSS and not use an arbitrary scaling factor)
local.gridsizex = 6 * maxlength;
// now get the font size, and set local.gridsizex in relation to the max length and font size.
var header = local.menu.selectAll("g.header")
.data(colheader)
// antony - fix scaling factor
var headerEnter = header.enter().append("svg:g") // a group of elements inside an svg canvas
.attr("class", "header")
.attr("transform", function (d, i) {
return "translate(" + (local.gridsizex * i) + "," + -(local.gridsizey + 5 * local.headerGap) + ")";
})
headerEnter.append("svg:rect")
.attr("fill", function (d, i) {
return getCellColor(i)
})
.attr("width", local.gridsizex)
.attr("height", local.gridsizey * 2)
// antony - fix scaling factor
headerEnter.append("svg:text")
.attr("dx", local.textoffsetx)
.attr("dy", local.textoffsety * 1.6)
.text(function (d) {
return d;
})
}
function initialiseminiCanvas() {
brushHeight = local.nodes.length + 60;
$("#chart-mini").height(brushHeight);
var chartbrush = d3.select("#chart-mini").append("svg:svg")
.attr("width", local.width + local.margin.left + local.margin.right)
.attr("height", brushHeight)
.attr('class', 'chartbrush');
chartbrush.append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', local.width + local.margin.left + local.margin.right) // set width of rect width of chart
.attr('height', brushHeight); // rect is mainheight high
// antony - fix scaling factor
var mini = chartbrush.append('g')
.attr("transform", "translate(" + local.chartoffsetx + "," + 0 + ")") // translate the group to these coordinates
.attr('width', local.width + local.margin.left + local.margin.right) // set width of rect width of chart
.attr('height', brushHeight)
.attr('class', 'mini'); // sets the class attribute to 'main'
return mini;
}
// initialises the header canvas.
function initialiseHeaderCanvas() {
var chartheader = d3.select("#chart-headertop").append("svg:svg")
.attr("width", local.width + local.margin.left + local.margin.right)
.attr("height", local.gridsizey + local.gridsizey * 0.5 + local.headerGap)
.attr('class', 'chartheader');
chartheader.append('defs').append('clipPath')
.attr('id', 'clipmini')
.append('rect')
.attr('width', local.width + local.margin.left + local.margin.right) // set width of rect width of chart
.attr('height', local.gridsizey + local.gridsizey * 0.5 + local.headerGap); // rect is mainheight high
var mini = chartheader.append('g')
.attr("transform", "translate(" + local.chartoffsetx + "," + (local.chartoffsety - 0.8 * local.headerGap) + ")") // translate the group to these coordinates
.attr('width', local.width + local.margin.left + local.margin.right) // set width of rect width of chart
.attr('height', local.gridsizey + local.gridsizey * 0.5 + local.headerGap) // rect is mainheight high
.attr('class', 'mini'); // sets the class attribute to 'main'
return mini;
}
// initialise
function initialiseChartCanvas() {
var chart = d3.select('#chart-container')
.append('svg:svg')
.attr('width', local.width + local.margin.right + local.margin.left)
.attr('height', local.height + local.margin.top + local.margin.bottom)
.attr('class', 'chart');
chart.append('defs').append('clipPath') // add clip path to 'defs'
.attr('id', 'clipmain') // assign id to clip path
.append('rect') // append rectangle to clip
.attr("class", "cliprect")
.attr('width', local.width) // set width of rect width of chart
.attr('height', local.height); // rect is mainheight high
// creating a grouping in the chart, which is a transform (translation left and to the top)
var main = chart.append('g')
.attr("transform", "translate(" + local.chartoffsetx + "," + local.chartoffsety + ")") // translate the group to these coordinates
.attr('width', local.width) // set height and width of main
.attr('height', local.height)
.attr('class', 'main')
return main;
}
function update(source) {
// get tree nodes.
local.nodes = local.tree.nodes(local.root);
// update the layout of nodes (removing collapsed nodes from the list in the next update pass), and do node transition animations.
updateNodeLayout(source);
// do the link transition animations.
updateLinkLayout(source);
// update the canvas height based on number of expanded tree elements.
updateCanvasHeight();
}
// Toggle children on click
// (for next update pass, only visible children will be drawn)
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
// calculate the depth of the tree.
function getTreeDepth(nodes) {
var depth = 0;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].depth > depth) {
depth = nodes[i].depth;
}
}
return depth;
}
function updateLinkLayout(source) {
// enter new links at parent's previous location.
var link = local.chart.selectAll("path.link")
.data(local.tree.links(local.nodes), function (d) {
return d.target.id;
});
link.enter().insert("svg:path", "g")
.attr("class", "link")
.attr("d", function (d) {
var o = {
x: source.x0 + local.gridsizex * 0.5,
y: source.y0
};
return local.diagonal({
source: o,
target: o
});
})
.transition()
.duration(local.duration)
.attr("d", local.diagonal);
// Transition links to their new position.
link.transition()
.duration(local.duration)
.attr("d", local.diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition().duration(local.duration).attr("d", function (d) {
var o = {
x: source.x,
y: source.y
};
return local.diagonal({
source: o,
target: o
});
}).remove();
// update lanes
// Stash the old positions for transition.
local.nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function openWin(text) {
popupGanttWindow = window.open('', '', 'width=200,height=100');
popupGanttWindow.document.write(text);
popupGanttWindow.focus();
}
function updateNodeLayout(source) {
// Compute the "layout".
var minExtent = local.brush.extent()[0];
var maxExtent = local.brush.extent()[1];
var visItems = local.nodes.filter(function (d) {
return d._start < maxExtent && d._end > minExtent;
});
local.mini.select(".brush").call(local.brush.extent([minExtent, maxExtent]));
local.xScale.domain([minExtent, maxExtent]);
//console.log(maxExtent-minExtent)
if ((maxExtent - minExtent) > 103452230000) {
local.xHeaderDateAxis.ticks(d3.time.months, 6).tickFormat(d3.time.format('%d/%m/%Y')).tickSize(6, 3, 0);
//x1MonthAxis.ticks(d3.time.mondays, 1).tickFormat(d3.time.format('%b - Week %W'))
} else if ((maxExtent - minExtent) > 73452230000) {
local.xHeaderDateAxis.ticks(d3.time.months, 3).tickFormat(d3.time.format('%d/%m/%Y')).tickSize(6, 3, 0);
//x1MonthAxis.ticks(d3.time.mondays, 1).tickFormat(d3.time.format('%b - Week %W'))
} else if ((maxExtent - minExtent) > 43452240000) {
local.xHeaderDateAxis.ticks(d3.time.months, 2).tickFormat(d3.time.format('%d/%m/%Y')).tickSize(6, 3, 0);
//x1MonthAxis.ticks(d3.time.mondays, 1).tickFormat(d3.time.format('%b - Week %W'))
} else if ((maxExtent - minExtent) > 20452240000) {
local.xHeaderDateAxis.ticks(d3.time.months, 1).tickFormat(d3.time.format('%d/%m/%Y')).tickSize(6, 3, 0);
//x1MonthAxis.ticks(d3.time.mondays, 1).tickFormat(d3.time.format('%b - Week %W'))
} else if ((maxExtent - minExtent) > 7452240000) {
local.xHeaderDateAxis.ticks(d3.time.weeks, 2).tickFormat(d3.time.format('%d/%m/%Y')).tickSize(6, 3, 0);
} else if ((maxExtent - minExtent) > 4452240000) {
local.xHeaderDateAxis.ticks(d3.time.weeks, 1).tickFormat(d3.time.format('%d/%m/%Y')).tickSize(6, 3, 0);
} else if ((maxExtent - minExtent) > 3052240000) {
local.xHeaderDateAxis.ticks(d3.time.days, 2).tickFormat(d3.time.format('%d/%m')).tickSize(6, 3, 0);
} else if ((maxExtent - minExtent) > 297200000) {
local.xHeaderDateAxis.ticks(d3.time.days, 1).tickFormat(d3.time.format('%d/%m')).tickSize(6, 3, 0);
} else if ((maxExtent - minExtent) > 287200000) {
local.xHeaderDateAxis.ticks(d3.time.hours, 12).tickFormat(d3.time.format('%a-%H:%M')).tickSize(6, 3, 0);
} else if ((maxExtent - minExtent) > 267200000) {
local.xHeaderDateAxis.ticks(d3.time.hours, 6).tickFormat(d3.time.format('%a-%H:%M')).tickSize(6, 3, 0);
} else if ((maxExtent - minExtent) > 247200000) {
local.xHeaderDateAxis.ticks(d3.time.hours, 4).tickFormat(d3.time.format('%a-%H:%M')).tickSize(6, 3, 0);
} else if ((maxExtent - minExtent) > 57200000) {
local.xHeaderDateAxis.ticks(d3.time.hours, 3).tickFormat(d3.time.format('%a-%H:%M')).tickSize(6, 3, 0);
} else if ((maxExtent - minExtent) > 4800000) {
local.xHeaderDateAxis.ticks(d3.time.hours, 1).tickFormat(d3.time.format('%a-%H:%M')).tickSize(6, 3, 0);
}
local.menu.select('.main.axis.date').call(local.xHeaderDateAxis);
// reset the visited node map so tree collapse updates correctly.
local.treeIndexOffsetCounter = 0;
local.visitedNodeMap = [];
local.nodes.forEach(function (n, i) {
// no mod for root node
if (!n.parent) {
n.x = n.depth * local.gridsizex;
n.x0 = n.x;
local.treeIndexOffsetCounter = 0;
n.y = (local.treeIndexOffsetCounter - 1) * local.gridsizey;
n.y0 = n.y;
} else {
// leaf node has not been seen under this parent before, therefore
// we add it to the visitedNodeMap for leaf nodes under the same parent,
// so the y coordinate can be reused.
if (!local.visitedNodeMap[n.parent.label + n.label]) {
local.treeIndexOffsetCounter += 1;
n.x = n.depth * local.gridsizex;
n.x0 = n.x;
n.y = (local.treeIndexOffsetCounter - 1) * local.gridsizey;
n.y0 = n.y;
local.visitedNodeMap[n.parent.label + n.label] = {
y: n.y,
y0: n.y0
};
}
// this leaf node under this parent has been seen before, therefore
// the calculation for the y position should use the previous y position
// for this leaf node under this parent.
// This re-uses the y coordinate of the first occurence of this leaf node
// under the same parent.
else {
n.x = n.depth * local.gridsizex;
n.x0 = n.x;
properNodePosition = local.visitedNodeMap[n.parent.label + n.label];
n.y = properNodePosition.y;
n.y0 = properNodePosition.y0;
}
}
});
// update the nodes
var node = local.chart.selectAll("g.node")
.data(local.nodes, function (d) {
if (!d.id) {
d.id = ++local.idCount;
return d.id;
}
return d.id;
});
// enter any new nodes at the parent's previous position
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.style("opacity", 1e-6)
// reset visited map so we only draw the guide lines once
local.visitedNodeMap = [];
nodeEnter.append("svg:line")
.attr("class", "lane")
.attr("x1", function (d) {
return 0;
})
.attr("y1", function (d) {
return local.gridsizey / 2;
})
.attr("x2", local.width + local.margin.left + local.margin.right)
.attr("y2", function (d) {
return local.gridsizey / 2;
})
.attr("stroke", function (d) {
return getCellColor(d.depth);
})
.attr("opacity", function (d) {
if (d.parent) {
if (local.visitedNodeMap[d.parent.label + d.label]) {
return 0.0;
} else {
local.visitedNodeMap[d.parent.label + d.label] = true;
return 1.0;
}
}
return 1.0;
})
.attr("stroke-width", "4px");
// need to add clip
node.selectAll(".activitybar")
.attr('x', function (d) {
return (local.xScale(d._start) - (d.depth) * local.gridsizex);
})
.attr('width', function (d) {
var time = (local.xScale(d._end) - local.xScale(d._start));
return time;
})
node.append("rect")
.attr("class", "activitybar")
.attr('x', function (d) {
return (local.xScale(d._start) - (d.depth) * local.gridsizex);
})
.attr('y', function (d) {
return 0;
})
.attr('width', function (d) {
var time = (local.xScale(d._end) - local.xScale(d._start));
if (time == 0) {
return local.width;
}
return time;
})
.attr('opacity', function (d) {
var time = (local.xScale(d._end) - local.xScale(d._start));
if (time == 0) {
return 0.3;
}
return 1.0;
})
.attr('height', function (d) {
return local.gridsizey - 4;
})
.attr('fill', function (d) {
var time = (local.xScale(d._end) - local.xScale(d._start));
if (time == 0) {
return 'grey';
}
return getCellColor(d.depth);
})
.attr("stroke", function (d) {
return "black";
})
.on("click", function (d) {
openWin("label : " + d.label + "<br>start : " + d._start + "<br>end : " + d._end);
})
.append("svg:title")
.text(function (d, i) {
var time = (local.xScale(d._end) - local.xScale(d._start));
var str = ("label : " + d.label + "\nstart : " + d._start + "\nend : " + d._end);
if (time == 0) {
return str + "\n No duration provided for this activity.";
}
return str;
});
nodeEnter.append("svg:rect")
.attr("class", "treenode")
.attr("fill", function (d) {
return getCellColor(d.depth);
})
.attr("rx", roundX)
.attr("ry", roundY)
.attr("width", local.gridsizex)
.attr("height", local.gridsizey - local.nodeGap)
.on("click", click)
nodeEnter.append("svg:text")
.attr("dx", local.textoffsetx)
.attr("dy", local.textoffsety)
.text(function (d) {
return d.label;
})
// Transition nodes to their new position.
nodeEnter.transition()
.duration(local.duration)
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
})
.style("opacity", 1);
node.transition()
.duration(local.duration)
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
})
.style("opacity", 1)
.select("rect")
.style("fill", function (d) {
return getCellColor(d)
});
// Transition exiting nodes to the parent's new position.
node.exit().transition()
.duration(local.duration)
.attr("transform", function (d) {
return "translate(" + source.x + "," + source.y + ")";
})
.style("opacity", 1e-6)
.remove();
}
function updateCanvasHeight() {
dynamicheight = local.nodes.length * local.gridsizey + 100; // 100 is padding
if (dynamicheight > local.height) {
d3.select("#chart-container svg").attr("height", dynamicheight + local.gridsizey / 2)
}
}
function getCellColor(treedepth) {
// mod function rolls over colorisations if more than depth of 6 in the tree
// (i.e. the 7th takes on the color of the first).
var index = treedepth % (colors.length);
return colors[index];
}
// returns an in order array of column headings.
function getColHeader(nodes) {
// iterate over nodes and get the type for each depth, and return it in a list.
// Expects depth order.
var headermap = {}
var headerarr = []
// get unique keys
for (var i = 0; i < nodes.length; i++) {
headermap[nodes[i].depth] = nodes[i].type;
}
// assign column titles to an array
for (key in headermap) {
headerarr.push(headermap[key])
}
return headerarr;
}
/**
* adds a duration to a JSON string date and returns a js date object (assuming duration is split by char ':' )
* @param {Object} d
*/
function addDurationAndGetDate(d) {
var splitDuration = d.duration.split(":", 3);
var minutesInMilliseconds = parseInt(splitDuration[2]) * 60 * 1000; // convert to ms
var hoursInMilliseconds = parseInt(splitDuration[1]) * 60 * 60 * 1000; // convert to ms
var daysInMilliseconds = parseInt(splitDuration[0]) * 24 * 60 * 60 * 1000; // convert to ms
// get start date from node and construct date object
// var startDate = new Date($.datepicker.formatDate(d.start,dateFormat));
var startDate = moment(d.start, dateFormat).toDate();
// construct end date by adding time in ms for start date to ms from duration of activity.
var endDate = new Date(startDate.getTime() + daysInMilliseconds + hoursInMilliseconds + minutesInMilliseconds);
return endDate;
}
// returns a date object from a JSON string date
function getDate(d) {
// var date = $.datepicker.formatDate(d.start,dateFormat)
var date = moment(d.start, dateFormat).toDate();
//console.log(d.start + " " +moment(d.start, dateFormat) + " " + date)
return date;
// return new Date(date);
}
}
$(document).ready(function () {
jsonData = {
"children": [{
"children": [{
"children": [{
"duration": "0:0:0",
"label": "1",
"start": "2002-01-01",
"type": "Subtask Lvl.3"
}, {
"duration": "0:0:0",
"label": "2",
"start": "2002-01-01",
"type": "Subtask Lvl.3"
}, {
"duration": "0:0:0",
"label": "3",
"start": "2002-01-01",
"type": "Subtask Lvl.3"
}],
"label": "Subtask01",
"type": "Subtask"
}, {
"children": [{
"duration": "32:0:34",
"label": "4",
"start": "2002-01-24",
"type": "Subtask Lvl.3"
}, {
"duration": "16:2:38",
"label": "5",
"start": "2002-03-09",
"type": "Subtask Lvl.3"
}, {
"duration": "0:0:1",
"label": "5",
"start": "2002-04-01",
"type": "Subtask Lvl.3"
}, {
"duration": "14:9:52",
"label": "5",
"start": "2002-04-14",
"type": "Subtask Lvl.3"
}, {
"duration": "20:17:2",
"label": "6",
"start": "2002-05-05",
"type": "Subtask Lvl.3"
}],
"label": "Subtask02",
"type": "Subtask"
}, {
"children": [{
"duration": "35:1:44",
"label": "7",
"start": "2002-10-13",
"type": "Subtask Lvl.3"
}, {
"duration": "17:16:15",
"label": "8",
"start": "2002-11-30",
"type": "Subtask Lvl.3"
}, {
"duration": "4:8:29",
"label": "8",
"start": "2002-12-26",
"type": "Subtask Lvl.3"
}, {
"duration": "11:13:33",
"label": "8",
"start": "2003-01-01",
"type": "Subtask Lvl.3"
}, {
"duration": "32:7:19",
"label": "9",
"start": "2003-01-17",
"type": "Subtask Lvl.3"
}],
"label": "Subtask03",
"type": "Subtask"
}, {
"children": [{
"duration": "30:7:4",
"label": "10",
"start": "2003-08-20",
"type": "Subtask Lvl.3"
}, {
"duration": "12:12:12",
"label": "10",
"start": "2003-10-01",
"type": "Subtask Lvl.3"
}, {
"duration": "42:14:38",
"label": "11",
"start": "2003-10-18",
"type": "Subtask Lvl.3"
}, {
"duration": "6:13:26",
"label": "12",
"start": "2003-12-15",
"type": "Subtask Lvl.3"
}, {
"duration": "4:8:29",
"label": "12",
"start": "2003-12-26",
"type": "Subtask Lvl.3"
}, {
"duration": "18:11:45",
"label": "12",
"start": "2004-01-01",
"type": "Subtask Lvl.3"
}, {
"duration": "4:7:42",
"label": "12",
"start": "2004-01-26",
"type": "Subtask Lvl.3"
}],
"label": "Subtask04",
"type": "Subtask"
}, {
"children": [{
"duration": "31:22:0",
"label": "13",
"start": "2004-08-18",
"type": "Subtask Lvl.3"
}, {
"duration": "19:6:57",
"label": "13",
"start": "2004-10-01",
"type": "Subtask Lvl.3"
}, {
"duration": "42:9:18",
"label": "14",
"start": "2004-10-27",
"type": "Subtask Lvl.3"
}, {
"duration": "4:8:29",
"label": "14",
"start": "2004-12-26",
"type": "Subtask Lvl.3"
}, {
"duration": "2:1:47",
"label": "14",
"start": "2005-01-01",
"type": "Subtask Lvl.3"
}, {
"duration": "48:10:31",
"label": "15",
"start": "2005-01-03",
"type": "Subtask Lvl.3"
}],
"label": "Subtask05",
"type": "Subtask"
}, {
"children": [{
"duration": "57:2:6",
"label": "16",
"start": "2005-08-17",
"type": "Subtask Lvl.3"
}, {
"duration": "56:13:27",
"label": "17",
"start": "2005-10-18",
"type": "Subtask Lvl.3"
}, {
"duration": "6:12:4",
"label": "18",
"start": "2005-12-17",
"type": "Subtask Lvl.3"
}, {
"duration": "5:13:44",
"label": "18",
"start": "2005-12-26",
"type": "Subtask Lvl.3"
}, {
"duration": "43:20:7",
"label": "18",
"start": "2006-01-01",
"type": "Subtask Lvl.3"
}],
"label": "Subtask06",
"type": "Subtask"
}, {
"children": [{
"duration": "56:4:17",
"label": "19",
"start": "2006-08-19",
"type": "Subtask Lvl.3"
}, {
"duration": "55:13:19",
"label": "20",
"start": "2006-10-18",
"type": "Subtask Lvl.3"
}, {
"duration": "7:2:8",
"label": "21",
"start": "2006-12-17",
"type": "Subtask Lvl.3"
}, {
"duration": "5:13:44",
"label": "21",
"start": "2006-12-26",
"type": "Subtask Lvl.3"
}, {
"duration": "42:4:38",
"label": "21",
"start": "2007-01-01",
"type": "Subtask Lvl.3"
}],
"label": "Subtask07",
"type": "Subtask"
}, {
"children": [{
"duration": "55:6:30",
"label": "22",
"start": "2007-08-15",
"type": "Subtask Lvl.3"
}, {
"duration": "54:13:9",
"label": "23",
"start": "2007-10-14",
"type": "Subtask Lvl.3"
}, {
"duration": "12:5:14",
"label": "24",
"start": "2007-12-11",
"type": "Subtask Lvl.3"
}, {
"duration": "5:13:44",
"label": "24",
"start": "2007-12-26",
"type": "Subtask Lvl.3"
}, {
"duration": "36:0:10",
"label": "24",
"start": "2008-01-01",
"type": "Subtask Lvl.3"
}],
"label": "Subtask08",
"type": "Subtask"
}],
"label": "Subtask 2",
"type": "Subtask"
}, {
"children": [{
"children": [{
"duration": "0:0:0",
"label": "25",
"start": "2002-01-02",
"type": "Subtask Lvl.3"
}, {
"duration": "0:0:0",
"label": "25",
"start": "2002-01-02",
"type": "Subtask Lvl.3"
}, {
"duration": "15:10:53",
"label": "26",
"start": "2002-01-02",
"type": "Subtask Lvl.3"
}],
"label": "Subtask201",
"type": "Subtask"
}, {
"children": [{
"duration": "17:5:31",
"label": "27",
"start": "2002-06-05",
"type": "Subtask Lvl.3"
}, {
"duration": "11:14:38",
"label": "27",
"start": "2002-07-01",
"type": "Subtask Lvl.3"
}, {
"duration": "2:10:16",
"label": "27",
"start": "2002-07-18",
"type": "Subtask Lvl.3"
}, {
"duration": "26:3:58",
"label": "28",
"start": "2002-07-21",
"type": "Subtask Lvl.3"
}, {
"duration": "25:19:52",
"label": "29",
"start": "2002-08-26",
"type": "Subtask Lvl.3"
}, {
"duration": "8:4:52",
"label": "29",
"start": "2002-10-01",
"type": "Subtask Lvl.3"
}],
"label": "Subtask202",
"type": "Subtask"
}, {
"children": [{
"duration": "19:1:59",
"label": "30",
"start": "2003-03-04",
"type": "Subtask Lvl.3"
}, {
"duration": "16:12:32",
"label": "30",
"start": "2003-04-13",
"type": "Subtask Lvl.3"
}, {
"duration": "33:20:21",
"label": "31",
"start": "2003-05-07",
"type": "Subtask Lvl.3"
}, {
"duration": "3:15:51",
"label": "32",
"start": "2003-06-25",
"type": "Subtask Lvl.3"
}, {
"duration": "11:14:38",
"label": "32",
"start": "2003-07-01",
"type": "Subtask Lvl.3"
}, {
"duration": "23:9:38",
"label": "32",
"start": "2003-07-18",
"type": "Subtask Lvl.3"
}],
"label": "Subtask203",
"type": "Subtask"
}, {
"children": [{
"duration": "41:11:59",
"label": "33",
"start": "2004-02-02",
"type": "Subtask Lvl.3"
}, {
"duration": "0:21:27",
"label": "33",
"start": "2004-04-13",
"type": "Subtask Lvl.3"
}, {
"duration": "38:3:56",
"label": "34",
"start": "2004-04-14",
"type": "Subtask Lvl.3"
}, {
"duration": "14:23:21",
"label": "35",
"start": "2004-06-09",
"type": "Subtask Lvl.3"
}, {
"duration": "13:1:28",
"label": "35",
"start": "2004-07-01",
"type": "Subtask Lvl.3"
}, {
"duration": "16:22:43",
"label": "35",
"start": "2004-07-20",
"type": "Subtask Lvl.3"
}, {
"duration": "3:9:9",
"label": "35",
"start": "2004-08-12",
"type": "Subtask Lvl.3"
}],
"label": "Subtask204",
"type": "Subtask"
}, {
"children": [{
"duration": "31:5:2",
"label": "36",
"start": "2005-02-25",
"type": "Subtask Lvl.3"
}, {
"duration": "17:21:34",
"label": "36",
"start": "2005-04-13",
"type": "Subtask Lvl.3"
}, {
"duration": "45:1:19",
"label": "37",
"start": "2005-05-02",
"type": "Subtask Lvl.3"
}, {
"duration": "26:4:43",
"label": "38",
"start": "2005-06-19",
"type": "Subtask Lvl.3"
}, {
"duration": "26:13:51",
"label": "38",
"start": "2005-07-19",
"type": "Subtask Lvl.3"
}],
"label": "Subtask205",
"type": "Subtask"
}, {
"children": [{
"duration": "37:21:14",
"label": "39",
"start": "2006-02-18",
"type": "Subtask Lvl.3"
}, {
"duration": "15:0:33",
"label": "39",
"start": "2006-04-13",
"type": "Subtask Lvl.3"
}, {
"duration": "48:0:41",
"label": "40",
"start": "2006-04-29",
"type": "Subtask Lvl.3"
}, {
"duration": "25:4:6",
"label": "41",
"start": "2006-06-19",
"type": "Subtask Lvl.3"
}, {
"duration": "28:20:2",
"label": "41",
"start": "2006-07-18",
"type": "Subtask Lvl.3"
}],
"label": "Subtask206",
"type": "Subtask"
}, {
"children": [{
"duration": "41:9:17",
"label": "42",
"start": "2007-02-16",
"type": "Subtask Lvl.3"
}, {
"duration": "10:15:54",
"label": "42",
"start": "2007-04-15",
"type": "Subtask Lvl.3"
}, {
"duration": "48:0:41",
"label": "43",
"start": "2007-04-26",
"type": "Subtask Lvl.3"
}, {
"duration": "27:16:10",
"label": "44",
"start": "2007-06-17",
"type": "Subtask Lvl.3"
}, {
"duration": "25:14:53",
"label": "44",
"start": "2007-07-18",
"type": "Subtask Lvl.3"
}],
"label": "Subtask207",
"type": "Subtask"
}, {
"children": [{
"duration": "46:15:54",
"label": "45",
"start": "2008-02-09",
"type": "Subtask Lvl.3"
}, {
"duration": "4:12:43",
"label": "45",
"start": "2008-04-13",
"type": "Subtask Lvl.3"
}, {
"duration": "48:0:41",
"label": "46",
"start": "2008-04-17",
"type": "Subtask Lvl.3"
}, {
"duration": "35:16:24",
"label": "47",
"start": "2008-06-08",
"type": "Subtask Lvl.3"
}, {
"duration": "16:21:37",
"label": "47",
"start": "2008-07-18",
"type": "Subtask Lvl.3"
}],
"label": "Subtask208",
"type": "Subtask Lvl.2"
}],
"label": "Subtask 1",
"type": "Subtask Lvl.1"
}],
"label": "P1",
"type": "Proj"
};
new GanttChart(jsonData, "YYYY-MM-DD");
});
</script>
<div id="chart-mini"></div>
</p>
</section>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment