UNPKG

light-chart

Version:

Charts for mobile visualization.

468 lines (420 loc) 12.8 kB
const Util = require('../util/common'); const List = require('../component/list'); const Global = require('../global'); const LEGEND_GAP = 12; const MARKER_SIZE = 3; const DEFAULT_CFG = { itemMarginBottom: 12, itemGap: 10, showTitle: false, titleStyle: { fontSize: 12, fill: '#808080', textAlign: 'start', textBaseline: 'top' }, nameStyle: { fill: '#808080', fontSize: 12, textAlign: 'start', textBaseline: 'middle' }, valueStyle: { fill: '#000000', fontSize: 12, textAlign: 'start', textBaseline: 'middle' }, unCheckStyle: { fill: '#bfbfbf' }, itemWidth: 'auto', wordSpace: 6, selectedMode: 'multiple' // 'multiple' or 'single' }; // Register the default configuration for Legend Global.legend = Util.deepMix({ common: DEFAULT_CFG, // common legend configuration right: Util.mix({ position: 'right', layout: 'vertical' }, DEFAULT_CFG), left: Util.mix({ position: 'left', layout: 'vertical' }, DEFAULT_CFG), top: Util.mix({ position: 'top', layout: 'horizontal' }, DEFAULT_CFG), bottom: Util.mix({ position: 'bottom', layout: 'horizontal' }, DEFAULT_CFG) }, Global.legend || {}); function getPaddingByPos(pos, appendPadding) { let padding = 0; appendPadding = Util.parsePadding(appendPadding); switch (pos) { case 'top': padding = appendPadding[0]; break; case 'right': padding = appendPadding[1]; break; case 'bottom': padding = appendPadding[2]; break; case 'left': padding = appendPadding[3]; break; default: break; } return padding; } class LegendController { constructor(cfg) { this.legendCfg = {}; this.enable = true; this.position = 'top'; Util.mix(this, cfg); const chart = this.chart; this.canvasDom = chart.get('canvas').get('el'); this.clear(); } addLegend(scale, items, filterVals) { const self = this; const legendCfg = self.legendCfg; const field = scale.field; const fieldCfg = legendCfg[field]; if (fieldCfg === false) { return null; } if (fieldCfg && fieldCfg.custom) { self.addCustomLegend(field); } else { let position = legendCfg.position || self.position; if (fieldCfg && fieldCfg.position) { position = fieldCfg.position; } if (scale.isCategory) { self._addCategoryLegend(scale, items, position, filterVals); } } } addCustomLegend(field) { const self = this; let legendCfg = self.legendCfg; if (field && legendCfg[field]) { legendCfg = legendCfg[field]; } const position = legendCfg.position || self.position; const legends = self.legends; legends[position] = legends[position] || []; const items = legendCfg.items; if (!items) { return null; } const container = self.container; Util.each(items, item => { if (!Util.isPlainObject(item.marker)) { item.marker = { symbol: item.marker || 'circle', fill: item.fill, radius: MARKER_SIZE }; } else { item.marker.radius = item.marker.radius || MARKER_SIZE; } item.checked = Util.isNil(item.checked) ? true : item.checked; item.name = item.name || item.value; }); const legend = new List(Util.deepMix({}, Global.legend[position], legendCfg, { maxLength: self._getMaxLength(position), items, parent: container })); legends[position].push(legend); } clear() { const legends = this.legends; Util.each(legends, legendItems => { Util.each(legendItems, legend => { legend.clear(); }); }); this.legends = {}; this.unBindEvents(); } _isFiltered(scale, values, value) { let rst = false; Util.each(values, val => { rst = rst || scale.getText(val) === scale.getText(value); if (rst) { return false; } }); return rst; } _getMaxLength(position) { const chart = this.chart; const appendPadding = Util.parsePadding(chart.get('appendPadding')); return (position === 'right' || position === 'left') ? chart.get('height') - (appendPadding[0] + appendPadding[2]) : chart.get('width') - (appendPadding[1] + appendPadding[3]); } _addCategoryLegend(scale, items, position, filterVals) { const self = this; const { legendCfg, legends, container, chart } = self; const field = scale.field; legends[position] = legends[position] || []; let symbol = 'circle'; if (legendCfg[field] && legendCfg[field].marker) { symbol = legendCfg[field].marker; } else if (legendCfg.marker) { symbol = legendCfg.marker; } Util.each(items, item => { if (Util.isPlainObject(symbol)) { Util.mix(item.marker, symbol); } else { item.marker.symbol = symbol; } if (filterVals) { item.checked = self._isFiltered(scale, filterVals, item.dataValue); } }); const legendItems = chart.get('legendItems'); legendItems[field] = items; const lastCfg = Util.deepMix({}, Global.legend[position], legendCfg[field] || legendCfg, { maxLength: self._getMaxLength(position), items, field, filterVals, parent: container }); if (lastCfg.showTitle) { Util.deepMix(lastCfg, { title: scale.alias || scale.field }); } const legend = new List(lastCfg); legends[position].push(legend); return legend; } _alignLegend(legend, pre, position) { const self = this; const { tl, bl } = self.plotRange; const chart = self.chart; let offsetX = legend.offsetX || 0; let offsetY = legend.offsetY || 0; const chartWidth = chart.get('width'); const chartHeight = chart.get('height'); const appendPadding = Util.parsePadding(chart.get('appendPadding')); const legendHeight = legend.getHeight(); const legendWidth = legend.getWidth(); let x = 0; let y = 0; if (position === 'left' || position === 'right') { const verticalAlign = legend.verticalAlign || 'middle'; const height = Math.abs(tl.y - bl.y); x = (position === 'left') ? appendPadding[3] : (chartWidth - legendWidth - appendPadding[1]); y = (height - legendHeight) / 2 + tl.y; if (verticalAlign === 'top') { y = tl.y; } else if (verticalAlign === 'bottom') { y = bl.y - legendHeight; } if (pre) { y = pre.get('y') - legendHeight - LEGEND_GAP; } } else { const align = legend.align || 'left'; x = appendPadding[3]; if (align === 'center') { x = chartWidth / 2 - legendWidth / 2; } else if (align === 'right') { x = chartWidth - (legendWidth + appendPadding[1]); } y = (position === 'top') ? appendPadding[0] + Math.abs(legend.container.getBBox().minY) : (chartHeight - legendHeight); if (pre) { const preWidth = pre.getWidth(); x = pre.x + preWidth + LEGEND_GAP; } } if (position === 'bottom' && offsetY > 0) { offsetY = 0; } if (position === 'right' && offsetX > 0) { offsetX = 0; } legend.moveTo(x + offsetX, y + offsetY); } alignLegends() { const self = this; const legends = self.legends; Util.each(legends, (legendItems, position) => { Util.each(legendItems, (legend, index) => { const pre = legendItems[index - 1]; self._alignLegend(legend, pre, position); }); }); return self; } handleEvent(ev) { const self = this; function findItem(x, y) { let result = null; const legends = self.legends; Util.each(legends, legendItems => { Util.each(legendItems, legend => { const { itemsGroup, legendHitBoxes } = legend; const children = itemsGroup.get('children'); if (children.length) { const legendPosX = legend.x; const legendPosY = legend.y; Util.each(legendHitBoxes, (box, index) => { if (x >= (box.x + legendPosX) && x <= (box.x + box.width + legendPosX) && y >= (box.y + legendPosY) && y <= (box.height + box.y + legendPosY)) { // inbox result = { clickedItem: children[index], clickedLegend: legend }; return false; } }); } }); }); return result; } const chart = self.chart; const { x, y } = Util.createEvent(ev, chart); const clicked = findItem(x, y); if (clicked && clicked.clickedLegend.clickable !== false) { const { clickedItem, clickedLegend } = clicked; if (clickedLegend.onClick) { ev.clickedItem = clickedItem; clickedLegend.onClick(ev); } else if (!clickedLegend.custom) { const checked = clickedItem.get('checked'); const value = clickedItem.get('dataValue'); const { filterVals, field, selectedMode } = clickedLegend; const isSingeSelected = selectedMode === 'single'; if (isSingeSelected) { chart.filter(field, val => { return val === value; }); } else { if (!checked) { filterVals.push(value); } else { Util.Array.remove(filterVals, value); } chart.filter(field, val => { return filterVals.indexOf(val) !== -1; }); } chart.repaint(); } } } bindEvents() { const legendCfg = this.legendCfg; const triggerOn = legendCfg.triggerOn || 'touchstart'; const method = Util.wrapBehavior(this, 'handleEvent'); Util.addEventListener(this.canvasDom, triggerOn, method); } unBindEvents() { const legendCfg = this.legendCfg; const triggerOn = legendCfg.triggerOn || 'touchstart'; const method = Util.getWrapBehavior(this, 'handleEvent'); Util.removeEventListener(this.canvasDom, triggerOn, method); } } module.exports = { init(chart) { const legendController = new LegendController({ container: chart.get('backPlot'), plotRange: chart.get('plotRange'), chart }); chart.set('legendController', legendController); chart.legend = function(field, cfg) { let legendCfg = legendController.legendCfg; legendController.enable = true; if (Util.isBoolean(field)) { legendController.enable = field; legendCfg = cfg || {}; } else if (Util.isObject(field)) { legendCfg = field; } else { legendCfg[field] = cfg; } legendController.legendCfg = legendCfg; return this; }; }, beforeGeomDraw(chart) { const legendController = chart.get('legendController'); if (!legendController.enable) return null; // legend is not displayed const legendCfg = legendController.legendCfg; if (legendCfg && legendCfg.custom) { legendController.addCustomLegend(); } else { const legendItems = chart.getLegendItems(); const scales = chart.get('scales'); const filters = chart.get('filters'); Util.each(legendItems, (items, field) => { const scale = scales[field]; const values = scale.values; let filterVals; if (filters && filters[field]) { filterVals = values.filter(filters[field]); } else { filterVals = values.slice(0); } legendController.addLegend(scale, items, filterVals); }); } if (legendCfg && legendCfg.clickable !== false) { legendController.bindEvents(); } const legends = legendController.legends; const legendRange = { top: 0, right: 0, bottom: 0, left: 0 }; Util.each(legends, (legendItems, position) => { let padding = 0; Util.each(legendItems, legend => { const width = legend.getWidth(); const height = legend.getHeight(); if (position === 'top' || position === 'bottom') { padding = Math.max(padding, height); if (legend.offsetY > 0) { padding += legend.offsetY; } } else { padding = Math.max(padding, width); if (legend.offsetX > 0) { padding += legend.offsetX; } } }); legendRange[position] = padding + getPaddingByPos(position, chart.get('appendPadding')); }); chart.set('legendRange', legendRange); }, afterGeomDraw(chart) { const legendController = chart.get('legendController'); legendController.alignLegends(); }, clearInner(chart) { const legendController = chart.get('legendController'); legendController.clear(); chart.set('legendRange', null); } };