Incident Timeline
by kelvinau
HTML
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="https://demo.kelvinau.net/vis/vis.js"></script>
<div>
<b>From 2019-02-06 09:00 to 2019-02-16 12:00</b>
</div>
<div id="visualization"></div>
SCSS
$blue : #5dabfb;
$dot-radius: 16px;
$dot-inner-white-circle: 24px;
$dot-inner-blue-circle: 20px;
$tick-size: 20px;
.header {
font-weight: bold;
}
div.vis-tooltip {
background-color: white;
}
.vis-timeline {
overflow: visible;
}
.custom-item {
&.vis-box {
border: none;
background-color: transparent;
transform: translateY(5px);
color: #1580ef;
font-weight: bold;
// for showing tooltip on dot
/* height: 170px */;
}
&.vis-line {
display: none;
}
&.vis-dot {
border-width: $dot-radius;
border-radius: $dot-radius;
background-color: $blue;
border-color: $blue;
&::before {
content: '';
position: absolute;
background: white;
border-color: white;
border-width: $dot-inner-white-circle;
border-radius: $dot-inner-white-circle;
width: $dot-inner-white-circle;
height: $dot-inner-white-circle;
transform: translate(-50%, -50%);
}
&::after {
// Text
content: '\2714'; // Can change it to \f2c9 after merged
color: white;
font-size: $tick-size;
line-height: $tick-size;
text-align: center;
// Circle
position: absolute;
background: $blue;
border-color: white;
border-width: $dot-inner-blue-circle;
border-radius: $dot-inner-blue-circle;
width: $dot-inner-blue-circle;
height: $dot-inner-blue-circle;
transform: translate(-50%, -50%);
}
}
}
JavaScript
const INCIDENT = {
id: 'IN00000160',
// Assumption: the cycle is in chronological order
cycle: [{
key: 'new',
content: 'New',
time: '2019-02-06 09:00',
PIC: 'mthompson',
// TODO: does cycle mean accumulation of time?
cycle: '3',
},
{
key: 'analysis',
content: 'Analysis',
time: '2019-02-08 13:00',
PIC: 'bsabetghadam',
cycle: '55',
},
{
key: 'response',
content: 'Response',
time: '2019-02-11 10:00',
PIC: 'kau',
cycle: '100',
},
{
key: 'closed',
content: 'Closed',
time: '2019-02-16 12:00',
PIC: 'scao',
cycle: '10',
},
]
};
class IncidentLifeCycle {
constructor(dom, incident, option = {}) {
this.dom = dom;
this.incident = incident;
this.tFactory = new TooltipFactory(incident);
this.userOption = option;
this.defaultOption = {
showCurrentTime: false,
tooltip: {
followMouse: true,
},
};
}
init() {
if (!this.incident.cycle.length) throw 'there are no items in cycle!';
const startTime = this.incident.cycle[0].time;
const endTime = this.incident.cycle[this.incident.cycle.length - 1].time;
const items = this.incident.cycle.map(({
key,
content,
...item
}, i) => ({
id: i,
content,
start: item.time,
title: this.tFactory[key](item),
className: 'custom-item',
}));
const option = Object.assign(this.defaultOption, {
min: moment(startTime).subtract(1, 'day'),
max: moment(endTime).add(1, 'day'),
}, this.userOption);
const dataSet = new vis.DataSet(items);
/* console.log(option); */
this.timeline = new vis.Timeline(this.dom, dataSet, option);
}
destroy() {
this.timeline.destroy();
this.dom = null;
}
}
class TooltipFactory {
constructor(incident) {
this.incident = incident;
this.header = `<div class="header">${incident.id}</div>`;
}
...