@dboxjs/timeline
Version:
dboxjs timeline
417 lines (369 loc) • 10.8 kB
JavaScript
import * as d3 from 'd3';
/* Simple timeline example
* Single and multiline timelines
*/
export default function(config, helper) {
var Timeline = Object.create(helper);
Timeline.init = function(config) {
var vm = this;
vm._config = config ? config : {};
vm._data = [];
vm._scales = {};
vm._axes = {};
vm._config.parseDate = d3.timeParse('%Y-%m-%d');
vm._config.curve = d3.curveLinear;
vm._tip = vm.utils.d3
.tip()
.attr(
'class',
'd3-tip ' +
(vm._config.tooltip && vm._config.tooltip.classed
? vm._config.tooltip.classed
: '')
)
.html(
vm._config.tip && vm._config.tip.html
? vm._config.tip.html
: function(d) {
let scaleColor =
vm._scales.color !== false
? vm._scales.color(d.name)
: vm._getQuantileColor(d.name, 'default');
if (vm.chart.config.styles) {
var html = `<div style='
line-height: 1;
opacity: ${vm.chart.style.tooltip.opacity};
font-weight: ${vm.chart.style.tooltip.text.fontWeight};
font-size: ${vm.chart.style.tooltip.text.fontSize};
color: ${vm.chart.style.tooltip.text.textColor};
font-family: ${vm.chart.style.tooltip.text.fontFamily};
background-color: ${vm.chart.style.tooltip.backgroundColor};
padding: ${vm.chart.style.tooltip.text.padding};
border: ${vm.chart.style.tooltip.border.width} solid ${vm.chart.style.tooltip.border.color};
border-radius: ${vm.chart.style.tooltip.border.radius};'>`;
html += `<strong style='color:${vm.chart.style.tooltip.text.fontColor};'>`;
} else {
var html = '<div> <strong>';
}
html +=
`<strong style='color:${scaleColor}'>` + d.name + ': </strong>';
html += d.y
? `<span >` +
(Number.isNaN(+d.y)
? d.y
: vm.utils.format(vm._config.yAxis)(d.y)) +
'</span>'
: '';
html += '</div>';
return html;
}
);
};
//-------------------------------
//User config functions
Timeline.x = function(col) {
var vm = this;
vm._config.x = col;
return vm;
};
Timeline.parseDate = function(format) {
var vm = this;
vm._config.parseDate = d3.timeParse(format);
return vm;
};
Timeline.y = function(col) {
var vm = this;
vm._config.y = col;
return vm;
};
Timeline.series = function(arr) {
var vm = this;
vm._config.series = arr;
return vm;
};
Timeline.curve = function(curve) {
var vm = this;
vm._config.curve = curve;
return vm;
};
Timeline.fill = function(col) {
var vm = this;
vm._config.fill = col;
return vm;
};
Timeline.colors = function(colors) {
var vm = this;
if (Array.isArray(colors)) {
//Using an array of colors for the range
vm._config.colors = colors;
} else {
//Using a preconfigured d3.scale
vm._scales.color = colors;
}
return vm;
};
Timeline.tip = function(tip) {
var vm = this;
vm._config.tip = tip;
vm._tip.html(vm._config.tip);
return vm;
};
//-------------------------------
//Triggered by the chart.js;
Timeline.data = function(data) {
var vm = this;
vm._data = [];
data.forEach(function(d) {
var tmp = Object.assign({}, d);
if (d[vm._config.x]) {
try {
d[vm._config.x].getTime();
if (!Number.isNaN(d[vm._config.x].getTime())) {
tmp.x = d[vm._config.x];
}
} catch (err) {
tmp.x = vm._config.parseDate(d[vm._config.x]);
}
}
tmp.color = d[vm._config.fill];
delete tmp[vm._config.x];
vm._data.push(tmp);
});
//Sort the data by d.x
vm._data = vm._data.sort(function(a, b) {
return d3.ascending(a.x, b.x);
});
vm._lines = vm._config.y ? vm._config.y : vm._config.series;
vm._lines = vm._lines.map(function(name) {
return {
name: name,
values: vm._data.map(function(d) {
return { x: d.x, y: +d[name] };
}),
};
});
vm._lines.forEach(n => {
n.values = n.values.filter(v => {
return !isNaN(v.y);
});
});
vm._line = d3
.line()
.curve(vm._config.curve)
.defined(function(d) {
return d.y !== undefined;
})
.x(function(d) {
return vm._scales.x(d.x);
})
.y(function(d) {
return vm._scales.y(d.y);
});
vm._area = d3
.area()
.curve(vm._config.curve)
.x(function(d) {
if (d.alreadyScaled && d.alreadyScaled === true) {
return d.x;
} else {
return vm._scales.x(d.x);
}
})
.y1(function(d) {
if (d.alreadyScaled && d.alreadyScaled === true) {
return d.y;
} else {
return vm._scales.y(d.y);
}
});
return vm;
};
Timeline.scales = function() {
var vm = this;
vm._xMinMax = d3.extent(vm._data, function(d) {
return d.x;
});
vm._yMinMax = [
vm._config.yAxis.minZero
? 0
: d3.min(vm._lines, function(c) {
return d3.min(c.values, function(v) {
return v.y;
});
}),
d3.max(vm._lines, function(c) {
return d3.max(c.values, function(v) {
return v.y;
});
}),
];
config = {
column: vm._config.x,
type: vm._config.xAxis.scale,
range: [0, vm.chart.width],
minZero: false,
};
vm._scales.x = vm.utils.generateScale(vm._data, config);
config = {
column: vm._config.y,
type: vm._config.yAxis.scale,
range: [vm.chart.height, 0],
minZero: vm._config.yAxis.minZero,
};
vm._scales.y = vm.utils.generateScale(vm._data, config);
vm._scales.x.domain(vm._xMinMax);
vm._scales.y.domain(vm._yMinMax).nice();
if (vm._config.hasOwnProperty('colors'))
vm._scales.color = d3.scaleOrdinal(vm._config.colors);
else vm._scales.color = d3.scaleOrdinal(d3.schemeCategory10);
if (
vm._scales.x.domain()[0].getTime() == vm._scales.x.domain()[1].getTime()
) {
// max and min are the same, there's only one datum
var oldDomain = vm._scales.x.domain();
var oldRange = vm._scales.x.range();
vm._scales.x
.domain([
new Date(oldDomain[0].getTime() - 1),
oldDomain[0],
oldDomain[1],
])
.range([0, oldRange[0] + (oldRange[1] - oldRange[0]) / 2, oldRange[1]]);
}
return vm;
};
Timeline.drawLabels = function() {
var vm = this;
var chartW = vm.chart.width;
vm.chart
.svg()
.selectAll('.dots')
.each(function(dat) {
var el = this;
dat.values.forEach(function(c, index) {
d3.select(el)
.append('text')
.attr('class', 'dbox-label')
.attr('text-anchor', 'start')
.attr('transform', function(d) {
if (vm._scales.x(d.values[index].x) >= chartW) {
d3.select(this).attr('text-anchor', 'end');
return (
'translate (' +
(vm._scales.x(d.values[index].x) - 10) +
',' +
(vm._scales.y(d.values[index].y) + 4) +
')'
);
}
d3.select(this).attr('text-anchor', 'start');
return (
'translate (' +
(vm._scales.x(d.values[index].x) + 10) +
',' +
(vm._scales.y(d.values[index].y) + 4) +
')'
);
})
.text(function() {
return c.y ? vm.utils.format(vm._config.yAxis, true)(c.y) : '';
});
});
});
};
Timeline.draw = function() {
var vm = this;
//Call the tip
vm.chart.svg().call(vm._tip);
if (vm._scales.x.domain().length === 3) {
vm.chart
.svg()
.select('.x.axis .tick')
.remove();
}
var lines = vm.chart
.svg()
.selectAll('.lines')
.data(vm._lines)
.enter()
.append('g')
.attr('class', 'lines');
var path = vm.chart
.svg()
.selectAll('.lines')
.append('path')
.attr('class', 'line')
.attr('d', function(d) {
return vm._line(d.values);
})
.attr('stroke', function(d) {
return vm._scales.color !== false
? vm._scales.color(d.name)
: vm._getQuantileColor(d.name, 'default');
})
.attr('stroke-width', 4)
.attr('fill', 'none');
/**By default it draws dots on data points with 4px radius*/
var dots = vm.chart
.svg()
.selectAll('.dots')
.data(vm._lines)
.enter()
.append('g')
.attr('class', 'dots')
.selectAll('.circle')
.data(function(d) {
d.values.forEach(el => {
el.name = d.name;
});
return d.values;
})
.enter()
.append('circle')
.attr('class', 'dot')
.attr('cx', function(d, i) {
return vm._scales.x(d.x);
})
.attr('cy', function(d) {
return vm._scales.y(d.y);
})
.attr('r', 4)
.style('stroke', function(d) {
return vm._scales.color !== false
? vm._scales.color(d.name)
: vm._getQuantileColor(d.name, 'default');
})
.style('stroke-width', 2)
.style('fill', '#fff')
.style('fill-opacity', 0.5)
.on('mouseover', function(d, i) {
if (vm._config.mouseover) {
//vm._config.mouseover.call(vm, d, i);
}
vm._tip.show(d, d3.select(this).node());
})
.on('mouseout', function(d, i) {
if (vm._config.mouseout) {
//vm._config.mouseout.call(this, d, i);
}
vm._tip.hide(d, d3.select(this).node());
});
Timeline.drawLabels();
//var t = textures.lines().thicker();
//vm.chart.svg().call(t);
/* vm._area.y0(vm._scales.y(vm._yMinMax[0]));
var areas = vm.chart.svg().selectAll(".areas")
.data(vm._lines)
.enter().append("g")
.attr("class", "areas");
var pathArea = vm.chart.svg().selectAll(".areas").append("path")
.attr("class", "area")
.attr("d", function(d) {
return vm._area(d.values);
}) */
//.attr("fill", t.url());
return vm;
};
Timeline.init(config);
return Timeline;
}