UNPKG

framework7

Version:

Full featured mobile HTML framework for building iOS & Android apps

467 lines (465 loc) 13.7 kB
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;