UNPKG

framework7-vue

Version:

Build full featured iOS & Android apps using Framework7 & Vue

359 lines 12.8 kB
import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, resolveDynamicComponent as _resolveDynamicComponent, createBlock as _createBlock, normalizeClass as _normalizeClass, toDisplayString as _toDisplayString, createCommentVNode as _createCommentVNode, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, withCtx as _withCtx, renderSlot as _renderSlot } from "vue"; const _hoisted_1 = ["width", "height", "viewBox"]; const _hoisted_2 = ["data-index", "x1", "x2", "y2"]; const _hoisted_3 = { key: 0, class: "area-chart-axis" }; const _hoisted_4 = { key: 0 }; const _hoisted_5 = { key: 1, class: "area-chart-legend" }; function render(_ctx, _cache) { return _openBlock(), _createElementBlock("div", { ref: "elRef", class: _normalizeClass(_ctx.classes) }, [(_openBlock(), _createElementBlock("svg", { ref: "svgElRef", xmlns: "http://www.w3.org/2000/svg", width: _ctx.width, height: _ctx.height, viewBox: `0 0 ${_ctx.width} ${_ctx.height}`, preserveAspectRatio: "none" }, [(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.chartData, (data, index) => { return _openBlock(), _createBlock(_resolveDynamicComponent(_ctx.ChartTag), { key: `${_ctx.ChartTag}-${index}`, fill: _ctx.lineChart ? undefined : data.color, stroke: _ctx.lineChart ? data.color : undefined, "fill-rule": "evenodd", points: _ctx.lineChart ? undefined : data.points, d: _ctx.lineChart ? data.points : undefined }, null, 8, ["fill", "stroke", "points", "d"]); }), 128)), (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.verticalLines, (line, index) => { return _openBlock(), _createElementBlock("line", { key: `line-${index}`, "data-index": index, fill: "#000", x1: line, y1: 0, x2: line, y2: _ctx.height, class: _normalizeClass(_ctx.classNames({ 'area-chart-current-line': _ctx.currentIndex === index })) }, null, 10, _hoisted_2); }), 128))], 8, _hoisted_1)), _ctx.axis ? (_openBlock(), _createElementBlock("div", _hoisted_3, [(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.axisLabels, (label, index) => { return _openBlock(), _createElementBlock("span", { key: index }, [_ctx.visibleLegends.includes(label) ? (_openBlock(), _createElementBlock("span", _hoisted_4, _toDisplayString(_ctx.formatAxisLabelMethod(label)), 1)) : _createCommentVNode("", true)]); }), 128))])) : _createCommentVNode("", true), _ctx.legend ? (_openBlock(), _createElementBlock("div", _hoisted_5, [(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.datasets, (dataset, index) => { return _openBlock(), _createBlock(_resolveDynamicComponent(_ctx.LegendItemTag), { key: index, class: _normalizeClass(_ctx.classNames('area-chart-legend-item', { 'area-chart-legend-item-hidden': _ctx.hiddenDatasets.includes(index), 'area-chart-legend-button': _ctx.toggleDatasets })), type: _ctx.toggleDatasets ? 'button' : undefined, onClick: $event => _ctx.toggleDataset(index) }, { default: _withCtx(() => [_createElementVNode("span", { style: _normalizeStyle({ backgroundColor: dataset.color }) }, null, 4), _createTextVNode(" " + _toDisplayString(_ctx.formatLegendLabelMethod(dataset.label)), 1)]), _: 2 }, 1032, ["class", "type", "onClick"]); }), 128))])) : _createCommentVNode("", true), _renderSlot(_ctx.$slots, "default")], 2); } import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { classNames } from '../shared/utils.js'; import { f7 } from '../shared/f7.js'; export default { name: 'f7-area-chart', render, props: { lineChart: Boolean, datasets: { type: Array, default: () => [] }, axis: Boolean, axisLabels: { type: Array, default: () => [] }, tooltip: Boolean, legend: Boolean, toggleDatasets: Boolean, width: { type: Number, default: 640 }, height: { type: Number, default: 320 }, maxAxisLabels: { type: Number, default: 8 }, formatAxisLabel: Function, formatLegendLabel: Function, formatTooltip: Function, formatTooltipAxisLabel: Function, formatTooltipTotal: Function, formatTooltipDataset: Function }, emits: ['select'], setup(props, { emit }) { let f7Tooltip = null; const currentIndex = ref(null); const hiddenDatasets = ref([]); const elRef = ref(null); const svgElRef = ref(null); const linesOffsets = ref(null); const visibleLegends = computed(() => { if (!props.maxAxisLabels || props.axisLabels.length <= props.maxAxisLabels) return props.axisLabels; const skipStep = Math.ceil(props.axisLabels.length / props.maxAxisLabels); const filtered = props.axisLabels.filter((label, index) => index % skipStep === 0); return filtered; }); const summValues = computed(() => { const summ = []; props.datasets.filter((dataset, index) => !hiddenDatasets.value.includes(index)).forEach(({ values }) => { values.forEach((value, valueIndex) => { if (!summ[valueIndex]) summ[valueIndex] = 0; summ[valueIndex] += value; }); }); return summ; }); const chartData = computed(() => { const { datasets, lineChart, width, height } = props; const data = []; if (!datasets.length) { return data; } const lastValues = datasets[0].values.map(() => 0); let maxValue = 0; if (lineChart) { datasets.forEach(({ values }) => { const datasetMaxValue = Math.max(...values); if (datasetMaxValue > maxValue) maxValue = datasetMaxValue; }); } else { maxValue = Math.max(...summValues.value); } datasets.filter((dataset, index) => !hiddenDatasets.value.includes(index)).forEach(({ label, values, color }) => { 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(); }); const verticalLines = computed(() => { const lines = []; if (!props.datasets.length) { return lines; } const values = props.datasets[0].values; values.forEach((value, valueIndex) => { const x = valueIndex / (values.length - 1) * props.width; lines.push(x); }); return lines; }); const toggleDataset = index => { if (!props.toggleDatasets) return; if (hiddenDatasets.value.includes(index)) { hiddenDatasets.value.splice(hiddenDatasets.value.indexOf(index), 1); } else { hiddenDatasets.value.push(index); } hiddenDatasets.value = [...hiddenDatasets.value]; }; const formatAxisLabelMethod = label => { if (props.formatAxisLabel) return props.formatAxisLabel(label); return label; }; const formatLegendLabelMethod = label => { if (props.formatLegendLabel) return props.formatLegendLabel(label); return label; }; const calcLinesOffsets = () => { const lines = svgElRef.value.querySelectorAll('line'); linesOffsets.value = []; for (let i = 0; i < lines.length; i += 1) { linesOffsets.value.push(lines[i].getBoundingClientRect().left); } }; const formatTooltip = () => { const index = currentIndex.value; if (index === null) return ''; let total = 0; const currentValues = props.datasets.filter((dataset, i) => !hiddenDatasets.value.includes(i)).map(dataset => ({ color: dataset.color, label: dataset.label, value: dataset.values[index] })); currentValues.forEach(dataset => { total += dataset.value; }); if (props.formatTooltip) { return props.formatTooltip({ index, total, datasets: currentValues }); } let labelText = props.formatTooltipAxisLabel ? props.formatTooltipAxisLabel(props.axisLabels[index]) : formatAxisLabelMethod(props.axisLabels[index]); if (!labelText) labelText = ''; const totalText = props.formatTooltipTotal ? props.formatTooltipTotal(total) : total; // prettier-ignore const datasetsText = currentValues.length > 0 ? ` <ul class="area-chart-tooltip-list"> ${currentValues.map(({ label, color, value }) => { const valueText = props.formatTooltipDataset ? props.formatTooltipDataset(label, value, color) : `${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} `; }; const setTooltip = () => { const { tooltip, datasets } = props; const index = currentIndex.value; if (!tooltip) return; const hasVisibleDataSets = datasets.filter((dataset, i) => !hiddenDatasets.value.includes(i)).length > 0; if (!hasVisibleDataSets) { if (f7Tooltip && f7Tooltip.hide) f7Tooltip.hide(); return; } if (index !== null && !f7Tooltip) { f7Tooltip = f7.tooltip.create({ trigger: 'manual', containerEl: elRef.value, targetEl: svgElRef.value.querySelector(`line[data-index="${index}"]`), text: formatTooltip(), cssClass: 'area-chart-tooltip' }); if (f7Tooltip && f7Tooltip.show) { f7Tooltip.show(); } return; } if (!f7Tooltip || !f7Tooltip.hide || !f7Tooltip.show) { return; } if (index !== null) { f7Tooltip.setText(formatTooltip()); f7Tooltip.setTargetEl(svgElRef.value.querySelector(`line[data-index="${index}"]`)); f7Tooltip.show(); } else { f7Tooltip.hide(); } }; const onMouseEnter = () => { calcLinesOffsets(); }; const onMouseMove = e => { if (!linesOffsets.value) { calcLinesOffsets(); } let currentLeft = e.pageX; if (typeof currentLeft === 'undefined') currentLeft = 0; const distances = linesOffsets.value.map(left => Math.abs(currentLeft - left)); const minDistance = Math.min(...distances); const closestIndex = distances.indexOf(minDistance); currentIndex.value = closestIndex; }; const onMouseLeave = () => { currentIndex.value = null; }; watch(() => currentIndex.value, () => { emit('select', currentIndex.value); setTooltip(); }); onMounted(() => { if (!svgElRef.value) return; svgElRef.value.addEventListener('mouseenter', onMouseEnter); svgElRef.value.addEventListener('mousemove', onMouseMove); svgElRef.value.addEventListener('mouseleave', onMouseLeave); }); onBeforeUnmount(() => { if (f7Tooltip && f7Tooltip.destroy) { f7Tooltip.destroy(); } f7Tooltip = null; if (!svgElRef.value) return; svgElRef.value.removeEventListener('mouseenter', onMouseEnter); svgElRef.value.removeEventListener('mousemove', onMouseMove); svgElRef.value.removeEventListener('mouseleave', onMouseLeave); }); const classes = computed(() => classNames('area-chart')); const LegendItemTag = computed(() => props.toggleDatasets ? 'button' : 'span'); const ChartTag = computed(() => props.lineChart ? 'path' : 'polygon'); return { currentIndex, hiddenDatasets, visibleLegends, chartData, verticalLines, elRef, svgElRef, classes, toggleDataset, formatAxisLabelMethod, formatLegendLabelMethod, LegendItemTag, ChartTag, classNames }; } };