function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } /** * @fileOverview G2 图表的入口文件 * @author dxq613@gmail.com */ var Util = require('../util'); var View = require('./view'); var G = require('../renderer'); var Canvas = G.Canvas; var DomUtil = Util.DomUtil; var Global = require('../global'); var Plot = require('../component/plot'); var Controller = require('./controller/index'); var mergeBBox = require('./util/merge-bbox'); var bboxOfBackPlot = require('./util/bbox-of-back-plot'); var plotRange2BBox = require('./util/plot-range2bbox'); var AUTO_STR = 'auto'; function _isScaleExist(scales, compareScale) { var flag = false; Util.each(scales, function (scale) { var scaleValues = [].concat(scale.values); var compareScaleValues = [].concat(compareScale.values); if (scale.type === compareScale.type && scale.field === compareScale.field && scaleValues.sort().toString() === compareScaleValues.sort().toString()) { flag = true; return; } }); return flag; } function isEqualArray(arr1, arr2) { return Util.isEqualWith(arr1, arr2, function (v1, v2) { return v1 === v2; }); } /** * 图表的入口 * @class Chart */ var Chart = /*#__PURE__*/function (_View) { _inheritsLoose(Chart, _View); function Chart() { return _View.apply(this, arguments) || this; } var _proto = Chart.prototype; /** * 获取默认的配置属性 * @protected * @return {Object} 默认属性 */ _proto.getDefaultCfg = function getDefaultCfg() { var viewCfg = _View.prototype.getDefaultCfg.call(this); return Util.mix(viewCfg, { id: null, forceFit: false, container: null, wrapperEl: null, canvas: null, width: 500, height: 500, pixelRatio: null, backPlot: null, frontPlot: null, plotBackground: null, padding: Global.plotCfg.padding, background: null, autoPaddingAppend: 5, limitInPlot: false, renderer: Global.renderer, // renderer: 'svg', views: [] }); }; _proto.init = function init() { var self = this; var viewTheme = self.get('viewTheme'); self._initCanvas(); self._initPlot(); self._initEvents(); _View.prototype.init.call(this); var tooltipController = new Controller.Tooltip({ chart: self, viewTheme: viewTheme, options: {} }); self.set('tooltipController', tooltipController); var legendController = new Controller.Legend({ chart: self, viewTheme: viewTheme }); self.set('legendController', legendController); self.set('_id', 'chart'); // 防止同用户设定的 id 同名 self.emit('afterinit'); // 初始化完毕 }; _proto._isAutoPadding = function _isAutoPadding() { var padding = this.get('padding'); if (Util.isArray(padding)) { return padding.includes(AUTO_STR); } return padding === AUTO_STR; }; _proto._getAutoPadding = function _getAutoPadding() { var padding = this.get('padding'); // 图例在最前面的一层 var frontPlot = this.get('frontPlot'); var frontBBox = frontPlot.getBBox(); // 坐标轴在最后面的一层 var backPlot = this.get('backPlot'); // 这段代码临时处理了title 过长的情况,但是是非常不好的代码 var backBBox = bboxOfBackPlot(backPlot, plotRange2BBox(this.get('plotRange'))); var box = mergeBBox(frontBBox, backBBox); var outter = [0 - box.minY, // 上面超出的部分 box.maxX - this.get('width'), // 右边超出的部分 box.maxY - this.get('height'), // 下边超出的部分 0 - box.minX]; // 如果原始的 padding 内部存在 'auto' 则替换对应的边 var autoPadding = Util.toAllPadding(padding); for (var i = 0; i < autoPadding.length; i++) { if (autoPadding[i] === AUTO_STR) { var tmp = Math.max(0, outter[i]); autoPadding[i] = tmp + this.get('autoPaddingAppend'); } } return autoPadding; } // 初始化画布 ; _proto._initCanvas = function _initCanvas() { var container = this.get('container'); var id = this.get('id'); // 如果未设置 container 使用 ID, 兼容 2.x 版本 if (!container && id) { container = id; this.set('container', id); } var width = this.get('width'); var height = this.get('height'); if (Util.isString(container)) { container = document.getElementById(container); if (!container) { throw new Error('Please specify the container for the chart!'); } this.set('container', container); } var wrapperEl = DomUtil.createDom('
'); container.appendChild(wrapperEl); this.set('wrapperEl', wrapperEl); if (this.get('forceFit')) { width = DomUtil.getWidth(container, width); this.set('width', width); } var renderer = this.get('renderer'); var canvas = new Canvas({ containerDOM: wrapperEl, width: width, height: height, // NOTICE: 有问题找青湳 pixelRatio: renderer === 'svg' ? 1 : this.get('pixelRatio'), renderer: renderer }); this.set('canvas', canvas); } // 初始化绘图区间 ; _proto._initPlot = function _initPlot() { var self = this; self._initPlotBack(); // 最底层的是背景相关的 group var canvas = self.get('canvas'); var backPlot = canvas.addGroup({ zIndex: 1 }); // 图表最后面的容器 var plotContainer = canvas.addGroup({ zIndex: 0 }); // 图表所在的容器 var frontPlot = canvas.addGroup({ zIndex: 3 }); // 图表前面的容器 self.set('backPlot', backPlot); self.set('middlePlot', plotContainer); self.set('frontPlot', frontPlot); } // 初始化背景 ; _proto._initPlotBack = function _initPlotBack() { var self = this; var canvas = self.get('canvas'); var viewTheme = self.get('viewTheme'); var plot = canvas.addGroup(Plot, { padding: this.get('padding'), plotBackground: Util.mix({}, viewTheme.plotBackground, self.get('plotBackground')), background: Util.mix({}, viewTheme.background, self.get('background')) }); self.set('plot', plot); self.set('plotRange', plot.get('plotRange')); }; _proto._initEvents = function _initEvents() { if (this.get('forceFit')) { window.addEventListener('resize', Util.wrapBehavior(this, '_initForceFitEvent')); } }; _proto._initForceFitEvent = function _initForceFitEvent() { var timer = setTimeout(Util.wrapBehavior(this, 'forceFit'), 200); clearTimeout(this.get('resizeTimer')); this.set('resizeTimer', timer); } // 绘制图例 ; _proto._renderLegends = function _renderLegends() { var options = this.get('options'); var legendOptions = options.legends; if (Util.isNil(legendOptions) || legendOptions !== false) { // 没有关闭图例 var legendController = this.get('legendController'); legendController.options = legendOptions || {}; legendController.plotRange = this.get('plotRange'); if (legendOptions && legendOptions.custom) { // 用户自定义图例 legendController.addCustomLegend(); } else { var geoms = this.getAllGeoms(); var scales = []; Util.each(geoms, function (geom) { var view = geom.get('view'); var attrs = geom.getAttrsForLegend(); Util.each(attrs, function (attr) { var type = attr.type; var scale = attr.getScale(type); if (scale.field && scale.type !== 'identity' && !_isScaleExist(scales, scale)) { scales.push(scale); var filteredValues = view.getFilteredOutValues(scale.field); legendController.addLegend(scale, attr, geom, filteredValues); } }); }); // 双轴的情况 var yScales = this.getYScales(); if (scales.length === 0 && yScales.length > 1) { legendController.addMixedLegend(yScales, geoms); } } legendController.alignLegends(); } } // 绘制 tooltip ; _proto._renderTooltips = function _renderTooltips() { var options = this.get('options'); if (Util.isNil(options.tooltip) || options.tooltip !== false) { // 用户没有关闭 tooltip var tooltipController = this.get('tooltipController'); tooltipController.options = options.tooltip || {}; tooltipController.renderTooltip(); } } /** * 获取所有的几何标记 * @return {Array} 所有的几何标记 */ ; _proto.getAllGeoms = function getAllGeoms() { var geoms = []; geoms = geoms.concat(this.get('geoms')); var views = this.get('views'); Util.each(views, function (view) { geoms = geoms.concat(view.get('geoms')); }); return geoms; } /** * 自适应宽度 * @chainable * @return {Chart} 图表对象 */ ; _proto.forceFit = function forceFit() { var self = this; if (!self || self.destroyed) { return; } var container = self.get('container'); var oldWidth = self.get('width'); var width = DomUtil.getWidth(container, oldWidth); if (width !== 0 && width !== oldWidth) { var height = self.get('height'); self.changeSize(width, height); } return self; }; _proto.resetPlot = function resetPlot() { var plot = this.get('plot'); var padding = this.get('padding'); if (!isEqualArray(padding, plot.get('padding'))) { // 重置 padding,仅当padding 发生更改 plot.set('padding', padding); plot.repaint(); } } /** * 改变大小 * @param {Number} width 图表宽度 * @param {Number} height 图表高度 * @return {Chart} 图表对象 */ ; _proto.changeSize = function changeSize(width, height) { var self = this; var canvas = self.get('canvas'); canvas.changeSize(width, height); var plot = this.get('plot'); self.set('width', width); self.set('height', height); // change size 时重新计算边框 plot.repaint(); // 保持边框不变,防止自动 padding 时绘制多遍 this.set('keepPadding', true); self.repaint(); this.set('keepPadding', false); this.emit('afterchangesize'); return self; } /** * 改变宽度 * @param {Number} width 图表宽度 * @return {Chart} 图表对象 */ ; _proto.changeWidth = function changeWidth(width) { return this.changeSize(width, this.get('height')); } /** * 改变宽度 * @param {Number} height 图表高度 * @return {Chart} 图表对象 */ ; _proto.changeHeight = function changeHeight(height) { return this.changeSize(this.get('width'), height); } /** * 创建一个视图 * @param {Object} cfg 视图的配置项 * @return {View} 视图对象 */ ; _proto.view = function view(cfg) { cfg = cfg || {}; cfg.theme = this.get('theme'); cfg.parent = this; cfg.backPlot = this.get('backPlot'); cfg.middlePlot = this.get('middlePlot'); cfg.frontPlot = this.get('frontPlot'); cfg.canvas = this.get('canvas'); if (Util.isNil(cfg.animate)) { cfg.animate = this.get('animate'); } cfg.options = Util.mix({}, this._getSharedOptions(), cfg.options); var view = new View(cfg); view.set('_id', 'view' + this.get('views').length); // 标识 ID,防止同用户设定的 id 重名 this.get('views').push(view); this.emit('addview', { view: view }); return view; } // isShapeInView() { // return true; // } ; _proto.removeView = function removeView(view) { var views = this.get('views'); Util.Array.remove(views, view); view.destroy(); }; _proto._getSharedOptions = function _getSharedOptions() { var options = this.get('options'); var sharedOptions = {}; Util.each(['scales', 'coord', 'axes'], function (name) { sharedOptions[name] = Util.cloneDeep(options[name]); }); return sharedOptions; } /** * @override * 当前chart 的范围 */ ; _proto.getViewRegion = function getViewRegion() { var plotRange = this.get('plotRange'); return { start: plotRange.bl, end: plotRange.tr }; } /** * 设置图例配置信息 * @param {String|Object} field 字段名 * @param {Object} [cfg] 图例的配置项 * @return {Chart} 当前的图表对象 */ ; _proto.legend = function legend(field, cfg) { var options = this.get('options'); if (!options.legends) { options.legends = {}; } var legends = {}; if (field === false) { options.legends = false; } else if (Util.isObject(field)) { legends = field; } else if (Util.isString(field)) { legends[field] = cfg; } else { legends = cfg; } Util.mix(options.legends, legends); return this; } /** * 设置提示信息 * @param {String|Object} visible 是否可见 * @param {Object} [cfg] 提示信息的配置项 * @return {Chart} 当前的图表对象 */ ; _proto.tooltip = function tooltip(visible, cfg) { var options = this.get('options'); if (!options.tooltip) { options.tooltip = {}; } if (visible === false) { options.tooltip = false; } else if (Util.isObject(visible)) { Util.mix(options.tooltip, visible); } else { Util.mix(options.tooltip, cfg); } return this; } /** * 清空图表 * @return {Chart} 当前的图表对象 */ ; _proto.clear = function clear() { this.emit('beforeclear'); var views = this.get('views'); while (views.length > 0) { var view = views.shift(); view.destroy(); } _View.prototype.clear.call(this); var canvas = this.get('canvas'); this.resetPlot(); canvas.draw(); this.emit('afterclear'); return this; }; _proto.clearInner = function clearInner() { var views = this.get('views'); Util.each(views, function (view) { view.clearInner(); }); var tooltipController = this.get('tooltipController'); tooltipController && tooltipController.clear(); if (!this.get('keepLegend')) { var legendController = this.get('legendController'); legendController && legendController.clear(); } _View.prototype.clearInner.call(this); } // chart 除了view 上绘制的组件外,还会绘制图例和 tooltip ; _proto.drawComponents = function drawComponents() { _View.prototype.drawComponents.call(this); // 一般是点击图例时,仅仅隐藏某些选项,而不销毁图例 if (!this.get('keepLegend')) { this._renderLegends(); // 渲染图例 } } /** * 绘制图表 * @override */ ; _proto.render = function render() { var self = this; // 需要自动计算边框,则重新设置 if (!self.get('keepPadding') && self._isAutoPadding()) { self.beforeRender(); // 初始化各个 view 和 绘制 self.drawComponents(); var autoPadding = self._getAutoPadding(); var plot = self.get('plot'); // 在计算出来的边框不一致的情况,重新改变边框 if (!isEqualArray(plot.get('padding'), autoPadding)) { plot.set('padding', autoPadding); plot.repaint(); } } var middlePlot = self.get('middlePlot'); if (self.get('limitInPlot') && !middlePlot.attr('clip')) { var clip = Util.getClipByRange(self.get('plotRange')); // TODO Polar coord middlePlot.attr('clip', clip); // clip.attr('fill', 'grey'); // clip.attr('opacity', 0.5); // middlePlot.add(clip); } _View.prototype.render.call(this); self._renderTooltips(); // 渲染 tooltip }; _proto.repaint = function repaint() { // 重绘时需要判定当前的 padding 是否发生过改变,如果发生过改变进行调整 // 需要判定是否使用了自动 padding if (!this.get('keepPadding')) { this.resetPlot(); } _View.prototype.repaint.call(this); } /** * @override * 显示或者隐藏 */ ; _proto.changeVisible = function changeVisible(visible) { var wrapperEl = this.get('wrapperEl'); var visibleStr = visible ? '' : 'none'; wrapperEl.style.display = visibleStr; } /** * 返回图表的 dataUrl 用于生成图片 * @return {String} dataUrl 路径 */ ; _proto.toDataURL = function toDataURL() { var chart = this; var canvas = chart.get('canvas'); var renderer = chart.get('renderer'); var canvasDom = canvas.get('el'); var dataURL = ''; if (renderer === 'svg') { var clone = canvasDom.cloneNode(true); var svgDocType = document.implementation.createDocumentType('svg', '-//W3C//DTD SVG 1.1//EN', 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'); var svgDoc = document.implementation.createDocument('http://www.w3.org/2000/svg', 'svg', svgDocType); svgDoc.replaceChild(clone, svgDoc.documentElement); var svgData = new XMLSerializer().serializeToString(svgDoc); dataURL = 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(svgData); } else if (renderer === 'canvas') { dataURL = canvasDom.toDataURL('image/png'); } return dataURL; } /** * 图表导出功能 * @param {String} [name] 图片的名称,默认为 chart(.png|.svg) */ ; _proto.downloadImage = function downloadImage(name) { var chart = this; var link = document.createElement('a'); var renderer = chart.get('renderer'); var filename = (name || 'chart') + (renderer === 'svg' ? '.svg' : '.png'); var canvas = chart.get('canvas'); canvas.get('timeline').stopAllAnimations(); setTimeout(function () { var dataURL = chart.toDataURL(); if (window.Blob && window.URL && renderer !== 'svg') { var arr = dataURL.split(','); var mime = arr[0].match(/:(.*?);/)[1]; var bstr = atob(arr[1]); var n = bstr.length; var u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } var blobObj = new Blob([u8arr], { type: mime }); if (window.navigator.msSaveBlob) { window.navigator.msSaveBlob(blobObj, filename); } else { link.addEventListener('click', function () { link.download = filename; link.href = window.URL.createObjectURL(blobObj); }); } } else { link.addEventListener('click', function () { link.download = filename; link.href = dataURL; }); } var e = document.createEvent('MouseEvents'); e.initEvent('click', false, false); link.dispatchEvent(e); }, 16); } /** * 根据坐标点显示对应的 tooltip * @param {Object} point 画布上的点 * @return {Chart} 返回 chart 实例 */ ; _proto.showTooltip = function showTooltip(point) { var views = this.getViewsByPoint(point); if (views.length) { var tooltipController = this.get('tooltipController'); tooltipController.showTooltip(point, views); } return this; } /** * 将tooltip 锁定到当前位置不能移动 * @return {Chart} 返回 chart 实例 */ ; _proto.lockTooltip = function lockTooltip() { var tooltipController = this.get('tooltipController'); tooltipController.lockTooltip(); return this; } /** * 将tooltip 锁定解除 * @return {Chart} 返回 chart 实例 */ ; _proto.unlockTooltip = function unlockTooltip() { var tooltipController = this.get('tooltipController'); tooltipController.unlockTooltip(); return this; } /** * 隐藏 tooltip * @return {Chart} 返回 chart 实例 */ ; _proto.hideTooltip = function hideTooltip() { var tooltipController = this.get('tooltipController'); tooltipController.hideTooltip(); return this; } /** * 根据传入的画布坐标,获取该处的 tooltip 上的记录信息 * @param {Object} point 画布坐标点 * @return {Array} 返回结果 */ ; _proto.getTooltipItems = function getTooltipItems(point) { var self = this; var views = self.getViewsByPoint(point); var rst = []; Util.each(views, function (view) { var geoms = view.get('geoms'); Util.each(geoms, function (geom) { var dataArray = geom.get('dataArray'); var items = []; Util.each(dataArray, function (data) { var tmpPoint = geom.findPoint(point, data); if (tmpPoint) { var subItems = geom.getTipItems(tmpPoint); items = items.concat(subItems); } }); rst = rst.concat(items); }); }); return rst; } /** * @override * 销毁图表 */ ; _proto.destroy = function destroy() { this.emit('beforedestroy'); clearTimeout(this.get('resizeTimer')); var canvas = this.get('canvas'); var wrapperEl = this.get('wrapperEl'); wrapperEl.parentNode.removeChild(wrapperEl); _View.prototype.destroy.call(this); canvas.destroy(); window.removeEventListener('resize', Util.getWrapBehavior(this, '_initForceFitEvent')); this.emit('afterdestroy'); }; return Chart; }(View); module.exports = Chart;