GanttChart = {
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: 'white',
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))
.rangeRoundBands([
0,
state.height - OFFSET_Y
], BAR_SPACING, 0);
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)
.orient("left");
let xAxis = d3.svg.axis()
.scale(x)
.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)
});
console.debug('Axis', self.getState());
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({width: 0})
.style({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('Build', 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;
}
}
return GanttChart;
}