Skip to content

Instantly share code, notes, and snippets.

@OscardR
Last active October 31, 2017 19:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OscardR/9298ae970a1b039b511ef6b813d4b42c to your computer and use it in GitHub Desktop.
Save OscardR/9298ae970a1b039b511ef6b813d4b42c to your computer and use it in GitHub Desktop.
Gantt Chart made with ESNext.bin
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Gantt Chart</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.0/css/foundation.min.css">
</head>
<body>
<h1>Gantt Chart</h1>
<div id="chart"></div>
</body>
</html>
import * as d3 from "d3";
let tasks = [{
name: 'Do things',
category: 'Óscar',
from: '02/03/2017 08:00:00',
to: '02/03/2017 17:30:00',
comment: 'Things never end'
}, {
name: 'More stuff',
category: 'Peter',
from: '02/03/2017 12:30:00',
to: '02/03/2017 18:00:00',
comment: 'Even more stuff'
}, {
name: 'Last but not least',
category: 'Johnny',
from: '02/03/2017 17:30:00',
to: '02/03/2017 21:00:00',
comment: 'Agggh'
}];
const BAR_HEIGHT = 192,
OFFSET_X = 80,
OFFSET_Y = 20;
const _state = new WeakMap();
class GanttChart {
constructor(options) {
let self = this,
state = self.getState(),
$root = d3.select(`#${options.htmlObject}`),
$svg = $root.append('svg').attr({
width: options.width,
height: options.height,
fill: 'cobalt'
}),
$bg = $svg.append('rect').attr({
fill: 'aliceblue',
x: 0,
y: 0,
width: options.width,
height: options.height
}),
$yAxis = $svg.append("g").attr({
class: "y axis",
transform: `translate(${OFFSET_X}, 0)`
}),
$xAxis = $svg.append("g").attr({
class: "x axis",
transform: `translate(${OFFSET_X}, ${options.height - OFFSET_Y})`
});
Object.assign(options, {
$root,
$svg,
$xAxis,
$yAxis
});
self.setState(options);
console.debug('Construct', self.getState());
}
_setData(data = this.getState().data) {
let self = this,
state = self.getState();
self.setState({
data: data,
$tasks: //state.$root
state.$svg
.selectAll('.task')
.data(data, d => `${d.category}~${d.name}`)
});
console.debug('Set Data', self.getState());
return self;
}
_axisDraw() {
let self = this,
state = self.getState();
let y = d3.scale.ordinal()
.domain(state.data.map(d => d.category))
.rangeBands([
OFFSET_Y,
state.height - OFFSET_Y
], BAR_HEIGHT / state.height);
let x = d3.time.scale()
.domain([
d3.min(state.data.map(d => new Date(d.from))),
d3.max(state.data.map(d => new Date(d.to)))
]).range([0, state.width - OFFSET_X]);
let yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.orient("left");
let xAxis = d3.svg.axis()
.scale(x)
.tickSize(1)
.orient("bottom")
self.setState({
$xScale: x,
$yScale: y,
$yAxisDraw: yAxis,
$xAxisDraw: xAxis,
$yAxis: state.$svg.transition().duration(1000).select('.y.axis').call(yAxis),
$xAxis: state.$svg.transition().duration(1000).select('.x.axis').call(xAxis)
});
return self;
}
_tasksEnter() {
let self = this,
state = self.getState(),
$tasksEnter = state.$tasks.enter()
.append('g').attr({
class: 'task'
});
$tasksEnter.append('rect')
.attr({
class: 'bar',
x: OFFSET_X,
y: d => state.$yScale(d.category),
fill: 'white',
width: 0,
height: state.$yScale.rangeBand()
});
$tasksEnter.append('text').attr({
x: OFFSET_X,
y: d => state.$yScale(d.category) + state.$yScale.rangeBand() / 2,
dy: '.25em',
'text-anchor': 'end',
fill: 'white'
}).text((d, i) => `${i}: ${d.name}`);
self.setState({
$tasksEnter
});
console.debug('Enter', self.getState());
return self;
}
_tasksUpdate() {
let self = this,
state = self.getState(),
x = state.$xScale,
y = state.$yScale,
taskWidth = d => x(new Date(d.to)) - x(new Date(d.from));
self.setState({
$tasksUpdate: state.$tasks
.select('.bar')
.transition().duration(1000)
.attr({
x: d => OFFSET_X + x(new Date(d.from)),
y: d => y(d.category),
fill: d => `rgb(${~~(255 * taskWidth(d) / state.width)}, 0, 0)`,
width: d => taskWidth(d),
height: state.$yScale.rangeBand()
}),
$textsUpdate: state.$tasks
.select('text')
.transition().duration(1000)
.attr({
x: d => OFFSET_X + x(new Date(d.to)) - 10,
y: d => state.$yScale(d.category) + state.$yScale.rangeBand() / 2
})
});
console.debug('Update', self.getState());
return self;
}
_tasksExit() {
let self = this,
state = self.getState();
self.setState({
$tasksExit: state.$tasks //Enter
.exit()
.transition().duration(1000)
.attr('opacity', 0)
.remove()
})
console.debug('Exit', self.getState());
return self;
}
build(data = this.getState().data) {
let self = this,
state = self.getState();
self._setData(data)
._axisDraw()
._tasksEnter()
._tasksUpdate()
._tasksExit();
console.debug(self.getState().$tasks, data);
return self;
}
getState() {
let self = this;
return _state.get(self) || {};
}
setState(newState) {
let self = this,
currentState = self.getState();
_state.set(self, Object.assign(currentState, newState));
return self;
}
}
let gc = new GanttChart({
htmlObject: 'chart',
data: tasks,
height: 480,
width: 720
}).build();
// Modify dataset
setTimeout(() => {
gc.build(tasks = tasks.concat([{
name: 'Do more things',
category: 'Other',
from: '02/03/2017 17:00:00',
to: '02/03/2017 23:59:00',
comment: 'Some more things'
}]))
}, 2000);
setTimeout(() => {
gc.build(tasks = tasks.concat([{
name: 'Do even more things',
category: 'Óscar',
from: '02/04/2017 00:00:00',
to: '02/04/2017 07:00:00',
comment: 'Can you believe it?'
}]))
}, 4000);
setTimeout(() => {
gc.build(tasks = tasks.splice(1))
}, 6000);
setTimeout(() => {
gc.build(tasks = tasks.splice(1))
}, 8000);
{
"name": "gantt-chart",
"version": "0.0.1",
"dependencies": {
"d3": "3.5.1",
"babel-runtime": "6.23.0"
}
}
'use strict';
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _weakMap = require('babel-runtime/core-js/weak-map');
var _weakMap2 = _interopRequireDefault(_weakMap);
var _d = require('d3');
var d3 = _interopRequireWildcard(_d);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var tasks = [{
name: 'Do things',
category: 'Óscar',
from: '02/03/2017 08:00:00',
to: '02/03/2017 17:30:00',
comment: 'Things never end'
}, {
name: 'More stuff',
category: 'Peter',
from: '02/03/2017 12:30:00',
to: '02/03/2017 18:00:00',
comment: 'Even more stuff'
}, {
name: 'Last but not least',
category: 'Johnny',
from: '02/03/2017 17:30:00',
to: '02/03/2017 21:00:00',
comment: 'Agggh'
}];
var BAR_HEIGHT = 192,
OFFSET_X = 80,
OFFSET_Y = 20;
var _state = new _weakMap2.default();
var GanttChart = function () {
function GanttChart(options) {
(0, _classCallCheck3.default)(this, GanttChart);
var self = this,
state = self.getState(),
$root = d3.select('#' + options.htmlObject),
$svg = $root.append('svg').attr({
width: options.width,
height: options.height,
fill: 'cobalt'
}),
$bg = $svg.append('rect').attr({
fill: 'aliceblue',
x: 0,
y: 0,
width: options.width,
height: options.height
}),
$yAxis = $svg.append("g").attr({
class: "y axis",
transform: 'translate(' + OFFSET_X + ', 0)'
}),
$xAxis = $svg.append("g").attr({
class: "x axis",
transform: 'translate(' + OFFSET_X + ', ' + (options.height - OFFSET_Y) + ')'
});
(0, _extends3.default)(options, {
$root: $root,
$svg: $svg,
$xAxis: $xAxis,
$yAxis: $yAxis
});
self.setState(options);
console.debug('Construct', self.getState());
}
(0, _createClass3.default)(GanttChart, [{
key: '_setData',
value: function _setData() {
var data = arguments.length <= 0 || arguments[0] === undefined ? this.getState().data : arguments[0];
var self = this,
state = self.getState();
self.setState({
data: data,
$tasks: //state.$root
state.$svg.selectAll('.task').data(data, function (d) {
return d.category + '~' + d.name;
})
});
console.debug('Set Data', self.getState());
return self;
}
}, {
key: '_axisDraw',
value: function _axisDraw() {
var self = this,
state = self.getState();
var y = d3.scale.ordinal().domain(state.data.map(function (d) {
return d.category;
})).rangeBands([OFFSET_Y, state.height - OFFSET_Y], BAR_HEIGHT / state.height);
var x = d3.time.scale().domain([d3.min(state.data.map(function (d) {
return new Date(d.from);
})), d3.max(state.data.map(function (d) {
return new Date(d.to);
}))]).range([0, state.width - OFFSET_X]);
var yAxis = d3.svg.axis().scale(y).tickSize(1).orient("left");
var xAxis = d3.svg.axis().scale(x).tickSize(1).orient("bottom");
self.setState({
$xScale: x,
$yScale: y,
$yAxisDraw: yAxis,
$xAxisDraw: xAxis,
$yAxis: state.$svg.transition().duration(1000).select('.y.axis').call(yAxis),
$xAxis: state.$svg.transition().duration(1000).select('.x.axis').call(xAxis)
});
return self;
}
}, {
key: '_tasksEnter',
value: function _tasksEnter() {
var self = this,
state = self.getState(),
$tasksEnter = state.$tasks.enter().append('g').attr({
class: 'task'
});
$tasksEnter.append('rect').attr({
class: 'bar',
x: OFFSET_X,
y: function y(d) {
return state.$yScale(d.category);
},
fill: 'white',
width: 0,
height: state.$yScale.rangeBand()
});
$tasksEnter.append('text').attr({
x: OFFSET_X,
y: function y(d) {
return state.$yScale(d.category) + state.$yScale.rangeBand() / 2;
},
dy: '.25em',
'text-anchor': 'end',
fill: 'white'
}).text(function (d, i) {
return i + ': ' + d.name;
});
self.setState({
$tasksEnter: $tasksEnter
});
console.debug('Enter', self.getState());
return self;
}
}, {
key: '_tasksUpdate',
value: function _tasksUpdate() {
var self = this,
state = self.getState(),
_x2 = state.$xScale,
_y = state.$yScale,
taskWidth = function taskWidth(d) {
return _x2(new Date(d.to)) - _x2(new Date(d.from));
};
self.setState({
$tasksUpdate: state.$tasks.select('.bar').transition().duration(1000).attr({
x: function x(d) {
return OFFSET_X + _x2(new Date(d.from));
},
y: function y(d) {
return _y(d.category);
},
fill: function fill(d) {
return 'rgb(' + ~ ~(255 * taskWidth(d) / state.width) + ', 0, 0)';
},
width: function width(d) {
return taskWidth(d);
},
height: state.$yScale.rangeBand()
}),
$textsUpdate: state.$tasks.select('text').transition().duration(1000).attr({
x: function x(d) {
return OFFSET_X + _x2(new Date(d.to)) - 10;
},
y: function y(d) {
return state.$yScale(d.category) + state.$yScale.rangeBand() / 2;
}
})
});
console.debug('Update', self.getState());
return self;
}
}, {
key: '_tasksExit',
value: function _tasksExit() {
var self = this,
state = self.getState();
self.setState({
$tasksExit: state.$tasks //Enter
.exit().transition().duration(1000).attr('opacity', 0).remove()
});
console.debug('Exit', self.getState());
return self;
}
}, {
key: 'build',
value: function build() {
var data = arguments.length <= 0 || arguments[0] === undefined ? this.getState().data : arguments[0];
var self = this,
state = self.getState();
self._setData(data)._axisDraw()._tasksEnter()._tasksUpdate()._tasksExit();
console.debug(self.getState().$tasks, data);
return self;
}
}, {
key: 'getState',
value: function getState() {
var self = this;
return _state.get(self) || {};
}
}, {
key: 'setState',
value: function setState(newState) {
var self = this,
currentState = self.getState();
_state.set(self, (0, _extends3.default)(currentState, newState));
return self;
}
}]);
return GanttChart;
}();
var gc = new GanttChart({
htmlObject: 'chart',
data: tasks,
height: 480,
width: 720
}).build();
// Modify dataset
setTimeout(function () {
gc.build(tasks = tasks.concat([{
name: 'Do more things',
category: 'Other',
from: '02/03/2017 17:00:00',
to: '02/03/2017 23:59:00',
comment: 'Some more things'
}]));
}, 2000);
setTimeout(function () {
gc.build(tasks = tasks.concat([{
name: 'Do even more things',
category: 'Óscar',
from: '02/04/2017 00:00:00',
to: '02/04/2017 07:00:00',
comment: 'Can you believe it?'
}]));
}, 4000);
setTimeout(function () {
gc.build(tasks = tasks.splice(1));
}, 6000);
setTimeout(function () {
gc.build(tasks = tasks.splice(1));
}, 8000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment