/** * Copyright (C) 2011 BonitaSoft S.A. * BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble * 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.0 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, see <http://www.gnu.org/licenses/>. */ package org.bonitasoft.web.toolkit.client.ui.component.chart; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.bonitasoft.web.toolkit.client.common.AbstractTreeNode; import org.bonitasoft.web.toolkit.client.common.TreeIndexed; import org.bonitasoft.web.toolkit.client.common.json.JSonSerializer; import org.bonitasoft.web.toolkit.client.ui.JsId; import org.bonitasoft.web.toolkit.client.ui.component.core.Component; import org.bonitasoft.web.toolkit.client.ui.html.HTML; import org.bonitasoft.web.toolkit.client.ui.html.HTMLClass; import org.bonitasoft.web.toolkit.client.ui.html.XML; import org.bonitasoft.web.toolkit.client.ui.utils.Color; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.user.client.Element; /** * @author Séverin Moussel * */ public abstract class Chart extends Component { public static enum LEGEND_POSITION { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }; private JavaScriptObject flotObject = null; protected int maxPoints = -1; protected int maxItems = 10; protected final TreeIndexed<Object> options = new TreeIndexed<Object>(); private final List<Color> defaultColors = new LinkedList<Color>(); protected final Map<String, ChartItem> data = new LinkedHashMap<String, ChartItem>(); protected boolean autoRedraw = true; // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CONSTRUCTORS // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public Chart() { this(null); } public Chart(final JsId jsid) { super(jsid); initOptions(); } // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // OPTIONS // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected final void addOption(final String path, final Object value) { this.addOption(this.options, path.split("\\."), value); } protected final void addOption(final TreeIndexed<Object> options, final String path, final Object value) { this.addOption(options, path.split("\\."), value); } protected final void addOption(final TreeIndexed<Object> root, final String[] path, final Object value) { int i = 0; while (i < path.length && path[i] == null) { i++; } if (i >= path.length) { return; } AbstractTreeNode<Object> node = root.get(path[i]); if (node == null) { node = new TreeIndexed<Object>(); root.addNode(path[i], node); } if (i == path.length - 1 || !(node instanceof TreeIndexed<?>)) { root.addValue(path[i], value); return; } path[i] = null; this.addOption((TreeIndexed<Object>) node, path, value); } protected final void removeOption(final String path) { this.removeOption(this.options, path.split("\\."), 0); } protected final boolean removeOption(final TreeIndexed<Object> root, final String[] path, final int pos) { final String key = path[pos]; final AbstractTreeNode<Object> node = root.get(key); if (node == null) { // The path lead to a non existent node return false; } // The path lead to if (pos == path.length - 1) { root.removeNode(key); return true; } // The path can't be traveled to its end if (!(node instanceof TreeIndexed<?>)) { return false; } // Continue to the end of the path final boolean result = this.removeOption((TreeIndexed<Object>) node, path, pos + 1); // If deletion has been done, we delete empty nodes if (result && ((TreeIndexed<Object>) node).size() == 0) { root.removeNode(key); } return result; } protected void initOptions() { // this.setDefaultColors( // new Color("#c00"), // new Color("#f99"), // new Color("#b88"), // new Color("#711"), // new Color("#f00"), // new Color("#ebb"), // new Color("#755"), // new Color("#422") // ); } public Chart setAutoRedraw(final boolean autoRedraw) { this.autoRedraw = autoRedraw; return this; } public boolean isAutoRedraw() { return this.autoRedraw; } public final Chart setDefaultColors(final List<Color> colors) { this.defaultColors.clear(); this.defaultColors.addAll(colors); return this; } public final Chart setDefaultColors(final Color... colors) { this.defaultColors.clear(); for (int i = 0; i < colors.length; i++) { this.defaultColors.add(colors[i]); } return this; } public final Chart setDefaultColors(final String... colors) { this.defaultColors.clear(); for (int i = 0; i < colors.length; i++) { this.defaultColors.add(new Color(colors[i])); } return this; } public final Chart setMaxPoints(final int maxPoints) { this.maxPoints = maxPoints; for (final ChartItem item : this.data.values()) { if (item instanceof ChartSerie) { ((ChartSerie) item).setMaxLength(maxPoints); } } return this; } public int getMaxPoints() { return this.maxPoints; } public final Chart setMaxItems(final int maxItems) { this.maxItems = maxItems; return this; } public int getMaxItems() { return this.maxItems; } public final Chart setLegendDisplay(final boolean display) { this.addOption("legend.show", display); return this; } public final Chart setLegendLabelBorderColor(final String color) { return this.setLegendLabelBorderColor(new Color(color)); } public final Chart setLegendLabelBorderColor(final Color color) { this.addOption("legend.labelBoxBorderColor", color.toHexString()); return this; } public final Chart setLegendColumnsNumber(final int columns) { this.addOption("legend.noColumns", columns); return this; } public final Chart setLegendPosition(final LEGEND_POSITION position) { String pos = null; switch (position) { default: case TOP_LEFT: pos = "ne"; break; case TOP_RIGHT: pos = "nw"; break; case BOTTOM_LEFT: pos = "se"; break; case BOTTOM_RIGHT: pos = "sw"; break; } this.addOption("legend.position", pos); return this; } public final Chart setLegendMargin(final int margin) { this.addOption("legend.margin", margin); return this; } public final Chart setLegendBackgroundColor(final String color) { return this.setLegendBackgroundColor(new Color(color)); } public final Chart setLegendBackgroundColor(final Color color) { this.addOption("legend.labelBoxBorderColor", "#" + Integer.toHexString(color.getRed()) + Integer.toHexString(color.getGreen()) + Integer.toHexString(color.getBlue()) ); this.addOption("legend.backgroundOpacity", color.getAlpha() / 255); return this; } // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // DATA // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected Chart setItem(final String label, final ChartItem item) { if (!this.data.containsKey(label) && this.defaultColors.size() > this.data.size()) { item.setColor(this.defaultColors.get(this.data.size())); } this.data.put(label, item); reduce(); return this; } protected final ChartItem getItem(final String label) { return this.getItem(label, false); } protected final ChartItem getItem(final String label, final boolean createIfNotExists) { if (this.data.get(label) == null && createIfNotExists) { setItem(label, createNewItem(label)); } return this.data.get(label); } protected abstract ChartItem createNewItem(String label); private void reduce() { if (this.data.size() > this.maxItems) { for (final String label : this.data.keySet()) { if (this.data.size() <= this.maxItems) { return; } this.data.remove(label); } } } // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // OUTPUT // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings({ "rawtypes", "unchecked" }) private String prepareData() { return JSonSerializer.serialize(new LinkedList(this.data.values())); } protected String prepareOptions() { return JSonSerializer.serialize(this.options); } public final Chart refresh() { // For debug purpose only this.element.setAttribute("title", JSonSerializer.serialize(prepareOptions())); this.flotObject = this.updateFlot(this.element, prepareData(), prepareOptions()); return this; } @Override protected final Element makeElement() { // Add a placeholder return XML.makeElement(HTML.div(new HTMLClass("chart"))); } @Override protected final void onLoad() { this.flotObject = initFlot(this.element, prepareData(), prepareOptions()); super.onLoad(); } private native JavaScriptObject initFlot(final Element placeholder, final String data, String options) /*-{ options = $wnd.$.extend(true, eval('(' + options + ')'), { series:{ pie:{ label:{ formatter: function(label, series){ return '<div style="font-size:8pt;text-align:center;padding:2px;color:white;">' + label + '</div>';} } } } }); return $wnd.$.plot($wnd.$(placeholder), eval('(' + data + ')'), options); }-*/; private native JavaScriptObject updateFlot(JavaScriptObject flotObject, String data) /*-{ flotObject.setData(eval('(' + data + ')')); flotObject.draw(); return flotObject; }-*/; private native JavaScriptObject updateFlot(final Element placeholder, String data, String options) /*-{ options = eval('(' + options + ')'); // Set the pie label design options = $wnd.$.extend( true, { xaxis:{tickFormatter:undefined}, yaxis:{tickFormatter:undefined} }, options, { series:{ pie:{ label:{ formatter: function(label, series){ return '<div style="font-size:8pt;text-align:center;padding:2px;color:white">' + label + '</div>'; } } } } } ); // Set the axis formatters if (options.xaxis.tickFormatter) { if (options.xaxis.tickFormatter == "ROUND") { options.xaxis.tickFormatter = function(val, axis) { return Math.round(val); }; } else if (options.xaxis.tickFormatter.substring(0,5) == "WRAP{") { options.xaxis['tickFormat'] = eval('(' + options.xaxis.tickFormatter.substring(4) + ')'); options.xaxis.tickFormatter = function(val, axis) { return options.xaxis.tickFormat.before + val + options.xaxis.tickFormat.after; }; } else if (options.xaxis.tickFormatter != undefined){ options.xaxis['tickFormat'] = options.xaxis.tickFormatter; options.xaxis.tickFormatter = function(val, axis) { return $wnd.dateFormat(new Date(val), options.xaxis.tickFormat); }; } } if (options.yaxis.tickFormatter) { if (options.yaxis.tickFormatter == "ROUND") { options.yaxis.tickFormatter = function(val, axis) { return Math.round(val); }; } else if (options.yaxis.tickFormatter.substring(0,5) == "WRAP{") { options.yaxis['tickFormat'] = eval('(' + options.yaxis.tickFormatter.substring(4) + ')'); options.yaxis.tickFormatter = function(val, axis) { return options.yaxis.tickFormat.before + val + options.yaxis.tickFormat.after; }; } else if (options.yaxis.tickFormatter == undefined){ options.yaxis['tickFormat'] = options.yaxis.tickFormatter; options.yaxis.tickFormatter = function(val, axis) { return $wnd.dateFormat(new Date(val), options.yaxis.tickFormat); }; } } return $wnd.$.plot($wnd.$(placeholder), eval('(' + data + ')'), options); }-*/; }