made with esnextbin
Last active
October 31, 2017 19:33
-
-
Save OscardR/9298ae970a1b039b511ef6b813d4b42c to your computer and use it in GitHub Desktop.
Gantt Chart made with ESNext.bin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "gantt-chart", | |
"version": "0.0.1", | |
"dependencies": { | |
"d3": "3.5.1", | |
"babel-runtime": "6.23.0" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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