IDEMPIERE-5797 Zk: Upgrade Billboard chart to 3.9.0 (#1930)

* IDEMPIERE-5797 Zk: Upgrade Billboard chart to 3.9.0

* IDEMPIERE-5797 Zk: Upgrade Billboard chart to 3.9.0

- Add basic localization support

* IDEMPIERE-5797 Zk: Upgrade Billboard chart to 3.9.0

- update Readme

* IDEMPIERE-5797 Zk: Upgrade Billboard chart to 3.9.0

- Fix intermittent sizing and rendering issue
This commit is contained in:
hengsin 2023-07-13 20:34:06 +08:00 committed by GitHub
parent 47e5f01206
commit b47544b55a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 45221 additions and 53124 deletions

View File

@ -57,6 +57,6 @@ Copyright (C) 2007 Ashley G Ramdass (ADempiere WebUI).
<!-- this js module doesn't actually exists and it is here for default theme version -->
<!-- since loading of js module is on demand, it doesn't cause any error as long as you don't try to load it -->
<javascript-module name="idempiere.theme.default" version="202306220900" />
<javascript-module name="idempiere.theme.default" version="202307100900" />
</language>

View File

@ -196,7 +196,7 @@ public class WPerformanceIndicator extends Panel implements EventListener<Event>
}
this.getChildren().clear();
renderChart(width, height);
Events.sendEvent(this, new Event(ON_AFTER_RENDER_CHART_EVENT));
Events.sendEvent(this, new Event(ON_AFTER_RENDER_CHART_EVENT, this));
}
/**

View File

@ -21,6 +21,9 @@ import org.compiere.model.MGoal;
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.Events;
import org.zkoss.zk.ui.event.MaximizeEvent;
import org.zkoss.zk.ui.event.OpenEvent;
import org.zkoss.zk.ui.util.Clients;
/**
@ -49,6 +52,30 @@ public class DPPerformance extends DashboardPanel {
paPanel = new WPAPanel();
appendChild(paPanel);
paPanel.addEventListener(WPerformanceIndicator.ON_AFTER_RENDER_CHART_EVENT, e -> onPostRender());
this.addEventListener(Events.ON_OPEN, (OpenEvent e) -> {
if (e.isOpen())
onPostRestore();
});
this.addEventListener(Events.ON_MAXIMIZE, (MaximizeEvent e) -> {
if (!e.isMaximized())
onPostRestore();
});
}
/**
* After state of dashboard panel change from collapse to open or from maximize to normal.
*/
private void onPostRestore() {
if (this.getFirstChild() != null && this.getParent() != null) {
Component grid = this.getFirstChild().getFirstChild();
String script = "setTimeout(function() { let grid = jq('#" + grid.getUuid() + "');";
script = script + "let pa = jq('#" + this.getFirstChild().getUuid() + "');";
script = script + "let pc = jq('#" + this.getParent().getUuid() + "');";
script = script + "pa.height(grid.css('height'));";
script = script + "pc.height(grid.css('height'));}, 10);";
if (Executions.getCurrent() != null)
Clients.response(new AuScript(script));
}
}
@Override

View File

@ -1142,6 +1142,13 @@ public class DashboardController implements EventListener<Event> {
//following 2 line needed for restore to size the panel correctly
ZKUpdateUtil.setHflex(panel, (String)panel.getAttribute(FLEX_GROW_ATTRIBUTE));
ZKUpdateUtil.setHeight(panel, "100%");
//notify panel content component
if (panel.getPanelchildren() != null) {
panel.getPanelchildren().getChildren().forEach(child -> {
Executions.schedule(dashboardLayout.getDesktop(), e -> Events.postEvent(child, event), new Event("onPostRestore"));
});
}
}
}
else if(eventName.equals(Events.ON_CLICK))
@ -1252,6 +1259,13 @@ public class DashboardController implements EventListener<Event> {
PO.clearCrossTenantSafe();
}
}
//notify panel content component
if (panel.getPanelchildren() != null) {
for(Component c : panel.getPanelchildren().getChildren()) {
Events.postEvent(c, event);
}
}
}
}
}

View File

