framework7
Version:
Full featured mobile HTML framework for building iOS & Android apps
467 lines (465 loc) • 13.7 kB
JavaScript
import $ from '../../shared/dom7.js';
import { extend, deleteProps } from '../../shared/utils.js';
import Framework7Class from '../../shared/class.js';
/** @jsx $jsx */
import $jsx from '../../shared/$jsx.js';
class AreaChart extends Framework7Class {
constructor(app, params) {
if (params === void 0) {
params = {};
}
super(params, [app]);
const self = this;
const defaults = extend({}, app.params.areaChart);
// Extend defaults with modules params
self.useModulesParams(defaults);
self.params = extend(defaults, params);
const {
el
} = self.params;
if (!el) return self;
const $el = $(el);
if ($el.length === 0) return self;
if ($el[0].f7AreaChart) return $el[0].f7AreaChart;
extend(self, {
app,
$el,
el: $el && $el[0],
currentIndex: null,
hiddenDatasets: [],
f7Tooltip: null,
linesOffsets: null
});
$el[0].f7AreaChart = self;
// Install Modules
self.useModules();
self.onMouseEnter = self.onMouseEnter.bind(self);
self.onMouseMove = self.onMouseMove.bind(self);
self.onMouseLeave = self.onMouseLeave.bind(self);
self.onLegendClick = self.onLegendClick.bind(self);
self.init();
return self;
}
getVisibleLabels() {
const {
maxAxisLabels,
axisLabels
} = this.params;
if (!maxAxisLabels || axisLabels.length <= maxAxisLabels) return axisLabels;
const skipStep = Math.ceil(axisLabels.length / maxAxisLabels);
const filtered = axisLabels.filter((label, index) => index % skipStep === 0);
return filtered;
}
getSummValues() {
const {
datasets
} = this.params;
const {
hiddenDatasets
} = this;
const summValues = [];
datasets.filter((dataset, index) => !hiddenDatasets.includes(index)).forEach(_ref => {
let {
values
} = _ref;
values.forEach((value, valueIndex) => {
if (!summValues[valueIndex]) summValues[valueIndex] = 0;
summValues[valueIndex] += value;
});
});
return summValues;
}
getChartData() {
const {
datasets,
lineChart,
width,
height
} = this.params;
const {
hiddenDatasets
} = this;
const data = [];
if (!datasets.length) {
return data;
}
const lastValues = datasets[0].values.map(() => 0);
let maxValue = 0;
if (lineChart) {
datasets.filter((dataset, index) => !hiddenDatasets.includes(index)).forEach(_ref2 => {
let {
values
} = _ref2;
const datasetMaxValue = Math.max(...values);
if (datasetMaxValue > maxValue) maxValue = datasetMaxValue;
});
} else {
maxValue = Math.max(...this.getSummValues());
}
datasets.filter((dataset, index) => !hiddenDatasets.includes(index)).forEach(_ref3 => {
let {
label,
values,
color
} = _ref3;
const points = values.map((originalValue, valueIndex) => {
lastValues[valueIndex] += originalValue;
const value = lineChart ? originalValue : lastValues[valueIndex];
const x = valueIndex / (values.length - 1) * width;
const y = height - value / maxValue * height;
if (lineChart) {
return `${valueIndex === 0 ? 'M' : 'L'}${x},${y}`;
}
return `${x} ${y}`;
});
if (!lineChart) {
points.push(`${width} ${height} 0 ${height}`);
}
data.push({
label,
points: points.join(' '),
color
});
});
return data.reverse();
}
getVerticalLines() {
const {
datasets,
width
} = this.params;
const lines = [];
if (!datasets.length) {
return lines;
}
const values = datasets[0].values;
values.forEach((value, valueIndex) => {
const x = valueIndex / (values.length - 1) * width;
lines.push(x);
});
return lines;
}
toggleDataset(index) {
const {
hiddenDatasets,
params: {
toggleDatasets
}
} = this;
if (!toggleDatasets) return;
if (hiddenDatasets.includes(index)) {
hiddenDatasets.splice(hiddenDatasets.indexOf(index), 1);
} else {
hiddenDatasets.push(index);
}
if (this.$legendEl) {
this.$legendEl.find('.area-chart-legend-item').removeClass('area-chart-legend-item-hidden');
hiddenDatasets.forEach(i => {
this.$legendEl.find(`.area-chart-legend-item[data-index="${i}"]`).addClass('area-chart-legend-item-hidden');
});
}
this.update({}, true);
}
formatAxisLabel(label) {
const {
formatAxisLabel
} = this.params;
if (formatAxisLabel) return formatAxisLabel.call(this, label);
return label;
}
formatLegendLabel(label) {
const {
formatLegendLabel
} = this.params;
if (formatLegendLabel) return formatLegendLabel.call(this, label);
return label;
}
calcLinesOffsets() {
const lines = this.svgEl.querySelectorAll('line');
this.linesOffsets = [];
for (let i = 0; i < lines.length; i += 1) {
this.linesOffsets.push(lines[i].getBoundingClientRect().left);
}
}
formatTooltip() {
const self = this;
const {
currentIndex,
hiddenDatasets,
params: {
datasets,
axisLabels,
formatTooltip,
formatTooltipTotal,
formatTooltipAxisLabel,
formatTooltipDataset
}
} = self;
if (currentIndex === null) return '';
let total = 0;
const currentValues = datasets.filter((dataset, index) => !hiddenDatasets.includes(index)).map(dataset => ({
color: dataset.color,
label: dataset.label,
value: dataset.values[currentIndex]
}));
currentValues.forEach(dataset => {
total += dataset.value;
});
if (formatTooltip) {
return formatTooltip({
index: currentIndex,
total,
datasets: currentValues
});
}
let labelText = formatTooltipAxisLabel ? formatTooltipAxisLabel.call(self, axisLabels[currentIndex]) : this.formatAxisLabel(axisLabels[currentIndex]);
if (!labelText) labelText = '';
const totalText = formatTooltipTotal ? formatTooltipTotal.call(self, total) : total;
// prettier-ignore
const datasetsText = currentValues.length > 0 ? `
<ul class="area-chart-tooltip-list">
${currentValues.map(_ref4 => {
let {
label,
color,
value
} = _ref4;
const valueText = formatTooltipDataset ? formatTooltipDataset.call(self, label, value, color) : `${label ? `${label}: ` : ''}${value}`;
return `
<li><span style="background-color: ${color};"></span>${valueText}</li>
`;
}).join('')}
</ul>` : '';
// prettier-ignore
return `
<div class="area-chart-tooltip-label">${labelText}</div>
<div class="area-chart-tooltip-total">${totalText}</div>
${datasetsText}
`;
}
setTooltip() {
const self = this;
const {
app,
el,
svgEl,
hiddenDatasets,
currentIndex,
params: {
tooltip,
datasets
}
} = self;
if (!tooltip) return;
const hasVisibleDataSets = datasets.filter((dataset, index) => !hiddenDatasets.includes(index)).length > 0;
if (!hasVisibleDataSets) {
if (self.f7Tooltip && self.f7Tooltip.hide) self.f7Tooltip.hide();
return;
}
if (currentIndex !== null && !self.f7Tooltip) {
self.f7Tooltip = app.tooltip.create({
trigger: 'manual',
containerEl: el,
targetEl: svgEl.querySelector(`line[data-index="${currentIndex}"]`),
text: self.formatTooltip(),
cssClass: 'area-chart-tooltip'
});
if (self.f7Tooltip && self.f7Tooltip.show) {
self.f7Tooltip.show();
}
return;
}
if (!self.f7Tooltip || !self.f7Tooltip.hide || !self.f7Tooltip.show) {
return;
}
if (currentIndex !== null) {
self.f7Tooltip.setText(self.formatTooltip());
self.f7Tooltip.setTargetEl(svgEl.querySelector(`line[data-index="${currentIndex}"]`));
self.f7Tooltip.show();
} else {
self.f7Tooltip.hide();
}
}
setCurrentIndex(index) {
if (index === this.currentIndex) return;
this.currentIndex = index;
this.$el.trigger('areachart:select', {
index
});
this.emit('local::select areaChartSelect', this, index);
this.$svgEl.find('line').removeClass('area-chart-current-line');
this.$svgEl.find(`line[data-index="${index}"]`).addClass('area-chart-current-line');
this.setTooltip();
}
onLegendClick(e) {
const index = parseInt($(e.target).closest('.area-chart-legend-item').attr('data-index'), 10);
this.toggleDataset(index);
}
onMouseEnter() {
this.calcLinesOffsets();
}
onMouseMove(e) {
const self = this;
if (!self.linesOffsets) {
self.calcLinesOffsets();
}
let currentLeft = e.pageX;
if (typeof currentLeft === 'undefined') currentLeft = 0;
const distances = self.linesOffsets.map(left => Math.abs(currentLeft - left));
const minDistance = Math.min(...distances);
const closestIndex = distances.indexOf(minDistance);
self.setCurrentIndex(closestIndex);
}
onMouseLeave() {
this.setCurrentIndex(null);
}
attachEvents() {
const {
svgEl,
$el
} = this;
if (!svgEl) return;
svgEl.addEventListener('mouseenter', this.onMouseEnter);
svgEl.addEventListener('mousemove', this.onMouseMove);
svgEl.addEventListener('mouseleave', this.onMouseLeave);
$el.on('click', '.area-chart-legend-item', this.onLegendClick);
}
detachEvents() {
const {
svgEl,
$el
} = this;
if (!svgEl) return;
svgEl.removeEventListener('mouseenter', this.onMouseEnter);
svgEl.removeEventListener('mousemove', this.onMouseMove);
svgEl.removeEventListener('mouseleave', this.onMouseLeave);
$el.off('click', '.area-chart-legend-item', this.onLegendClick);
}
render() {
const self = this;
const {
lineChart,
toggleDatasets,
width,
height,
axis,
axisLabels,
legend,
datasets
} = self.params;
const chartData = self.getChartData();
const verticalLines = self.getVerticalLines();
const visibleLegends = self.getVisibleLabels();
const LegendItemTag = toggleDatasets ? 'button' : 'span';
return $jsx("div", null, $jsx("svg", {
xmlns: "http://www.w3.org/2000/svg",
width: width,
height: height,
viewBox: `0 0 ${width} ${height}`,
preserveAspectRatio: "none"
}, chartData.map(data => lineChart ? $jsx("path", {
stroke: data.color,
"fill-rule": "evenodd",
d: data.points
}) : $jsx("polygon", {
fill: data.color,
"fill-rule": "evenodd",
points: data.points
})), verticalLines.map((line, index) => $jsx("line", {
"data-index": index,
fill: "#000",
x1: line,
y1: 0,
x2: line,
y2: height
}))), axis && $jsx("div", {
class: "area-chart-axis"
}, axisLabels.map(label => $jsx("span", null, visibleLegends.includes(label) && $jsx("span", null, self.formatAxisLabel(label))))), legend && $jsx("div", {
class: "area-chart-legend"
}, datasets.map((dataset, index) => $jsx(LegendItemTag, {
"data-index": index,
class: `area-chart-legend-item ${toggleDatasets ? 'area-chart-legend-button' : ''}`,
_type: toggleDatasets ? 'button' : undefined
}, $jsx("span", {
style: `background-color: ${dataset.color}`
}), self.formatLegendLabel(dataset.label)))));
}
update(newParams, onlySvg) {
if (newParams === void 0) {
newParams = {};
}
if (onlySvg === void 0) {
onlySvg = false;
}
const self = this;
const {
params
} = self;
Object.keys(newParams).forEach(param => {
if (typeof newParams[param] !== 'undefined') {
params[param] = newParams[param];
}
});
if (self.$svgEl.length === 0) return self;
self.detachEvents();
self.$svgEl.remove();
if (!onlySvg) {
self.$axisEl.remove();
self.$legendEl.remove();
}
const $rendered = $(self.render());
const $svgEl = $rendered.find('svg');
extend(self, {
svgEl: $svgEl && $svgEl[0],
$svgEl
});
if (!onlySvg) {
const $axisEl = $rendered.find('.area-chart-axis');
const $legendEl = $rendered.find('.area-chart-legend');
extend(self, {
$axisEl,
$legendEl
});
self.$el.append($axisEl);
self.$el.append($legendEl);
}
self.$el.prepend($svgEl);
self.attachEvents();
return self;
}
init() {
const self = this;
const $rendered = $(self.render());
const $svgEl = $rendered.find('svg');
const $axisEl = $rendered.find('.area-chart-axis');
const $legendEl = $rendered.find('.area-chart-legend');
extend(self, {
svgEl: $svgEl && $svgEl[0],
$svgEl,
$axisEl,
$legendEl
});
self.$el.append($svgEl);
self.$el.append($axisEl);
self.$el.append($legendEl);
self.attachEvents();
return self;
}
destroy() {
const self = this;
if (!self.$el || self.destroyed) return;
self.$el.trigger('piechart:beforedestroy');
self.emit('local::beforeDestroy areaChartBeforeDestroy', self);
self.detachEvents();
self.$svgEl.remove();
self.$axisEl.remove();
self.$legendEl.remove();
if (self.f7Tooltip && self.f7Tooltip.destroy) {
self.f7Tooltip.destroy();
}
delete self.$el[0].f7AreaChart;
deleteProps(self);
self.destroyed = true;
}
}
export default AreaChart;