diff --git a/plugin-demo/README.md b/plugin-demo/README.md
index 905dc1c..273f47d 100644
--- a/plugin-demo/README.md
+++ b/plugin-demo/README.md
@@ -1,21 +1,12 @@
-# bi-plugin-starter
+# 箱线图插件示例
-FineBI插件模板
++ 编辑界面添加箱线图
-+ 目录结构(需要添加工程依赖,依赖指向BI中WEB-INF中的lib)
- - lib中放引用的第三方jar包
- - src 代码&资源
- - build.xml Ant编译脚本
- - plugin.xml 插件
-
-+ build.xml参数
- - line4: JDK路径
- - line8: 依赖的lib,从crm下bi的jar就行了
- - line17: value="插件名"
+![箱线图](./screenshots/boxplot.png)
+
++ 预览和编辑展示箱线图
+
++ 箱线图与其他图形的联动效果
+
+![联动效果](./screenshots/link.png)
-+ plugin.xml参数
- - id:插件id
- - name: 插件名
- - ventor: 插件作者
- - description:插件描述
- - AbstractWebResourceProvider&function-recorder的class:插件类
\ No newline at end of file
diff --git a/plugin-demo/plugin.xml b/plugin-demo/plugin.xml
index b6ab6a5..694cdee 100644
--- a/plugin-demo/plugin.xml
+++ b/plugin-demo/plugin.xml
@@ -1,17 +1,18 @@
- com.finebi.plugin.demo
+ com.finebi.plugin.boxplotchart
com.finebi
-
+
yes
1.0
- 8.0
+ 10.0
2019-02-29
imp
-
+
-
+
+
-
-
+
+
\ No newline at end of file
diff --git a/plugin-demo/screenshots/boxplot.png b/plugin-demo/screenshots/boxplot.png
new file mode 100644
index 0000000..2352559
Binary files /dev/null and b/plugin-demo/screenshots/boxplot.png differ
diff --git a/plugin-demo/screenshots/link.png b/plugin-demo/screenshots/link.png
new file mode 100644
index 0000000..b1f137e
Binary files /dev/null and b/plugin-demo/screenshots/link.png differ
diff --git a/plugin-demo/src/main/java/com/finebi/plugin/BIBoxPlotChartComponent.java b/plugin-demo/src/main/java/com/finebi/plugin/BIBoxPlotChartComponent.java
new file mode 100644
index 0000000..16cef2e
--- /dev/null
+++ b/plugin-demo/src/main/java/com/finebi/plugin/BIBoxPlotChartComponent.java
@@ -0,0 +1,24 @@
+package com.finebi.plugin;
+
+import com.fr.web.struct.Component;
+import com.fr.web.struct.category.ParserType;
+import com.fr.web.struct.category.ScriptPath;
+import com.fr.web.struct.category.StylePath;
+
+public class BIBoxPlotChartComponent extends Component {
+
+ public static final BIBoxPlotChartComponent KEY = new BIBoxPlotChartComponent();
+
+ private BIBoxPlotChartComponent() {
+
+ }
+
+ @Override
+ public ScriptPath script() {
+ return ScriptPath.build("com/finebi/plugin/web/js/chart.js");
+ }
+
+ public StylePath style() {
+ return StylePath.build("com/finebi/plugin/web/css/chart.css", ParserType.DYNAMIC);
+ }
+}
\ No newline at end of file
diff --git a/plugin-demo/src/main/java/com/finebi/plugin/BIDesignBoxPlotChart.java b/plugin-demo/src/main/java/com/finebi/plugin/BIDesignBoxPlotChart.java
new file mode 100644
index 0000000..1e98512
--- /dev/null
+++ b/plugin-demo/src/main/java/com/finebi/plugin/BIDesignBoxPlotChart.java
@@ -0,0 +1,23 @@
+package com.finebi.plugin;
+
+import com.finebi.conf.internalimp.component.ReportComponent;
+import com.fr.decision.fun.impl.AbstractWebResourceProvider;
+import com.fr.intelli.record.Focus;
+import com.fr.intelli.record.Original;
+import com.fr.record.analyzer.EnableMetrics;
+import com.fr.web.struct.Atom;
+
+@EnableMetrics
+public class BIDesignBoxPlotChart extends AbstractWebResourceProvider {
+
+ @Override
+ public Atom attach() {
+ return ReportComponent.KEY;
+ }
+
+ @Override
+ @Focus(id = "com.finebi.plugin.BIDesignBoxPlotChart", text = "箱线图", source = Original.PLUGIN)
+ public Atom client() {
+ return BIBoxPlotChartComponent.KEY;
+ }
+}
\ No newline at end of file
diff --git a/plugin-demo/src/main/java/com/finebi/plugin/BIPluginDemoComponent.java b/plugin-demo/src/main/java/com/finebi/plugin/BIPluginDemoComponent.java
deleted file mode 100644
index beb0ec1..0000000
--- a/plugin-demo/src/main/java/com/finebi/plugin/BIPluginDemoComponent.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.finebi.plugin;
-
-import com.fr.web.struct.Component;
-import com.fr.web.struct.browser.RequestClient;
-import com.fr.web.struct.category.ScriptPath;
-import com.fr.web.struct.category.StylePath;
-
-public class BIPluginDemoComponent extends Component {
-
- public static final BIPluginDemoComponent KEY = new BIPluginDemoComponent();
-
- private BIPluginDemoComponent() {
-
- }
-
- @Override
- public ScriptPath script(RequestClient client) {
- return ScriptPath.build("com/finebi/plugin/web/BIPluginDemo.js");
- }
-
- @Override
- public StylePath style(RequestClient client) {
- return StylePath.build("com/finebi/plugin/web/BIPluginDemo.css");
- }
-}
\ No newline at end of file
diff --git a/plugin-demo/src/main/java/com/finebi/plugin/BIPluginDemo.java b/plugin-demo/src/main/java/com/finebi/plugin/BIShowBoxPlotChart.java
similarity index 67%
rename from plugin-demo/src/main/java/com/finebi/plugin/BIPluginDemo.java
rename to plugin-demo/src/main/java/com/finebi/plugin/BIShowBoxPlotChart.java
index 692bb71..74fc108 100644
--- a/plugin-demo/src/main/java/com/finebi/plugin/BIPluginDemo.java
+++ b/plugin-demo/src/main/java/com/finebi/plugin/BIShowBoxPlotChart.java
@@ -8,7 +8,7 @@ import com.fr.record.analyzer.EnableMetrics;
import com.fr.web.struct.Atom;
@EnableMetrics
-public class BIPluginDemo extends AbstractWebResourceProvider {
+public class BIShowBoxPlotChart extends AbstractWebResourceProvider {
@Override
public Atom attach() {
@@ -16,8 +16,8 @@ public class BIPluginDemo extends AbstractWebResourceProvider {
}
@Override
- @Focus(id = "com.finebi.plugin.demo", text = "BI插件测试", source = Original.PLUGIN)
+ @Focus(id = "com.finebi.plugin.BIShowBoxPlotChart", text = "箱线图", source = Original.PLUGIN)
public Atom client() {
- return BIPluginDemoComponent.KEY;
+ return BIBoxPlotChartComponent.KEY;
}
}
\ No newline at end of file
diff --git a/plugin-demo/src/main/resources/com/finebi/plugin/web/BIPluginDemo.css b/plugin-demo/src/main/resources/com/finebi/plugin/web/BIPluginDemo.css
deleted file mode 100644
index 0fc104e..0000000
--- a/plugin-demo/src/main/resources/com/finebi/plugin/web/BIPluginDemo.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.bi-dashboard-widget:hover .operator-region, .bi-control-widget:hover .operator-region {
- display: none;
-}
\ No newline at end of file
diff --git a/plugin-demo/src/main/resources/com/finebi/plugin/web/BIPluginDemo.js b/plugin-demo/src/main/resources/com/finebi/plugin/web/BIPluginDemo.js
deleted file mode 100644
index 250ba35..0000000
--- a/plugin-demo/src/main/resources/com/finebi/plugin/web/BIPluginDemo.js
+++ /dev/null
@@ -1,6 +0,0 @@
-BI.Plugin.registerObject("bi.show.widget.image", function (widget) {
- widget.img.un(BI.ImageButton.EVENT_CHANGE);
- widget.img.element.on("click", function () {
- location.href = BI.Format.formatAddress("www.baidu.com");
- });
-});
\ No newline at end of file
diff --git a/plugin-demo/src/main/resources/com/finebi/plugin/web/css/chart.css b/plugin-demo/src/main/resources/com/finebi/plugin/web/css/chart.css
new file mode 100644
index 0000000..b6385ac
--- /dev/null
+++ b/plugin-demo/src/main/resources/com/finebi/plugin/web/css/chart.css
@@ -0,0 +1,17 @@
+.chart-type-boxplot-column-icon .x-icon {
+ display: block;
+ background: url('${fineServletURL}/resources?path=/com/finebi/plugin/web/image/boxplot.png') no-repeat center center;
+ background-size: contain;
+}
+.chart-type-boxplot-column-icon .x-icon.hack {
+ background: url('${fineServletURL}/resources?path=/com/finebi/plugin/web/image/boxplot.png') no-repeat center center;
+}
+
+.chart-type-boxplot-column-disabled-icon .x-icon {
+ display: block;
+ background: url('${fineServletURL}/resources?path=/com/finebi/plugin/web/image/disable.png') no-repeat center center;
+ background-size: contain;
+}
+.chart-type-boxplot-column-disabled-icon .x-icon.hack {
+ background: url('${fineServletURL}/resources?path=/com/finebi/plugin/web/image/disable.png') no-repeat center center;
+}
\ No newline at end of file
diff --git a/plugin-demo/src/main/resources/com/finebi/plugin/web/image/boxplot.png b/plugin-demo/src/main/resources/com/finebi/plugin/web/image/boxplot.png
new file mode 100644
index 0000000..f7c0d3c
Binary files /dev/null and b/plugin-demo/src/main/resources/com/finebi/plugin/web/image/boxplot.png differ
diff --git a/plugin-demo/src/main/resources/com/finebi/plugin/web/image/disable.png b/plugin-demo/src/main/resources/com/finebi/plugin/web/image/disable.png
new file mode 100644
index 0000000..b97b02c
Binary files /dev/null and b/plugin-demo/src/main/resources/com/finebi/plugin/web/image/disable.png differ
diff --git a/plugin-demo/src/main/resources/com/finebi/plugin/web/js/chart.js b/plugin-demo/src/main/resources/com/finebi/plugin/web/js/chart.js
new file mode 100644
index 0000000..d0e58fb
--- /dev/null
+++ b/plugin-demo/src/main/resources/com/finebi/plugin/web/js/chart.js
@@ -0,0 +1,43997 @@
+(function (factory) {
+ typeof define === 'function' && define.amd ? define(factory) :
+ factory();
+}(function () { 'use strict';
+
+ var config = {
+ type: "boxplot",
+ text: "箱线图",
+ cls: "chart-type-boxplot-column-icon",
+ disabledCls: "chart-type-boxplot-column-disabled-icon",
+ resultType: BICst.DESIGN.WIDGET.DETAIL,
+ providers: {
+ chartProvider: {
+ // 自己注册的provider
+ type: "bi.provider.share.chart"
+ }
+ // chartEditProvider: {
+ // type: "bi.provider.design.chart.edit"
+ // }
+ },
+ expanders: [{
+ type: "bi.provider.share.expander",
+ text: "自定义设置项",
+ value: "customAttr"
+ }],
+ required: [
+ {
+ dimension: ">=1",
+ measure: ">=1"
+ }
+ ]
+ };
+
+ function expanderProvider() {
+ this.render = function () {
+ var self = this;
+ return {
+ type: "bi.vertical",
+ items: [{
+ type: "bi.multi_select_item",
+ selected: BI.bind(getAttr, this)("showLine"),
+ value: "显示折线图",
+ height: 16,
+ handler: function () {
+ BI.bind(setAttr, self)("showLine", this.isSelected());
+ }
+ },{
+ type: "bi.multi_select_item",
+ selected: BI.bind(getAttr, this)("showDirectData"),
+ value: "直接展示分位数据",
+ height: 16,
+ handler: function () {
+ BI.bind(setAttr, self)("showDirectData", this.isSelected());
+ }
+ },{
+ type: "bi.multi_select_item",
+ selected: BI.bind(getAttr, this)("showMedianLabel"),
+ value: "显示中位数标签",
+ height: 16,
+ handler: function () {
+ BI.bind(setAttr, self)("showMedianLabel", this.isSelected());
+ }
+ },{
+ type: "bi.multi_select_item",
+ selected: BI.bind(getAttr, this)("showDarkTheme"),
+ value: "使用深色配色",
+ height: 16,
+ handler: function () {
+ BI.bind(setAttr, self)("showDarkTheme", this.isSelected());
+ }
+ }],
+ vgap: 10,
+ hgap: 15
+ }
+ };
+
+ function getAttr(attrType) {
+ var attr = this.getInjectAttr();
+ return attr[attrType];
+ }
+
+ function setAttr(attrType,value) {
+ this.setInjectAttr(attrType, value);
+ }
+ }
+
+ function chartDisplayProvider() {
+
+
+ /**
+ * -----------------------------------------------------------------------
+ * ------------------------这里是原来的FormatData方法---------------------
+ */
+ function formatDataOriginal(data) {
+ var header = data.header;
+ var items = data.items;
+ var dataGroup = {},
+ dimId,
+ seriesName,
+ targetId;
+ var countMap = {};
+ var maxMap = {};
+ var minMap = {};
+ BI.each(header, function(i, header) {
+ if (!dimId && BI.Utils.isDimDimensionById(header.dId)) {
+ dimId = header.dId;
+ seriesName = header.text;
+ }
+ if (!targetId && BI.Utils.isTargetById(header.dId)) {
+ targetId = header.dId;
+ }
+ });
+ BI.each(items, function(i, row) {
+ var key, value;
+ BI.each(row, function(idx, cell) {
+ if (cell.dId === dimId) {
+ key = cell.value;
+ }
+ if (cell.dId === targetId) {
+ value = cell.value;
+ }
+ });
+ key = key || "0";
+ dataGroup[key] = dataGroup[key] || [];
+ dataGroup[key].push(BI.parseFloat(value));
+ });
+ // 这里处理一下总数的数据
+ BI.each(dataGroup, function(key, arr) {
+ countMap[key] = arr.length;
+ maxMap[key] = findMax(arr);
+ minMap[key] = findMin(arr);
+ });
+ return {
+ dataGroup: dataGroup,
+ seriesName: seriesName,
+ id: dimId,
+ countMap: countMap,
+ maxMap: maxMap,
+ minMap: minMap
+ };
+ }
+
+ function findMax(arr) {
+ var max = arr[0];
+ var len = arr.length;
+ for(var i = 1; i < len; i++){
+ if(arr[i] > max){
+ max = arr[i];
+ }
+ }
+ return max;
+ }
+
+ function findMin(arr) {
+ var min = arr[0];
+ var len = arr.length;
+ for(var i = 1; i < len; i++){
+ if(arr[i] < min){
+ min = arr[i];
+ }
+ }
+ return min;
+ }
+
+
+ /**
+ * -----------------------------------------------------------------------
+ * ------------------------这里再实现一套FormatData方法---------------------
+ */
+
+ function formatDataDirect(data) {
+
+ var header = data.header;
+ var items = data.items;
+ var nameIdMap = new Map();
+ var seriesName = "";
+ var dimId = {};
+ var dataGroup = {};
+
+ BI.each(header, function(i, header) {
+ if (BI.Utils.isDimDimensionById(header.dId)) {
+ dimId = header.dId;
+ seriesName = header.text;
+ }
+ nameIdMap.set(header.dId, header.text);
+ });
+
+ BI.each(items, function(i, row) {
+ var key;
+ BI.each(row, function(idx, cell) {
+ if (cell.dId === dimId) {
+ key = cell.value;
+ } else {
+ dataGroup[key] = dataGroup[key] || new Array(8);
+
+ switch (nameIdMap.get(cell.dId)) {
+ case "下边缘":
+ dataGroup[key][0] = BI.parseFloat(cell.value);
+ break;
+ case "下四分位":
+ dataGroup[key][1] = BI.parseFloat(cell.value);
+ break;
+ case "中位数":
+ dataGroup[key][2] = BI.parseFloat(cell.value);
+ break;
+ case "上四分位":
+ dataGroup[key][3] = BI.parseFloat(cell.value);
+ break;
+ case "上边缘":
+ dataGroup[key][4] = BI.parseFloat(cell.value);
+ break;
+ case "数据总量":
+ dataGroup[key][5] = BI.parseFloat(cell.value);
+ break;
+ case "最大值":
+ dataGroup[key][6] = BI.parseFloat(cell.value);
+ break;
+ case "最小值":
+ dataGroup[key][7] = BI.parseFloat(cell.value);
+ break;
+ default:
+ break;
+ }
+ }
+ });
+ });
+
+ return {
+ seriesName: seriesName,
+ dataGroup: dataGroup,
+ axisData: BI.keys(dataGroup)
+ };
+ }
+
+ /**
+ * --------------------------------------------------------------------------
+ * ---------------------------数据点声明--------------------------------------
+ */
+
+ this.render = function(el, d) {
+ var self = this;
+ // 获取WID
+ var configObj = BI.Utils.getReportInjectionById(this.model.wId);
+ console.log(d);
+ console.log(configObj);
+ var series;
+ var axisData;
+ // 在这里读取配置项
+ if (configObj.showDirectData == false || configObj.showDirectData == undefined) {
+ // 原始数据的情况
+ var result = formatDataOriginal(d),
+ series = [];
+ var data = BI.map(result.dataGroup, function(i, item) {
+ return item;
+ });
+ axisData = BI.keys(result.dataGroup);
+ var preData = echarts.dataTool.prepareBoxplotData(data);
+ var boxData = [];
+ var lineData = [];
+ var markPointData = [];
+ var markPointDataObj = {};
+ BI.each(preData.boxData, function(i, item) {
+ boxData.push({
+ name: axisData[i],
+ value: item
+ });
+ lineData.push(item[2]);
+
+ // 在这里处理MarkPoint所需数据
+ markPointDataObj = new Object();
+ markPointDataObj.value = item[2];
+ markPointDataObj.coord = [];
+ markPointDataObj.coord.push(i);
+ markPointDataObj.coord.push(item[2]);
+ markPointData.push(markPointDataObj);
+ });
+
+ var boxPlotSeries = {
+ name: result.seriesName,
+ id: result.id,
+ type: "boxplot",
+ data: boxData,
+ tooltip: {
+ formatter: function(param) {
+ return [
+ "分类名: " + param.name,
+ "数据总量:" + result.countMap[param.name],
+ "最大值:" + result.maxMap[param.name],
+ "上边缘: " + param.value[5],
+ "上四分位: " + param.value[4],
+ "中位数: " + param.value[3],
+ "下四分位: " + param.value[2],
+ "下边缘: " + param.value[1],
+ "最小值:" + result.minMap[param.name]
+ ].join("
");
+ }
+ },
+ emphasis: {
+ itemStyle: {
+ color: configObj.showDarkTheme == true ? "rgba(132, 185, 203, 1)" : "rgba(134, 180, 230, 1)"
+ }
+ },
+ itemStyle: {
+ color: configObj.showDarkTheme == true ? "rgba(132, 185, 203, 1)" : "rgba(134, 180, 230, 1)",
+ borderWidth: 2
+ }
+ };
+
+ var boxPlotSeriesWithMarkPoint = {
+ name: result.seriesName,
+ id: result.id,
+ type: "boxplot",
+ data: boxData,
+ tooltip: {
+ formatter: function(param) {
+ return [
+ "分类名: " + param.name,
+ "数据总量:" + result.countMap[param.name],
+ "最大值:" + result.maxMap[param.name],
+ "上边缘: " + param.value[5],
+ "上四分位: " + param.value[4],
+ "中位数: " + param.value[3],
+ "下四分位: " + param.value[2],
+ "下边缘: " + param.value[1],
+ "最小值:" + result.minMap[param.name]
+ ].join("
");
+ }
+ },
+ emphasis: {
+ itemStyle: {
+ color: configObj.showDarkTheme == true ? "rgba(132, 185, 203, 1)" : "rgba(134, 180, 230, 1)"
+ }
+ },
+ markPoint: {
+ data: markPointData,
+ symbol:"circle",
+ symbolSize:1,
+ label: {
+ position:"top",
+ fontSize:12,
+ fontWeight:"normal",
+ color:"black"
+ }
+ },
+ itemStyle: {
+ color: configObj.showDarkTheme == true ? "rgba(132, 185, 203, 1)" : "rgba(134, 180, 230, 1)",
+ borderWidth: 2
+ }
+ };
+
+ var lineSeries = {
+ type: "line",
+ data: lineData,
+ zlevel: 1,
+ itemStyle: {
+ color: "rgb(224, 178, 60)"
+ },
+ lineStyle: {
+ width: 3
+ }
+ };
+
+ var lineSeriesWithMarkPoint = {
+ type: "line",
+ data: lineData,
+ zlevel: 1,
+ itemStyle: {
+ color: "rgb(224, 178, 60)"
+ },
+ markPoint: {
+ data: markPointData,
+ symbol:"circle",
+ symbolSize:1,
+ label: {
+ position:"top",
+ fontSize:12,
+ fontWeight:"normal",
+ color:"black"
+ }
+ },
+ lineStyle: {
+ width: 3
+ }
+ };
+ if (configObj.showLine == true && configObj.showMedianLabel == true) {
+ series = [boxPlotSeries, lineSeriesWithMarkPoint];
+ } else if (configObj.showLine == true && (configObj.showMedianLabel == false || configObj.showMedianLabel == undefined)) {
+ series = [boxPlotSeries, lineSeries];
+ } else if ((configObj.showLine == false || configObj.showLine == undefined) && configObj.showMedianLabel == true){
+ series = [boxPlotSeriesWithMarkPoint];
+ } else {
+ series = [boxPlotSeries];
+ }
+
+ } else if (configObj.showDirectData == true) {
+ // 直接展示统计好的数据的情况
+ var result = formatDataDirect(d);
+ var boxData = [];
+ var lineData = [];
+ var markPointData = [];
+ var markPointDataObj = {};
+ var index = 0;
+ axisData = result.axisData;
+ // 生成BOXDATA
+ // TODO 这里的FOREACH要重新做!!不能遍历result了
+ BI.each(result.dataGroup, function(name, arr) {
+ boxData.push({
+ name: name,
+ value: arr
+ });
+ lineData.push(arr[2]);
+ // 在这里处理MarkPoint所需数据
+ markPointDataObj = new Object();
+ markPointDataObj.value = arr[2];
+ markPointDataObj.coord = [];
+ markPointDataObj.coord.push(index);
+ markPointDataObj.coord.push(arr[2]);
+ markPointData.push(markPointDataObj);
+ index++;
+ });
+
+ var boxPlotSeries = {
+ name: result.seriesName,
+ id: result.id,
+ type: "boxplot",
+ data: boxData,
+ tooltip: {
+ formatter: function(param) {
+ console.log("param:" + param);
+ return [
+ "分类名: " + param.name,
+ "数据总量:" + result.dataGroup[param.name][5],
+ "最大值:" + result.dataGroup[param.name][6],
+ "上边缘: " + result.dataGroup[param.name][4],
+ "上四分位: " + result.dataGroup[param.name][3],
+ "中位数: " + result.dataGroup[param.name][2],
+ "下四分位: " + result.dataGroup[param.name][1],
+ "下边缘: " + result.dataGroup[param.name][0],
+ "最小值:" + result.dataGroup[param.name][7]
+ ].join("
");
+ }
+ },
+ emphasis: {
+ itemStyle: {
+ color: configObj.showDarkTheme == true ? "rgba(132, 185, 203, 1)" : "rgba(134, 180, 230, 1)"
+ }
+ },
+ itemStyle: {
+ color: configObj.showDarkTheme == true ? "rgba(132, 185, 203, 1)" : "rgba(134, 180, 230, 1)",
+ borderWidth: 2
+ }
+ };
+
+ var boxPlotSeriesWithMarkPoint = {
+ name: result.seriesName,
+ id: result.id,
+ type: "boxplot",
+ data: boxData,
+ tooltip: {
+ formatter: function(param) {
+ console.log("param:" + param);
+ return [
+ "分类名: " + param.name,
+ "数据总量:" + result.dataGroup[param.name][5],
+ "最大值:" + result.dataGroup[param.name][6],
+ "上边缘: " + result.dataGroup[param.name][4],
+ "上四分位: " + result.dataGroup[param.name][3],
+ "中位数: " + result.dataGroup[param.name][2],
+ "下四分位: " + result.dataGroup[param.name][1],
+ "下边缘: " + result.dataGroup[param.name][0],
+ "最小值:" + result.dataGroup[param.name][7]
+ ].join("
");
+ }
+ },
+ markPoint: {
+ data: markPointData,
+ symbol:"circle",
+ symbolSize:1,
+ label: {
+ position:"top",
+ fontSize:12,
+ fontWeight:"normal",
+ color:"black"
+ }
+ },
+ emphasis: {
+ itemStyle: {
+ color: configObj.showDarkTheme == true ? "rgba(132, 185, 203, 1)" : "rgba(134, 180, 230, 1)"
+ }
+ },
+ itemStyle: {
+ color: configObj.showDarkTheme == true ? "rgba(132, 185, 203, 1)" : "rgba(134, 180, 230, 1)",
+ borderWidth: 2
+ }
+ };
+
+ var lineSeries = {
+ type: "line",
+ data: lineData,
+ zlevel: 1,
+ itemStyle: {
+ color: "rgb(224, 178, 60)"
+ },
+ lineStyle: {
+ width: 3
+ }
+ };
+
+ var lineSeriesWithMarkPoint = {
+ type: "line",
+ data: lineData,
+ zlevel: 1,
+ itemStyle: {
+ color: "rgb(224, 178, 60)"
+ },
+ markPoint: {
+ data: markPointData,
+ symbol:"circle",
+ symbolSize:1,
+ label: {
+ position:"top",
+ fontSize:12,
+ fontWeight:"normal",
+ color:"black"
+ }
+ },
+ lineStyle: {
+ width: 3
+ }
+ };
+ if (configObj.showLine == true && configObj.showMedianLabel == true) {
+ series = [boxPlotSeries, lineSeriesWithMarkPoint];
+ } else if (configObj.showLine == true && (configObj.showMedianLabel == false || configObj.showMedianLabel == undefined)) {
+ series = [boxPlotSeries, lineSeries];
+ } else if ((configObj.showLine == false || configObj.showLine == undefined) && configObj.showMedianLabel == true){
+ series = [boxPlotSeriesWithMarkPoint];
+ } else {
+ series = [boxPlotSeries];
+ }
+ }
+
+ // 切换图表的时候 创建过的实例不会创建,先dispose
+ echarts.dispose(el);
+ this.chart = echarts.init(el);
+
+ var options = {
+ color: ["#61a0a8"],
+ backgroundColor: configObj.showDarkTheme == true ? "#4D4D4D" : "#FFFFFF",
+ title: [
+ {
+ text: "upper: Q3 + 1.5 * IQR \nlower: Q1 - 1.5 * IQR",
+ borderColor: configObj.showDarkTheme == true ? "#FFFFFF" : "#999999",
+ borderWidth: 1,
+ textStyle: {
+ fontSize: 14,
+ color: configObj.showDarkTheme == true ? "#FFFFFF" : "#000000"
+ },
+ left: "10%",
+ top: "90%"
+ }
+ ],
+ tooltip: {
+ trigger: "item",
+ axisPointer: {
+ type: "shadow"
+ }
+ },
+ grid: {
+ left: "10%",
+ right: "10%",
+ bottom: "15%"
+ },
+ xAxis: {
+ type: "category",
+ data: axisData,
+ boundaryGap: true,
+ nameGap: 30,
+ axisTick: {
+ show: false
+ },
+ splitArea: {
+ show: false
+ },
+ axisLabel: {
+ formatter: "{value}",
+ textStyle: {
+ color: configObj.showDarkTheme == true ? "#FFFFFF" : "#000000"
+ }
+ },
+ splitLine: {
+ show: false
+ },
+ axisLine: {
+ show: true,
+ lineStyle: {
+ color: configObj.showDarkTheme == true ? "#FFFFFF" : "#000000"
+ }
+ }
+ },
+ yAxis: {
+ type: "value",
+ name: "",
+ axisLine: {
+ show: false,
+ lineStyle: {
+ color: configObj.showDarkTheme == true ? "#FFFFFF" : "#000000"
+ }
+ },
+ axisTick: {
+ show: false
+ },
+ splitArea: {
+ show: false
+ },
+ splitLine: {
+ show: true
+ },
+ axisLabel: {
+ textStyle: {
+ color: configObj.showDarkTheme == true ? "#FFFFFF" : "#000000"
+ }
+ }
+ },
+ series: series
+ };
+ this.chart.setOption(options);
+
+ // var pointPara = {
+ // row: {
+ // "点击的点的维度id": "维度值",
+ // "点击的点的维度1id": "维度值",
+ // "点击的点的指标id": "指标值",
+ // "点击的点的指标2id": "指标值"
+ // },
+ // metaData: [{id: "维度字段id"}, {id: "指标字段id"}],
+ // pos: {x: 12, y:24} // 相对位置
+ // };
+
+ // var dimensionPara = {
+ // dId: "String",
+ // value: [{dId: string, text: ""}]
+ // };
+ // 触发联动 钻取
+ // this.pointTrigger();
+ // 触发维度联动
+ // this.dimensionTrigger(dimensionPara);
+ this.chart.on("click", function(para) {
+ self.dimensionTrigger({
+ dId: para.seriesId,
+ value: [{dId: para.seriesId, text: para.name}]
+ });
+ });
+
+ // echarts找到 横轴上维度点击的事件,但箱线图横轴维度点击不了,如果可以应该按如下写法
+ // this.chart.on("dimensionClick", function () {
+ // var dimensionPara = {
+ // dId: "String",
+ // value: [{dId: string, text: ""}]
+ // };
+ // self.dimensionTrigger(dimensionPara);
+ // })
+ };
+
+ this.resize = function(width, height) {
+ this.chart &&
+ this.chart.resize({
+ width: width,
+ height: height
+ });
+ };
+ }
+
+ BI.provider("bi.provider.share.chart", chartDisplayProvider);
+ // BI.provider("bi.provider.design.chart.edit", chartChangeProvider);
+ BI.provider("bi.provider.share.expander", expanderProvider);
+
+ BI.config("bi.provider.chart", function(provider) {
+ provider.inject(config);
+ });
+
+}));
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (factory((global.echarts = {})));
+}(this, (function (exports) { 'use strict';
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+// (1) The code `if (__DEV__) ...` can be removed by build tool.
+// (2) If intend to use `__DEV__`, this module should be imported. Use a global
+// variable `__DEV__` may cause that miss the declaration (see #6535), or the
+// declaration is behind of the using position (for example in `Model.extent`,
+// And tools like rollup can not analysis the dependency if not import).
+
+/**
+ * zrender: 生成唯一id
+ *
+ * @author errorrik (errorrik@gmail.com)
+ */
+var idStart = 0x0907;
+var guid = function () {
+ return idStart++;
+};
+
+/**
+ * echarts设备环境识别
+ *
+ * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。
+ * @author firede[firede@firede.us]
+ * @desc thanks zepto.
+ */
+var env = {};
+
+if (typeof wx === 'object' && typeof wx.getSystemInfoSync === 'function') {
+ // In Weixin Application
+ env = {
+ browser: {},
+ os: {},
+ node: false,
+ wxa: true,
+ // Weixin Application
+ canvasSupported: true,
+ svgSupported: false,
+ touchEventsSupported: true,
+ domSupported: false
+ };
+} else if (typeof document === 'undefined' && typeof self !== 'undefined') {
+ // In worker
+ env = {
+ browser: {},
+ os: {},
+ node: false,
+ worker: true,
+ canvasSupported: true,
+ domSupported: false
+ };
+} else if (typeof navigator === 'undefined') {
+ // In node
+ env = {
+ browser: {},
+ os: {},
+ node: true,
+ worker: false,
+ // Assume canvas is supported
+ canvasSupported: true,
+ svgSupported: true,
+ domSupported: false
+ };
+} else {
+ env = detect(navigator.userAgent);
+}
+
+var env$1 = env; // Zepto.js
+// (c) 2010-2013 Thomas Fuchs
+// Zepto.js may be freely distributed under the MIT license.
+
+function detect(ua) {
+ var os = {};
+ var browser = {}; // var webkit = ua.match(/Web[kK]it[\/]{0,1}([\d.]+)/);
+ // var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
+ // var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
+ // var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
+ // var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
+ // var webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/);
+ // var touchpad = webos && ua.match(/TouchPad/);
+ // var kindle = ua.match(/Kindle\/([\d.]+)/);
+ // var silk = ua.match(/Silk\/([\d._]+)/);
+ // var blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/);
+ // var bb10 = ua.match(/(BB10).*Version\/([\d.]+)/);
+ // var rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/);
+ // var playbook = ua.match(/PlayBook/);
+ // var chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/);
+
+ var firefox = ua.match(/Firefox\/([\d.]+)/); // var safari = webkit && ua.match(/Mobile\//) && !chrome;
+ // var webview = ua.match(/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/) && !chrome;
+
+ var ie = ua.match(/MSIE\s([\d.]+)/) // IE 11 Trident/7.0; rv:11.0
+ || ua.match(/Trident\/.+?rv:(([\d.]+))/);
+ var edge = ua.match(/Edge\/([\d.]+)/); // IE 12 and 12+
+
+ var weChat = /micromessenger/i.test(ua); // Todo: clean this up with a better OS/browser seperation:
+ // - discern (more) between multiple browsers on android
+ // - decide if kindle fire in silk mode is android or not
+ // - Firefox on Android doesn't specify the Android version
+ // - possibly devide in os, device and browser hashes
+ // if (browser.webkit = !!webkit) browser.version = webkit[1];
+ // if (android) os.android = true, os.version = android[2];
+ // if (iphone && !ipod) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.');
+ // if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.');
+ // if (ipod) os.ios = os.ipod = true, os.version = ipod[3] ? ipod[3].replace(/_/g, '.') : null;
+ // if (webos) os.webos = true, os.version = webos[2];
+ // if (touchpad) os.touchpad = true;
+ // if (blackberry) os.blackberry = true, os.version = blackberry[2];
+ // if (bb10) os.bb10 = true, os.version = bb10[2];
+ // if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2];
+ // if (playbook) browser.playbook = true;
+ // if (kindle) os.kindle = true, os.version = kindle[1];
+ // if (silk) browser.silk = true, browser.version = silk[1];
+ // if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true;
+ // if (chrome) browser.chrome = true, browser.version = chrome[1];
+
+ if (firefox) {
+ browser.firefox = true;
+ browser.version = firefox[1];
+ } // if (safari && (ua.match(/Safari/) || !!os.ios)) browser.safari = true;
+ // if (webview) browser.webview = true;
+
+
+ if (ie) {
+ browser.ie = true;
+ browser.version = ie[1];
+ }
+
+ if (edge) {
+ browser.edge = true;
+ browser.version = edge[1];
+ } // It is difficult to detect WeChat in Win Phone precisely, because ua can
+ // not be set on win phone. So we do not consider Win Phone.
+
+
+ if (weChat) {
+ browser.weChat = true;
+ } // os.tablet = !!(ipad || playbook || (android && !ua.match(/Mobile/)) ||
+ // (firefox && ua.match(/Tablet/)) || (ie && !ua.match(/Phone/) && ua.match(/Touch/)));
+ // os.phone = !!(!os.tablet && !os.ipod && (android || iphone || webos ||
+ // (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) ||
+ // (firefox && ua.match(/Mobile/)) || (ie && ua.match(/Touch/))));
+
+
+ return {
+ browser: browser,
+ os: os,
+ node: false,
+ // 原生canvas支持,改极端点了
+ // canvasSupported : !(browser.ie && parseFloat(browser.version) < 9)
+ canvasSupported: !!document.createElement('canvas').getContext,
+ svgSupported: typeof SVGRect !== 'undefined',
+ // works on most browsers
+ // IE10/11 does not support touch event, and MS Edge supports them but not by
+ // default, so we dont check navigator.maxTouchPoints for them here.
+ touchEventsSupported: 'ontouchstart' in window && !browser.ie && !browser.edge,
+ // .
+ pointerEventsSupported: 'onpointerdown' in window // Firefox supports pointer but not by default, only MS browsers are reliable on pointer
+ // events currently. So we dont use that on other browsers unless tested sufficiently.
+ // Although IE 10 supports pointer event, it use old style and is different from the
+ // standard. So we exclude that. (IE 10 is hardly used on touch device)
+ && (browser.edge || browser.ie && browser.version >= 11),
+ // passiveSupported: detectPassiveSupport()
+ domSupported: typeof document !== 'undefined'
+ };
+} // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
+// function detectPassiveSupport() {
+// // Test via a getter in the options object to see if the passive property is accessed
+// var supportsPassive = false;
+// try {
+// var opts = Object.defineProperty({}, 'passive', {
+// get: function() {
+// supportsPassive = true;
+// }
+// });
+// window.addEventListener('testPassive', function() {}, opts);
+// } catch (e) {
+// }
+// return supportsPassive;
+// }
+
+/**
+ * @module zrender/core/util
+ */
+// 用于处理merge时无法遍历Date等对象的问题
+var BUILTIN_OBJECT = {
+ '[object Function]': 1,
+ '[object RegExp]': 1,
+ '[object Date]': 1,
+ '[object Error]': 1,
+ '[object CanvasGradient]': 1,
+ '[object CanvasPattern]': 1,
+ // For node-canvas
+ '[object Image]': 1,
+ '[object Canvas]': 1
+};
+var TYPED_ARRAY = {
+ '[object Int8Array]': 1,
+ '[object Uint8Array]': 1,
+ '[object Uint8ClampedArray]': 1,
+ '[object Int16Array]': 1,
+ '[object Uint16Array]': 1,
+ '[object Int32Array]': 1,
+ '[object Uint32Array]': 1,
+ '[object Float32Array]': 1,
+ '[object Float64Array]': 1
+};
+var objToString = Object.prototype.toString;
+var arrayProto = Array.prototype;
+var nativeForEach = arrayProto.forEach;
+var nativeFilter = arrayProto.filter;
+var nativeSlice = arrayProto.slice;
+var nativeMap = arrayProto.map;
+var nativeReduce = arrayProto.reduce; // Avoid assign to an exported variable, for transforming to cjs.
+
+var methods = {};
+function $override(name, fn) {
+ // Clear ctx instance for different environment
+ if (name === 'createCanvas') {
+ _ctx = null;
+ }
+
+ methods[name] = fn;
+}
+/**
+ * Those data types can be cloned:
+ * Plain object, Array, TypedArray, number, string, null, undefined.
+ * Those data types will be assgined using the orginal data:
+ * BUILTIN_OBJECT
+ * Instance of user defined class will be cloned to a plain object, without
+ * properties in prototype.
+ * Other data types is not supported (not sure what will happen).
+ *
+ * Caution: do not support clone Date, for performance consideration.
+ * (There might be a large number of date in `series.data`).
+ * So date should not be modified in and out of echarts.
+ *
+ * @param {*} source
+ * @return {*} new
+ */
+
+function clone(source) {
+ if (source == null || typeof source !== 'object') {
+ return source;
+ }
+
+ var result = source;
+ var typeStr = objToString.call(source);
+
+ if (typeStr === '[object Array]') {
+ if (!isPrimitive(source)) {
+ result = [];
+
+ for (var i = 0, len = source.length; i < len; i++) {
+ result[i] = clone(source[i]);
+ }
+ }
+ } else if (TYPED_ARRAY[typeStr]) {
+ if (!isPrimitive(source)) {
+ var Ctor = source.constructor;
+
+ if (source.constructor.from) {
+ result = Ctor.from(source);
+ } else {
+ result = new Ctor(source.length);
+
+ for (var i = 0, len = source.length; i < len; i++) {
+ result[i] = clone(source[i]);
+ }
+ }
+ }
+ } else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) {
+ result = {};
+
+ for (var key in source) {
+ if (source.hasOwnProperty(key)) {
+ result[key] = clone(source[key]);
+ }
+ }
+ }
+
+ return result;
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {*} target
+ * @param {*} source
+ * @param {boolean} [overwrite=false]
+ */
+
+function merge(target, source, overwrite) {
+ // We should escapse that source is string
+ // and enter for ... in ...
+ if (!isObject$1(source) || !isObject$1(target)) {
+ return overwrite ? clone(source) : target;
+ }
+
+ for (var key in source) {
+ if (source.hasOwnProperty(key)) {
+ var targetProp = target[key];
+ var sourceProp = source[key];
+
+ if (isObject$1(sourceProp) && isObject$1(targetProp) && !isArray(sourceProp) && !isArray(targetProp) && !isDom(sourceProp) && !isDom(targetProp) && !isBuiltInObject(sourceProp) && !isBuiltInObject(targetProp) && !isPrimitive(sourceProp) && !isPrimitive(targetProp)) {
+ // 如果需要递归覆盖,就递归调用merge
+ merge(targetProp, sourceProp, overwrite);
+ } else if (overwrite || !(key in target)) {
+ // 否则只处理overwrite为true,或者在目标对象中没有此属性的情况
+ // NOTE,在 target[key] 不存在的时候也是直接覆盖
+ target[key] = clone(source[key], true);
+ }
+ }
+ }
+
+ return target;
+}
+/**
+ * @param {Array} targetAndSources The first item is target, and the rests are source.
+ * @param {boolean} [overwrite=false]
+ * @return {*} target
+ */
+
+function mergeAll(targetAndSources, overwrite) {
+ var result = targetAndSources[0];
+
+ for (var i = 1, len = targetAndSources.length; i < len; i++) {
+ result = merge(result, targetAndSources[i], overwrite);
+ }
+
+ return result;
+}
+/**
+ * @param {*} target
+ * @param {*} source
+ * @memberOf module:zrender/core/util
+ */
+
+function extend(target, source) {
+ for (var key in source) {
+ if (source.hasOwnProperty(key)) {
+ target[key] = source[key];
+ }
+ }
+
+ return target;
+}
+/**
+ * @param {*} target
+ * @param {*} source
+ * @param {boolean} [overlay=false]
+ * @memberOf module:zrender/core/util
+ */
+
+function defaults(target, source, overlay) {
+ for (var key in source) {
+ if (source.hasOwnProperty(key) && (overlay ? source[key] != null : target[key] == null)) {
+ target[key] = source[key];
+ }
+ }
+
+ return target;
+}
+var createCanvas = function () {
+ return methods.createCanvas();
+};
+
+methods.createCanvas = function () {
+ return document.createElement('canvas');
+}; // FIXME
+
+
+var _ctx;
+
+function getContext() {
+ if (!_ctx) {
+ // Use util.createCanvas instead of createCanvas
+ // because createCanvas may be overwritten in different environment
+ _ctx = createCanvas().getContext('2d');
+ }
+
+ return _ctx;
+}
+/**
+ * 查询数组中元素的index
+ * @memberOf module:zrender/core/util
+ */
+
+function indexOf(array, value) {
+ if (array) {
+ if (array.indexOf) {
+ return array.indexOf(value);
+ }
+
+ for (var i = 0, len = array.length; i < len; i++) {
+ if (array[i] === value) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+/**
+ * 构造类继承关系
+ *
+ * @memberOf module:zrender/core/util
+ * @param {Function} clazz 源类
+ * @param {Function} baseClazz 基类
+ */
+
+function inherits(clazz, baseClazz) {
+ var clazzPrototype = clazz.prototype;
+
+ function F() {}
+
+ F.prototype = baseClazz.prototype;
+ clazz.prototype = new F();
+
+ for (var prop in clazzPrototype) {
+ clazz.prototype[prop] = clazzPrototype[prop];
+ }
+
+ clazz.prototype.constructor = clazz;
+ clazz.superClass = baseClazz;
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {Object|Function} target
+ * @param {Object|Function} sorce
+ * @param {boolean} overlay
+ */
+
+function mixin(target, source, overlay) {
+ target = 'prototype' in target ? target.prototype : target;
+ source = 'prototype' in source ? source.prototype : source;
+ defaults(target, source, overlay);
+}
+/**
+ * Consider typed array.
+ * @param {Array|TypedArray} data
+ */
+
+function isArrayLike(data) {
+ if (!data) {
+ return;
+ }
+
+ if (typeof data === 'string') {
+ return false;
+ }
+
+ return typeof data.length === 'number';
+}
+/**
+ * 数组或对象遍历
+ * @memberOf module:zrender/core/util
+ * @param {Object|Array} obj
+ * @param {Function} cb
+ * @param {*} [context]
+ */
+
+function each$1(obj, cb, context) {
+ if (!(obj && cb)) {
+ return;
+ }
+
+ if (obj.forEach && obj.forEach === nativeForEach) {
+ obj.forEach(cb, context);
+ } else if (obj.length === +obj.length) {
+ for (var i = 0, len = obj.length; i < len; i++) {
+ cb.call(context, obj[i], i, obj);
+ }
+ } else {
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ cb.call(context, obj[key], key, obj);
+ }
+ }
+ }
+}
+/**
+ * 数组映射
+ * @memberOf module:zrender/core/util
+ * @param {Array} obj
+ * @param {Function} cb
+ * @param {*} [context]
+ * @return {Array}
+ */
+
+function map(obj, cb, context) {
+ if (!(obj && cb)) {
+ return;
+ }
+
+ if (obj.map && obj.map === nativeMap) {
+ return obj.map(cb, context);
+ } else {
+ var result = [];
+
+ for (var i = 0, len = obj.length; i < len; i++) {
+ result.push(cb.call(context, obj[i], i, obj));
+ }
+
+ return result;
+ }
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {Array} obj
+ * @param {Function} cb
+ * @param {Object} [memo]
+ * @param {*} [context]
+ * @return {Array}
+ */
+
+function reduce(obj, cb, memo, context) {
+ if (!(obj && cb)) {
+ return;
+ }
+
+ if (obj.reduce && obj.reduce === nativeReduce) {
+ return obj.reduce(cb, memo, context);
+ } else {
+ for (var i = 0, len = obj.length; i < len; i++) {
+ memo = cb.call(context, memo, obj[i], i, obj);
+ }
+
+ return memo;
+ }
+}
+/**
+ * 数组过滤
+ * @memberOf module:zrender/core/util
+ * @param {Array} obj
+ * @param {Function} cb
+ * @param {*} [context]
+ * @return {Array}
+ */
+
+function filter(obj, cb, context) {
+ if (!(obj && cb)) {
+ return;
+ }
+
+ if (obj.filter && obj.filter === nativeFilter) {
+ return obj.filter(cb, context);
+ } else {
+ var result = [];
+
+ for (var i = 0, len = obj.length; i < len; i++) {
+ if (cb.call(context, obj[i], i, obj)) {
+ result.push(obj[i]);
+ }
+ }
+
+ return result;
+ }
+}
+/**
+ * 数组项查找
+ * @memberOf module:zrender/core/util
+ * @param {Array} obj
+ * @param {Function} cb
+ * @param {*} [context]
+ * @return {*}
+ */
+
+function find(obj, cb, context) {
+ if (!(obj && cb)) {
+ return;
+ }
+
+ for (var i = 0, len = obj.length; i < len; i++) {
+ if (cb.call(context, obj[i], i, obj)) {
+ return obj[i];
+ }
+ }
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {Function} func
+ * @param {*} context
+ * @return {Function}
+ */
+
+function bind(func, context) {
+ var args = nativeSlice.call(arguments, 2);
+ return function () {
+ return func.apply(context, args.concat(nativeSlice.call(arguments)));
+ };
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {Function} func
+ * @return {Function}
+ */
+
+function curry(func) {
+ var args = nativeSlice.call(arguments, 1);
+ return function () {
+ return func.apply(this, args.concat(nativeSlice.call(arguments)));
+ };
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {*} value
+ * @return {boolean}
+ */
+
+function isArray(value) {
+ return objToString.call(value) === '[object Array]';
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {*} value
+ * @return {boolean}
+ */
+
+function isFunction$1(value) {
+ return typeof value === 'function';
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {*} value
+ * @return {boolean}
+ */
+
+function isString(value) {
+ return objToString.call(value) === '[object String]';
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {*} value
+ * @return {boolean}
+ */
+
+function isObject$1(value) {
+ // Avoid a V8 JIT bug in Chrome 19-20.
+ // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
+ var type = typeof value;
+ return type === 'function' || !!value && type === 'object';
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {*} value
+ * @return {boolean}
+ */
+
+function isBuiltInObject(value) {
+ return !!BUILTIN_OBJECT[objToString.call(value)];
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {*} value
+ * @return {boolean}
+ */
+
+function isTypedArray(value) {
+ return !!TYPED_ARRAY[objToString.call(value)];
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {*} value
+ * @return {boolean}
+ */
+
+function isDom(value) {
+ return typeof value === 'object' && typeof value.nodeType === 'number' && typeof value.ownerDocument === 'object';
+}
+/**
+ * Whether is exactly NaN. Notice isNaN('a') returns true.
+ * @param {*} value
+ * @return {boolean}
+ */
+
+function eqNaN(value) {
+ return value !== value;
+}
+/**
+ * If value1 is not null, then return value1, otherwise judget rest of values.
+ * Low performance.
+ * @memberOf module:zrender/core/util
+ * @return {*} Final value
+ */
+
+function retrieve(values) {
+ for (var i = 0, len = arguments.length; i < len; i++) {
+ if (arguments[i] != null) {
+ return arguments[i];
+ }
+ }
+}
+function retrieve2(value0, value1) {
+ return value0 != null ? value0 : value1;
+}
+function retrieve3(value0, value1, value2) {
+ return value0 != null ? value0 : value1 != null ? value1 : value2;
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {Array} arr
+ * @param {number} startIndex
+ * @param {number} endIndex
+ * @return {Array}
+ */
+
+function slice() {
+ return Function.call.apply(nativeSlice, arguments);
+}
+/**
+ * Normalize css liked array configuration
+ * e.g.
+ * 3 => [3, 3, 3, 3]
+ * [4, 2] => [4, 2, 4, 2]
+ * [4, 3, 2] => [4, 3, 2, 3]
+ * @param {number|Array.} val
+ * @return {Array.}
+ */
+
+function normalizeCssArray(val) {
+ if (typeof val === 'number') {
+ return [val, val, val, val];
+ }
+
+ var len = val.length;
+
+ if (len === 2) {
+ // vertical | horizontal
+ return [val[0], val[1], val[0], val[1]];
+ } else if (len === 3) {
+ // top | horizontal | bottom
+ return [val[0], val[1], val[2], val[1]];
+ }
+
+ return val;
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {boolean} condition
+ * @param {string} message
+ */
+
+function assert$1(condition, message) {
+ if (!condition) {
+ throw new Error(message);
+ }
+}
+/**
+ * @memberOf module:zrender/core/util
+ * @param {string} str string to be trimed
+ * @return {string} trimed string
+ */
+
+function trim(str) {
+ if (str == null) {
+ return null;
+ } else if (typeof str.trim === 'function') {
+ return str.trim();
+ } else {
+ return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
+ }
+}
+var primitiveKey = '__ec_primitive__';
+/**
+ * Set an object as primitive to be ignored traversing children in clone or merge
+ */
+
+function setAsPrimitive(obj) {
+ obj[primitiveKey] = true;
+}
+function isPrimitive(obj) {
+ return obj[primitiveKey];
+}
+/**
+ * @constructor
+ * @param {Object} obj Only apply `ownProperty`.
+ */
+
+function HashMap(obj) {
+ var isArr = isArray(obj); // Key should not be set on this, otherwise
+ // methods get/set/... may be overrided.
+
+ this.data = {};
+ var thisMap = this;
+ obj instanceof HashMap ? obj.each(visit) : obj && each$1(obj, visit);
+
+ function visit(value, key) {
+ isArr ? thisMap.set(value, key) : thisMap.set(key, value);
+ }
+}
+
+HashMap.prototype = {
+ constructor: HashMap,
+ // Do not provide `has` method to avoid defining what is `has`.
+ // (We usually treat `null` and `undefined` as the same, different
+ // from ES6 Map).
+ get: function (key) {
+ return this.data.hasOwnProperty(key) ? this.data[key] : null;
+ },
+ set: function (key, value) {
+ // Comparing with invocation chaining, `return value` is more commonly
+ // used in this case: `var someVal = map.set('a', genVal());`
+ return this.data[key] = value;
+ },
+ // Although util.each can be performed on this hashMap directly, user
+ // should not use the exposed keys, who are prefixed.
+ each: function (cb, context) {
+ context !== void 0 && (cb = bind(cb, context));
+
+ for (var key in this.data) {
+ this.data.hasOwnProperty(key) && cb(this.data[key], key);
+ }
+ },
+ // Do not use this method if performance sensitive.
+ removeKey: function (key) {
+ delete this.data[key];
+ }
+};
+function createHashMap(obj) {
+ return new HashMap(obj);
+}
+function concatArray(a, b) {
+ var newArray = new a.constructor(a.length + b.length);
+
+ for (var i = 0; i < a.length; i++) {
+ newArray[i] = a[i];
+ }
+
+ var offset = a.length;
+
+ for (i = 0; i < b.length; i++) {
+ newArray[i + offset] = b[i];
+ }
+
+ return newArray;
+}
+function noop() {}
+
+var zrUtil = (Object.freeze || Object)({
+ $override: $override,
+ clone: clone,
+ merge: merge,
+ mergeAll: mergeAll,
+ extend: extend,
+ defaults: defaults,
+ createCanvas: createCanvas,
+ getContext: getContext,
+ indexOf: indexOf,
+ inherits: inherits,
+ mixin: mixin,
+ isArrayLike: isArrayLike,
+ each: each$1,
+ map: map,
+ reduce: reduce,
+ filter: filter,
+ find: find,
+ bind: bind,
+ curry: curry,
+ isArray: isArray,
+ isFunction: isFunction$1,
+ isString: isString,
+ isObject: isObject$1,
+ isBuiltInObject: isBuiltInObject,
+ isTypedArray: isTypedArray,
+ isDom: isDom,
+ eqNaN: eqNaN,
+ retrieve: retrieve,
+ retrieve2: retrieve2,
+ retrieve3: retrieve3,
+ slice: slice,
+ normalizeCssArray: normalizeCssArray,
+ assert: assert$1,
+ trim: trim,
+ setAsPrimitive: setAsPrimitive,
+ isPrimitive: isPrimitive,
+ createHashMap: createHashMap,
+ concatArray: concatArray,
+ noop: noop
+});
+
+var ArrayCtor = typeof Float32Array === 'undefined' ? Array : Float32Array;
+/**
+ * 创建一个向量
+ * @param {number} [x=0]
+ * @param {number} [y=0]
+ * @return {Vector2}
+ */
+
+function create(x, y) {
+ var out = new ArrayCtor(2);
+
+ if (x == null) {
+ x = 0;
+ }
+
+ if (y == null) {
+ y = 0;
+ }
+
+ out[0] = x;
+ out[1] = y;
+ return out;
+}
+/**
+ * 复制向量数据
+ * @param {Vector2} out
+ * @param {Vector2} v
+ * @return {Vector2}
+ */
+
+function copy(out, v) {
+ out[0] = v[0];
+ out[1] = v[1];
+ return out;
+}
+/**
+ * 克隆一个向量
+ * @param {Vector2} v
+ * @return {Vector2}
+ */
+
+function clone$1(v) {
+ var out = new ArrayCtor(2);
+ out[0] = v[0];
+ out[1] = v[1];
+ return out;
+}
+/**
+ * 设置向量的两个项
+ * @param {Vector2} out
+ * @param {number} a
+ * @param {number} b
+ * @return {Vector2} 结果
+ */
+
+function set(out, a, b) {
+ out[0] = a;
+ out[1] = b;
+ return out;
+}
+/**
+ * 向量相加
+ * @param {Vector2} out
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ */
+
+function add(out, v1, v2) {
+ out[0] = v1[0] + v2[0];
+ out[1] = v1[1] + v2[1];
+ return out;
+}
+/**
+ * 向量缩放后相加
+ * @param {Vector2} out
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ * @param {number} a
+ */
+
+function scaleAndAdd(out, v1, v2, a) {
+ out[0] = v1[0] + v2[0] * a;
+ out[1] = v1[1] + v2[1] * a;
+ return out;
+}
+/**
+ * 向量相减
+ * @param {Vector2} out
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ */
+
+function sub(out, v1, v2) {
+ out[0] = v1[0] - v2[0];
+ out[1] = v1[1] - v2[1];
+ return out;
+}
+/**
+ * 向量长度
+ * @param {Vector2} v
+ * @return {number}
+ */
+
+function len(v) {
+ return Math.sqrt(lenSquare(v));
+}
+var length = len; // jshint ignore:line
+
+/**
+ * 向量长度平方
+ * @param {Vector2} v
+ * @return {number}
+ */
+
+function lenSquare(v) {
+ return v[0] * v[0] + v[1] * v[1];
+}
+var lengthSquare = lenSquare;
+/**
+ * 向量乘法
+ * @param {Vector2} out
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ */
+
+function mul(out, v1, v2) {
+ out[0] = v1[0] * v2[0];
+ out[1] = v1[1] * v2[1];
+ return out;
+}
+/**
+ * 向量除法
+ * @param {Vector2} out
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ */
+
+function div(out, v1, v2) {
+ out[0] = v1[0] / v2[0];
+ out[1] = v1[1] / v2[1];
+ return out;
+}
+/**
+ * 向量点乘
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ * @return {number}
+ */
+
+function dot(v1, v2) {
+ return v1[0] * v2[0] + v1[1] * v2[1];
+}
+/**
+ * 向量缩放
+ * @param {Vector2} out
+ * @param {Vector2} v
+ * @param {number} s
+ */
+
+function scale(out, v, s) {
+ out[0] = v[0] * s;
+ out[1] = v[1] * s;
+ return out;
+}
+/**
+ * 向量归一化
+ * @param {Vector2} out
+ * @param {Vector2} v
+ */
+
+function normalize(out, v) {
+ var d = len(v);
+
+ if (d === 0) {
+ out[0] = 0;
+ out[1] = 0;
+ } else {
+ out[0] = v[0] / d;
+ out[1] = v[1] / d;
+ }
+
+ return out;
+}
+/**
+ * 计算向量间距离
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ * @return {number}
+ */
+
+function distance(v1, v2) {
+ return Math.sqrt((v1[0] - v2[0]) * (v1[0] - v2[0]) + (v1[1] - v2[1]) * (v1[1] - v2[1]));
+}
+var dist = distance;
+/**
+ * 向量距离平方
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ * @return {number}
+ */
+
+function distanceSquare(v1, v2) {
+ return (v1[0] - v2[0]) * (v1[0] - v2[0]) + (v1[1] - v2[1]) * (v1[1] - v2[1]);
+}
+var distSquare = distanceSquare;
+/**
+ * 求负向量
+ * @param {Vector2} out
+ * @param {Vector2} v
+ */
+
+function negate(out, v) {
+ out[0] = -v[0];
+ out[1] = -v[1];
+ return out;
+}
+/**
+ * 插值两个点
+ * @param {Vector2} out
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ * @param {number} t
+ */
+
+function lerp(out, v1, v2, t) {
+ out[0] = v1[0] + t * (v2[0] - v1[0]);
+ out[1] = v1[1] + t * (v2[1] - v1[1]);
+ return out;
+}
+/**
+ * 矩阵左乘向量
+ * @param {Vector2} out
+ * @param {Vector2} v
+ * @param {Vector2} m
+ */
+
+function applyTransform(out, v, m) {
+ var x = v[0];
+ var y = v[1];
+ out[0] = m[0] * x + m[2] * y + m[4];
+ out[1] = m[1] * x + m[3] * y + m[5];
+ return out;
+}
+/**
+ * 求两个向量最小值
+ * @param {Vector2} out
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ */
+
+function min(out, v1, v2) {
+ out[0] = Math.min(v1[0], v2[0]);
+ out[1] = Math.min(v1[1], v2[1]);
+ return out;
+}
+/**
+ * 求两个向量最大值
+ * @param {Vector2} out
+ * @param {Vector2} v1
+ * @param {Vector2} v2
+ */
+
+function max(out, v1, v2) {
+ out[0] = Math.max(v1[0], v2[0]);
+ out[1] = Math.max(v1[1], v2[1]);
+ return out;
+}
+
+var vector = (Object.freeze || Object)({
+ create: create,
+ copy: copy,
+ clone: clone$1,
+ set: set,
+ add: add,
+ scaleAndAdd: scaleAndAdd,
+ sub: sub,
+ len: len,
+ length: length,
+ lenSquare: lenSquare,
+ lengthSquare: lengthSquare,
+ mul: mul,
+ div: div,
+ dot: dot,
+ scale: scale,
+ normalize: normalize,
+ distance: distance,
+ dist: dist,
+ distanceSquare: distanceSquare,
+ distSquare: distSquare,
+ negate: negate,
+ lerp: lerp,
+ applyTransform: applyTransform,
+ min: min,
+ max: max
+});
+
+// TODO Draggable for group
+// FIXME Draggable on element which has parent rotation or scale
+function Draggable() {
+ this.on('mousedown', this._dragStart, this);
+ this.on('mousemove', this._drag, this);
+ this.on('mouseup', this._dragEnd, this);
+ this.on('globalout', this._dragEnd, this); // this._dropTarget = null;
+ // this._draggingTarget = null;
+ // this._x = 0;
+ // this._y = 0;
+}
+
+Draggable.prototype = {
+ constructor: Draggable,
+ _dragStart: function (e) {
+ var draggingTarget = e.target;
+
+ if (draggingTarget && draggingTarget.draggable) {
+ this._draggingTarget = draggingTarget;
+ draggingTarget.dragging = true;
+ this._x = e.offsetX;
+ this._y = e.offsetY;
+ this.dispatchToElement(param(draggingTarget, e), 'dragstart', e.event);
+ }
+ },
+ _drag: function (e) {
+ var draggingTarget = this._draggingTarget;
+
+ if (draggingTarget) {
+ var x = e.offsetX;
+ var y = e.offsetY;
+ var dx = x - this._x;
+ var dy = y - this._y;
+ this._x = x;
+ this._y = y;
+ draggingTarget.drift(dx, dy, e);
+ this.dispatchToElement(param(draggingTarget, e), 'drag', e.event);
+ var dropTarget = this.findHover(x, y, draggingTarget).target;
+ var lastDropTarget = this._dropTarget;
+ this._dropTarget = dropTarget;
+
+ if (draggingTarget !== dropTarget) {
+ if (lastDropTarget && dropTarget !== lastDropTarget) {
+ this.dispatchToElement(param(lastDropTarget, e), 'dragleave', e.event);
+ }
+
+ if (dropTarget && dropTarget !== lastDropTarget) {
+ this.dispatchToElement(param(dropTarget, e), 'dragenter', e.event);
+ }
+ }
+ }
+ },
+ _dragEnd: function (e) {
+ var draggingTarget = this._draggingTarget;
+
+ if (draggingTarget) {
+ draggingTarget.dragging = false;
+ }
+
+ this.dispatchToElement(param(draggingTarget, e), 'dragend', e.event);
+
+ if (this._dropTarget) {
+ this.dispatchToElement(param(this._dropTarget, e), 'drop', e.event);
+ }
+
+ this._draggingTarget = null;
+ this._dropTarget = null;
+ }
+};
+
+function param(target, e) {
+ return {
+ target: target,
+ topTarget: e && e.topTarget
+ };
+}
+
+/**
+ * Event Mixin
+ * @module zrender/mixin/Eventful
+ * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
+ * pissang (https://www.github.com/pissang)
+ */
+var arrySlice = Array.prototype.slice;
+/**
+ * Event dispatcher.
+ *
+ * @alias module:zrender/mixin/Eventful
+ * @constructor
+ * @param {Object} [eventProcessor] The object eventProcessor is the scope when
+ * `eventProcessor.xxx` called.
+ * @param {Function} [eventProcessor.normalizeQuery]
+ * param: {string|Object} Raw query.
+ * return: {string|Object} Normalized query.
+ * @param {Function} [eventProcessor.filter] Event will be dispatched only
+ * if it returns `true`.
+ * param: {string} eventType
+ * param: {string|Object} query
+ * return: {boolean}
+ * @param {Function} [eventProcessor.afterTrigger] Call after all handlers called.
+ * param: {string} eventType
+ */
+
+var Eventful = function (eventProcessor) {
+ this._$handlers = {};
+ this._$eventProcessor = eventProcessor;
+};
+
+Eventful.prototype = {
+ constructor: Eventful,
+
+ /**
+ * The handler can only be triggered once, then removed.
+ *
+ * @param {string} event The event name.
+ * @param {string|Object} [query] Condition used on event filter.
+ * @param {Function} handler The event handler.
+ * @param {Object} context
+ */
+ one: function (event, query, handler, context) {
+ return on(this, event, query, handler, context, true);
+ },
+
+ /**
+ * Bind a handler.
+ *
+ * @param {string} event The event name.
+ * @param {string|Object} [query] Condition used on event filter.
+ * @param {Function} handler The event handler.
+ * @param {Object} [context]
+ */
+ on: function (event, query, handler, context) {
+ return on(this, event, query, handler, context, false);
+ },
+
+ /**
+ * Whether any handler has bound.
+ *
+ * @param {string} event
+ * @return {boolean}
+ */
+ isSilent: function (event) {
+ var _h = this._$handlers;
+ return !_h[event] || !_h[event].length;
+ },
+
+ /**
+ * Unbind a event.
+ *
+ * @param {string} event The event name.
+ * @param {Function} [handler] The event handler.
+ */
+ off: function (event, handler) {
+ var _h = this._$handlers;
+
+ if (!event) {
+ this._$handlers = {};
+ return this;
+ }
+
+ if (handler) {
+ if (_h[event]) {
+ var newList = [];
+
+ for (var i = 0, l = _h[event].length; i < l; i++) {
+ if (_h[event][i].h !== handler) {
+ newList.push(_h[event][i]);
+ }
+ }
+
+ _h[event] = newList;
+ }
+
+ if (_h[event] && _h[event].length === 0) {
+ delete _h[event];
+ }
+ } else {
+ delete _h[event];
+ }
+
+ return this;
+ },
+
+ /**
+ * Dispatch a event.
+ *
+ * @param {string} type The event name.
+ */
+ trigger: function (type) {
+ var _h = this._$handlers[type];
+ var eventProcessor = this._$eventProcessor;
+
+ if (_h) {
+ var args = arguments;
+ var argLen = args.length;
+
+ if (argLen > 3) {
+ args = arrySlice.call(args, 1);
+ }
+
+ var len = _h.length;
+
+ for (var i = 0; i < len;) {
+ var hItem = _h[i];
+
+ if (eventProcessor && eventProcessor.filter && hItem.query != null && !eventProcessor.filter(type, hItem.query)) {
+ i++;
+ continue;
+ } // Optimize advise from backbone
+
+
+ switch (argLen) {
+ case 1:
+ hItem.h.call(hItem.ctx);
+ break;
+
+ case 2:
+ hItem.h.call(hItem.ctx, args[1]);
+ break;
+
+ case 3:
+ hItem.h.call(hItem.ctx, args[1], args[2]);
+ break;
+
+ default:
+ // have more than 2 given arguments
+ hItem.h.apply(hItem.ctx, args);
+ break;
+ }
+
+ if (hItem.one) {
+ _h.splice(i, 1);
+
+ len--;
+ } else {
+ i++;
+ }
+ }
+ }
+
+ eventProcessor && eventProcessor.afterTrigger && eventProcessor.afterTrigger(type);
+ return this;
+ },
+
+ /**
+ * Dispatch a event with context, which is specified at the last parameter.
+ *
+ * @param {string} type The event name.
+ */
+ triggerWithContext: function (type) {
+ var _h = this._$handlers[type];
+ var eventProcessor = this._$eventProcessor;
+
+ if (_h) {
+ var args = arguments;
+ var argLen = args.length;
+
+ if (argLen > 4) {
+ args = arrySlice.call(args, 1, args.length - 1);
+ }
+
+ var ctx = args[args.length - 1];
+ var len = _h.length;
+
+ for (var i = 0; i < len;) {
+ var hItem = _h[i];
+
+ if (eventProcessor && eventProcessor.filter && hItem.query != null && !eventProcessor.filter(type, hItem.query)) {
+ i++;
+ continue;
+ } // Optimize advise from backbone
+
+
+ switch (argLen) {
+ case 1:
+ hItem.h.call(ctx);
+ break;
+
+ case 2:
+ hItem.h.call(ctx, args[1]);
+ break;
+
+ case 3:
+ hItem.h.call(ctx, args[1], args[2]);
+ break;
+
+ default:
+ // have more than 2 given arguments
+ hItem.h.apply(ctx, args);
+ break;
+ }
+
+ if (hItem.one) {
+ _h.splice(i, 1);
+
+ len--;
+ } else {
+ i++;
+ }
+ }
+ }
+
+ eventProcessor && eventProcessor.afterTrigger && eventProcessor.afterTrigger(type);
+ return this;
+ }
+};
+
+function normalizeQuery(host, query) {
+ var eventProcessor = host._$eventProcessor;
+
+ if (query != null && eventProcessor && eventProcessor.normalizeQuery) {
+ query = eventProcessor.normalizeQuery(query);
+ }
+
+ return query;
+}
+
+function on(eventful, event, query, handler, context, isOnce) {
+ var _h = eventful._$handlers;
+
+ if (typeof query === 'function') {
+ context = handler;
+ handler = query;
+ query = null;
+ }
+
+ if (!handler || !event) {
+ return eventful;
+ }
+
+ query = normalizeQuery(eventful, query);
+
+ if (!_h[event]) {
+ _h[event] = [];
+ }
+
+ for (var i = 0; i < _h[event].length; i++) {
+ if (_h[event][i].h === handler) {
+ return eventful;
+ }
+ }
+
+ var wrap = {
+ h: handler,
+ one: isOnce,
+ query: query,
+ ctx: context || eventful,
+ // FIXME
+ // Do not publish this feature util it is proved that it makes sense.
+ callAtLast: handler.zrEventfulCallAtLast
+ };
+ var lastIndex = _h[event].length - 1;
+ var lastWrap = _h[event][lastIndex];
+ lastWrap && lastWrap.callAtLast ? _h[event].splice(lastIndex, 0, wrap) : _h[event].push(wrap);
+ return eventful;
+} // ----------------------
+
+/**
+ * 事件辅助类
+ * @module zrender/core/event
+ * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
+ */
+var isDomLevel2 = typeof window !== 'undefined' && !!window.addEventListener;
+var MOUSE_EVENT_REG = /^(?:mouse|pointer|contextmenu|drag|drop)|click/;
+
+function getBoundingClientRect(el) {
+ // BlackBerry 5, iOS 3 (original iPhone) don't have getBoundingRect
+ return el.getBoundingClientRect ? el.getBoundingClientRect() : {
+ left: 0,
+ top: 0
+ };
+} // `calculate` is optional, default false
+
+
+function clientToLocal(el, e, out, calculate) {
+ out = out || {}; // According to the W3C Working Draft, offsetX and offsetY should be relative
+ // to the padding edge of the target element. The only browser using this convention
+ // is IE. Webkit uses the border edge, Opera uses the content edge, and FireFox does
+ // not support the properties.
+ // (see http://www.jacklmoore.com/notes/mouse-position/)
+ // In zr painter.dom, padding edge equals to border edge.
+ // FIXME
+ // When mousemove event triggered on ec tooltip, target is not zr painter.dom, and
+ // offsetX/Y is relative to e.target, where the calculation of zrX/Y via offsetX/Y
+ // is too complex. So css-transfrom dont support in this case temporarily.
+
+ if (calculate || !env$1.canvasSupported) {
+ defaultGetZrXY(el, e, out);
+ } // Caution: In FireFox, layerX/layerY Mouse position relative to the closest positioned
+ // ancestor element, so we should make sure el is positioned (e.g., not position:static).
+ // BTW1, Webkit don't return the same results as FF in non-simple cases (like add
+ // zoom-factor, overflow / opacity layers, transforms ...)
+ // BTW2, (ev.offsetY || ev.pageY - $(ev.target).offset().top) is not correct in preserve-3d.
+ //
+ // BTW3, In ff, offsetX/offsetY is always 0.
+ else if (env$1.browser.firefox && e.layerX != null && e.layerX !== e.offsetX) {
+ out.zrX = e.layerX;
+ out.zrY = e.layerY;
+ } // For IE6+, chrome, safari, opera. (When will ff support offsetX?)
+ else if (e.offsetX != null) {
+ out.zrX = e.offsetX;
+ out.zrY = e.offsetY;
+ } // For some other device, e.g., IOS safari.
+ else {
+ defaultGetZrXY(el, e, out);
+ }
+
+ return out;
+}
+
+function defaultGetZrXY(el, e, out) {
+ // This well-known method below does not support css transform.
+ var box = getBoundingClientRect(el);
+ out.zrX = e.clientX - box.left;
+ out.zrY = e.clientY - box.top;
+}
+/**
+ * 如果存在第三方嵌入的一些dom触发的事件,或touch事件,需要转换一下事件坐标.
+ * `calculate` is optional, default false.
+ */
+
+
+function normalizeEvent(el, e, calculate) {
+ e = e || window.event;
+
+ if (e.zrX != null) {
+ return e;
+ }
+
+ var eventType = e.type;
+ var isTouch = eventType && eventType.indexOf('touch') >= 0;
+
+ if (!isTouch) {
+ clientToLocal(el, e, e, calculate);
+ e.zrDelta = e.wheelDelta ? e.wheelDelta / 120 : -(e.detail || 0) / 3;
+ } else {
+ var touch = eventType !== 'touchend' ? e.targetTouches[0] : e.changedTouches[0];
+ touch && clientToLocal(el, touch, e, calculate);
+ } // Add which for click: 1 === left; 2 === middle; 3 === right; otherwise: 0;
+ // See jQuery: https://github.com/jquery/jquery/blob/master/src/event.js
+ // If e.which has been defined, if may be readonly,
+ // see: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which
+
+
+ var button = e.button;
+
+ if (e.which == null && button !== undefined && MOUSE_EVENT_REG.test(e.type)) {
+ e.which = button & 1 ? 1 : button & 2 ? 3 : button & 4 ? 2 : 0;
+ } // [Caution]: `e.which` from browser is not always reliable. For example,
+ // when press left button and `mousemove (pointermove)` in Edge, the `e.which`
+ // is 65536 and the `e.button` is -1. But the `mouseup (pointerup)` and
+ // `mousedown (pointerdown)` is the same as Chrome does.
+
+
+ return e;
+}
+/**
+ * @param {HTMLElement} el
+ * @param {string} name
+ * @param {Function} handler
+ */
+
+function addEventListener(el, name, handler) {
+ if (isDomLevel2) {
+ // Reproduct the console warning:
+ // [Violation] Added non-passive event listener to a scroll-blocking event.
+ // Consider marking event handler as 'passive' to make the page more responsive.
+ // Just set console log level: verbose in chrome dev tool.
+ // then the warning log will be printed when addEventListener called.
+ // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
+ // We have not yet found a neat way to using passive. Because in zrender the dom event
+ // listener delegate all of the upper events of element. Some of those events need
+ // to prevent default. For example, the feature `preventDefaultMouseMove` of echarts.
+ // Before passive can be adopted, these issues should be considered:
+ // (1) Whether and how a zrender user specifies an event listener passive. And by default,
+ // passive or not.
+ // (2) How to tread that some zrender event listener is passive, and some is not. If
+ // we use other way but not preventDefault of mousewheel and touchmove, browser
+ // compatibility should be handled.
+ // var opts = (env.passiveSupported && name === 'mousewheel')
+ // ? {passive: true}
+ // // By default, the third param of el.addEventListener is `capture: false`.
+ // : void 0;
+ // el.addEventListener(name, handler /* , opts */);
+ el.addEventListener(name, handler);
+ } else {
+ el.attachEvent('on' + name, handler);
+ }
+}
+function removeEventListener(el, name, handler) {
+ if (isDomLevel2) {
+ el.removeEventListener(name, handler);
+ } else {
+ el.detachEvent('on' + name, handler);
+ }
+}
+/**
+ * preventDefault and stopPropagation.
+ * Notice: do not do that in zrender. Upper application
+ * do that if necessary.
+ *
+ * @memberOf module:zrender/core/event
+ * @method
+ * @param {Event} e : event对象
+ */
+
+var stop = isDomLevel2 ? function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.cancelBubble = true;
+} : function (e) {
+ e.returnValue = false;
+ e.cancelBubble = true;
+};
+/**
+ * This method only works for mouseup and mousedown. The functionality is restricted
+ * for fault tolerance, See the `e.which` compatibility above.
+ *
+ * @param {MouseEvent} e
+ * @return {boolean}
+ */
+
+
+/**
+ * To be removed.
+ * @deprecated
+ */
+
+ // 做向上兼容
+
+/**
+ * Only implements needed gestures for mobile.
+ */
+var GestureMgr = function () {
+ /**
+ * @private
+ * @type {Array.