All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pepstock.charba.client.impl.plugins.HtmlLegend Maven / Gradle / Ivy

There is a newer version: 6.5-gwt
Show newest version
/**
    Copyright 2017 Andrea "Stock" Stocchero

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

	    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/
package org.pepstock.charba.client.impl.plugins;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.pepstock.charba.client.IsChart;
import org.pepstock.charba.client.colors.tiles.TilesFactory;
import org.pepstock.charba.client.configuration.Legend;
import org.pepstock.charba.client.defaults.IsDefaultScaledOptions;
import org.pepstock.charba.client.dom.BaseElement;
import org.pepstock.charba.client.dom.BaseHtmlElement;
import org.pepstock.charba.client.dom.BaseNode;
import org.pepstock.charba.client.dom.DOMBuilder;
import org.pepstock.charba.client.dom.NodeList;
import org.pepstock.charba.client.dom.elements.Canvas;
import org.pepstock.charba.client.dom.elements.Div;
import org.pepstock.charba.client.dom.elements.TableCell;
import org.pepstock.charba.client.dom.enums.Unit;
import org.pepstock.charba.client.dom.safehtml.SafeHtml;
import org.pepstock.charba.client.enums.DefaultPlugin;
import org.pepstock.charba.client.enums.Position;
import org.pepstock.charba.client.impl.plugins.HtmlLegendOptionsFactory.HtmlLegendBuilderDefaultsOptionsFactory;
import org.pepstock.charba.client.items.LegendLabelItem;
import org.pepstock.charba.client.plugins.AbstractPlugin;

/**
 * This plugin implements a HTML legend in order to give more flexibility to who needs to customize the legend.
* It uses the {@link HtmlLegendLabelsCallback} to generated HTML legend. * * @author Andrea "Stock" Stocchero */ public final class HtmlLegend extends AbstractPlugin { /** * Plugin ID {@value ID}. */ public static final String ID = "charbahtmllegend"; /** * The factory to create options for plugin. */ public static final HtmlLegendOptionsFactory FACTORY = new HtmlLegendOptionsFactory(); // factory instance to read the options from default global static final HtmlLegendBuilderDefaultsOptionsFactory DEFAULTS_FACTORY = new HtmlLegendBuilderDefaultsOptionsFactory(); // singleton instance private static final HtmlLegend INSTANCE = new HtmlLegend(); // suffix label for main HTML legend element id private static final String SUFFIX_LEGEND_ELEMENT_ID = "_legend"; // static callback to generate legend into HTML private static final HtmlLegendLabelsCallback CALLBACK = new HtmlLegendLabelsCallback(); // cache to store options in order do not load every time the options private final Map pluginOptions = new HashMap<>(); // cache to store legend items managed by chart private final Map> pluginLegendLabelsItems = new HashMap<>(); // cache to store the chart id in order to know when new legend must be created private final Set pluginAddedLegendStatus = new HashSet<>(); // cache to store DIV element which contains legend for each chart private final Map pluginDivElements = new HashMap<>(); // cache to store easing during drawing for each chart // this cache is needed in order to recreate the legend when a chart update // is invoked during a previous update private final Map pluginEasingStatus = new HashMap<>(); // cache to store the original value of legend in order to // manage the change of the legend display after chart creation private final Map pluginLegendDisplayStatus = new HashMap<>(); // cache to store the callback proxies of legend private final Map pluginCallbackProxies = new HashMap<>(); /** * To avoid any instantiation */ private HtmlLegend() { // do nothing } /** * Returns the singleton instance of plugin. * * @return the singleton instance of plugin */ public static HtmlLegend get() { return INSTANCE; } /* * (non-Javadoc) * * @see org.pepstock.charba.client.Plugin#getId() */ @Override public String getId() { return ID; } /* * (non-Javadoc) * * @see org.pepstock.charba.client.plugins.AbstractPlugin#onConfigure(org.pepstock.charba.client. AbstractChart) */ @Override public void onConfigure(IsChart chart) { // checks if argument is consistent if (IsChart.isConsistent(chart)) { // adds into a map the callback proxy instance // to catch events form legend if (!pluginCallbackProxies.containsKey(chart.getId())) { pluginCallbackProxies.put(chart.getId(), new HtmlLegendCallbackProxy()); } HtmlLegendOptions pOptions = null; // loads chart options for the chart IsDefaultScaledOptions options = chart.getWholeOptions(); // if not, loads and cache // creates the plugin options using the java script object // passing also the default color set at constructor. if (options.getPlugins().hasOptions(ID)) { pOptions = options.getPlugins().getOptions(ID, FACTORY); } else { pOptions = new HtmlLegendOptions(HtmlLegendDefaultsOptions.DEFAULTS_INSTANCE); } pluginOptions.put(chart.getId(), pOptions); pOptions.setCurrentCursor(chart.getInitialCursor()); // checks if the plugin is configured to show legend if (pOptions.isDisplay()) { // if the legend is set do not display // or the OOTB legend plugin has been disable // it respects it then ignore it and the plugin in // will be disable manageLegendDisplay(chart, pOptions); } else { // resets status of plugin // because display is false resetStatus(chart); } } } /* * (non-Javadoc) * * @see org.pepstock.charba.client.plugins.AbstractPlugin#onBeforeUpdate(org.pepstock.charba.client.IsChart) */ @Override public boolean onBeforeUpdate(IsChart chart) { // checks if argument is consistent if (mustBeDisplay(chart)) { // gets the legend Legend legend = chart.getOptions().getLegend(); // creates legend DIV element reference Div legendElement = null; // checks if element is alreayd created if (!pluginDivElements.containsKey(chart.getId())) { // if new // creates a DIV element legendElement = DOMBuilder.get().createDivElement(); // sets the id by chart instance legendElement.setId(formatLegendElementId(chart)); // stores into map pluginDivElements.put(chart.getId(), legendElement); } else { // if here, DIV element already exists then it retrieves it legendElement = pluginDivElements.get(chart.getId()); } // checks if there is a parent if (legendElement.getParentNode() == null) { // if no parent, means new object to add to chart element addLegendElement(chart.getChartElement(), legendElement, legend.getPosition(), legend.getLabels().getPadding()); } else { // removes the item // in order to after draw to create the legend pluginAddedLegendStatus.remove(chart.getId()); // if here, the div has got the parent // then it checks if the position is the same when it has been created // otherwise it will move to the right position manageLegendElement(chart, legendElement, legend.getPosition()); } // checks if is full width has been set if (legend.isFullWidth()) { // sets 100% of width legendElement.getStyle().setWidth(Unit.PCT.format(100)); } } return true; } /* * (non-Javadoc) * * @see org.pepstock.charba.client.plugins.AbstractPlugin#onBeforeDraw(org.pepstock.charba.client.IsChart, double) */ @Override public boolean onBeforeDraw(IsChart chart, double easing) { // checks if argument is consistent // checks if reloading during previous drawing // if the previous stored easing is greater than the current one her // means that the is chart is updating without waiting for ending // the previous update if (mustBeDisplay(chart) && pluginEasingStatus.put(chart.getId(), easing) > easing) { // removes the item // in order to after draw to create the legend pluginAddedLegendStatus.remove(chart.getId()); } return true; } /* * (non-Javadoc) * * @see org.pepstock.charba.client.plugins.AbstractPlugin#onAfterDraw(org.pepstock.charba.client. AbstractChart, double) */ @Override public void onAfterDraw(IsChart chart, double easing) { // checks if argument is consistent if (mustBeDisplay(chart)) { // checks if the legend must be created // the legend will be created if there is the legend element // and the chart is is NOT in the set if (pluginDivElements.containsKey(chart.getId()) && !pluginAddedLegendStatus.contains(chart.getId())) { // gets div element Div legendElement = pluginDivElements.get(chart.getId()); // invokes the legend callback to have the HTML of legend SafeHtml html = CALLBACK.generateLegend(chart); // removes all children of div element legendElement.removeAllChildren(); // sets as inner HTML legendElement.setInnerHTML(html.asString()); // removes all listeners removeListeners(chart, legendElement); // adds the event listeners to element addListeners(chart, legendElement); // adds into set // in order do not add the inner html every easing pluginAddedLegendStatus.add(chart.getId()); } // if end of drawing, // removes charts from set if (easing == 1D) { // removes chart for items // in order to add next cycle pluginAddedLegendStatus.remove(chart.getId()); // sets easing to zero pluginEasingStatus.put(chart.getId(), 0D); } } } /* * (non-Javadoc) * * @see org.pepstock.charba.client.plugins.AbstractPlugin#onDestroy(org.pepstock.charba.client.IsChart) */ @Override public void onDestroy(IsChart chart) { // checks if argument is consistent if (IsChart.isValid(chart)) { // resets all status items resetStatus(chart); // removes status of legend display pluginLegendDisplayStatus.remove(chart.getId()); // removes callback proxies pluginCallbackProxies.remove(chart.getId()); // removes the chart from options HtmlLegendOptions oldOptions = pluginOptions.remove(chart.getId()); // scans all options to see if the options is used in another chart for (HtmlLegendOptions options : pluginOptions.values()) { // checks if the option si s equals to old one if (options.getCharbaId() == oldOptions.getCharbaId()) { return; } } // if here, the old options is no longer used // then it removes the legend callback from cache FACTORY.store(oldOptions.getCharbaId(), null); } } /** * Manages if the legend must be displayed or not based on choice of user and what can be changed into chart configuration after the chart initialization bu a * chart.reconfigure. * * @param chart chart instance to manage * @param pOptions plugin option for the chart */ private void manageLegendDisplay(IsChart chart, HtmlLegendOptions pOptions) { // if the legend is set do not display // or the OOTB legend plugin has been disable // it respects it then ignore it and the plugin in // will be disable boolean cachedValue = false; // check if the display must be stored because was changed by user if (pluginLegendDisplayStatus.containsKey(chart.getId())) { // if here the plugin was already initialized // and then it stored the display value cachedValue = pluginLegendDisplayStatus.get(chart.getId()); // because is not the first round // the legend value should be false because set by plugin // if is true, means the user changed it programmatically if (chart.getOptions().getLegend().isDisplay() && !cachedValue) { // stored the legend display value because is changed pluginLegendDisplayStatus.put(chart.getId(), chart.getOptions().getLegend().isDisplay()); } } else { // stored the legend display value because is missing pluginLegendDisplayStatus.put(chart.getId(), chart.getOptions().getLegend().isDisplay()); } boolean mustBeChecked = chart.getOptions().getLegend().isDisplay() || cachedValue; if (mustBeChecked && !chart.getOptions().getPlugins().isForcedlyDisabled(DefaultPlugin.LEGEND)) { // disable legend chart.getOptions().getLegend().setDisplay(false); // sets legend callback // overriding whatever other callback has been set chart.getOptions().setLegendCallback(CALLBACK); // sets easing to zero pluginEasingStatus.put(chart.getId(), 0D); // creates options instance } else { // resets all status items if there are // this is in case of update options resetStatus(chart); // disables display of plugin pOptions.setDisplay(false); } } /** * Returns true if the plugin must be manage the legend, creating it. * * @param chart chart instance. * @return true if the plugin must be manage the legend, creating it. */ private boolean mustBeDisplay(IsChart chart) { // checks if argument is consistent if (IsChart.isValid(chart) && pluginOptions.containsKey(chart.getId())) { // gets stored option HtmlLegendOptions options = pluginOptions.get(chart.getId()); // returns if must be display return options.isDisplay(); } // if here, chart is not consistent or no option // then returns false return false; } /** * Removes all status items from all maps. * * @param chart chart instance */ private void resetStatus(IsChart chart) { // checks if there is div element for legend if (pluginDivElements.containsKey(chart.getId())) { // removes from map Div legendElement = pluginDivElements.remove(chart.getId()); // removes all listeners removeListeners(chart, legendElement); // removes all children legendElement.removeAllChildren(); // removes from parent legendElement.removeFromParent(); } // removes the chart status pluginAddedLegendStatus.remove(chart.getId()); // removes the chart legend labels items pluginLegendLabelsItems.remove(chart.getId()); // removes the chart from easing status pluginEasingStatus.remove(chart.getId()); // removes cached point style from tile factory // if there are HtmlLegendItem htmlLegendItem = new HtmlLegendItem(chart); TilesFactory.clearHtmlLegendItems(htmlLegendItem); } /** * Creates a HTML element id for chart legend.
* The format is:
* [chartId]-legend
* * @param chart chart instance * @return a string representation of HTML element id. */ private String formatLegendElementId(IsChart chart) { return chart.getId() + SUFFIX_LEGEND_ELEMENT_ID; } /** * Adds the event listeners to all elements created by legend callback. * * @param chart chart instance * @param legendElement DIV legend element which contains the custom HTML legend. */ private void addListeners(IsChart chart, Div legendElement) { // gets all nodes with TAG TD NodeList tds = legendElement.getElementsByTagName(TableCell.TAG); // scans all nodes for (int i = 0; i < tds.length(); i++) { // gets element BaseElement td = tds.item(i); // checks if the element has got a correct ID // starting with chart id if (td.getId().startsWith(chart.getId()) && pluginCallbackProxies.containsKey(chart.getId())) { HtmlLegendCallbackProxy callbackProxy = pluginCallbackProxies.get(chart.getId()); // adds to the element all event listeners callbackProxy.addListeners(td); } } } /** * Removes the event listeners from all elements created by legend callback. * * @param chart chart instance * @param legendElement DIV legend element which contains the custom HTML legend. */ private void removeListeners(IsChart chart, Div legendElement) { // gets all nodes with TAG TD NodeList tds = legendElement.getElementsByTagName(TableCell.TAG); // scans all nodes for (int i = 0; i < tds.length(); i++) { // gets element BaseElement td = tds.item(i); // checks if the element has got a correct ID // starting with chart id if (td.getId().startsWith(chart.getId()) && pluginCallbackProxies.containsKey(chart.getId())) { HtmlLegendCallbackProxy callbackProxy = pluginCallbackProxies.get(chart.getId()); // removes to the element all event listeners callbackProxy.removeListeners(td); } } } /** * Adds the HTML legend element to the right position into chart element, depending on {@link Position} set for legend. * * @param chartElement chart HTML element * @param legendElement legend HTML element * @param position position set by legend configuration object * @param padding padding set by legend configuration object */ private void addLegendElement(Div chartElement, Div legendElement, Position position, int padding) { if (mustAddToBottom(position)) { // appends the legend element chartElement.appendChild(legendElement); // and sets the bottom padding legendElement.getStyle().setPaddingBottom(Unit.PX.format(padding)); } else { // if not bottom, inserts the legend element as first chartElement.insertBefore(legendElement, chartElement.getFirstChild()); // and sets the top padding legendElement.getStyle().setPaddingTop(Unit.PX.format(padding)); } } /** * Manages the HTML legend element to the right position into chart element, depending on {@link Position} set for legend.
* This method is called when a legend element is already added and the position could be changed comparing when the element has been created. * * @param chart chart instance * @param legendElement legend HTML element * @param position position set by legend configuration object */ private void manageLegendElement(IsChart chart, Div legendElement, Position position) { // gets chart element Div chartElement = chart.getChartElement(); // gets if the legend element has been defined after the canvas boolean isAfterCanvas = isAfterCanvas(chart, legendElement); // gets if the legend must be added to bottom boolean mustBeAddedToBottom = mustAddToBottom(position); if (mustBeAddedToBottom && !isAfterCanvas) { // removes the legend element from parent legendElement.removeFromParent(); // and appends at the end chartElement.appendChild(legendElement); } else if (!mustBeAddedToBottom && isAfterCanvas) { // if here the position is not bottom but // legend element is after the canvas // then it removes from parent legendElement.removeFromParent(); // and inserts it at the beginning chartElement.insertBefore(legendElement, chartElement.getFirstChild()); } } /** * Checks if the legend must be added into chart element on top or bottom. * * @param position position set by legend configuration object * @return true if the legend must be added to bottom */ private boolean mustAddToBottom(Position position) { // if position is bottom or right // legend is to bottom return Position.RIGHT.equals(position) || Position.BOTTOM.equals(position); } /** * Returns true if the legend element has been added to chart element after the canvas one. * * @param chart chart instance * @param legendElement legend HTML element * @return true if the legend element has been added to chart element after the canvas one */ private boolean isAfterCanvas(IsChart chart, Div legendElement) { // gets chart element Div chartElement = chart.getChartElement(); // retrieves canvas id of chart String canvasId = chart.getCanvas().getId(); // scans all children of chart element NodeList children = chartElement.getChildNodes(); for (int i = 0; i < children.length(); i++) { // gets the node BaseNode childNode = children.item(i); // checks if is html element if (childNode instanceof BaseHtmlElement) { BaseHtmlElement childElement = (BaseHtmlElement) childNode; // checks if the legend element is equals to the scanned node // by its id if (childElement.getNodeName().equalsIgnoreCase(Div.TAG) && legendElement.getId().equalsIgnoreCase(childElement.getId())) { // if here, means that the legend element has been found before the canvas element // then returns false return false; } else if (childElement.getNodeName().equalsIgnoreCase(Canvas.TAG) && childElement.getId().equalsIgnoreCase(canvasId)) { // if here, means that the canvas element has been found before the legend element // then returns true return true; } } } // if here, nothing was found then return false // it should not happen return false; } /** * Returns the map of cached plugin options. * * @return the map of cached plugin options */ Map getPluginOptions() { return pluginOptions; } /** * Returns the map of cached legend labels items. * * @return the map of cached legend labels items */ Map> getPluginLegendLabelsItems() { return pluginLegendLabelsItems; } /** * Returns the map of cached added legend labels status. * * @return the map of cached added legend labels status */ Set getPluginAddedLegendStatus() { return pluginAddedLegendStatus; } /** * Returns the map of cached DIV elements of HTML legend. * * @return the map of cached DIV elements of HTML legend */ Map getPluginDivElements() { return pluginDivElements; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy