IDEMPIERE-5402 Replace Jfree Chart with Billboard (#1463)

- replace jfreechart with https://github.com/naver/billboard.js
This commit is contained in:
hengsin 2022-09-07 06:36:43 +08:00 committed by GitHub
parent 8748f11ddf
commit 92cdb06129
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 56662 additions and 11 deletions

View File

@ -183,11 +183,11 @@
<setEntry value="org.apache.felix.webconsole@default:true"/>
<setEntry value="org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec@default:default"/>
<setEntry value="org.apache.geronimo.specs.geronimo-jms_1.1_spec@default:default"/>
<setEntry value="org.apache.httpcomponents.client5.httpclient5@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5-h2@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5@default:default"/>
<setEntry value="org.apache.httpcomponents.httpclient@default:default"/>
<setEntry value="org.apache.httpcomponents.httpcore@default:default"/>
<setEntry value="org.apache.httpcomponents.client5.httpclient5@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5-h2@default:default"/>
<setEntry value="org.apache.neethi@default:default"/>
<setEntry value="org.apache.poi.ooxml-schemas@default:default"/>
<setEntry value="org.apache.servicemix.bundles.cglib@default:default"/>
@ -393,6 +393,8 @@
<setEntry value="org.idempiere.felix.webconsole@default:true"/>
<setEntry value="org.idempiere.hazelcast.service@default:default"/>
<setEntry value="org.idempiere.webservices@default:default"/>
<setEntry value="org.idempiere.zk.billboard.chart@default:default"/>
<setEntry value="org.idempiere.zk.billboard@default:default"/>
<setEntry value="org.idempiere.zk.extra@default:default"/>
</setAttribute>
<booleanAttribute key="show_selected_only" value="false"/>

View File

@ -187,11 +187,11 @@
<setEntry value="org.apache.felix.webconsole@default:true"/>
<setEntry value="org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec@default:default"/>
<setEntry value="org.apache.geronimo.specs.geronimo-jms_1.1_spec@default:default"/>
<setEntry value="org.apache.httpcomponents.client5.httpclient5@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5-h2@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5@default:default"/>
<setEntry value="org.apache.httpcomponents.httpclient@default:default"/>
<setEntry value="org.apache.httpcomponents.httpcore@default:default"/>
<setEntry value="org.apache.httpcomponents.client5.httpclient5@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5-h2@default:default"/>
<setEntry value="org.apache.neethi@default:default"/>
<setEntry value="org.apache.poi.ooxml-schemas@default:default"/>
<setEntry value="org.apache.servicemix.bundles.batik@default:default"/>
@ -415,6 +415,8 @@
<setEntry value="org.idempiere.keikai@default:false"/>
<setEntry value="org.idempiere.printformat.editor@default:default"/>
<setEntry value="org.idempiere.webservices@default:default"/>
<setEntry value="org.idempiere.zk.billboard.chart@default:default"/>
<setEntry value="org.idempiere.zk.billboard@default:false"/>
<setEntry value="org.idempiere.zk.extra@default:default"/>
</setAttribute>
<booleanAttribute key="show_selected_only" value="false"/>

View File

@ -57,4 +57,18 @@
version="0.0.0"
unpack="false"/>
<plugin
id="org.idempiere.zk.billboard"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
<plugin
id="org.idempiere.zk.billboard.chart"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>

View File

@ -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

View File

@ -2,7 +2,7 @@
<!DOCTYPE scr:component>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.adempiere.webui.apps.graph.jfreegraph.ChartRendererServiceImpl">
<implementation class="org.adempiere.webui.apps.graph.jfreegraph.ChartRendererServiceImpl"/>
<property name="service.ranking" type="Integer" value="0"/>
<property name="service.ranking" type="Integer" value="-1"/>
<service>
<provide interface="org.adempiere.webui.apps.graph.IChartRendererService"/>
</service>

View File

@ -172,11 +172,11 @@
<setEntry value="org.apache.felix.scr@1:true"/>
<setEntry value="org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec@default:default"/>
<setEntry value="org.apache.geronimo.specs.geronimo-jms_1.1_spec@default:default"/>
<setEntry value="org.apache.httpcomponents.client5.httpclient5@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5-h2@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5@default:default"/>
<setEntry value="org.apache.httpcomponents.httpclient@default:default"/>
<setEntry value="org.apache.httpcomponents.httpcore@default:default"/>
<setEntry value="org.apache.httpcomponents.client5.httpclient5@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5@default:default"/>
<setEntry value="org.apache.httpcomponents.core5.httpcore5-h2@default:default"/>
<setEntry value="org.apache.neethi@default:default"/>
<setEntry value="org.apache.poi.ooxml-schemas@default:default"/>
<setEntry value="org.apache.servicemix.bundles.batik@default:default"/>
@ -393,6 +393,8 @@
<setEntry value="org.idempiere.hazelcast.service@default:default"/>
<setEntry value="org.idempiere.test@default:default"/>
<setEntry value="org.idempiere.webservices@default:default"/>
<setEntry value="org.idempiere.zk.billboard.chart@default:default"/>
<setEntry value="org.idempiere.zk.billboard@default:default"/>
<setEntry value="org.idempiere.zk.extra@default:default"/>
</setAttribute>
<booleanAttribute key="show_selected_only" value="false"/>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.idempiere.zk.billboard.chart</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ds.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View File

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@ -0,0 +1,3 @@
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false

View File

@ -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

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.idempiere.zk.billboard.chart.ChartRendererServiceImpl">
<property name="service.ranking" type="Integer" value="0"/>
<service>
<provide interface="org.adempiere.webui.apps.graph.IChartRendererService"/>
</service>
<implementation class="org.idempiere.zk.billboard.chart.ChartRendererServiceImpl"/>
</scr:component>

View File

@ -0,0 +1,5 @@
source.. = src/
output.. = target/classes/
bin.includes = META-INF/,\
.,\
OSGI-INF/

View File

@ -0,0 +1,12 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.idempiere</groupId>
<artifactId>org.idempiere.parent</artifactId>
<version>${revision}</version>
<relativePath>../org.idempiere.parent/pom.xml</relativePath>
</parent>
<artifactId>org.idempiere.zk.billboard.chart</artifactId>
<packaging>eclipse-plugin</packaging>
</project>

View File

@ -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;
}
}

View File

@ -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<String,MQuery> 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<String,MQuery>();
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<String, MQuery> 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<String, MQuery> 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<Comparable<?>> 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<Comparable<?>> 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<Comparable<?>> seriesList = model.getSeries();
Map<Comparable<?>, BigDecimal> valueMap = new HashMap<Comparable<?>, BigDecimal>();
Collection<Comparable<?>> 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;
}
}

View File

@ -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<Event> {
private Map<String, MQuery> queries;
private org.zkoss.zul.ChartModel model;
private ZoomListener(Map<String,MQuery> 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);
}
}
}

View File

@ -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<Double> intervals = new ArrayList<Double>();
List<String> intervalColors = new ArrayList<String>();
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<Double> ticks = new ArrayList<Double>(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<GraphColumn> 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<GraphColumn> 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<GraphColumn> 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<Double> intervals = new ArrayList<Double>();
List<String> intervalColors = new ArrayList<String>();
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<Event> {
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<GraphColumn> 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);
}
}
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.idempiere.zk.billboard</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View File

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@ -0,0 +1,3 @@
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,4 @@
source.. = src/
output.. = target/classes/
bin.includes = META-INF/,\
.

View File

@ -0,0 +1,12 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.idempiere</groupId>
<artifactId>org.idempiere.parent</artifactId>
<version>${revision}</version>
<relativePath>../org.idempiere.parent/pom.xml</relativePath>
</parent>
<artifactId>org.idempiere.zk.billboard</artifactId>
<packaging>eclipse-plugin</packaging>
</project>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<language-addon>
<addon-name>billboard</addon-name>
<depends>zul</depends>
<language-name>xul/html</language-name>
<version>
<version-class>org.idempiere.zk.billboard.Version</version-class>
<version-uid>3.5.1.20220905</version-uid>
</version>
<component>
<component-name>billboard</component-name>
<component-class>org.idempiere.zk.billboard.Billboard</component-class>
<mold>
<mold-name>default</mold-name>
<widget-class>zul.billboard.Billboard</widget-class>
<mold-uri>mold/billboard.js</mold-uri>
</mold>
</component>
<stylesheet href="~./js/zul/billboard/css/billboard.css" type="text/css"/>
<javascript-module name="zul.billboard" version="3.5.1.20220905"/>
<!-- this js module doesn't actually exists and it is here for billboard.css version -->
<javascript-module name="zul.billboard.css" version="3.5.1.20220905"/>
</language-addon>

View File

@ -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<String, Object> _rendererOptions;
private Map<String, Object> _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<String, Object> 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<String, Object>();
_rendererOptions.put(key, value);
}
/**
*
* @param key
* @param value
*/
public void addLegendOptions(String key, Object value) {
if (_legend == null)
_legend = new HashMap<String, Object>();
_legend.put(key, value);
}
@SuppressWarnings("rawtypes")
private List<JSONObject> transferToJSONObject(ChartModel model) {
LinkedList<JSONObject> list = new LinkedList<JSONObject>();
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<JSONObject> list) {
// list may be null.
if (list == null || list.isEmpty())
return "";
final StringBuffer sb = new StringBuffer().append('[');
for (Iterator<JSONObject> 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<Object> _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;
}
}

View File

@ -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";
}

View File

@ -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<seriesMap.length; i++) {
var label = seriesMap[i];
seriesLabel.push({label: label});
}
this.setSeries(seriesLabel);
}
// End data prepare
this.setSeriesData(seriesData);
this.setTicks(ticks);
},
_chartPrepare : function() {
var wgt = this;
// In this phase, we need to decide following variables
var axes = {};
var seriesDefaults = {};
seriesDefaults.rendererOptions = {};
// Start chart prepare
if (this.getRendererOptions()) {
var options = jq.evalJSON(this.getRendererOptions());
if (seriesDefaults.rendererOptions)
seriesDefaults.rendererOptions = jQuery.extend({}, seriesDefaults.rendererOptions, options);
else
seriesDefaults.rendererOptions = options;
}
// Horizontal or Vertical ?
if(this.getType() != 'pie' && this.getType() != 'gauge' && this.getType() != 'donut') {
var axisRenderer = this.getTimeSeries() ? "timeseries" : "category";
// Vertical
axes.xaxis = {
renderer : axisRenderer ,
ticks: wgt.getTicks()
};
if (this.getTimeSeries()) {
axes.xaxis.tickInterval = this.getTimeSeriesInterval();
axes.xaxis.tickOptions = {formatString: this.getTimeSeriesFormat()};
}
if (this.getXAxisAngle() != 0) {
if (axes.xaxis.tickOptions) {
axes.xaxis.tickOptions.angle = this.getXAxisAngle();
} else {
axes.xaxis.tickOptions = {
angle: this.getXAxisAngle()
};
}
}
axes.yaxis = {};
if (this.getTickAxisLabel()) {
axes.xaxis.label = this.getTickAxisLabel();
}
if (this.getValueAxisLabel()) {
axes.yaxis.label = this.getValueAxisLabel();
}
if(this.getOrient() == 'horizontal') {
axes.rotated = true;
}
}
// End chart prepare
this.setAxes(axes);
this.setSeriesDefaults(seriesDefaults);
},
_chartPlot : function() {
var wgt = this;
var legend = this.getLegend() ? jq.evalJSON(this.getLegend()) : null;
var seriesColors = this.getSeriesColors() ? jq.evalJSON(this.getSeriesColors()) : null;
var nodata = false;
if (typeof wgt.getSeriesData() == "undefined" || wgt.getSeriesData() == null) {
nodata = true;
} else if (wgt.getSeriesData().length == 0){
nodata = true;
} else if (Object.prototype.toString.call(wgt.getSeriesData()[0]) === '[object Array]') {
var count = 0;
for(var i = 0; i < wgt.getSeriesData().length; i++) {
count = count + wgt.getSeriesData()[i].length;
}
nodata = (count == 0);
}
if (!nodata) {
var c = zk.$import('zul.billboard.Billboard');
if (c._renderers[wgt._type]) {
var model = c._renderers[wgt._type].render(wgt);
if (legend) {
if (legend.show == true) {
model.legend.show = true;
} else if (legend.show == false) {
model.legend.show = true;
}
if (legend.placement) {
if (legend.placement == "insideGrid") {
model.legend.position = 'inset';
} else if (legend.placement == "outsideGrid") {
if (legend.location)
model.legend.position = legend.location;
else
model.legend.position = 'bottom';
}
}
}
if (seriesColors) {
if (model.data.groups) {
var grparr = model.data.groups[0];
var colors = {};
for(var i = 0; i < seriesColors.length && i < grparr.length; i++) {
colors[grparr[i]] = seriesColors[i];
}
model.data["colors"] = colors;
} else if (wgt.getSeries()) {
var colors = {};
var seriesarr = wgt.getSeries();
for(var i = 0; i < seriesColors.length && i < seriesarr.length; i++) {
colors[seriesarr[i]] = seriesColors[i];
}
model.data["colors"] = colors;
}
}
var chart = bb.generate(model);
}
}
},
bind_ : function() {
this.$supers(Billboard, 'bind_', arguments);
// Step 1
this._dataPrepare();
// Step 2
this._chartPrepare();
// Step 3
this._chartPlot();
},
unbind_ : function() {
this.$supers(Billboard, 'unbind_', arguments);
},
doClick_ : function(event) {
var ts = new Date().getTime();
if ((ts - this._dataClickTS) > 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 : {}});
})();