@ -42,6 +42,7 @@ import org.adempiere.webui.component.Tabpanel;
import org.adempiere.webui.component.ToolBar;
import org.adempiere.webui.component.ToolBarButton;
import org.adempiere.webui.component.Window;
import org.adempiere.webui.dashboard.DashboardPanel;
import org.adempiere.webui.event.MenuListener;
import org.adempiere.webui.event.ZKBroadCastManager;
import org.adempiere.webui.panel.ADForm;
@ -49,6 +50,7 @@ import org.adempiere.webui.panel.BroadcastMessageWindow;
import org.adempiere.webui.panel.HeaderPanel;
import org.adempiere.webui.panel.HelpController;
import org.adempiere.webui.panel.TimeoutPanel;
import org.adempiere.webui.part.ITabOnSelectHandler;
import org.adempiere.webui.session.SessionManager;
import org.adempiere.webui.theme.ThemeManager;
import org.adempiere.webui.util.UserPreference;
@ -604,6 +606,13 @@ public class DefaultDesktop extends TabbedDesktop implements MenuListener, Seria
dashboardController.render(homeTab, this, true);
if (homeTab.getFirstChild() != null) {
ITabOnSelectHandler handler = () -> {
invalidateDashboardPanel(homeTab.getFirstChild().getChildren());
};
homeTab.getFirstChild().setAttribute(ITabOnSelectHandler.ATTRIBUTE_KEY, handler);
}
homeTab.setAttribute(HOME_TAB_RENDER_ATTR, Boolean.TRUE);
West w = layout.getWest();
@ -656,6 +665,20 @@ public class DefaultDesktop extends TabbedDesktop implements MenuListener, Seria
homeTab.invalidate();
}
/**
* Redraw dashboard panel after switching back to home tab
* @param childrens
*/
private void invalidateDashboardPanel(List<Component> childrens) {
for (Component children : childrens) {
if (children instanceof DashboardPanel) {
children.invalidate();
} else {
invalidateDashboardPanel(children.getChildren());
}
}
}
/**
* Set width of popup for side panel
* @param popup

View File

@ -212,7 +212,7 @@
.performance-indicator-box {
background-color: #eee;
border: 1px solid #d8d8d8;
border-radius: 11px;
border-radius: 5px;
cursor: pointer;
}
.performance-indicator-title {

View File

@ -458,6 +458,7 @@ public class ChartBuilder {
billboard.setValueAxisLabel(mChart.get_Translation(MChart.COLUMNNAME_RangeLabel));
billboard.setTitle(mChart.get_Translation(MChart.COLUMNNAME_Name));
billboard.setType(type);
billboard.setLocale(Env.getContext(Env.getCtx(), Env.LOCALE));
return billboard;
}

View File

@ -68,8 +68,6 @@ public class PerformanceGraphBuilder {
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++) {
@ -78,13 +76,10 @@ public class PerformanceGraphBuilder {
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());
billboard.addRendererOptions("showNeedle", Boolean.TRUE);
}
private DialModel createDialModel(IndicatorModel model)

View File

@ -2,6 +2,20 @@
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 ).
2. Get latest billboard.min.js and billboard.js from https://github.com/naver/billboard.js. Following Zk naming convention, rename billboard.min.js to billboard.js and rename billboard.js to billboard.src.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
3. Get d3 from https://d3js.org/. The current version use by billboard is v6 so the corresponding link is https://d3js.org/d3.v6.js and https://d3js.org/d3.v6.min.js. Again, following Zk naming conversion, we need to rename d3.v6.js to d3.v6.src.js and rename d3.v6.min.js to d3.v6.js.
4. 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
* Change font-size of text.bb-chart-arcs-gauge-title from 2.7em to 1.5em.
* Original: text.bb-chart-arcs-gauge-title {
dominant-baseline: middle;
font-size: 2.7em; }
* Updated: text.bb-chart-arcs-gauge-title {
dominant-baseline: middle;
font-size: 1.5em; }
5. Update version-uid in metainfo.zk/lang-addon.xml and org.idempiere.zk.billboard.Version.UID (both value must match).
6. Update version of "zul.billboard" and "zul.billboard.css" javascript-module in metainfo.zk/lang-addon.xml.

View File

@ -6,7 +6,7 @@
<language-name>xul/html</language-name>
<version>
<version-class>org.idempiere.zk.billboard.Version</version-class>
<version-uid>3.5.1.20220908</version-uid>
<version-uid>3.9.0.20230713</version-uid>
</version>
<component>
<component-name>billboard</component-name>
@ -19,7 +19,7 @@
</component>
<stylesheet href="~./js/zul/billboard/css/billboard.css" type="text/css"/>
<javascript-module name="zul.billboard" version="3.5.1.20221027"/>
<javascript-module name="zul.billboard" version="3.9.0.202307131000"/>
<!-- 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>
<javascript-module name="zul.billboard.css" version="3.9.0.202307131000"/>
</language-addon>

View File

@ -53,7 +53,7 @@ public class Billboard extends XulElement {
/**
* generated serial id
*/
private static final long serialVersionUID = -3888636406033151303L;
private static final long serialVersionUID = -2164790050418874185L;
// Must
private ChartModel _model;
@ -73,6 +73,7 @@ public class Billboard extends XulElement {
private String valueAxisLabel = null;
private String[] seriesColors = null;
private int xAxisAngle = 0;
private String locale = null;
public static final String ON_DATA_CLICK_EVENT = "onDataClick";
@ -95,6 +96,7 @@ public class Billboard extends XulElement {
render(renderer, "orient", _orient);
render(renderer, "timeSeries", timeSeries);
render(renderer, "xAxisAngle", xAxisAngle);
render(renderer, "locale", toD3Locale(locale));
if (timeSeries) {
if (timeSeriesInterval != null)
render(renderer, "timeSeriesInterval", timeSeriesInterval);
@ -139,6 +141,15 @@ public class Billboard extends XulElement {
*/
}
private String toD3Locale(String locale) {
if (locale == null)
return null;
if ("es_CO".equals(locale))
return "es-ES";
else
return locale.replaceFirst("[_]", "-");
}
private JSONObject mapToJSON(Map<String, Object> map) {
JSONObject jData = new JSONObject();
for(String key : map.keySet()) {
@ -466,4 +477,8 @@ public class Billboard extends XulElement {
public void setXAxisAngle(int xAxisAngle) {
this.xAxisAngle = xAxisAngle;
}
public void setLocale(String locale) {
this.locale = locale;
}
}

View File

@ -30,7 +30,9 @@ package org.idempiere.zk.billboard;
*
*/
public class Version {
/** Returns the version UID.
/**
* Returns the version UID.<br/>
* Must match with version-uid value in lang-addon.xml
*/
public static final String UID = "3.5.1.20220908";
public static final String UID = "3.9.0.20230713";
}

View File

@ -1,5 +1,8 @@
(function() {
const __d3_formatLocaleCache = new Map();
const __d3_timeFormatLocaleCache = new Map();
var Billboard =
zul.billboard.Billboard = zk.$extends(zk.Widget,
{
@ -10,6 +13,7 @@
_cursor : false,
_highlighter : true,
_dataClickTS : 0,
_locale: null,
$define : {
title: null,
@ -35,10 +39,51 @@
timeSeriesInterval: null,
timeSeriesFormat: null,
xAxisAngle: null,
chart: null
chart: null,
locale: null
},
_loadFormatLocale: async function(url) {
try {
const definition = await d3.json(url);
d3.formatDefaultLocale(definition);
__d3_formatLocaleCache.set(url, definition);
} catch (error) {
__d3_formatLocaleCache.set(url, null);
}
},
_loadTimeFormatLocale: async function(url) {
try {
const definition = await d3.json(url);
d3.timeFormatDefaultLocale(definition);
__d3_timeFormatLocaleCache.set(url, definition);
} catch (error) {
__d3_timeFormatLocaleCache.set(url, null);
}
},
_dataPrepare : function() {
//load locale
if (this.getLocale() != null) {
const formatURL = "https://unpkg.com/d3-format@3.1.0/locale/"+this.getLocale()+".json";
if (__d3_formatLocaleCache.has(formatURL)) {
const definition = __d3_formatLocaleCache.get(formatURL);
if (definition != null)
d3.formatDefaultLocale(definition);
} else {
this._loadFormatLocale(formatURL);
}
const timeFormatURL = "https://unpkg.com/d3-time-format@4.1.0/locale/"+this.getLocale()+".json";
if (__d3_timeFormatLocaleCache.has(timeFormatURL)) {
const definition = __d3_timeFormatLocaleCache.get(timeFormatURL);
if (definition != null)
d3.timeFormatDefaultLocale(definition);
} else {
this._loadTimeFormatLocale(timeFormatURL);
}
}
var dataModel = this.getModel();
var data = [];
try {

View File

@ -5,7 +5,7 @@
* billboard.js, JavaScript chart library
* https://naver.github.io/billboard.js/
*
* @version 3.5.1
* @version 3.9.0
*/
/*-- Chart --*/
.bb svg {
@ -24,11 +24,12 @@
.bb-legend-item-tile,
.bb-xgrid-focus,
.bb-ygrid-focus,
.bb-ygrid,
.bb-event-rect,
.bb-bars path {
.bb-ygrid {
shape-rendering: crispEdges; }
.bb-chart-arcs .bb-needle {
fill: #000; }
.bb-chart-arc .bb-gauge-value {
fill: #000; }
@ -131,6 +132,11 @@
.bb-title {
font: 14px sans-serif; }
/*-- Treemap --*/
.bb-chart-treemaps rect {
stroke: #fff;
stroke-width: 1px; }
/*-- Tooltip --*/
.bb-tooltip-container {
z-index: 10;
@ -142,9 +148,8 @@
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; }
box-shadow: 7px 7px 12px -9px #777777;
white-space: nowrap; }
.bb-tooltip tr {
border: 1px solid #CCC; }
.bb-tooltip th {
@ -164,7 +169,7 @@
height: 10px;
margin-right: 6px; }
.bb-tooltip.value {
text-align: right; }
text-align: right !important; }
/*-- Area --*/
.bb-area {
@ -178,7 +183,7 @@
text.bb-chart-arcs-gauge-title {
dominant-baseline: middle;
font-size: 2.7em; }
font-size: 1.5em; }
.bb-chart-arcs {
/*-- Polar --*/ }

View File

@ -105,7 +105,7 @@ billboard.AreaRenderer.prototype.render = function(wgt) {
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>';
h = h + d3.format('.2f')(c.value) + '</td></tr></tbody></table>';
return h;
}
},

View File

@ -105,7 +105,7 @@ billboard.BarRenderer.prototype.render = function(wgt) {
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>';
h = h + d3.format('.2f')(c.value) + '</td></tr></tbody></table>';
return h;
}
},

View File

@ -54,7 +54,6 @@ billboard.DonutRenderer.prototype.render = function(wgt) {
};
if (wgt.getTitle())
model["donut"]["title"] = wgt.getTitle();
//model["title"] = {text: wgt.getTitle()};
return model;
};

View File

@ -1,56 +1,82 @@
var billboard = billboard || {};
billboard.GaugeRenderer = function() {};
billboard.GaugeRenderer = class {
constructor() { }
render(wgt) {
var columns = [wgt.getSeriesData()[0], wgt.getSeriesData()[1]];
var color = {};
var gauge = {
units: ""
};
let showNeedle = false;
var rendererOptions = wgt._rendererOptions ? jq.evalJSON(wgt._rendererOptions) : null;
if (rendererOptions) {
if (rendererOptions["showNeedle"] && rendererOptions["showNeedle"] == true) {
showNeedle = true;
gauge.title = "\n{=NEEDLE_VALUE}%";
gauge.width = 20;
gauge.label = {
format: function(_value, _ratio, id) { return id; }
};
}
if (rendererOptions["intervalColors"]) {
color["pattern"] = new Array();
rendererOptions["intervalColors"].forEach((x, _i) => color["pattern"].push(x));
}
if (rendererOptions["intervals"]) {
if (!showNeedle) {
color["threshold"] = {values: []};
rendererOptions["intervals"].forEach((x, _i) => color["threshold"]["values"].push(x));
} else {
columns = [];
let prev = 0;
rendererOptions["intervals"].forEach((x, _i) => {
let step = x - prev;
prev = x;
columns.push([x + "%", step]);
});
}
}
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;
if (rendererOptions["background"]) {
gauge["background"] = rendererOptions["background"];
}
}
var model = {
bindto: "#" + wgt.$n().id,
data: {
columns: showNeedle ? 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 }
};
if (showNeedle) {
model.arc = {
needle: {
show: true,
value: wgt.getSeriesData()[1][0]
}
};
model.interaction = {
enabled: false
};
}
return model;
}
};
zul.billboard.Billboard._renderers["gauge"] = new billboard.GaugeRenderer();

File diff suppressed because one or more lines are too long

View File

@ -105,7 +105,7 @@ billboard.LineRenderer.prototype.render = function(wgt) {
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>';
h = h + d3.format('.2f')(c.value) + '</td></tr></tbody></table>';
return h;
}
},

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -110,7 +110,7 @@ billboard.WaterfallRenderer.prototype.render = function(wgt) {
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>';
h = h + d3.format('.2f')(c.value[1]) + '</td></tr></tbody></table>';
return h;
}
},

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,8 @@
<package name="zul.billboard" language="xul/html" depends="zul">
<widget name="Billboard" />
<script src="ext/billboard.pkgd.js" />
<script src="ext/d3.v6.js" />
<script src="ext/billboard.js" />
<script src="ext/billboard.gauge.js" />
<script src="ext/billboard.bar.js" />
<script src="ext/billboard.line.js" />