From 92cdb06129d6db1f12c16077241dd6eedd0a5f09 Mon Sep 17 00:00:00 2001 From: hengsin Date: Wed, 7 Sep 2022 06:36:43 +0800 Subject: [PATCH] IDEMPIERE-5402 Replace Jfree Chart with Billboard (#1463) - replace jfreechart with https://github.com/naver/billboard.js --- .../server.product.functionaltest.launch | 8 +- .../server.product.launch | 8 +- org.adempiere.ui.zk-feature/feature.xml | 14 + org.adempiere.ui.zk/META-INF/MANIFEST.MF | 3 +- .../OSGI-INF/jfgchartrenderer.xml | 2 +- org.idempiere.test/idempiere.unit.test.launch | 8 +- org.idempiere.zk.billboard.chart/.project | 39 + .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.m2e.core.prefs | 4 + .../.settings/org.eclipse.pde.core.prefs | 3 + .../META-INF/MANIFEST.MF | 27 + ...llboard.chart.ChartRendererServiceImpl.xml | 8 + .../build.properties | 5 + org.idempiere.zk.billboard.chart/pom.xml | 12 + .../zk/billboard/chart/Activator.java | 51 + .../zk/billboard/chart/ChartBuilder.java | 545 + .../chart/ChartRendererServiceImpl.java | 242 + .../chart/PerformanceGraphBuilder.java | 363 + org.idempiere.zk.billboard/.project | 34 + .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.m2e.core.prefs | 4 + .../.settings/org.eclipse.pde.core.prefs | 3 + .../META-INF/MANIFEST.MF | 23 + org.idempiere.zk.billboard/README.md | 7 + org.idempiere.zk.billboard/build.properties | 4 + org.idempiere.zk.billboard/pom.xml | 12 + .../src/metainfo/zk/lang-addon.xml | 25 + .../org/idempiere/zk/billboard/Billboard.java | 469 + .../org/idempiere/zk/billboard/Version.java | 36 + .../src/web/js/zul/billboard/Billboard.js | 270 + .../web/js/zul/billboard/css/billboard.css | 235 + .../js/zul/billboard/ext/billboard.area.js | 153 + .../web/js/zul/billboard/ext/billboard.bar.js | 153 + .../js/zul/billboard/ext/billboard.donut.js | 62 + .../js/zul/billboard/ext/billboard.gauge.js | 56 + .../js/zul/billboard/ext/billboard.line.js | 153 + .../web/js/zul/billboard/ext/billboard.pie.js | 61 + .../js/zul/billboard/ext/billboard.pkgd.js | 25 + .../zul/billboard/ext/billboard.pkgd.src.js | 53016 ++++++++++++++++ .../billboard/ext/billboard.stacked_area.js | 174 + .../billboard/ext/billboard.stacked_bar.js | 174 + .../zul/billboard/ext/billboard.waterfall.js | 158 + .../web/js/zul/billboard/mold/billboard.js | 3 + .../src/web/js/zul/billboard/zk.wpd | 15 + pom.xml | 2 + 45 files changed, 56662 insertions(+), 11 deletions(-) create mode 100644 org.idempiere.zk.billboard.chart/.project create mode 100644 org.idempiere.zk.billboard.chart/.settings/org.eclipse.core.resources.prefs create mode 100644 org.idempiere.zk.billboard.chart/.settings/org.eclipse.m2e.core.prefs create mode 100755 org.idempiere.zk.billboard.chart/.settings/org.eclipse.pde.core.prefs create mode 100644 org.idempiere.zk.billboard.chart/META-INF/MANIFEST.MF create mode 100644 org.idempiere.zk.billboard.chart/OSGI-INF/org.idempiere.zk.billboard.chart.ChartRendererServiceImpl.xml create mode 100644 org.idempiere.zk.billboard.chart/build.properties create mode 100644 org.idempiere.zk.billboard.chart/pom.xml create mode 100644 org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/Activator.java create mode 100644 org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/ChartBuilder.java create mode 100644 org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/ChartRendererServiceImpl.java create mode 100644 org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/PerformanceGraphBuilder.java create mode 100644 org.idempiere.zk.billboard/.project create mode 100644 org.idempiere.zk.billboard/.settings/org.eclipse.core.resources.prefs create mode 100644 org.idempiere.zk.billboard/.settings/org.eclipse.m2e.core.prefs create mode 100755 org.idempiere.zk.billboard/.settings/org.eclipse.pde.core.prefs create mode 100644 org.idempiere.zk.billboard/META-INF/MANIFEST.MF create mode 100644 org.idempiere.zk.billboard/README.md create mode 100644 org.idempiere.zk.billboard/build.properties create mode 100644 org.idempiere.zk.billboard/pom.xml create mode 100644 org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml create mode 100644 org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Billboard.java create mode 100644 org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/Billboard.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/css/billboard.css create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.area.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.bar.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.donut.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.line.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.pie.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.pkgd.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.pkgd.src.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.stacked_area.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.stacked_bar.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.waterfall.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/mold/billboard.js create mode 100644 org.idempiere.zk.billboard/src/web/js/zul/billboard/zk.wpd diff --git a/org.adempiere.server-feature/server.product.functionaltest.launch b/org.adempiere.server-feature/server.product.functionaltest.launch index 7a8babe344..a37df902c7 100644 --- a/org.adempiere.server-feature/server.product.functionaltest.launch +++ b/org.adempiere.server-feature/server.product.functionaltest.launch @@ -183,11 +183,11 @@ + + + - - - @@ -393,6 +393,8 @@ + + diff --git a/org.adempiere.server-feature/server.product.launch b/org.adempiere.server-feature/server.product.launch index 5e1cf188eb..0abcba73f7 100644 --- a/org.adempiere.server-feature/server.product.launch +++ b/org.adempiere.server-feature/server.product.launch @@ -187,11 +187,11 @@ + + + - - - @@ -415,6 +415,8 @@ + + diff --git a/org.adempiere.ui.zk-feature/feature.xml b/org.adempiere.ui.zk-feature/feature.xml index 7955aa5e01..e326803159 100644 --- a/org.adempiere.ui.zk-feature/feature.xml +++ b/org.adempiere.ui.zk-feature/feature.xml @@ -57,4 +57,18 @@ version="0.0.0" unpack="false"/> + + + + diff --git a/org.adempiere.ui.zk/META-INF/MANIFEST.MF b/org.adempiere.ui.zk/META-INF/MANIFEST.MF index c39679ed5a..0ff3a10623 100644 --- a/org.adempiere.ui.zk/META-INF/MANIFEST.MF +++ b/org.adempiere.ui.zk/META-INF/MANIFEST.MF @@ -213,7 +213,8 @@ Require-Bundle: org.adempiere.base;bundle-version="0.0.0", com.sun.activation.jakarta.activation;bundle-version="1.2.1", org.adempiere.base.process, com.github.librepdf.openpdf;bundle-version="1.3.26", - com.github.librepdf.openpdf-fonts-extra;bundle-version="1.3.26" + com.github.librepdf.openpdf-fonts-extra;bundle-version="1.3.26", + org.idempiere.zk.billboard Bundle-Activator: org.adempiere.webui.WebUIActivator Eclipse-ExtensibleAPI: true Web-ContextPath: webui diff --git a/org.adempiere.ui.zk/OSGI-INF/jfgchartrenderer.xml b/org.adempiere.ui.zk/OSGI-INF/jfgchartrenderer.xml index 235c359f1e..fa2495214c 100644 --- a/org.adempiere.ui.zk/OSGI-INF/jfgchartrenderer.xml +++ b/org.adempiere.ui.zk/OSGI-INF/jfgchartrenderer.xml @@ -2,7 +2,7 @@ - + diff --git a/org.idempiere.test/idempiere.unit.test.launch b/org.idempiere.test/idempiere.unit.test.launch index 256b8489c7..2a0a9ef5fa 100644 --- a/org.idempiere.test/idempiere.unit.test.launch +++ b/org.idempiere.test/idempiere.unit.test.launch @@ -172,11 +172,11 @@ + + + - - - @@ -393,6 +393,8 @@ + + diff --git a/org.idempiere.zk.billboard.chart/.project b/org.idempiere.zk.billboard.chart/.project new file mode 100644 index 0000000000..4a2ed66b71 --- /dev/null +++ b/org.idempiere.zk.billboard.chart/.project @@ -0,0 +1,39 @@ + + + org.idempiere.zk.billboard.chart + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.idempiere.zk.billboard.chart/.settings/org.eclipse.core.resources.prefs b/org.idempiere.zk.billboard.chart/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000000..99f26c0203 --- /dev/null +++ b/org.idempiere.zk.billboard.chart/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/org.idempiere.zk.billboard.chart/.settings/org.eclipse.m2e.core.prefs b/org.idempiere.zk.billboard.chart/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000000..f897a7f1cb --- /dev/null +++ b/org.idempiere.zk.billboard.chart/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/org.idempiere.zk.billboard.chart/.settings/org.eclipse.pde.core.prefs b/org.idempiere.zk.billboard.chart/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 0000000000..f29e940a00 --- /dev/null +++ b/org.idempiere.zk.billboard.chart/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/org.idempiere.zk.billboard.chart/META-INF/MANIFEST.MF b/org.idempiere.zk.billboard.chart/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..c981d2fc91 --- /dev/null +++ b/org.idempiere.zk.billboard.chart/META-INF/MANIFEST.MF @@ -0,0 +1,27 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Billboard.js Chart +Bundle-SymbolicName: org.idempiere.zk.billboard.chart +Bundle-Version: 10.0.0.qualifier +Bundle-Activator: org.idempiere.zk.billboard.chart.Activator +Bundle-Vendor: iDempiere +Bundle-RequiredExecutionEnvironment: JavaSE-11 +Automatic-Module-Name: org.idempiere.zk.billboard.chart +Import-Package: org.osgi.framework, + org.osgi.service.component, + org.osgi.service.component.annotations +Bundle-ActivationPolicy: lazy +Require-Bundle: org.adempiere.ui.zk, + org.adempiere.base, + zul, + zcommon, + zel, + zhtml, + zjavassist, + zk, + zkbind, + zkplus, + zkwebfragment, + zweb, + org.idempiere.zk.billboard +Service-Component: OSGI-INF/org.idempiere.zk.billboard.chart.ChartRendererServiceImpl.xml diff --git a/org.idempiere.zk.billboard.chart/OSGI-INF/org.idempiere.zk.billboard.chart.ChartRendererServiceImpl.xml b/org.idempiere.zk.billboard.chart/OSGI-INF/org.idempiere.zk.billboard.chart.ChartRendererServiceImpl.xml new file mode 100644 index 0000000000..16a3686729 --- /dev/null +++ b/org.idempiere.zk.billboard.chart/OSGI-INF/org.idempiere.zk.billboard.chart.ChartRendererServiceImpl.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/org.idempiere.zk.billboard.chart/build.properties b/org.idempiere.zk.billboard.chart/build.properties new file mode 100644 index 0000000000..67e6565494 --- /dev/null +++ b/org.idempiere.zk.billboard.chart/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = target/classes/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/ diff --git a/org.idempiere.zk.billboard.chart/pom.xml b/org.idempiere.zk.billboard.chart/pom.xml new file mode 100644 index 0000000000..5894c539bd --- /dev/null +++ b/org.idempiere.zk.billboard.chart/pom.xml @@ -0,0 +1,12 @@ + + 4.0.0 + + org.idempiere + org.idempiere.parent + ${revision} + ../org.idempiere.parent/pom.xml + + org.idempiere.zk.billboard.chart + eclipse-plugin + diff --git a/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/Activator.java b/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/Activator.java new file mode 100644 index 0000000000..4410098983 --- /dev/null +++ b/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/Activator.java @@ -0,0 +1,51 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - hengsin * + **********************************************************************/ +package org.idempiere.zk.billboard.chart; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +/** + * + * @author hengsin + * + */ +public class Activator implements BundleActivator { + + private static BundleContext context; + + static BundleContext getContext() { + return context; + } + + public void start(BundleContext bundleContext) throws Exception { + Activator.context = bundleContext; + } + + public void stop(BundleContext bundleContext) throws Exception { + Activator.context = null; + } + +} diff --git a/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/ChartBuilder.java b/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/ChartBuilder.java new file mode 100644 index 0000000000..b79eda8d85 --- /dev/null +++ b/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/ChartBuilder.java @@ -0,0 +1,545 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - hengsin * + **********************************************************************/ +package org.idempiere.zk.billboard.chart; + +import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import org.adempiere.exceptions.DBException; +import org.compiere.model.MChart; +import org.compiere.model.MChartDatasource; +import org.compiere.model.MQuery; +import org.compiere.model.MRole; +import org.compiere.model.MTable; +import org.compiere.util.CLogger; +import org.compiere.util.DB; +import org.compiere.util.Env; +import org.compiere.util.Util; +import org.idempiere.zk.billboard.Billboard; +import org.zkoss.zul.CategoryModel; +import org.zkoss.zul.ChartModel; +import org.zkoss.zul.PieModel; +import org.zkoss.zul.SimpleCategoryModel; +import org.zkoss.zul.SimplePieModel; +import org.zkoss.zul.SimpleXYModel; +import org.zkoss.zul.XYModel; + +/** + * Render AD_Chart using zk-billboard. + * Note: 3d chart not supported by zk-billboard + * @author hengsin + * + */ +public class ChartBuilder { + + private final static CLogger log = CLogger.getCLogger(ChartBuilder.class); + protected final SimpleDateFormat tsDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + + private MChart mChart; + private HashMap queries; + private ChartModel chartModel; + private Date minDate; + private Date maxDate; + + public ChartBuilder(MChart chart) { + this.mChart = chart; + } + + /** + * + * @param type + * @return Billboard + */ + public Billboard createChart() { + String type = mChart.getChartType(); + + if (MChart.CHARTTYPE_BarChart.equals(type)) + { + if (mChart.isTimeSeries()) + return createXYBarChart(); + return createBarChart(); + } + else if (MChart.CHARTTYPE_3DBarChart.equals(type)) + { + return create3DBarChart(); + } + else if (MChart.CHARTTYPE_StackedBarChart.equals(type)) + { + if (mChart.isTimeSeries()) + return createXYBarChart(); + return createStackedBarChart(); + } + else if (MChart.CHARTTYPE_3DStackedBarChart.equals(type)) + { + return create3DStackedBarChart(); + } + else if (MChart.CHARTTYPE_3DPieChart.equals(type)) + { + return create3DPieChart(); + } + else if (MChart.CHARTTYPE_PieChart.equals(type)) + { + return createPieChart(); + } + else if (MChart.CHARTTYPE_3DLineChart.equals(type)) + { + return create3DLineChart(); + } + else if (MChart.CHARTTYPE_AreaChart.equals(type)) + { + return createAreaChart(); + } + else if (MChart.CHARTTYPE_StackedAreaChart.equals(type)) + { + return createStackedAreaChart(); + } + else if (MChart.CHARTTYPE_LineChart.equals(type)) + { + if (mChart.isTimeSeries()) + return createTimeSeriesChart(); + return createLineChart(); + } + else if (MChart.CHARTTYPE_RingChart.equals(type)) + { + return createRingChart(); + } + else if (MChart.CHARTTYPE_WaterfallChart.equals(type)) + { + return createWaterfallChart(); + } + else + { + throw new IllegalArgumentException("unknown chart type=" + type); + } + } + + public void loadData() { + queries = new HashMap(); + for ( MChartDatasource ds : mChart.getDatasources() ) + { + addData(ds); + } + } + + private void addData(MChartDatasource ds) { + + String value = ds.getValueColumn(); + String category; + String unit = "D"; + + if ( !mChart.isTimeSeries() ) + category = ds.getCategoryColumn(); + else + { + if ( mChart.getTimeUnit().equals(MChart.TIMEUNIT_Week)) + { + unit = "W"; + } + else if ( mChart.getTimeUnit().equals(MChart.TIMEUNIT_Month)) + { + unit = "MM"; + } + else if ( mChart.getTimeUnit().equals(MChart.TIMEUNIT_Quarter)) + { + unit = "Q"; + } + else if ( mChart.getTimeUnit().equals(MChart.TIMEUNIT_Year)) + { + unit = "Y"; + } + + category = " TRUNC(" + ds.getDateColumn() + ", '" + unit + "') "; + } + + String series = DB.TO_STRING(ds.getName()); + boolean hasSeries = false; + if (ds.getSeriesColumn() != null) + { + series = ds.getSeriesColumn(); + hasSeries = true; + } + + String where = ds.getWhereClause(); + if ( !Util.isEmpty(where)) + { + where = Env.parseContext(Env.getCtx(), mChart.getWindowNo(), where, true); + } + + boolean hasWhere = false; + + String sql = "SELECT " + value + ", " + category + ", " + series + + " FROM " + ds.getFromClause(); + if ( !Util.isEmpty(where)) + { + sql += " WHERE " + where; + hasWhere = true; + } + + Date currentDate = Env.getContextAsDate(Env.getCtx(), "#Date"); + Date startDate = null; + Date endDate = null; + + int scope = mChart.getTimeScope(); + int offset = ds.getTimeOffset(); + + if ( mChart.isTimeSeries() && scope != 0 ) + { + offset += -scope; + startDate = increment(currentDate, mChart.getTimeUnit(), offset); + endDate = increment(startDate, mChart.getTimeUnit(), scope); + } + + if ( startDate != null && endDate != null ) + { + sql += hasWhere ? " AND " : " WHERE "; + sql += category + ">=TRUNC(" + DB.TO_DATE(new Timestamp(startDate.getTime())) + ", '" + unit + "') AND "; + sql += category + "<=TRUNC(" + DB.TO_DATE(new Timestamp(endDate.getTime())) + ", '" + unit + "') "; + } + + if (sql.indexOf('@') >= 0) { + sql = Env.parseContext(Env.getCtx(), 0, sql, false, true); + } + + MRole role = MRole.getDefault(Env.getCtx(), false); + sql = role.addAccessSQL(sql, null, true, false); + + if (hasSeries) + sql += " GROUP BY " + series + ", " + category + " ORDER BY " + series + ", " + category; + else + sql += " GROUP BY " + category + " ORDER BY " + category; + + log.log(Level.FINE, sql); + + PreparedStatement pstmt = null; + ResultSet rs = null; + ChartModel dataset = getChartModel(); + minDate = null; + maxDate = null; + + try + { + pstmt = DB.prepareStatement(sql, null); + rs = pstmt.executeQuery(); + while(rs.next()) + { + String key = rs.getString(2); + String seriesName = rs.getString(3); + if (seriesName == null) + seriesName = ds.getName(); + String queryWhere = ""; + if ( hasWhere ) + queryWhere += where + " AND "; + + queryWhere += series + " = " + DB.TO_STRING(seriesName) + " AND " + category + " = " ; + + if (mChart.isTimeSeries()) + { + Date date = rs.getDate(2); + if (minDate == null || minDate.compareTo(date) > 0) + minDate = date; + if (maxDate == null || maxDate.compareTo(date) < 0) + maxDate = date; + } + + if ( mChart.isTimeSeries() && dataset instanceof XYModel ) + { + XYModel xy = (XYModel) dataset; + + Date date = rs.getDate(2); + BigDecimal tsvalue = rs.getBigDecimal(1); + xy.addValue(seriesName, date.getTime(), tsvalue); + key = tsDateFormat.format(date); + queryWhere += DB.TO_DATE(new Timestamp(date.getTime())); + } + else { + queryWhere += DB.TO_STRING(key); + } + + MQuery query = new MQuery(ds.getAD_Table_ID()); + String keyCol = MTable.get(Env.getCtx(), ds.getAD_Table_ID()).getKeyColumns()[0]; + String whereClause = keyCol + " IN (SELECT " + ds.getKeyColumn() + " FROM " + + ds.getFromClause() + " WHERE " + queryWhere + " )"; + query.addRestriction(whereClause.toString()); + query.setRecordCount(1); + + HashMap map = getQueries(); + + if (dataset instanceof PieModel) { + ((PieModel) dataset).setValue(key, rs.getBigDecimal(1)); + map.put(key, query); + } + else if ( dataset instanceof CategoryModel ) { + ((CategoryModel) dataset).setValue(seriesName, key, rs.getBigDecimal(1)); + map.put(seriesName + "__" + key, query); + } + else if (dataset instanceof XYModel ) { + map.put(seriesName + "__" + key, query); + } + } + } + catch (SQLException e) + { + throw new DBException(e, sql); + } + finally + { + DB.close(rs, pstmt); + rs = null; pstmt = null; + } + } + + private Date increment(Date lastDate, String timeUnit, int qty) { + + if ( lastDate == null ) + return null; + + Calendar cal = Calendar.getInstance(); + cal.setTime(lastDate); + + if ( timeUnit.equals(MChart.TIMEUNIT_Day)) + cal.add(Calendar.DAY_OF_YEAR, qty); + else if ( timeUnit.equals(MChart.TIMEUNIT_Week)) + cal.add(Calendar.WEEK_OF_YEAR, qty); + else if ( timeUnit.equals(MChart.TIMEUNIT_Month)) + cal.add(Calendar.MONTH, qty); + else if ( timeUnit.equals(MChart.TIMEUNIT_Quarter)) + cal.add(Calendar.MONTH, 3*qty); + else if ( timeUnit.equals(MChart.TIMEUNIT_Year)) + cal.add(Calendar.YEAR, qty); + + return cal.getTime(); + } + + public CategoryModel getCategoryModel() { + chartModel = new SimpleCategoryModel(); + loadData(); + return (CategoryModel) chartModel; + } + + public XYModel getXYModel() { + chartModel = new SimpleXYModel(); + loadData(); + return (XYModel) chartModel; + } + + public PieModel getPieModel() { + chartModel = new SimplePieModel(); + loadData(); + return (PieModel) chartModel; + } + + public ChartModel getChartModel() { + return chartModel; + } + + public HashMap getQueries() { + return queries; + } + + public MQuery getQuery(String key) { + + + if ( queries.containsKey(key) ) + { + return queries.get(key); + } + + return null; + } + + private Billboard createXYBarChart() { + Billboard billboard = newBillboard("bar"); + XYModel xymodel = getXYModel(); + CategoryModel model = new SimpleCategoryModel(); + Collection> seriesList = xymodel.getSeries(); + for(Comparable series : seriesList) { + int count = xymodel.getDataCount(series); + for(int i = 0; i < count; i++) { + Number value = xymodel.getY(series, i); + Number category = xymodel.getX(series, i); + Date date = new Date(category.longValue()); + String categoryLabel = null; + categoryLabel = tsDateFormat.format(date); + Number oldValue = model.getValue(series, categoryLabel); + if (oldValue != null) + value = oldValue.doubleValue() + value.doubleValue(); + model.setValue(series, categoryLabel, value); + } + } + billboard.setModel(model); + return billboard; + } + + private Billboard createTimeSeriesChart() { + Billboard billboard = newBillboard("line"); + XYModel xymodel = getXYModel(); + CategoryModel model = new SimpleCategoryModel(); + Collection> seriesList = xymodel.getSeries(); + for(Comparable series : seriesList) { + int count = xymodel.getDataCount(series); + for(int i = 0; i < count; i++) { + Number value = xymodel.getY(series, i); + Number category = xymodel.getX(series, i); + Date date = new Date(category.longValue()); + String categoryLabel = null; + categoryLabel = tsDateFormat.format(date); + Number oldValue = model.getValue(series, categoryLabel); + if (oldValue != null) + value = oldValue.doubleValue() + value.doubleValue(); + model.setValue(series, categoryLabel, value); + } + } + billboard.setModel(model); + return billboard; + } + + private Billboard createWaterfallChart() { + Billboard billboard = newBillboard("waterfall"); + CategoryModel model = getCategoryModel(); + CategoryModel waterfallModel = new SimpleCategoryModel(); + Collection> seriesList = model.getSeries(); + Map, BigDecimal> valueMap = new HashMap, BigDecimal>(); + Collection> categories = model.getCategories(); + for(Comparable series : seriesList) { + for(Comparable category : categories) { + BigDecimal value = (BigDecimal) model.getValue(series, category); + BigDecimal diff = value; + BigDecimal oldValue = valueMap.get(series); + if (oldValue != null) { + diff = diff.subtract(oldValue); + } + valueMap.put(series, value); + waterfallModel.setValue(series, category, diff); + } + } + billboard.setModel(waterfallModel); + return billboard; + } + + private Billboard newBillboard(String type) { + Billboard billboard = new Billboard(); + if (mChart.isDisplayLegend()) { + billboard.setLegend(true, false); + billboard.addLegendOptions("location", "bottom"); //bottom, right + } + billboard.setTickAxisLabel(mChart.getDomainLabel()); + billboard.setValueAxisLabel(mChart.getRangeLabel()); + billboard.setTitle(mChart.getName()); + billboard.setType(type); + return billboard; + } + + private Billboard createRingChart() { + Billboard billboard = newBillboard("donut"); + billboard.setModel(getPieModel()); + return billboard; + } + + private Billboard createPieChart() { + Billboard billboard = newBillboard("pie"); + billboard.setModel(getPieModel()); + return billboard; + } + + private Billboard create3DPieChart() { + return createPieChart(); + } + + private Billboard createBarChart() { + Billboard billboard = newBillboard("bar"); + billboard.setModel(getCategoryModel()); + if (MChart.CHARTORIENTATION_Vertical.equals(mChart.getChartOrientation())) + billboard.setOrient(Billboard.VERTICAL_ORIENTATION); + else if (MChart.CHARTORIENTATION_Horizontal.equals(mChart.getChartOrientation())) + billboard.setOrient(Billboard.HORIZONTAL_ORIENTATION); + return billboard; + } + + private Billboard create3DBarChart() { + return createBarChart(); + } + + private Billboard createStackedBarChart() { + Billboard billboard = newBillboard("stacked_bar"); + billboard.setModel(getCategoryModel()); + if (MChart.CHARTORIENTATION_Vertical.equals(mChart.getChartOrientation())) + billboard.setOrient(Billboard.VERTICAL_ORIENTATION); + else if (MChart.CHARTORIENTATION_Horizontal.equals(mChart.getChartOrientation())) + billboard.setOrient(Billboard.HORIZONTAL_ORIENTATION); + return billboard; + } + + private Billboard create3DStackedBarChart() { + return createStackedBarChart(); + } + + private Billboard createAreaChart() { + Billboard billboard = newBillboard("area"); + billboard.setModel(getCategoryModel()); + return billboard; + } + + private Billboard createStackedAreaChart() { + Billboard billboard = newBillboard("stacked_area"); + billboard.setModel(getCategoryModel()); + return billboard; + } + + private Billboard createLineChart() { + Billboard billboard = newBillboard("line"); + billboard.setModel(getCategoryModel()); + return billboard; + } + + private Billboard create3DLineChart() { + return createLineChart(); + } + + public Date getMinDate() { + return minDate; + } + + public void setMinDate(Date minDate) { + this.minDate = minDate; + } + + public Date getMaxDate() { + return maxDate; + } + + public void setMaxDate(Date maxDate) { + this.maxDate = maxDate; + } +} diff --git a/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/ChartRendererServiceImpl.java b/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/ChartRendererServiceImpl.java new file mode 100644 index 0000000000..97e632a300 --- /dev/null +++ b/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/ChartRendererServiceImpl.java @@ -0,0 +1,242 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - hengsin * + **********************************************************************/ +package org.idempiere.zk.billboard.chart; + +import java.util.Calendar; +import java.util.Date; +import java.util.Map; + +import org.adempiere.webui.apps.AEnv; +import org.adempiere.webui.apps.graph.IChartRendererService; +import org.adempiere.webui.apps.graph.model.ChartModel; +import org.adempiere.webui.apps.graph.model.GoalModel; +import org.adempiere.webui.apps.graph.model.IndicatorModel; +import org.adempiere.webui.component.Label; +import org.compiere.model.MChart; +import org.compiere.model.MQuery; +import org.compiere.model.MSysConfig; +import org.compiere.util.Msg; +import org.idempiere.zk.billboard.Billboard; +import org.zkoss.json.JSONObject; +import org.zkoss.zk.au.out.AuScript; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.Executions; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.util.Clients; +import org.zkoss.zul.CategoryModel; +import org.zkoss.zul.Div; +import org.zkoss.zul.PieModel; + +/** + * @author hengsin + * + */ +@org.osgi.service.component.annotations.Component(name="org.idempiere.zk.billboard.chart.ChartRendererServiceImpl", immediate = true, +service = IChartRendererService.class, property = {"service.ranking:Integer=0"}) +public class ChartRendererServiceImpl implements IChartRendererService { + + /** + * + */ + public ChartRendererServiceImpl() { + } + + /* (non-Javadoc) + */ + @Override + public boolean renderPerformanceIndicator(Component parent, int chartWidth, int chartHeight, IndicatorModel model) { + PerformanceGraphBuilder builder = new PerformanceGraphBuilder(); + Billboard billboard = builder.createIndicatorChart(model); + billboard.setStyle("width: "+chartWidth+"px;height: "+chartHeight+"px;"); + parent.appendChild(billboard); + + return true; + } + + @Override + public boolean renderPerformanceGraph(Component parent, int chartWidth, int chartHeight, + GoalModel goalModel) { + PerformanceGraphBuilder builder = new PerformanceGraphBuilder(); + Billboard billboard = builder.createPerformanceChart(goalModel, chartWidth, chartHeight); + parent.appendChild(billboard); + + return true; + } + + @Override + public boolean renderChart(Component parent, int width, int height, + ChartModel chartModel, boolean showTitle) { + ChartBuilder builder = new ChartBuilder(chartModel.chart); + Billboard billboard = builder.createChart(); + billboard.setStyle("width: " + width + "px;" + " height: " + height + "px;"); + if (!showTitle) + billboard.setTitle(""); + updateUI(parent, chartModel, builder, billboard, width, height); + + return true; + } + + private void updateUI(Component parent, ChartModel chartModel, ChartBuilder builder, Billboard billboard, int width, int height) { + // set billboard time series properties + Date minDate = builder.getMinDate(); + Date maxDate = builder.getMaxDate(); + if (chartModel.chart.isTimeSeries() && minDate != null && maxDate != null) + { + billboard.setTimeSeries(true); + + int noOfPeriod = 0; + if (width < MSysConfig.getIntValue("CHART_MIN_WIDTH_3_PERIOD", 230, chartModel.chart.getAD_Client_ID())) + noOfPeriod = 3; + else if (width < MSysConfig.getIntValue("CHART_MIN_WIDTH_6_PERIOD", 320, chartModel.chart.getAD_Client_ID())) + noOfPeriod = 6; + + Calendar c = Calendar.getInstance(); + c.setTime(maxDate); + + String timeUnit = chartModel.chart.getTimeUnit(); + if (chartModel.chart.getTimeScope() == 1) + { + if (timeUnit.equals(MChart.TIMEUNIT_Week)) + timeUnit = MChart.TIMEUNIT_Day; + else if (timeUnit.equals(MChart.TIMEUNIT_Month)) + timeUnit = MChart.TIMEUNIT_Week; + else if (timeUnit.equals(MChart.TIMEUNIT_Quarter)) + timeUnit = MChart.TIMEUNIT_Month; + else if (timeUnit.equals(MChart.TIMEUNIT_Year)) + timeUnit = MChart.TIMEUNIT_Quarter; + } + + if (timeUnit.equals(MChart.TIMEUNIT_Day)) + { + billboard.setTimeSeriesInterval("1 days"); + billboard.setTimeSeriesFormat("%D"); // e.g. 03/26/08 %m/%d/%y + if (noOfPeriod != 0) + c.add(Calendar.DAY_OF_MONTH, -1 * (noOfPeriod - 1)); + } + else if (timeUnit.equals(MChart.TIMEUNIT_Week)) + { + billboard.setTimeSeriesInterval("1 weeks"); + billboard.setTimeSeriesFormat("%D"); // e.g. 03/26/08 %m/%d/%y + if (noOfPeriod != 0) + c.add(Calendar.WEEK_OF_YEAR, -1 * (noOfPeriod - 1)); + } + else if (timeUnit.equals(MChart.TIMEUNIT_Month)) + { + billboard.setTimeSeriesInterval("1 months"); + billboard.setTimeSeriesFormat("%b %Y"); // e.g. Sep 2008 + if (noOfPeriod != 0) + c.add(Calendar.MONTH, -1 * (noOfPeriod - 1)); + } + else if (timeUnit.equals(MChart.TIMEUNIT_Quarter)) + { + billboard.setTimeSeriesInterval("3 months"); + billboard.setTimeSeriesFormat("%b %Y"); // e.g. Sep 2008 + if (noOfPeriod != 0) + c.add(Calendar.MONTH, -1 * (noOfPeriod - 1) * 3); + } + else if (timeUnit.equals(MChart.TIMEUNIT_Year)) + { + billboard.setTimeSeriesInterval("1 years"); + billboard.setTimeSeriesFormat("%Y"); // e.g. 2008 + if (noOfPeriod != 0) + c.add(Calendar.YEAR, -1 * (noOfPeriod - 1)); + } + + if (noOfPeriod != 0) + { + Date startDate = c.getTime(); + if (minDate.before(startDate)) + minDate = startDate; + } + + } + + parent.getChildren().clear(); + parent.appendChild(billboard); + + Label label = new Label(Msg.translate(chartModel.chart.getCtx(), "NoDataAvailable")); + Div labelDiv = new Div(); + labelDiv.setStyle("padding: 10px;"); + labelDiv.appendChild(label); + Div div = new Div(); + div.appendChild(labelDiv); + parent.appendChild(div); + div.setVisible(false); + + if (Executions.getCurrent() != null) + { + String script = "var parent = jq('#" + parent.getUuid() + "');"; + script += "var billboard = parent.children().first(); "; + script += "var div = parent.children().eq(1); "; + script += "if (billboard.children().length == 0) {"; + script += "div.show(); "; + script += "billboard.hide(); "; + script += "parent.height(div.css('height')); }"; + script += "else {"; + script += "div.hide(); "; + script += "billboard.show(); "; + script += "}"; + Clients.response(new AuScript(script)); + } + + ZoomListener listener = new ZoomListener(builder.getQueries(), billboard.getModel()); + billboard.addEventListener("onDataClick", listener); + } + + private static class ZoomListener implements EventListener { + private Map queries; + private org.zkoss.zul.ChartModel model; + + private ZoomListener(Map queries, org.zkoss.zul.ChartModel model) { + this.queries = queries; + this.model = model; + } + + @Override + public void onEvent(Event event) throws Exception { + JSONObject json = (JSONObject) event.getData(); + Number seriesIndex = (Number) json.get("seriesIndex"); + Number pointIndex = (Number) json.get("pointIndex"); + if (pointIndex == null) + pointIndex = Integer.valueOf(0); + + MQuery query = null; + if (model instanceof PieModel) { + PieModel pieModel = (PieModel) model; + Object category = pieModel.getCategory(pointIndex.intValue()); + if (category != null) + query = queries.get(category.toString()); + } else if (model instanceof CategoryModel) { + CategoryModel categoryModel = (CategoryModel) model; + Object series = categoryModel.getSeries(seriesIndex.intValue()); + Object category = categoryModel.getCategory(pointIndex.intValue()); + query = queries.get(series.toString()+"__"+category.toString()); + } + if (query != null) + AEnv.zoom(query); + } + } +} diff --git a/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/PerformanceGraphBuilder.java b/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/PerformanceGraphBuilder.java new file mode 100644 index 0000000000..edf715c897 --- /dev/null +++ b/org.idempiere.zk.billboard.chart/src/org/idempiere/zk/billboard/chart/PerformanceGraphBuilder.java @@ -0,0 +1,363 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - hengsin * + **********************************************************************/ +package org.idempiere.zk.billboard.chart; + +import java.awt.Font; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import org.adempiere.apps.graph.GraphColumn; +import org.adempiere.webui.apps.AEnv; +import org.adempiere.webui.apps.graph.model.GoalModel; +import org.adempiere.webui.apps.graph.model.IndicatorModel; +import org.adempiere.webui.component.ZkCssHelper; +import org.compiere.model.MColorSchema; +import org.compiere.model.MGoal; +import org.compiere.model.MQuery; +import org.compiere.model.X_PA_Goal; +import org.idempiere.zk.billboard.Billboard; +import org.zkoss.json.JSONObject; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zul.CategoryModel; +import org.zkoss.zul.DialModel; +import org.zkoss.zul.DialModelRange; +import org.zkoss.zul.DialModelScale; +import org.zkoss.zul.PieModel; +import org.zkoss.zul.SimpleCategoryModel; +import org.zkoss.zul.SimplePieModel; + +/** + * + * @author hengsin + * + */ +public class PerformanceGraphBuilder { + + public Billboard createIndicatorChart(IndicatorModel model) { + Billboard billboard = new Billboard(); + billboard.setType("gauge"); + DialModel dialModel = createDialModel(model); + buildDialRendererOptions(billboard, dialModel); + billboard.setModel(dialModel); + return billboard; + } + + private void buildDialRendererOptions(Billboard billboard, DialModel dialModel) { + DialModelScale dialScale = dialModel.getScale(0); + billboard.addRendererOptions("min", 0); + billboard.addRendererOptions("max", dialScale.getScaleUpperBound()); + List intervals = new ArrayList(); + List intervalColors = new ArrayList(); + for(int i = 0; i < dialScale.rangeSize(); i++) { + DialModelRange dialRange = dialScale.getRange(i); + double upperBound = dialRange.getUpperBound(); + intervals.add(upperBound); + intervalColors.add(dialRange.getRangeColor()); + } + List ticks = new ArrayList(intervals); + ticks.add(0, 0d); + billboard.addRendererOptions("ticks", ticks.toArray(new Double[0])); + billboard.addRendererOptions("intervals", intervals.toArray(new Double[0])); + billboard.addRendererOptions("intervalColors", intervalColors.toArray(new String[0])); + billboard.addRendererOptions("tickColor", dialScale.getTickColor()); + billboard.addRendererOptions("background", dialModel.getFrameBgColor()); + } + + private DialModel createDialModel(IndicatorModel model) + { + DialModel dialModel = new DialModel(); + + MColorSchema colorSchema = model.goalModel.getColorSchema(); + int upperBound = 0; + int [] rangeBounds = new int[]{colorSchema.getMark1Percent(), colorSchema.getMark2Percent(), colorSchema.getMark3Percent(), colorSchema.getMark4Percent()}; + for (int rangeBound : rangeBounds) + { + if (rangeBound > upperBound) + { + if (rangeBound == 9999) + { + upperBound = (int) Math.floor(upperBound*1.5); + break; + } + else + { + upperBound = rangeBound; + } + } + } + DialModelScale dialScale = dialModel.newScale(0, upperBound, 180, -180, colorSchema.getMark2Percent() - colorSchema.getMark1Percent(), 5); + int rangeLo = 0; + for (int rangeHi : rangeBounds){ + if (rangeHi==9999) + rangeHi = (int) Math.floor(rangeLo*1.5); + if (rangeLo < rangeHi) { + dialScale.newRange(rangeLo, rangeHi, "#"+ZkCssHelper.createHexColorString(colorSchema.getColor(rangeHi)), 0.5, 0.5); + rangeLo = rangeHi; + } + } + + dialModel.setFrameBgColor("#"+ZkCssHelper.createHexColorString(model.dialBackground)); + dialScale.setTickFont(new Font("SansSerif", Font.BOLD, 8)); + dialScale.setValueFont(new Font("SansSerif", Font.BOLD, 8)); + dialModel.setFrameFgColor("#000000"); + dialScale.setTickColor("#"+ZkCssHelper.createHexColorString(model.tickColor)); + // + dialScale.setValue(model.goalModel.getPercent()); + return dialModel; + } + + public Billboard createPerformanceChart(GoalModel goalModel, int chartWidth, int chartHeight) { + if(X_PA_Goal.CHARTTYPE_BarChart.equals(goalModel.chartType)) + { + return createBarChart(goalModel, chartWidth, chartHeight); + } + else if (X_PA_Goal.CHARTTYPE_PieChart.equals(goalModel.chartType)) + { + return createPieChart(goalModel, chartWidth, chartHeight); + } + else if (X_PA_Goal.CHARTTYPE_AreaChart.equals(goalModel.chartType)) + { + return createAreaChart(goalModel, chartWidth, chartHeight); + } + else if (X_PA_Goal.CHARTTYPE_LineChart.equals(goalModel.chartType)) + { + return createLineChart(goalModel, chartWidth, chartHeight); + } + else if (X_PA_Goal.CHARTTYPE_RingChart.equals(goalModel.chartType)) + { + return createDonutChart(goalModel, chartWidth, chartHeight); + } + else if (X_PA_Goal.CHARTTYPE_WaterfallChart.equals(goalModel.chartType)) + { + return createWaterfallChart(goalModel, chartWidth, chartHeight); + } + else + { + throw new IllegalArgumentException("unknown chart type=" + goalModel.chartType); + } + } + + private Billboard createWaterfallChart(GoalModel goalModel, int chartWidth, + int chartHeight) { + Billboard billboard = new Billboard(); + billboard.setType("waterfall"); + CategoryModel chartModel = createWaterfallModel(goalModel); + billboard.setModel(chartModel); + billboard.setStyle("width: " + chartWidth + "px" + + "; height: "+chartHeight+"px"); + billboard.addEventListener("onDataClick", new ZoomListener(goalModel, chartModel.getCategories().toArray(new Comparable[0]))); + if (goalModel.showTitle) + billboard.setTitle(goalModel.goal.getMeasure().getName()); + billboard.setTickAxisLabel(goalModel.xAxisLabel); + billboard.setValueAxisLabel(goalModel.yAxisLabel); + billboard.setLegend(false, false); + buildRendererOptions(billboard, goalModel); + return billboard; + } + + private Billboard createDonutChart(GoalModel goalModel, int chartWidth, + int chartHeight) { + Billboard billboard = new Billboard(); + billboard.setType("donut"); + PieModel chartModel = new SimplePieModel(); + List list = goalModel.columnList; + for (int i = 0; i < list.size(); i++){ + chartModel.setValue(list.get(i).getLabel(), list.get(i).getValue()); + } + billboard.setModel(chartModel); + billboard.setStyle("width: " + chartWidth + "px" + + "; height: "+chartHeight+"px"); + billboard.setLegend(true, true); + billboard.addEventListener("onDataClick", new ZoomListener(goalModel, chartModel.getCategories().toArray(new Comparable[0]))); + if (goalModel.showTitle) + billboard.setTitle(goalModel.goal.getMeasure().getName()); + return billboard; + } + + private Billboard createLineChart(GoalModel goalModel, int chartWidth, + int chartHeight) { + Billboard billboard = new Billboard(); + billboard.setType("line"); + CategoryModel chartModel = createCategoryModel(goalModel, true); + billboard.setModel(chartModel); + billboard.setStyle("width: " + chartWidth + "px" + + "; height: "+chartHeight+"px"); + billboard.addEventListener("onDataClick", new ZoomListener(goalModel, chartModel.getCategories().toArray(new Comparable[0]))); + if (goalModel.showTitle) + billboard.setTitle(goalModel.goal.getMeasure().getName()); + billboard.setTickAxisLabel(goalModel.xAxisLabel); + billboard.setValueAxisLabel(goalModel.yAxisLabel); + billboard.setLegend(false, false); + buildRendererOptions(billboard, goalModel); + return billboard; + } + + private Billboard createAreaChart(GoalModel goalModel, int chartWidth, + int chartHeight) { + Billboard billboard = new Billboard(); + billboard.setType("area"); + CategoryModel chartModel = createCategoryModel(goalModel, true); + billboard.setModel(chartModel); + billboard.setStyle("width: " + chartWidth + "px" + + "; height: "+chartHeight+"px"); + billboard.addEventListener("onDataClick", new ZoomListener(goalModel, chartModel.getCategories().toArray(new Comparable[0]))); + billboard.setLegend(false, false); + if (goalModel.showTitle) + billboard.setTitle(goalModel.goal.getMeasure().getName()); + billboard.setTickAxisLabel(goalModel.xAxisLabel); + billboard.setValueAxisLabel(goalModel.yAxisLabel); + buildRendererOptions(billboard, goalModel); + return billboard; + } + + private Billboard createPieChart(GoalModel goalModel, int chartWidth, + int chartHeight) { + Billboard billboard = new Billboard(); + billboard.setType("pie"); + PieModel chartModel = new SimplePieModel(); + List list = goalModel.columnList; + for (int i = 0; i < list.size(); i++){ + chartModel.setValue(list.get(i).getLabel(), list.get(i).getValue()); + } + billboard.setModel(chartModel); + billboard.setStyle("width: " + chartWidth + "px" + + "; height: "+chartHeight+"px"); + billboard.addEventListener("onDataClick", new ZoomListener(goalModel, chartModel.getCategories().toArray(new Comparable[0]))); + billboard.setLegend(true, true); + if (goalModel.showTitle) + billboard.setTitle(goalModel.goal.getMeasure().getName()); + return billboard; + } + + private Billboard createBarChart(final GoalModel goalModel, int chartWidth, int chartHeight) { + + Billboard billboard = new Billboard(); + billboard.setRenderdefer(500); + billboard.setType("bar"); + CategoryModel chartModel = createCategoryModel(goalModel, true); + billboard.setModel(chartModel); + billboard.setStyle("width: " + chartWidth + "px" + + "; height: "+chartHeight+"px"); + billboard.addEventListener("onDataClick", new ZoomListener(goalModel, chartModel.getCategories().toArray(new Comparable[0]))); + if (goalModel.showTitle) + billboard.setTitle(goalModel.goal.getMeasure().getName()); + billboard.setTickAxisLabel(goalModel.xAxisLabel); + billboard.setValueAxisLabel(goalModel.yAxisLabel); + billboard.setLegend(false, false); + buildRendererOptions(billboard, goalModel); + return billboard; + } + + private CategoryModel createCategoryModel(GoalModel goalModel, boolean linear) { + CategoryModel chartModel = new SimpleCategoryModel(); + List list = goalModel.columnList; + for (int i = 0; i < list.size(); i++){ + String series = goalModel.xAxisLabel; + if (!linear) { + if (list.get(i).getDate() != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(list.get(i).getDate()); + series = Integer.toString(cal.get(Calendar.YEAR)); + } + } + chartModel.setValue(series, list.get(i).getLabel(), list.get(i).getValue()); + } + return chartModel; + } + + private CategoryModel createWaterfallModel(GoalModel goalModel) { + return createCategoryModel(goalModel, true); + } + + private void buildRendererOptions(Billboard billboard, GoalModel goalModel) { + List intervals = new ArrayList(); + List intervalColors = new ArrayList(); + MColorSchema colorSchema = goalModel.goal.getColorSchema(); + int upperBound = 0; + int [] rangeBounds = new int[]{colorSchema.getMark1Percent(), colorSchema.getMark2Percent(), colorSchema.getMark3Percent(), colorSchema.getMark4Percent()}; + for (int rangeBound : rangeBounds) + { + if (rangeBound > upperBound) + { + if (rangeBound == 9999) + { + upperBound = (int) Math.floor(upperBound*1.5); + break; + } + else + { + upperBound = rangeBound; + } + } + } + int rangeLo = 0; + for (int rangeHi : rangeBounds){ + if (rangeHi==9999) + rangeHi = (int) Math.floor(rangeLo*1.5); + if (rangeLo < rangeHi) { + intervals.add(Double.valueOf(rangeHi)); + intervalColors.add("#"+ZkCssHelper.createHexColorString(colorSchema.getColor(rangeHi))); + rangeLo = rangeHi; + } + } + billboard.addRendererOptions("intervals", intervals.toArray(new Double[0])); + billboard.addRendererOptions("intervalColors", intervalColors.toArray(new String[0])); + } + + private static class ZoomListener implements EventListener { + private GoalModel goalModel; + private Comparable[] categories; + + private ZoomListener(GoalModel goalModel, Comparable[] comparables) { + this.goalModel = goalModel; + this.categories = comparables; + } + + @Override + public void onEvent(Event event) throws Exception { + JSONObject json = (JSONObject) event.getData(); + Number pointIndex = (Number) json.get("pointIndex"); + if (pointIndex == null) + pointIndex = Integer.valueOf(0); + Comparable categoryLabel = categories[pointIndex.intValue()]; + List list = goalModel.columnList; + for (int i = 0; i < list.size(); i++) { + if (list.get(i).getLabel().equals(categoryLabel)) { + zoom(goalModel.goal, list.get(i)); + return; + } + } + } + + private void zoom(MGoal goal, GraphColumn graphColumn) { + MQuery query = graphColumn.getMQuery(goal); + if (query != null) + AEnv.zoom(query); + } + + } +} diff --git a/org.idempiere.zk.billboard/.project b/org.idempiere.zk.billboard/.project new file mode 100644 index 0000000000..c873bc4b4d --- /dev/null +++ b/org.idempiere.zk.billboard/.project @@ -0,0 +1,34 @@ + + + org.idempiere.zk.billboard + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.idempiere.zk.billboard/.settings/org.eclipse.core.resources.prefs b/org.idempiere.zk.billboard/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000000..99f26c0203 --- /dev/null +++ b/org.idempiere.zk.billboard/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/org.idempiere.zk.billboard/.settings/org.eclipse.m2e.core.prefs b/org.idempiere.zk.billboard/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000000..f897a7f1cb --- /dev/null +++ b/org.idempiere.zk.billboard/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/org.idempiere.zk.billboard/.settings/org.eclipse.pde.core.prefs b/org.idempiere.zk.billboard/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 0000000000..f29e940a00 --- /dev/null +++ b/org.idempiere.zk.billboard/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/org.idempiere.zk.billboard/META-INF/MANIFEST.MF b/org.idempiere.zk.billboard/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..a318939cbd --- /dev/null +++ b/org.idempiere.zk.billboard/META-INF/MANIFEST.MF @@ -0,0 +1,23 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Billboard +Bundle-SymbolicName: org.idempiere.zk.billboard +Bundle-Version: 10.0.0.qualifier +Bundle-Vendor: iDempiere +Automatic-Module-Name: org.idempiere.zk.billboard +Bundle-RequiredExecutionEnvironment: JavaSE-11 +Export-Package: metainfo.zk, + org.idempiere.zk.billboard, + web.js.zul.billboard, + web.js.zul.billboard.css, + web.js.zul.billboard.ext, + web.js.zul.billboard.mold +Require-Bundle: zcommon, + zel, + zhtml, + zjavassist, + zk, + zkbind, + zkplus, + zul, + zweb diff --git a/org.idempiere.zk.billboard/README.md b/org.idempiere.zk.billboard/README.md new file mode 100644 index 0000000000..7e9d713702 --- /dev/null +++ b/org.idempiere.zk.billboard/README.md @@ -0,0 +1,7 @@ +# org.idempiere.zk.billboard + +1. Wrap https://github.com/naver/billboard.js as zk component. + +2. To update, replace billboard.pkgd.js and billboard.pkgd.src.js with latest billboard.pkgd.js and billboard.pkgd.min.js from https://github.com/naver/billboard.js (Note that due to naming convention of zk, billboard.pkgd.js=billboard.pkgd.min.js and billboard.pkgd.src.js=billboard.pkgd.js ). + +3. To update, replace billboard.css with latest billboard.css from https://github.com/naver/billboard.js. Add !important to padding and text-align of .bb-tooltip th and padding of .bb-tooltip td to fix conflict with zk css diff --git a/org.idempiere.zk.billboard/build.properties b/org.idempiere.zk.billboard/build.properties new file mode 100644 index 0000000000..56d7765555 --- /dev/null +++ b/org.idempiere.zk.billboard/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = target/classes/ +bin.includes = META-INF/,\ + . diff --git a/org.idempiere.zk.billboard/pom.xml b/org.idempiere.zk.billboard/pom.xml new file mode 100644 index 0000000000..44fa9ef201 --- /dev/null +++ b/org.idempiere.zk.billboard/pom.xml @@ -0,0 +1,12 @@ + + 4.0.0 + + org.idempiere + org.idempiere.parent + ${revision} + ../org.idempiere.parent/pom.xml + + org.idempiere.zk.billboard + eclipse-plugin + diff --git a/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml b/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml new file mode 100644 index 0000000000..22391a83e6 --- /dev/null +++ b/org.idempiere.zk.billboard/src/metainfo/zk/lang-addon.xml @@ -0,0 +1,25 @@ + + + + billboard + zul + xul/html + + org.idempiere.zk.billboard.Version + 3.5.1.20220905 + + + billboard + org.idempiere.zk.billboard.Billboard + + default + zul.billboard.Billboard + mold/billboard.js + + + + + + + + \ No newline at end of file diff --git a/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Billboard.java b/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Billboard.java new file mode 100644 index 0000000000..45cbb65f0e --- /dev/null +++ b/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Billboard.java @@ -0,0 +1,469 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - hengsin * + **********************************************************************/ +package org.idempiere.zk.billboard; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.zkoss.json.JSONArray; +import org.zkoss.json.JSONObject; +import org.zkoss.zk.au.AuRequest; +import org.zkoss.zk.ui.event.Events; +import org.zkoss.zul.CategoryModel; +import org.zkoss.zul.ChartModel; +import org.zkoss.zul.DialModel; +import org.zkoss.zul.PieModel; +import org.zkoss.zul.event.ChartDataEvent; +import org.zkoss.zul.event.ChartDataListener; +import org.zkoss.zul.impl.XulElement; + +/** + * + * @author hengsin + * + */ +public class Billboard extends XulElement { + /** + * generated serial id + */ + private static final long serialVersionUID = -3888636406033151303L; + + // Must + private ChartModel _model; + + private ChartDataListener _dataListener; + + // Optional + private String _title = ""; + private String _type = "line"; + private String _orient = "vertical"; + private Map _rendererOptions; + private Map _legend; + private boolean timeSeries = false; + private String timeSeriesInterval = "1 months"; //"1 days", "1 year", "1 weeks" + private String timeSeriesFormat = "%b %Y"; //%Y - year, %m - month, %#d - day + private String tickAxisLabel = null; + private String valueAxisLabel = null; + private String[] seriesColors = null; + private int xAxisAngle = 0; + + public static final String ON_DATA_CLICK_EVENT = "onDataClick"; + + public static final String VERTICAL_ORIENTATION = "vertical"; + public static final String HORIZONTAL_ORIENTATION = "horizontal"; + + // Event Listener + static { + addClientEvent(Billboard.class, Events.ON_CLICK, CE_IMPORTANT); + addClientEvent(Billboard.class, ON_DATA_CLICK_EVENT, CE_IMPORTANT); + } + + @Override + protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer) + throws java.io.IOException { + super.renderProperties(renderer); + + render(renderer, "type", _type); + render(renderer, "title", _title); + render(renderer, "orient", _orient); + render(renderer, "timeSeries", timeSeries); + render(renderer, "xAxisAngle", xAxisAngle); + if (timeSeries) { + if (timeSeriesInterval != null) + render(renderer, "timeSeriesInterval", timeSeriesInterval); + if (timeSeriesFormat != null) + render(renderer, "timeSeriesFormat", timeSeriesFormat); + } + + String model = toJSONArray(transferToJSONObject(getModel())); + render(renderer, "model", model); + + if (_rendererOptions != null && !_rendererOptions.isEmpty()) { + JSONObject jData = mapToJSON(_rendererOptions); + render(renderer, "rendererOptions", jData.toString()); + } + + if (_legend != null && !_legend.isEmpty()) { + JSONObject jData = mapToJSON(_legend); + render(renderer, "legend", jData.toString()); + } + + if (tickAxisLabel != null) + render(renderer, "tickAxisLabel", tickAxisLabel); + if (valueAxisLabel != null) + render(renderer, "valueAxisLabel", valueAxisLabel); + + if (seriesColors != null && seriesColors.length > 0) { + JSONArray jData = new JSONArray(); + for(String s : seriesColors) { + jData.add(s); + } + render(renderer, "seriesColors", jData.toString()); + } + /** + * JSON String Content + * "values": "X axis", "Line1":value1, "Line2": value2} + * [ + * {"values":"Q1","'2001'":20,"'2002'":40}, + * {"values":"Q2","'2001'":35,"'2002'":60}, + * {"values":"Q3","'2001'":40,"'2002'":70}, + * {"values":"Q4","'2001'":55,"'2002'":90} + * ] + */ + } + + private JSONObject mapToJSON(Map map) { + JSONObject jData = new JSONObject(); + for(String key : map.keySet()) { + Object value = map.get(key); + jData.put(key, value); + } + return jData; + } + + @Override + public void service(AuRequest request, boolean everError) { + if (Events.ON_CLICK.equals(request.getCommand())) { + Events.postEvent(Events.ON_CLICK, this, request.getData()); + } else if (ON_DATA_CLICK_EVENT.equals(request.getCommand())) { + Events.postEvent(ON_DATA_CLICK_EVENT, this, request.getData()); + } else { + super.service(request, everError); + } + } + + private class DefaultChartDataListener implements ChartDataListener, Serializable { + private static final long serialVersionUID = 20091125153002L; + + public void onChange(ChartDataEvent event) { + invalidate(); // Force redraw + } + } + + /** + * + * @return {@link ChartModel} + */ + public ChartModel getModel() { + return _model; + } + + /** + * + * @param model + */ + public void setModel(ChartModel model) { + if (_model != model) { + if (_model != null) + _model.removeChartDataListener(_dataListener); + + _model = model; + + if (_dataListener == null) { + _dataListener = new DefaultChartDataListener(); + _model.addChartDataListener(_dataListener); + } + invalidate(); // Always redraw + } + } + + /** + * + * @return chart title + */ + public String getTitle() { + return _title; + } + + /** + * set chart title + * @param title + */ + public void setTitle(String title) { + if(!title.equals(this._title)) { + this._title = title; + smartUpdate("title", _title); + invalidate(); + } + } + + /** + * + * @return chart type + */ + public String getType() { + return _type; + } + + /** + * set chart type + * @param type + */ + public void setType(String type) { + if(!type.equals(this._type)) { + if(isValid(type)) { + this._type = type; + smartUpdate("type", _type); + invalidate(); // Always redraw + } + } + } + + /** + * + * @return chart orientation (horizontal or vertical) + */ + public String getOrient() { + return _orient; + } + + /** + * set chart orientation + * @param orient + */ + public void setOrient(String orient) { + if(!orient.equals(this._orient)) { + this._orient = orient; + smartUpdate("orient", _orient); + invalidate(); + } + } + + /** + * + * @param key + * @param value + */ + public void addRendererOptions(String key, Object value) { + if (_rendererOptions == null) + _rendererOptions = new HashMap(); + _rendererOptions.put(key, value); + } + + /** + * + * @param key + * @param value + */ + public void addLegendOptions(String key, Object value) { + if (_legend == null) + _legend = new HashMap(); + _legend.put(key, value); + } + + @SuppressWarnings("rawtypes") + private List transferToJSONObject(ChartModel model) { + LinkedList list = new LinkedList(); + + if (model == null || _type == null) + return list; + + if ("gauge".equals(_type)) { + DialModel dialModel = (DialModel) model; + JSONObject json = new JSONObject(); + json.put("value", new double[]{dialModel.getValue(0)}); + list.add(json); + } + else if ("pie".equals(_type) || "donut".equals(_type)) { + PieModel tempModel = (PieModel) model; + for (int i = 0; i < tempModel.getCategories().size(); i++) { + Comparable category = tempModel.getCategory(i); + JSONObject json = new JSONObject(); + json.put("category", category); + json.put("value", tempModel.getValue(category)); + list.add(json); + } + + } else { + CategoryModel tempModel = (CategoryModel) model; + int seriesLength = tempModel.getSeries().size(); + for (int j = 0; j < seriesLength; j++) { + Comparable series = tempModel.getSeries(j); + for (int i = 0; i < tempModel.getCategories().size(); i++) { + Comparable category = tempModel.getCategory(i); + Number value = tempModel.getValue(series, category); + if (value != null) { + JSONObject jData = new JSONObject(); + jData.put("category", category); + jData.put("series", series); + jData.put("value", value != null ? value : 0.00d); + list.add(jData); + } + } + } + } + + return list; + } + + // Helper + private static String toJSONArray(List list) { + // list may be null. + if (list == null || list.isEmpty()) + return ""; + + final StringBuffer sb = new StringBuffer().append('['); + for (Iterator it = list.iterator(); it.hasNext();) { + String s = String.valueOf(it.next()); + sb.append(s).append(','); + } + sb.deleteCharAt(sb.length() - 1); + sb.append(']'); + return sb.toString().replaceAll("\\\\", ""); + } + + //supported chart type + private static final List _VALID_TYPES = Arrays.asList(new Object[] { + "pie", "line", "bar", "area", "stacked_bar", "stacked_area", "gauge", "donut", "waterfall" + }); + + private static boolean isValid(String type) { + return _VALID_TYPES.contains(type); + } + + /** + * The default zclass is "z-billboard" + */ + public String getZclass() { + return (this._zclass != null ? this._zclass : "z-billboard"); + } + + /** + * + * @param show + * @param insideGrid + */ + public void setLegend(boolean show, boolean insideGrid) { + if (show) { + addLegendOptions("show", Boolean.TRUE); + if (insideGrid) { + addLegendOptions("placement", "insideGrid"); + } else { + addLegendOptions("placement", "outsideGrid"); + } + } + } + + /** + * + * @return x axis label + */ + public String getTickAxisLabel() { + return tickAxisLabel; + } + + /** + * set x axis label + * @param tickAxisLabel + */ + public void setTickAxisLabel(String tickAxisLabel) { + this.tickAxisLabel = tickAxisLabel; + } + + /** + * + * @return y axis label + */ + public String getValueAxisLabel() { + return valueAxisLabel; + } + + /** + * set y axis label + * @param valueAxisLabel + */ + public void setValueAxisLabel(String valueAxisLabel) { + this.valueAxisLabel = valueAxisLabel; + } + + /** + * + * @return true if it is time series chart + */ + public boolean isTimeSeries() { + return timeSeries; + } + + /** + * + * @param _timeSeries + */ + public void setTimeSeries(boolean _timeSeries) { + this.timeSeries = _timeSeries; + } + + /** + * + * @return time series interval + */ + public String getTimeSeriesInterval() { + return timeSeriesInterval; + } + + /** + * + * @param _timeSeriesInterval + */ + public void setTimeSeriesInterval(String _timeSeriesInterval) { + this.timeSeriesInterval = _timeSeriesInterval; + } + + /** + * + * @return time series format + */ + public String getTimeSeriesFormat() { + return timeSeriesFormat; + } + + /** + * set time series format + * @param timeSeriesFormat + */ + public void setTimeSeriesFormat(String timeSeriesFormat) { + this.timeSeriesFormat = timeSeriesFormat; + } + + public String[] getSeriesColors() { + return seriesColors; + } + + public void setSeriesColors(String[] seriesColors) { + this.seriesColors = seriesColors; + } + + public int getXAxisAngle() { + return xAxisAngle; + } + + public void setXAxisAngle(int xAxisAngle) { + this.xAxisAngle = xAxisAngle; + } +} diff --git a/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java b/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java new file mode 100644 index 0000000000..28d5577bab --- /dev/null +++ b/org.idempiere.zk.billboard/src/org/idempiere/zk/billboard/Version.java @@ -0,0 +1,36 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - hengsin * + **********************************************************************/ +package org.idempiere.zk.billboard; + +/** + * + * @author hengsin + * + */ +public class Version { + /** Returns the version UID. + */ + public static final String UID = "3.5.1.20220905"; +} diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/Billboard.js b/org.idempiere.zk.billboard/src/web/js/zul/billboard/Billboard.js new file mode 100644 index 0000000000..43adfbea33 --- /dev/null +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/Billboard.js @@ -0,0 +1,270 @@ +(function() { + + var Billboard = + zul.billboard.Billboard = zk.$extends(zk.Widget, + { + + _title : '', + _type : 'line', + + _cursor : false, + _highlighter : true, + _dataClickTS : 0, + + $define : { + title: null, + type: null, + + width: null, + height: null, + + model : null, + series : null, + seriesData : null, + seriesDefaults : null, + seriesColors: null, + axes : null, + ticks : null, + tickAxisLabel: null, + valueAxisLabel: null, + + orient : null, + rendererOptions: null, + legend: null, + timeSeries: null, + timeSeriesInterval: null, + timeSeriesFormat: null, + xAxisAngle: null + }, + + _dataPrepare : function() { + var dataModel = this.getModel(); + var data = []; + try { + data = jq.evalJSON(dataModel); + } catch (error) { + console.log(error); + } + if (typeof data == "undefined") { + data = []; + } + + // In this phase, we need to decide following variables + var seriesData = []; + var ticks = []; + + // Start data prepare + if( this.getType() == 'gauge') { + seriesData.push("data"); + for ( var i = 0, len = data.length; i < len; i++) { + seriesData.push(data[i]['value']); + } + } else if( this.getType() == 'pie' || this.getType() == 'donut') { + for ( var i = 0, len = data.length; i < len; i++) { + seriesData.push([data[i]['category'], data[i]['value']]); + } + + seriesData = [ seriesData ]; + + } else { + var seriesMap = new Array(); + for ( var i = 0, len = data.length; i < len; i++) { + + var current = data[i]; + var seriesIndex = -1; + var seriesLabel = current['series']; + var categoryLabel = current['category']; + var categoryValue = current['value']; + var seriesIndex = seriesMap.indexOf(seriesLabel); + if (seriesIndex < 0) { + seriesMap.push(seriesLabel); + seriesIndex = seriesMap.length-1; + } + // Initial Array + if(!seriesData[seriesIndex]) { + seriesData[seriesIndex] = new Array(); + } + + if (seriesData[seriesIndex].length == 0) + seriesData[seriesIndex].push(seriesLabel); + seriesData[seriesIndex].push({category: categoryLabel, value: categoryValue}); + } + + var seriesLabel = new Array(); + for (var i=0; i 500) + this.$supers(Billboard, 'doClick_', arguments); + }, + + isBarType : function() { + return this._type == 'bar' || this._type == 'stacked_bar' || this._type == 'waterfall'; + }, + + getCursor : function() { + if(this._cursor) { + return { show: true, followMouse: true, + showTooltip: true, tooltipLocation:'sw', style: 'pointer'}; + } else { + return { show: false }; + } + }, + + setCursor : function(val) { + this._cursor = val; + }, + + getZclass : function() { + return this._zclass != null ? this._zclass : "z-billboard"; + } + + }, {_renderers : {}}); +})(); \ No newline at end of file diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/css/billboard.css b/org.idempiere.zk.billboard/src/web/js/zul/billboard/css/billboard.css new file mode 100644 index 0000000000..a786873a00 --- /dev/null +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/css/billboard.css @@ -0,0 +1,235 @@ +/*! + * Copyright (c) 2017 ~ present NAVER Corp. + * billboard.js project is licensed under the MIT license + * + * billboard.js, JavaScript chart library + * https://naver.github.io/billboard.js/ + * + * @version 3.5.1 + */ +/*-- Chart --*/ +.bb svg { + font: 10px sans-serif; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } + +.bb path, .bb line { + fill: none; + stroke: #000; } + +.bb text, .bb .bb-button { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; } + +.bb-legend-item-tile, +.bb-xgrid-focus, +.bb-ygrid-focus, +.bb-ygrid, +.bb-event-rect, +.bb-bars path { + shape-rendering: crispEdges; } + +.bb-chart-arc .bb-gauge-value { + fill: #000; } + +.bb-chart-arc path { + stroke: #fff; } + +.bb-chart-arc rect { + stroke: #fff; + stroke-width: 1; } + +.bb-chart-arc text { + fill: #fff; + font-size: 13px; } + +/*-- Axis --*/ +.bb-axis { + shape-rendering: crispEdges; } + +/*-- Grid --*/ +.bb-grid { + pointer-events: none; } + .bb-grid line { + stroke: #aaa; } + .bb-grid text { + fill: #aaa; } + +.bb-xgrid, .bb-ygrid { + stroke-dasharray: 3 3; } + +/*-- Text on Chart --*/ +.bb-text.bb-empty { + fill: #808080; + font-size: 2em; } + +/*-- Line --*/ +.bb-line { + stroke-width: 1px; } + +/*-- Point --*/ +.bb-circle._expanded_ { + stroke-width: 1px; + stroke: white; } + +.bb-selected-circle { + fill: white; + stroke-width: 2px; } + +/*-- Bar --*/ +.bb-bar { + stroke-width: 0; } + .bb-bar._expanded_ { + fill-opacity: 0.75; } + +/*-- Candlestick --*/ +.bb-candlestick { + stroke-width: 1px; } + .bb-candlestick._expanded_ { + fill-opacity: 0.75; } + +/*-- Focus --*/ +.bb-target.bb-focused, .bb-circles.bb-focused { + opacity: 1; } + +.bb-target.bb-focused path.bb-line, .bb-target.bb-focused path.bb-step, .bb-circles.bb-focused path.bb-line, .bb-circles.bb-focused path.bb-step { + stroke-width: 2px; } + +.bb-target.bb-defocused, .bb-circles.bb-defocused { + opacity: 0.3 !important; } + .bb-target.bb-defocused .text-overlapping, .bb-circles.bb-defocused .text-overlapping { + opacity: .05 !important; } + +/*-- Region --*/ +.bb-region { + fill: steelblue; + fill-opacity: .1; } + +/*-- Zoom region --*/ +.bb-zoom-brush { + fill-opacity: .1; } + +/*-- Brush --*/ +.bb-brush .extent { + fill-opacity: .1; } + +/*-- Legend --*/ +.bb-legend-item { + font-size: 12px; + user-select: none; } + +.bb-legend-item-hidden { + opacity: 0.15; } + +.bb-legend-background { + opacity: 0.75; + fill: white; + stroke: lightgray; + stroke-width: 1; } + +/*-- Title --*/ +.bb-title { + font: 14px sans-serif; } + +/*-- Tooltip --*/ +.bb-tooltip-container { + z-index: 10; + user-select: none; } + +.bb-tooltip { + border-collapse: collapse; + border-spacing: 0; + background-color: #fff; + empty-cells: show; + opacity: 0.9; + -webkit-box-shadow: 7px 7px 12px -9px #777777; + -moz-box-shadow: 7px 7px 12px -9px #777777; + box-shadow: 7px 7px 12px -9px #777777; } + .bb-tooltip tr { + border: 1px solid #CCC; } + .bb-tooltip th { + background-color: #aaa; + font-size: 14px; + padding: 2px 5px !important; + text-align: left !important; + color: #FFF; } + .bb-tooltip td { + font-size: 13px; + padding: 3px 6px !important; + background-color: #fff; + border-left: 1px dotted #999; } + .bb-tooltip td > span, .bb-tooltip td > svg { + display: inline-block; + width: 10px; + height: 10px; + margin-right: 6px; } + .bb-tooltip.value { + text-align: right; } + +/*-- Area --*/ +.bb-area { + stroke-width: 0; + opacity: 0.2; } + +/*-- Arc --*/ +.bb-chart-arcs-title { + dominant-baseline: middle; + font-size: 1.3em; } + +text.bb-chart-arcs-gauge-title { + dominant-baseline: middle; + font-size: 2.7em; } + +.bb-chart-arcs { + /*-- Polar --*/ } + .bb-chart-arcs .bb-chart-arcs-background { + fill: #e0e0e0; + stroke: #fff; } + .bb-chart-arcs .bb-chart-arcs-gauge-unit { + fill: #000; + font-size: 16px; } + .bb-chart-arcs .bb-chart-arcs-gauge-max { + fill: #777; } + .bb-chart-arcs .bb-chart-arcs-gauge-min { + fill: #777; } + .bb-chart-arcs .bb-levels circle { + fill: none; + stroke: #848282; + stroke-width: .5px; } + .bb-chart-arcs .bb-levels text { + fill: #848282; } + +/*-- Radar --*/ +.bb-chart-radars .bb-levels polygon { + fill: none; + stroke: #848282; + stroke-width: .5px; } + +.bb-chart-radars .bb-levels text { + fill: #848282; } + +.bb-chart-radars .bb-axis line { + stroke: #848282; + stroke-width: .5px; } + +.bb-chart-radars .bb-axis text { + font-size: 1.15em; + cursor: default; } + +.bb-chart-radars .bb-shapes polygon { + fill-opacity: .2; + stroke-width: 1px; } + +/*-- Button --*/ +.bb-button { + position: absolute; + top: 10px; + right: 10px; } + .bb-button .bb-zoom-reset { + font-size: 11px; + border: solid 1px #ccc; + background-color: #fff; + padding: 5px; + border-radius: 5px; + cursor: pointer; } + diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.area.js b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.area.js new file mode 100644 index 0000000000..13066e9576 --- /dev/null +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.area.js @@ -0,0 +1,153 @@ +var billboard = billboard || {}; + +billboard.AreaRenderer = function() {}; + +billboard.AreaRenderer.prototype.render = function(wgt) { + var columns = []; + var categories = new Array(); + wgt.getSeriesData().forEach((x, i) => { + x.forEach((y, j) => { + if (y.category) { + columns[columns.length-1].push(y.value); + if (!categories.indexOf[y.category]) + categories.push(y.category); + } else { + var values = new Array(); + columns.push(values); + values.push(y); + } + }); + }); + var color = {}; + var area = {}; + var background = {}; + var rendererOptions = wgt._rendererOptions ? jq.evalJSON(wgt._rendererOptions) : null; + if (rendererOptions) { + if (rendererOptions["intervalColors"]) { + color["pattern"] = new Array(); + rendererOptions["intervalColors"].forEach((x, i) => color["pattern"].push(x)); + } + if (rendererOptions["intervals"]) { + color["threshold"] = {values: []}; + rendererOptions["intervals"].forEach((x, i) => color["threshold"]["values"].push(x)); + } + + if (rendererOptions["background"]) { + background["color"] = rendererOptions["background"]; + } + } + var x = {tick: {}}; + var rotated = false; + var axes = wgt.getAxes(); + if (axes.rotated) + rotated = axes.rotated; + if (axes.xaxis.renderer == "timeseries") { + x["type"] = "timeseries"; + if (axes.xaxis.tickOptions) { + x["tick"]["format"] = axes.xaxis.tickOptions.formatString; + } + } else { + x["type"] = "category"; + } + if (x["type"] == "category") { + if (categories.length > 0) { + x["categories"] = []; + categories.forEach((v, i) => x["categories"].push(v)); + } + } else if (x["type"] == "timeseries") { + var ts = new Array(); + ts.push("x"); + categories.forEach((s, i) => ts.push(s)); + columns.push(ts); + x["tick"]["fit"] = true; + } + x["clipPath"] = false; + if (axes.xaxis.tickOptions) { + if (axes.xaxis.tickOptions.angle) { + if (axes.xaxis.tickOptions.angle != 0) { + x["tick"]["rotate"] = axes.xaxis.tickOptions.angle; + } + } + } + + var model = { + bindto: "#"+wgt.$n().id, + data: { + columns: columns, + type: wgt._type, + onclick: function(d, e) { + var i = 0; + var si = 0; + for(var s in wgt.getSeries()) { + if (s == d.id) { + si = i; + break; + } + i++; + } + wgt._dataClickTS = new Date().getTime(); + wgt.fire("onDataClick", { + seriesIndex : si, + pointIndex : d.index, + data : d.value, + ticks : wgt.getTicks() + }); + } + }, + color: color, + area: area, + tooltip: { + show: true, + doNotHide: false, + grouped: false, + contents: function(d, defaultTitleFormat, defaultValueFormat, color) { + var c = d[0]; + var h = '
'; + h = h + defaultTitleFormat(c.x); + h = h + '
'; + h = h + c.value + '
'; + return h; + } + }, + legend: {show: false}, + axis: { + x: x, + rotated: rotated + }, + grid: { + x: { + show: true, + }, + y: { + show: true, + }, + front: false, + focus: { + show: false, + + // Below options are available when 'tooltip.grouped=false' option is set + edge: true, + y: true + }, + lines: { + front: false + } + }, + padding: { + right: 30 + } + }; + if (x["type"] == "timeseries") { + model["data"]["x"] = "x"; + } + if (wgt.getTitle()) + model["title"] = {text: wgt.getTitle()}; + if (wgt.getTickAxisLabel()) + model["axis"]["x"]["label"] = {text: wgt.getTickAxisLabel(), position: "outer-right"}; + if (wgt.getValueAxisLabel()) + model["axis"]["y"] = { label: {text: wgt.getValueAxisLabel(), position: "outer-top"} }; + return model; +}; + +zul.billboard.Billboard._renderers["area"] = new billboard.AreaRenderer(); + \ No newline at end of file diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.bar.js b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.bar.js new file mode 100644 index 0000000000..6be7db5ceb --- /dev/null +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.bar.js @@ -0,0 +1,153 @@ +var billboard = billboard || {}; + +billboard.BarRenderer = function() {}; + +billboard.BarRenderer.prototype.render = function(wgt) { + var columns = []; + var categories = new Array(); + wgt.getSeriesData().forEach((x, i) => { + x.forEach((y, j) => { + if (y.category) { + columns[columns.length-1].push(y.value); + if (!categories.indexOf[y.category]) + categories.push(y.category); + } else { + var values = new Array(); + columns.push(values); + values.push(y); + } + }); + }); + var color = {}; + var bar = {}; + var background = {}; + var rendererOptions = wgt._rendererOptions ? jq.evalJSON(wgt._rendererOptions) : null; + if (rendererOptions) { + if (rendererOptions["intervalColors"]) { + color["pattern"] = new Array(); + rendererOptions["intervalColors"].forEach((x, i) => color["pattern"].push(x)); + } + if (rendererOptions["intervals"]) { + color["threshold"] = {values: []}; + rendererOptions["intervals"].forEach((x, i) => color["threshold"]["values"].push(x)); + } + + if (rendererOptions["background"]) { + background["color"] = rendererOptions["background"]; + } + } + var x = {tick: {}}; + var rotated = false; + var axes = wgt.getAxes(); + if (axes.rotated) + rotated = axes.rotated; + if (axes.xaxis.renderer == "timeseries") { + x["type"] = "timeseries"; + if (axes.xaxis.tickOptions) { + x["tick"]["format"] = axes.xaxis.tickOptions.formatString; + } + } else { + x["type"] = "category"; + } + if (x["type"] == "category") { + if (categories.length > 0) { + x["categories"] = []; + categories.forEach((v, i) => x["categories"].push(v)); + } + } else if (x["type"] == "timeseries") { + var ts = new Array(); + ts.push("x"); + categories.forEach((s, i) => ts.push(s)); + columns.push(ts); + x["tick"]["fit"] = true; + } + x["clipPath"] = false; + if (axes.xaxis.tickOptions) { + if (axes.xaxis.tickOptions.angle) { + if (axes.xaxis.tickOptions.angle != 0) { + x["tick"]["rotate"] = axes.xaxis.tickOptions.angle; + } + } + } + + var model = { + bindto: "#"+wgt.$n().id, + data: { + columns: columns, + type: wgt._type, + onclick: function(d, e) { + var i = 0; + var si = 0; + for(var s in wgt.getSeries()) { + if (s == d.id) { + si = i; + break; + } + i++; + } + wgt._dataClickTS = new Date().getTime(); + wgt.fire("onDataClick", { + seriesIndex : si, + pointIndex : d.index, + data : d.value, + ticks : wgt.getTicks() + }); + } + }, + color: color, + bar: bar, + tooltip: { + show: true, + doNotHide: false, + grouped: false, + contents: function(d, defaultTitleFormat, defaultValueFormat, color) { + var c = d[0]; + var h = '
'; + h = h + defaultTitleFormat(c.x); + h = h + '
'; + h = h + c.value + '
'; + return h; + } + }, + legend: {show: false}, + axis: { + x: x, + rotated: rotated + }, + grid: { + x: { + show: true, + }, + y: { + show: true, + }, + front: false, + focus: { + show: false, + + // Below options are available when 'tooltip.grouped=false' option is set + edge: true, + y: true + }, + lines: { + front: false + } + }, + padding: { + right: 30 + } + }; + if (x["type"] == "timeseries") { + model["data"]["x"] = "x"; + } + if (wgt.getTitle()) + model["title"] = {text: wgt.getTitle()}; + if (wgt.getTickAxisLabel()) + model["axis"]["x"]["label"] = {text: wgt.getTickAxisLabel(), position: "outer-right"}; + if (wgt.getValueAxisLabel()) + model["axis"]["y"] = { label: {text: wgt.getValueAxisLabel(), position: "outer-top"} }; + return model; +}; + +zul.billboard.Billboard._renderers["bar"] = new billboard.BarRenderer(); + \ No newline at end of file diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.donut.js b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.donut.js new file mode 100644 index 0000000000..2603ad35ab --- /dev/null +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.donut.js @@ -0,0 +1,62 @@ +var billboard = billboard || {}; + +billboard.DonutRenderer = function() {}; + +billboard.DonutRenderer.prototype.render = function(wgt) { + var columns = wgt.getSeriesData()[0]; + var color = {}; + var donut = {}; + var background = {}; + var rendererOptions = wgt._rendererOptions ? jq.evalJSON(wgt._rendererOptions) : null; + if (rendererOptions) { + if (rendererOptions["intervalColors"]) { + color["pattern"] = new Array(); + rendererOptions["intervalColors"].forEach((x, i) => color["pattern"].push(x)); + } + if (rendererOptions["intervals"]) { + color["threshold"] = {values: []}; + rendererOptions["intervals"].forEach((x, i) => color["threshold"]["values"].push(x)); + } + + if (rendererOptions["background"]) { + background["color"] = rendererOptions["background"]; + } + } + + var model = { + bindto: "#"+wgt.$n().id, + data: { + columns: columns, + type: wgt._type, + onclick: function(d, e) { + wgt._dataClickTS = new Date().getTime(); + wgt.fire("onDataClick", { + seriesIndex : 0, + pointIndex : d.index, + data : d.value, + ticks : wgt.getTicks() + }); + } + }, + color: color, + donut: donut, + tooltip: { + show: true, + doNotHide: false, + grouped: false, + format: { + title: function(x) { return ""; }, + name: function(name, ratio, id, index) { return name; }, + value: function(value, ratio, id, index) { return value; } + } + }, + legend: {show: true} + }; + if (wgt.getTitle()) + model["donut"]["title"] = wgt.getTitle(); + //model["title"] = {text: wgt.getTitle()}; + return model; +}; + +zul.billboard.Billboard._renderers["donut"] = new billboard.DonutRenderer(); + \ No newline at end of file diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js new file mode 100644 index 0000000000..b0d900b10c --- /dev/null +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.gauge.js @@ -0,0 +1,56 @@ +var billboard = billboard || {}; + +billboard.GaugeRenderer = function() {}; + +billboard.GaugeRenderer.prototype.render = function(wgt) { + var columns = [wgt.getSeriesData()[0], wgt.getSeriesData()[1]]; + var color = {}; + var gauge = {units: "%"}; + var rendererOptions = wgt._rendererOptions ? jq.evalJSON(wgt._rendererOptions) : null; + if (rendererOptions) { + if (rendererOptions["intervalColors"]) { + color["pattern"] = new Array(); + rendererOptions["intervalColors"].forEach((x, i) => color["pattern"].push(x)); + } + if (rendererOptions["intervals"]) { + color["threshold"] = {values: []}; + rendererOptions["intervals"].forEach((x, i) => color["threshold"]["values"].push(x)); + } + + /* + if (rendererOptions["min"]) { + gauge["min"] = rendererOptions["min"]; + } + if (rendererOptions["max"]) { + gauge["max"] = rendererOptions["max"]; + } + */ + if (rendererOptions["background"]) { + gauge["background"] = rendererOptions["background"]; + } + } + var model = { + bindto: "#"+wgt.$n().id, + data: { + columns: [columns], + type: wgt._type + }, + color: color, + gauge: gauge, + tooltip: { + show: true, + doNotHide: false, + grouped: false, + format: { + title: function(x) { return ""; }, + name: function(name, ratio, id, index) { return ""; }, + value: function(value, ratio, id, index) { return value; } + } + }, + legend: {show: false} + }; + return model; +}; + +zul.billboard.Billboard._renderers["gauge"] = new billboard.GaugeRenderer(); + \ No newline at end of file diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.line.js b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.line.js new file mode 100644 index 0000000000..03dfc74754 --- /dev/null +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.line.js @@ -0,0 +1,153 @@ +var billboard = billboard || {}; + +billboard.LineRenderer = function() {}; + +billboard.LineRenderer.prototype.render = function(wgt) { + var columns = []; + var categories = new Array(); + wgt.getSeriesData().forEach((x, i) => { + x.forEach((y, j) => { + if (y.category) { + columns[columns.length-1].push(y.value); + if (!categories.indexOf[y.category]) + categories.push(y.category); + } else { + var values = new Array(); + columns.push(values); + values.push(y); + } + }); + }); + var color = {}; + var line = {}; + var background = {}; + var rendererOptions = wgt._rendererOptions ? jq.evalJSON(wgt._rendererOptions) : null; + if (rendererOptions) { + if (rendererOptions["intervalColors"]) { + color["pattern"] = new Array(); + rendererOptions["intervalColors"].forEach((x, i) => color["pattern"].push(x)); + } + if (rendererOptions["intervals"]) { + color["threshold"] = {values: []}; + rendererOptions["intervals"].forEach((x, i) => color["threshold"]["values"].push(x)); + } + + if (rendererOptions["background"]) { + background["color"] = rendererOptions["background"]; + } + } + var x = {tick: {}}; + var rotated = false; + var axes = wgt.getAxes(); + if (axes.rotated) + rotated = axes.rotated; + if (axes.xaxis.renderer == "timeseries") { + x["type"] = "timeseries"; + if (axes.xaxis.tickOptions) { + x["tick"]["format"] = axes.xaxis.tickOptions.formatString; + } + } else { + x["type"] = "category"; + } + if (x["type"] == "category") { + if (categories.length > 0) { + x["categories"] = []; + categories.forEach((v, i) => x["categories"].push(v)); + } + } else if (x["type"] == "timeseries") { + var ts = new Array(); + ts.push("x"); + categories.forEach((s, i) => ts.push(s)); + columns.push(ts); + x["tick"]["fit"] = true; + } + x["clipPath"] = false; + if (axes.xaxis.tickOptions) { + if (axes.xaxis.tickOptions.angle) { + if (axes.xaxis.tickOptions.angle != 0) { + x["tick"]["rotate"] = axes.xaxis.tickOptions.angle; + } + } + } + + var model = { + bindto: "#"+wgt.$n().id, + data: { + columns: columns, + type: wgt._type, + onclick: function(d, e) { + var i = 0; + var si = 0; + for(var s in wgt.getSeries()) { + if (s == d.id) { + si = i; + break; + } + i++; + } + wgt._dataClickTS = new Date().getTime(); + wgt.fire("onDataClick", { + seriesIndex : si, + pointIndex : d.index, + data : d.value, + ticks : wgt.getTicks() + }); + } + }, + color: color, + line: line, + tooltip: { + show: true, + doNotHide: false, + grouped: false, + contents: function(d, defaultTitleFormat, defaultValueFormat, color) { + var c = d[0]; + var h = '
'; + h = h + defaultTitleFormat(c.x); + h = h + '
'; + h = h + c.value + '
'; + return h; + } + }, + legend: {show: false}, + axis: { + x: x, + rotated: rotated + }, + grid: { + x: { + show: true, + }, + y: { + show: true, + }, + front: false, + focus: { + show: false, + + // Below options are available when 'tooltip.grouped=false' option is set + edge: true, + y: true + }, + lines: { + front: false + } + }, + padding: { + right: 30 + } + }; + if (x["type"] == "timeseries") { + model["data"]["x"] = "x"; + } + if (wgt.getTitle()) + model["title"] = {text: wgt.getTitle()}; + if (wgt.getTickAxisLabel()) + model["axis"]["x"]["label"] = {text: wgt.getTickAxisLabel(), position: "outer-right"}; + if (wgt.getValueAxisLabel()) + model["axis"]["y"] = { label: {text: wgt.getValueAxisLabel(), position: "outer-top"} }; + return model; +}; + +zul.billboard.Billboard._renderers["line"] = new billboard.LineRenderer(); + \ No newline at end of file diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.pie.js b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.pie.js new file mode 100644 index 0000000000..f569e2fe03 --- /dev/null +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.pie.js @@ -0,0 +1,61 @@ +var billboard = billboard || {}; + +billboard.PieRenderer = function() {}; + +billboard.PieRenderer.prototype.render = function(wgt) { + var columns = wgt.getSeriesData()[0]; + var color = {}; + var pie = {}; + var background = {}; + var rendererOptions = wgt._rendererOptions ? jq.evalJSON(wgt._rendererOptions) : null; + if (rendererOptions) { + if (rendererOptions["intervalColors"]) { + color["pattern"] = new Array(); + rendererOptions["intervalColors"].forEach((x, i) => color["pattern"].push(x)); + } + if (rendererOptions["intervals"]) { + color["threshold"] = {values: []}; + rendererOptions["intervals"].forEach((x, i) => color["threshold"]["values"].push(x)); + } + + if (rendererOptions["background"]) { + background["color"] = rendererOptions["background"]; + } + } + + var model = { + bindto: "#"+wgt.$n().id, + data: { + columns: columns, + type: wgt._type, + onclick: function(d, e) { + wgt._dataClickTS = new Date().getTime(); + wgt.fire("onDataClick", { + seriesIndex : 0, + pointIndex : d.index, + data : d.value, + ticks : wgt.getTicks() + }); + } + }, + color: color, + pie: pie, + tooltip: { + show: true, + doNotHide: false, + grouped: false, + format: { + title: function(x) { return ""; }, + name: function(name, ratio, id, index) { return name; }, + value: function(value, ratio, id, index) { return value; } + } + }, + legend: {show: true} + }; + if (wgt.getTitle()) + model["title"] = {text: wgt.getTitle()}; + return model; +}; + +zul.billboard.Billboard._renderers["pie"] = new billboard.PieRenderer(); + \ No newline at end of file diff --git a/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.pkgd.js b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.pkgd.js new file mode 100644 index 0000000000..59a8e32902 --- /dev/null +++ b/org.idempiere.zk.billboard/src/web/js/zul/billboard/ext/billboard.pkgd.js @@ -0,0 +1,25 @@ +/*! + * Copyright (c) 2017 ~ present NAVER Corp. + * billboard.js project is licensed under the MIT license + * + * billboard.js, JavaScript chart library + * https://naver.github.io/billboard.js/ + * + * @version 3.5.1 + * + * All-in-one packaged file for ease use of 'billboard.js' with dependant d3.js modules & polyfills. + * - d3-axis ^3.0.0 + * - d3-brush ^3.0.0 + * - d3-drag ^3.0.0 + * - d3-dsv ^3.0.1 + * - d3-ease ^3.0.1 + * - d3-interpolate ^3.0.1 + * - d3-scale ^4.0.2 + * - d3-selection ^3.0.0 + * - d3-shape ^3.1.0 + * - d3-time-format ^4.1.0 + * - d3-transition ^3.0.1 + * - d3-zoom ^3.0.0 + */ +!function(t,e){if("object"==typeof exports&&"object"==typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n=e();for(var i in n)("object"==typeof exports?exports:t)[i]=n[i]}}(this,(function(){return function(){var t=[function(t,e,n){n(1),n(95),n(96),n(97),n(98),n(99),n(100),n(101),n(102),n(103),n(104),n(105),n(106),n(107),n(108),n(109),n(119),n(121),n(131),n(132),n(134),n(137),n(140),n(142),n(144),n(145),n(146),n(147),n(149),n(150),n(152),n(153),n(155),n(159),n(160),n(161),n(162),n(166),n(167),n(169),n(170),n(171),n(172),n(175),n(176),n(177),n(178),n(179),n(184),n(186),n(187),n(188),n(189),n(190),n(197),n(199),n(202),n(204),n(205),n(206),n(207),n(208),n(212),n(213),n(215),n(216),n(217),n(219),n(220),n(221),n(91),n(222),n(223),n(231),n(233),n(234),n(235),n(237),n(238),n(240),n(241),n(243),n(244),n(245),n(247),n(248),n(249),n(250),n(251),n(252),n(253),n(254),n(258),n(259),n(261),n(263),n(264),n(265),n(266),n(267),n(269),n(271),n(272),n(273),n(274),n(276),n(277),n(279),n(280),n(281),n(282),n(284),n(285),n(286),n(287),n(288),n(289),n(290),n(291),n(293),n(294),n(295),n(296),n(297),n(298),n(299),n(300),n(301),n(302),n(304),n(305),n(306),n(307),n(329),n(330),n(331),n(332),n(333),n(334),n(335),n(336),n(338),n(339),n(340),n(341),n(342),n(343),n(344),n(345),n(346),n(347),n(354),n(356),n(358),n(359),n(360),n(361),n(362),n(364),n(365),n(367),n(370),n(371),n(372),n(373),n(377),n(378),n(380),n(381),n(382),n(383),n(385),n(386),n(387),n(388),n(389),n(390),n(392),n(395),n(398),n(401),n(402),n(403),n(404),n(405),n(406),n(407),n(408),n(409),n(410),n(411),n(412),n(413),n(419),n(420),n(421),n(422),n(423),n(424),n(425),n(426),n(427),n(428),n(429),n(430),n(432),n(436),n(437),n(438),n(439),n(440),n(441),n(442),n(443),n(444),n(445),n(446),n(447),n(448),n(449),n(450),n(451),n(452),n(453),n(454),n(455),n(456),n(457),n(458),n(459),n(460),n(463),n(465),n(467),n(468),n(471),n(472),n(475),n(476),n(477),n(480),n(481),n(483),n(487),n(492),n(493),n(79)},function(t,e,n){n(2),n(88),n(90),n(91),n(94)},function(t,e,n){"use strict";var i=n(3),r=n(4),a=n(8),o=n(14),s=n(34),u=n(6),c=n(25),l=n(7),h=n(37),f=n(23),d=n(45),g=n(12),p=n(17),v=n(67),y=n(11),x=n(70),b=n(72),_=n(56),m=n(74),w=n(65),T=n(5),A=n(43),S=n(71),E=n(10),k=n(46),M=n(33),O=n(52),R=n(53),C=n(39),I=n(32),L=n(77),P=n(78),D=n(80),N=n(81),z=n(50),F=n(82).forEach,j=O("hidden"),B="Symbol",X=z.set,$=z.getterFor(B),U=Object.prototype,Y=r.Symbol,V=Y&&Y.prototype,G=r.TypeError,H=r.QObject,W=T.f,q=A.f,Z=m.f,K=E.f,J=o([].push),Q=M("symbols"),tt=M("op-symbols"),et=M("wks"),nt=!H||!H.prototype||!H.prototype.findChild,it=u&&l((function(){return 7!=x(q({},"a",{get:function(){return q(this,"a",{value:7}).a}})).a}))?function(t,e,n){var i=W(U,e);i&&delete U[e],q(t,e,n),i&&t!==U&&q(U,e,i)}:q,rt=function(t,e){var n=Q[t]=x(V);return X(n,{type:B,tag:t,description:e}),u||(n.description=e),n},at=function(t,e,n){t===U&&at(tt,e,n),d(t);var i=p(e);return d(n),h(Q,i)?(n.enumerable?(h(t,j)&&t[j][i]&&(t[j][i]=!1),n=x(n,{enumerable:y(0,!1)})):(h(t,j)||q(t,j,y(1,{})),t[j][i]=!0),it(t,i,n)):q(t,i,n)},ot=function(t,e){d(t);var n=g(e),i=b(n).concat(lt(n));return F(i,(function(e){u&&!a(st,n,e)||at(t,e,n[e])})),t},st=function(t){var e=p(t),n=a(K,this,e);return!(this===U&&h(Q,e)&&!h(tt,e))&&(!(n||!h(this,e)||!h(Q,e)||h(this,j)&&this[j][e])||n)},ut=function(t,e){var n=g(t),i=p(e);if(n!==U||!h(Q,i)||h(tt,i)){var r=W(n,i);return!r||!h(Q,i)||h(n,j)&&n[j][i]||(r.enumerable=!0),r}},ct=function(t){var e=Z(g(t)),n=[];return F(e,(function(t){h(Q,t)||h(R,t)||J(n,t)})),n},lt=function(t){var e=t===U,n=Z(e?tt:g(t)),i=[];return F(n,(function(t){!h(Q,t)||e&&!h(U,t)||J(i,Q[t])})),i};c||(Y=function(){if(f(V,this))throw G("Symbol is not a constructor");var t=arguments.length&&void 0!==arguments[0]?v(arguments[0]):void 0,e=C(t),n=function(t){this===U&&a(n,tt,t),h(this,j)&&h(this[j],e)&&(this[j][e]=!1),it(this,e,y(1,t))};return u&&nt&&it(U,e,{configurable:!0,set:n}),rt(e,t)},k(V=Y.prototype,"toString",(function(){return $(this).tag})),k(Y,"withoutSetter",(function(t){return rt(C(t),t)})),E.f=st,A.f=at,S.f=ot,T.f=ut,_.f=m.f=ct,w.f=lt,L.f=function(t){return rt(I(t),t)},u&&(q(V,"description",{configurable:!0,get:function(){return $(this).description}}),s||k(U,"propertyIsEnumerable",st,{unsafe:!0}))),i({global:!0,constructor:!0,wrap:!0,forced:!c,sham:!c},{Symbol:Y}),F(b(et),(function(t){P(t)})),i({target:B,stat:!0,forced:!c},{useSetter:function(){nt=!0},useSimple:function(){nt=!1}}),i({target:"Object",stat:!0,forced:!c,sham:!u},{create:function(t,e){return void 0===e?x(t):ot(x(t),e)},defineProperty:at,defineProperties:ot,getOwnPropertyDescriptor:ut}),i({target:"Object",stat:!0,forced:!c},{getOwnPropertyNames:ct}),D(),N(Y,B),R[j]=!0},function(t,e,n){var i=n(4),r=n(5).f,a=n(42),o=n(46),s=n(36),u=n(54),c=n(66);t.exports=function(t,e){var n,l,h,f,d,g=t.target,p=t.global,v=t.stat;if(n=p?i:v?i[g]||s(g,{}):(i[g]||{}).prototype)for(l in e){if(f=e[l],h=t.dontCallGetSet?(d=r(n,l))&&d.value:n[l],!c(p?l:g+(v?".":"#")+l,t.forced)&&void 0!==h){if(typeof f==typeof h)continue;u(f,h)}(t.sham||h&&h.sham)&&a(f,"sham",!0),o(n,l,f,t)}}},function(t){var e=function(t){return t&&t.Math==Math&&t};t.exports=e("object"==typeof globalThis&&globalThis)||e("object"==typeof window&&window)||e("object"==typeof self&&self)||e("object"==typeof global&&global)||function(){return this}()||Function("return this")()},function(t,e,n){var i=n(6),r=n(8),a=n(10),o=n(11),s=n(12),u=n(17),c=n(37),l=n(40),h=Object.getOwnPropertyDescriptor;e.f=i?h:function(t,e){if(t=s(t),e=u(e),l)try{return h(t,e)}catch(t){}if(c(t,e))return o(!r(a.f,t,e),t[e])}},function(t,e,n){var i=n(7);t.exports=!i((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},function(t){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,n){var i=n(9),r=Function.prototype.call;t.exports=i?r.bind(r):function(){return r.apply(r,arguments)}},function(t,e,n){var i=n(7);t.exports=!i((function(){var t=function(){}.bind();return"function"!=typeof t||t.hasOwnProperty("prototype")}))},function(t,e){"use strict";var n={}.propertyIsEnumerable,i=Object.getOwnPropertyDescriptor,r=i&&!n.call({1:2},1);e.f=r?function(t){var e=i(this,t);return!!e&&e.enumerable}:n},function(t){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e,n){var i=n(13),r=n(16);t.exports=function(t){return i(r(t))}},function(t,e,n){var i=n(14),r=n(7),a=n(15),o=Object,s=i("".split);t.exports=r((function(){return!o("z").propertyIsEnumerable(0)}))?function(t){return"String"==a(t)?s(t,""):o(t)}:o},function(t,e,n){var i=n(9),r=Function.prototype,a=r.bind,o=r.call,s=i&&a.bind(o,o);t.exports=i?function(t){return t&&s(t)}:function(t){return t&&function(){return o.apply(t,arguments)}}},function(t,e,n){var i=n(14),r=i({}.toString),a=i("".slice);t.exports=function(t){return a(r(t),8,-1)}},function(t){var e=TypeError;t.exports=function(t){if(null==t)throw e("Can't call method on "+t);return t}},function(t,e,n){var i=n(18),r=n(21);t.exports=function(t){var e=i(t,"string");return r(e)?e:e+""}},function(t,e,n){var i=n(8),r=n(19),a=n(21),o=n(28),s=n(31),u=n(32),c=TypeError,l=u("toPrimitive");t.exports=function(t,e){if(!r(t)||a(t))return t;var n,u=o(t,l);if(u){if(void 0===e&&(e="default"),n=i(u,t,e),!r(n)||a(n))return n;throw c("Can't convert object to primitive value")}return void 0===e&&(e="number"),s(t,e)}},function(t,e,n){var i=n(20);t.exports=function(t){return"object"==typeof t?null!==t:i(t)}},function(t){t.exports=function(t){return"function"==typeof t}},function(t,e,n){var i=n(22),r=n(20),a=n(23),o=n(24),s=Object;t.exports=o?function(t){return"symbol"==typeof t}:function(t){var e=i("Symbol");return r(e)&&a(e.prototype,s(t))}},function(t,e,n){var i=n(4),r=n(20),a=function(t){return r(t)?t:void 0};t.exports=function(t,e){return arguments.length<2?a(i[t]):i[t]&&i[t][e]}},function(t,e,n){var i=n(14);t.exports=i({}.isPrototypeOf)},function(t,e,n){var i=n(25);t.exports=i&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},function(t,e,n){var i=n(26),r=n(7);t.exports=!!Object.getOwnPropertySymbols&&!r((function(){var t=Symbol();return!String(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&i&&i<41}))},function(t,e,n){var i,r,a=n(4),o=n(27),s=a.process,u=a.Deno,c=s&&s.versions||u&&u.version,l=c&&c.v8;l&&(r=(i=l.split("."))[0]>0&&i[0]<4?1:+(i[0]+i[1])),!r&&o&&(!(i=o.match(/Edge\/(\d+)/))||i[1]>=74)&&(i=o.match(/Chrome\/(\d+)/))&&(r=+i[1]),t.exports=r},function(t,e,n){var i=n(22);t.exports=i("navigator","userAgent")||""},function(t,e,n){var i=n(29);t.exports=function(t,e){var n=t[e];return null==n?void 0:i(n)}},function(t,e,n){var i=n(20),r=n(30),a=TypeError;t.exports=function(t){if(i(t))return t;throw a(r(t)+" is not a function")}},function(t){var e=String;t.exports=function(t){try{return e(t)}catch(t){return"Object"}}},function(t,e,n){var i=n(8),r=n(20),a=n(19),o=TypeError;t.exports=function(t,e){var n,s;if("string"===e&&r(n=t.toString)&&!a(s=i(n,t)))return s;if(r(n=t.valueOf)&&!a(s=i(n,t)))return s;if("string"!==e&&r(n=t.toString)&&!a(s=i(n,t)))return s;throw o("Can't convert object to primitive value")}},function(t,e,n){var i=n(4),r=n(33),a=n(37),o=n(39),s=n(25),u=n(24),c=r("wks"),l=i.Symbol,h=l&&l.for,f=u?l:l&&l.withoutSetter||o;t.exports=function(t){if(!a(c,t)||!s&&"string"!=typeof c[t]){var e="Symbol."+t;s&&a(l,t)?c[t]=l[t]:c[t]=u&&h?h(e):f(e)}return c[t]}},function(t,e,n){var i=n(34),r=n(35);(t.exports=function(t,e){return r[t]||(r[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.23.4",mode:i?"pure":"global",copyright:"© 2014-2022 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.23.4/LICENSE",source:"https://github.com/zloirock/core-js"})},function(t){t.exports=!1},function(t,e,n){var i=n(4),r=n(36),a="__core-js_shared__",o=i[a]||r(a,{});t.exports=o},function(t,e,n){var i=n(4),r=Object.defineProperty;t.exports=function(t,e){try{r(i,t,{value:e,configurable:!0,writable:!0})}catch(n){i[t]=e}return e}},function(t,e,n){var i=n(14),r=n(38),a=i({}.hasOwnProperty);t.exports=Object.hasOwn||function(t,e){return a(r(t),e)}},function(t,e,n){var i=n(16),r=Object;t.exports=function(t){return r(i(t))}},function(t,e,n){var i=n(14),r=0,a=Math.random(),o=i(1..toString);t.exports=function(t){return"Symbol("+(void 0===t?"":t)+")_"+o(++r+a,36)}},function(t,e,n){var i=n(6),r=n(7),a=n(41);t.exports=!i&&!r((function(){return 7!=Object.defineProperty(a("div"),"a",{get:function(){return 7}}).a}))},function(t,e,n){var i=n(4),r=n(19),a=i.document,o=r(a)&&r(a.createElement);t.exports=function(t){return o?a.createElement(t):{}}},function(t,e,n){var i=n(6),r=n(43),a=n(11);t.exports=i?function(t,e,n){return r.f(t,e,a(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e,n){var i=n(6),r=n(40),a=n(44),o=n(45),s=n(17),u=TypeError,c=Object.defineProperty,l=Object.getOwnPropertyDescriptor,h="enumerable",f="configurable",d="writable";e.f=i?a?function(t,e,n){if(o(t),e=s(e),o(n),"function"==typeof t&&"prototype"===e&&"value"in n&&d in n&&!n.writable){var i=l(t,e);i&&i.writable&&(t[e]=n.value,n={configurable:f in n?n.configurable:i.configurable,enumerable:h in n?n.enumerable:i.enumerable,writable:!1})}return c(t,e,n)}:c:function(t,e,n){if(o(t),e=s(e),o(n),r)try{return c(t,e,n)}catch(t){}if("get"in n||"set"in n)throw u("Accessors not supported");return"value"in n&&(t[e]=n.value),t}},function(t,e,n){var i=n(6),r=n(7);t.exports=i&&r((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))},function(t,e,n){var i=n(19),r=String,a=TypeError;t.exports=function(t){if(i(t))return t;throw a(r(t)+" is not an object")}},function(t,e,n){var i=n(20),r=n(43),a=n(47),o=n(36);t.exports=function(t,e,n,s){s||(s={});var u=s.enumerable,c=void 0!==s.name?s.name:e;if(i(n)&&a(n,c,s),s.global)u?t[e]=n:o(e,n);else{try{s.unsafe?t[e]&&(u=!0):delete t[e]}catch(t){}u?t[e]=n:r.f(t,e,{value:n,enumerable:!1,configurable:!s.nonConfigurable,writable:!s.nonWritable})}return t}},function(t,e,n){var i=n(7),r=n(20),a=n(37),o=n(6),s=n(48).CONFIGURABLE,u=n(49),c=n(50),l=c.enforce,h=c.get,f=Object.defineProperty,d=o&&!i((function(){return 8!==f((function(){}),"length",{value:8}).length})),g=String(String).split("String"),p=t.exports=function(t,e,n){"Symbol("===String(e).slice(0,7)&&(e="["+String(e).replace(/^Symbol\(([^)]*)\)/,"$1")+"]"),n&&n.getter&&(e="get "+e),n&&n.setter&&(e="set "+e),(!a(t,"name")||s&&t.name!==e)&&(o?f(t,"name",{value:e,configurable:!0}):t.name=e),d&&n&&a(n,"arity")&&t.length!==n.arity&&f(t,"length",{value:n.arity});try{n&&a(n,"constructor")&&n.constructor?o&&f(t,"prototype",{writable:!1}):t.prototype&&(t.prototype=void 0)}catch(t){}var i=l(t);return a(i,"source")||(i.source=g.join("string"==typeof e?e:"")),t};Function.prototype.toString=p((function(){return r(this)&&h(this).source||u(this)}),"toString")},function(t,e,n){var i=n(6),r=n(37),a=Function.prototype,o=i&&Object.getOwnPropertyDescriptor,s=r(a,"name"),u=s&&"something"===function(){}.name,c=s&&(!i||i&&o(a,"name").configurable);t.exports={EXISTS:s,PROPER:u,CONFIGURABLE:c}},function(t,e,n){var i=n(14),r=n(20),a=n(35),o=i(Function.toString);r(a.inspectSource)||(a.inspectSource=function(t){return o(t)}),t.exports=a.inspectSource},function(t,e,n){var i,r,a,o=n(51),s=n(4),u=n(14),c=n(19),l=n(42),h=n(37),f=n(35),d=n(52),g=n(53),p="Object already initialized",v=s.TypeError,y=s.WeakMap;if(o||f.state){var x=f.state||(f.state=new y),b=u(x.get),_=u(x.has),m=u(x.set);i=function(t,e){if(_(x,t))throw new v(p);return e.facade=t,m(x,t,e),e},r=function(t){return b(x,t)||{}},a=function(t){return _(x,t)}}else{var w=d("state");g[w]=!0,i=function(t,e){if(h(t,w))throw new v(p);return e.facade=t,l(t,w,e),e},r=function(t){return h(t,w)?t[w]:{}},a=function(t){return h(t,w)}}t.exports={set:i,get:r,has:a,enforce:function(t){return a(t)?r(t):i(t,{})},getterFor:function(t){return function(e){var n;if(!c(e)||(n=r(e)).type!==t)throw v("Incompatible receiver, "+t+" required");return n}}}},function(t,e,n){var i=n(4),r=n(20),a=n(49),o=i.WeakMap;t.exports=r(o)&&/native code/.test(a(o))},function(t,e,n){var i=n(33),r=n(39),a=i("keys");t.exports=function(t){return a[t]||(a[t]=r(t))}},function(t){t.exports={}},function(t,e,n){var i=n(37),r=n(55),a=n(5),o=n(43);t.exports=function(t,e,n){for(var s=r(e),u=o.f,c=a.f,l=0;lc;)r(i,n=e[c++])&&(~o(l,n)||u(l,n));return l}},function(t,e,n){var i=n(12),r=n(59),a=n(62),o=function(t){return function(e,n,o){var s,u=i(e),c=a(u),l=r(o,c);if(t&&n!=n){for(;c>l;)if((s=u[l++])!=s)return!0}else for(;c>l;l++)if((t||l in u)&&u[l]===n)return t||l||0;return!t&&-1}};t.exports={includes:o(!0),indexOf:o(!1)}},function(t,e,n){var i=n(60),r=Math.max,a=Math.min;t.exports=function(t,e){var n=i(t);return n<0?r(n+e,0):a(n,e)}},function(t,e,n){var i=n(61);t.exports=function(t){var e=+t;return e!=e||0===e?0:i(e)}},function(t){var e=Math.ceil,n=Math.floor;t.exports=Math.trunc||function(t){var i=+t;return(i>0?n:e)(i)}},function(t,e,n){var i=n(63);t.exports=function(t){return i(t.length)}},function(t,e,n){var i=n(60),r=Math.min;t.exports=function(t){return t>0?r(i(t),9007199254740991):0}},function(t){t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},function(t,e){e.f=Object.getOwnPropertySymbols},function(t,e,n){var i=n(7),r=n(20),a=/#|\.prototype\./,o=function(t,e){var n=u[s(t)];return n==l||n!=c&&(r(e)?i(e):!!e)},s=o.normalize=function(t){return String(t).replace(a,".").toLowerCase()},u=o.data={},c=o.NATIVE="N",l=o.POLYFILL="P";t.exports=o},function(t,e,n){var i=n(68),r=String;t.exports=function(t){if("Symbol"===i(t))throw TypeError("Cannot convert a Symbol value to a string");return r(t)}},function(t,e,n){var i=n(69),r=n(20),a=n(15),o=n(32)("toStringTag"),s=Object,u="Arguments"==a(function(){return arguments}());t.exports=i?a:function(t){var e,n,i;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=function(t,e){try{return t[e]}catch(t){}}(e=s(t),o))?n:u?a(e):"Object"==(i=a(e))&&r(e.callee)?"Arguments":i}},function(t,e,n){var i={};i[n(32)("toStringTag")]="z",t.exports="[object z]"===String(i)},function(t,e,n){var i,r=n(45),a=n(71),o=n(64),s=n(53),u=n(73),c=n(41),l=n(52),h=l("IE_PROTO"),f=function(){},d=function(t){return"