View File

@ -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; }

View File

@ -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 = '<table class="bb-tooltip"><tbody><tr><th>';
h = h + defaultTitleFormat(c.x);
h = h + '</th></tr><tr class="bb-tooltip-name-data"><td class="value">';
h = h + c.value + '</td></tr></tbody></table>';
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();

View File

@ -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 = '<table class="bb-tooltip"><tbody><tr><th>';
h = h + defaultTitleFormat(c.x);
h = h + '</th></tr><tr class="bb-tooltip-name-data"><td class="value">';
h = h + c.value + '</td></tr></tbody></table>';
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();

View File

@ -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();

View File

@ -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();

View File

@ -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 = '<table class="bb-tooltip"><tbody><tr><th>';
h = h + defaultTitleFormat(c.x);
h = h + '</th></tr><tr class="bb-tooltip-name-data"><td class="value">';
h = h + c.value + '</td></tr></tbody></table>';
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();

View File

@ -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();

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,174 @@
var billboard = billboard || {};
billboard.StackedAreaRenderer = function() {};
billboard.StackedAreaRenderer.prototype.render = function(wgt) {
var columns = [];
var categories = new Array();
var seriesMap = {};
var currentSeries = "";
wgt.getSeriesData().forEach((x, i) => {
x.forEach((y, j) => {
if (y.category) {
if (!seriesMap[currentSeries])
seriesMap[currentSeries] = {};
var categoryMap = seriesMap[currentSeries];
if (!categoryMap[y.category])
categoryMap[y.category] = y.value;
else
categoryMap[y.category] += y.value;
if (categories.indexOf(y.category) < 0)
categories.push(y.category);
} else {
currentSeries = y;
}
});
});
categories.forEach((c, i) => {
for(var s in seriesMap) {
var categoryMap = seriesMap[s];
if (!categoryMap[c]) {
categoryMap[c] = 0;
}
}
});
for(var s in seriesMap) {
var row = new Array();
row.push(s);
var categoryMap = seriesMap[s];
categories.forEach((c, i) => {
row.push(categoryMap[c]);
});
columns.push(row);
}
var groups = new Array();
groups[0] = new Array();
for(var s in seriesMap) {
groups[0].push(s);
}
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: "area",
groups: groups,
onclick: function(d, e) {
var i = 0;
var si = 0;
for(var s in seriesMap) {
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: true
},
legend: {show: true},
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["stacked_area"] = new billboard.StackedAreaRenderer();

View File

@ -0,0 +1,174 @@
var billboard = billboard || {};
billboard.StackedBarRenderer = function() {};
billboard.StackedBarRenderer.prototype.render = function(wgt) {
var columns = [];
var categories = new Array();
var seriesMap = {};
var currentSeries = "";
wgt.getSeriesData().forEach((x, i) => {
x.forEach((y, j) => {
if (y.category) {
if (!seriesMap[currentSeries])
seriesMap[currentSeries] = {};
var categoryMap = seriesMap[currentSeries];
if (!categoryMap[y.category])
categoryMap[y.category] = y.value;
else
categoryMap[y.category] += y.value;
if (categories.indexOf(y.category) < 0)
categories.push(y.category);
} else {
currentSeries = y;
}
});
});
categories.forEach((c, i) => {
for(var s in seriesMap) {
var categoryMap = seriesMap[s];
if (!categoryMap[c]) {
categoryMap[c] = 0;
}
}
});
for(var s in seriesMap) {
var row = new Array();
row.push(s);
var categoryMap = seriesMap[s];
categories.forEach((c, i) => {
row.push(categoryMap[c]);
});
columns.push(row);
}
var groups = new Array();
groups[0] = new Array();
for(var s in seriesMap) {
groups[0].push(s);
}
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: "bar",
groups: groups,
onclick: function(d, e) {
var i = 0;
var si = 0;
for(var s in seriesMap) {
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: true
},
legend: {show: true},
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["stacked_bar"] = new billboard.StackedBarRenderer();

View File

@ -0,0 +1,158 @@
var billboard = billboard || {};
billboard.WaterfallRenderer = function() {};
billboard.WaterfallRenderer.prototype.render = function(wgt) {
var columns = [];
var categories = new Array();
var prev = 0;
wgt.getSeriesData().forEach((x, i) => {
x.forEach((y, j) => {
if (y.category) {
columns[columns.length-1].push([prev, y.value, prev, y.value]);
prev = 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 candlestick = {};
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: "candlestick",
selection: {
enabled: true,
grouped: true
},
onselected: 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,
candlestick: candlestick,
tooltip: {
show: true,
doNotHide: false,
contents: function(d, defaultTitleFormat, defaultValueFormat, color) {
var c = d[0];
var h = '<table class="bb-tooltip"><tbody><tr><th>';
h = h + defaultTitleFormat(c.x);
h = h + '</th></tr><tr class="bb-tooltip-name-data"><td class="value">';
h = h + c.value[1] + '</td></tr></tbody></table>';
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["waterfall"] = new billboard.WaterfallRenderer();

View File

@ -0,0 +1,3 @@
function (out) {
out.push('<div', this.domAttrs_(), '></div>');
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<package name="zul.billboard" language="xul/html" depends="zul">
<widget name="Billboard" />
<script src="ext/billboard.pkgd.js" />
<script src="ext/billboard.gauge.js" />
<script src="ext/billboard.bar.js" />
<script src="ext/billboard.line.js" />
<script src="ext/billboard.pie.js" />
<script src="ext/billboard.donut.js" />
<script src="ext/billboard.area.js" />
<script src="ext/billboard.waterfall.js" />
<script src="ext/billboard.stacked_bar.js" />
<script src="ext/billboard.stacked_area.js" />
</package>

View File

@ -38,6 +38,8 @@
<module>org.idempiere.zk.extra</module>
<module>org.idempiere.keikai</module>
<module>org.idempiere.printformat.editor</module>
<module>org.idempiere.zk.billboard</module>
<module>org.idempiere.zk.billboard.chart</module>
<module>org.adempiere.report.jasper-feature</module>
<module>org.adempiere.base-feature</module>
<module>org.adempiere.replication-feature</module>