com.invient.vaadin.charts.InvientCharts.java Source code

Java tutorial

Introduction

Here is the source code for com.invient.vaadin.charts.InvientCharts.java

Source

/*
 * Copyright 2011 Invient (www.invient.com)
 * 
 * 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 com.invient.vaadin.charts;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;

import com.invient.vaadin.charts.InvientCharts.SeriesCUR.SeriesCURType;
import com.invient.vaadin.charts.InvientChartsConfig.*;
import com.invient.vaadin.charts.InvientChartsConfig.AxisBase.PlotBand;
import com.invient.vaadin.charts.InvientChartsConfig.AxisBase.PlotLine;
import com.invient.vaadin.charts.widgetset.client.ui.VInvientCharts;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.ClientWidget;
import com.vaadin.ui.Component;

/**
 * A Vaddin component representing charts. It is a the main class of
 * InvientCharts library.
 * <p/>
 * A chart typically contains one or more series of same or different types.
 * This class allows us to specify series of different types say line and pie
 * and hence it makes it easy to build a combination chart.
 * <p/>
 * After a chart {@link InvientCharts} is created, the following changes to the
 * chart will be reflected rendered on the webkit.
 * <ul>
 * <li>Set or update chart {@link Title} and/or {@link SubTitle}</li>
 * <li>Modify chart size</li>
 * <li>Add, update and remove one or more instances of {@link PlotBand} and
 * {@link PlotLine}</li>
 * <li>Set or update axis categories</li>
 * <li>Set or update axis min and max values</li>
 * <li>Add, update and remove one or more instances of {@link Series}</li>
 * <li>Show or hide one or more instances of {@link Series}</li>
 * <li>Add and remove one or more instances of {@link Point}</li>
 * <li>Register and unregister event listeners</li>
 * </ul>
 *
 * @author Invient
 */
@SuppressWarnings("serial")
@ClientWidget(VInvientCharts.class)
public class InvientCharts extends AbstractComponent {

    private InvientChartsConfig chartConfig;
    private boolean isRetrieveSVG = false;
    private boolean isPrint = false;

    /**
     * Creates this chart object with given chart configuration
     *
     * @param chartConfig The chart configuration.
     */
    public InvientCharts(InvientChartsConfig chartConfig) {
        if (chartConfig == null) {
            throw new IllegalArgumentException("The chart cannot be created without chartConfig argument.");
        }
        this.chartConfig = chartConfig;
        this.chartConfig.setInvientCharts(this);
    }

    /**
     * Returns chart configuration object
     *
     * @return Returns chart configuration object
     */
    public InvientChartsConfig getConfig() {
        return this.chartConfig;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void paintContent(PaintTarget target) throws PaintException {
        super.paintContent(target);
        // Update all series with reference of x and y axis.
        this.setAxisInAllSeriesIfNotSetAlready();
        // Configurations options
        target.startTag("options");
        if (chartConfig != null) {
            InvientChartsUtil.writeTitleConfig(target, chartConfig.getTitle());
            InvientChartsUtil.writeSubtitleConfig(target, chartConfig.getSubtitle());
            InvientChartsUtil.writeCreditConfig(target, chartConfig.getCredit());
            InvientChartsUtil.writeLegendConfig(target, chartConfig.getLegend());
            InvientChartsUtil.writeTooltipConfig(target, chartConfig.getTooltip());
            InvientChartsUtil.writeGeneralChartConfig(target, chartConfig.getGeneralChartConfig());
            InvientChartsUtil.writeSeriesConfigPerSeriesType(target, chartConfig.getSeriesConfig());
            InvientChartsUtil.writeXAxes(target, chartConfig.getXAxes(), chartConfig);
            InvientChartsUtil.writeYAxes(target, chartConfig.getYAxes(), chartConfig);
            InvientChartsUtil.writeChartLabelConfig(target, chartConfig.getChartLabel());
        }
        target.endTag("options");

        target.startTag("chartData");
        InvientChartsUtil.writeSeries(target, chartConfig.getGeneralChartConfig().getType(), this.chartSeries,
                chartConfig.getXAxes(), chartConfig.getYAxes());
        target.endTag("chartData");
        // A flag to indicate whether to retrieve svg from client or not.
        target.addAttribute("isRetrieveSVG", isRetrieveSVG);
        // A flag to indicate whether to prompt print dialog on client or not
        target.addAttribute("isPrint", isPrint);

        // Events
        target.startTag("events");
        // Chart Events
        paintChartEvents(target);
        // Series/Point Events
        paintSeriesAndPointEvents(target);
        target.endTag("events");

        // If the flag reloadChartData is true then the
        // client will ignore seriesOperations and
        // remove all existing series of a chart and
        // add series info received from the server.
        target.addAttribute("reloadChartSeries", reloadChartSeries);
        target.startTag("chartDataUpdates");
        if (!reloadChartSeries) {
            InvientChartsUtil.writeChartDataUpdates(target, seriesCURMap);
        }
        target.endTag("chartDataUpdates");
        // reset flag
        reloadChartSeries = false;
        // reset series operations
        seriesCURMap.clear();
        // reset to not retrieve svg when other updates on the chart occurs.
        // The svg is retrieved only when a svg available listener is registered
        // on this chart
        isRetrieveSVG = false;
        isPrint = false;
    }

    private void paintChartEvents(PaintTarget target) throws PaintException {
        target.startTag("chartEvents");
        if (chartAddSeriesListener != null && chartAddSeriesListener.size() > 0) {
            target.addAttribute("addSeries", true);
        }
        if (chartClickListener != null && chartClickListener.size() > 0) {
            target.addAttribute("click", true);
        }
        if (chartZoomListener != null && chartZoomListener.size() > 0) {
            target.addAttribute("selection", true);
        }
        target.endTag("chartEvents");
    }

    private void paintSeriesAndPointEvents(PaintTarget target) throws PaintException {
        target.startTag("seriesEvents");
        // For each series type, check if listeners exist. If so then add.
        for (SeriesType seriesType : SeriesType.values()) {
            paintSeriesEvents(target, seriesType);
        }
        target.endTag("seriesEvents");
    }

    private void paintSeriesEvents(PaintTarget target, SeriesType seriesType) throws PaintException {
        String tagName = seriesType.getName();
        target.startTag(tagName);
        if (seriesClickListeners.containsKey(seriesType) && seriesClickListeners.get(seriesType).size() > 0) {
            target.addAttribute("click", true);
        }
        if (seriesHideListeners.containsKey(seriesType) && seriesHideListeners.get(seriesType).size() > 0) {
            target.addAttribute("hide", true);
        }
        if (seriesShowListeners.containsKey(seriesType) && seriesShowListeners.get(seriesType).size() > 0) {
            target.addAttribute("show", true);
        }
        if (seriesLegendItemClickListeners.containsKey(seriesType)
                && seriesLegendItemClickListeners.get(seriesType).size() > 0) {
            target.addAttribute("legendItemClick", true);
        }
        // Check for point events
        paintPointEvents(target, seriesType);
        //
        target.endTag(tagName);
    }

    private void paintPointEvents(PaintTarget target, SeriesType seriesType) throws PaintException {
        target.startTag("pointEvents");
        if (pointClickListeners.containsKey(seriesType) && pointClickListeners.get(seriesType).size() > 0) {
            target.addAttribute("click", true);
        }
        if (pointRemoveListeners.containsKey(seriesType) && pointRemoveListeners.get(seriesType).size() > 0) {
            target.addAttribute("remove", true);
        }
        if (pointSelectListeners.containsKey(seriesType) && pointSelectListeners.get(seriesType).size() > 0) {
            target.addAttribute("select", true);
        }
        if (pointUnselectListeners.containsKey(seriesType) && pointUnselectListeners.get(seriesType).size() > 0) {
            target.addAttribute("unselect", true);
        }
        // Event applicable only for pie chart
        if (SeriesType.PIE.equals(seriesType) && pieChartLegendItemClickListener.size() > 0) {
            target.addAttribute("legendItemClick", true);
        }
        target.endTag("pointEvents");
    }

    @Override
    @SuppressWarnings("unchecked")
    public void changeVariables(Object source, Map<String, Object> variables) {
        if (variables.containsKey("event")) {
            Map<String, Object> eventData = (Map<String, Object>) variables.get("eventData");
            String eventName = (String) variables.get("event");
            if (eventName.equalsIgnoreCase("addSeries")) {
                fireAddSeries();
            } else if (eventName.equalsIgnoreCase("chartClick")) {
                double xAxisPos = Double.parseDouble((String) eventData.get("xAxisPos"));
                double yAxisPos = Double.parseDouble((String) eventData.get("yAxisPos"));
                //
                MousePosition mousePosition = getClickPosition(eventData);
                fireChartClick(new DecimalPoint(xAxisPos, yAxisPos), mousePosition);
            } else if (eventName.equalsIgnoreCase("chartZoom")) {
                double xAxisMin = Double.parseDouble((String) eventData.get("xAxisMin"));
                double xAxisMax = Double.parseDouble((String) eventData.get("xAxisMax"));
                double yAxisMin = Double.parseDouble((String) eventData.get("yAxisMin"));
                double yAxisMax = Double.parseDouble((String) eventData.get("yAxisMax"));
                fireChartZoom(new ChartArea(xAxisMin, xAxisMax, yAxisMin, yAxisMax));
            } else if (eventName.equalsIgnoreCase("chartResetZoom")) {
                fireChartResetZoom();
            } else if (eventName.equalsIgnoreCase("chartSVGAvailable")) {
                fireChartSVGAvailable((String) eventData.get("svg"));
            } else if (eventName.equalsIgnoreCase("seriesClick")) {
                PointEventData pointEventData = getPointEventData(eventData);
                //
                MousePosition mousePosition = getClickPosition(eventData);
                fireSeriesClick(getSeriesFromEventData(pointEventData.getSeriesName()),
                        getPointFromEventData(pointEventData), mousePosition);
            } else if (eventName.equalsIgnoreCase("seriesHide")) {
                String seriesName = (String) eventData.get("seriesName");
                fireSeriesHide(getSeriesFromEventData(seriesName));
            } else if (eventName.equalsIgnoreCase("seriesShow")) {
                String seriesName = (String) eventData.get("seriesName");
                fireSeriesShow(getSeriesFromEventData(seriesName));
            } else if (eventName.equalsIgnoreCase("seriesLegendItemClick")) {
                String seriesName = (String) eventData.get("seriesName");
                fireSeriesLegendItemClick(getSeriesFromEventData(seriesName));
            } else if (eventName.equalsIgnoreCase("pieLegendItemClick")) {
                PointEventData pointEventData = getPointEventData(eventData);
                fireLegendItemClick(getPointFromEventData(pointEventData));
            } else if (eventName.equalsIgnoreCase("pointClick")) {
                MousePosition mousePosition = getClickPosition(eventData);
                //
                PointEventData pointEventData = getPointEventData(eventData);
                firePointClick(pointEventData.getCategory(), getPointFromEventData(pointEventData), mousePosition);
            } else if (eventName.equalsIgnoreCase("pointSelect")) {
                PointEventData pointEventData = getPointEventData(eventData);
                firePointSelect(pointEventData.getCategory(), getPointFromEventData(pointEventData));
            } else if (eventName.equalsIgnoreCase("pointUnselect")) {
                PointEventData pointEventData = getPointEventData(eventData);
                firePointUnselect(pointEventData.getCategory(), getPointFromEventData(pointEventData));
            } else if (eventName.equalsIgnoreCase("pointRemove")) {
                PointEventData pointEventData = getPointEventData(eventData);
                firePointRemove(pointEventData.getCategory(), getPointFromEventData(pointEventData));
            }
        }
    }

    private Point getPointFromEventData(PointEventData eventData) {
        // First locate a series and then point
        Series series = getSeriesFromEventData(eventData.getSeriesName());
        // TODO: [S73417H] There is something very wrong here with instanceof checks and equality.
        if (series != null) {
            if (series instanceof XYSeries) {
                XYSeries xySeries = (XYSeries) series;
                for (DecimalPoint point : xySeries.getPoints()) {
                    if (point.getY() != null && point.getY().compareTo(eventData.getPointY()) == 0
                            && point.getX() != null && point.getX().compareTo(eventData.getPointX()) == 0) {
                        return point;
                    }
                }
            } else {
                DateTimeSeries dateTimeSeries = (DateTimeSeries) series;
                for (DateTimePoint point : dateTimeSeries.getPoints()) {
                    if (point.getY() != null && point.getY().compareTo(eventData.getPointY()) == 0
                            && point.getX() != null && getDateInMilliseconds(point.getX(),
                                    ((DateTimeSeries) series).isIncludeTime()) == (long) eventData.getPointX()) {
                        return point;
                    }
                }
            }
        }
        return null;
    }

    private static Long getDateInMilliseconds(Date dt, boolean isIncludeTime) {
        if (dt == null) {
            return null;
        }
        if (!isIncludeTime) {
            return dt.getTime() - dt.getTime() % TimeUnit.DAYS.toMillis(1);
        } else {
            return dt.getTime();
        }
    }

    private Series getSeriesFromEventData(String seriesName) {
        for (Series series : this.chartSeries) {
            if (series.getName().equals(seriesName)) {
                return series;
            }
        }
        // Should not happen
        // If it happens then why? Any comments???
        return null;
    }

    private void fireAddSeries() {
        fireEvent(new ChartAddSeriesEvent(this, this));
    }

    private void fireChartClick(Point point, MousePosition mousePosition) {
        fireEvent(new ChartClickEvent(this, this, point, mousePosition));
    }

    private void fireChartZoom(ChartArea selectedArea) {
        fireEvent(new ChartZoomEvent(this, this, selectedArea));
    }

    private void fireChartSVGAvailable(String svg) {
        fireEvent(new ChartSVGAvailableEvent(this, this, svg));
    }

    private void fireChartResetZoom() {
        fireEvent(new ChartResetZoomEvent(this, this));
    }

    private void fireSeriesClick(Series series, Point point, MousePosition mousePosition) {
        fireEvent(new SeriesClickEvent(this, this, series, point, mousePosition));
    }

    private void fireSeriesShow(Series series) {
        fireEvent(new SeriesShowEvent(this, this, series));
    }

    private void fireSeriesHide(Series series) {
        fireEvent(new SeriesHideEvent(this, this, series));
    }

    private void fireSeriesLegendItemClick(Series series) {
        fireEvent(new SeriesLegendItemClickEvent(this, this, series));
    }

    private void firePointClick(String category, Point point, MousePosition mousePosition) {
        fireEvent(new PointClickEvent(this, this, category, point, mousePosition));
    }

    private void firePointSelect(String category, Point point) {
        fireEvent(new PointSelectEvent(this, this, category, point));
    }

    private void firePointUnselect(String category, Point point) {
        fireEvent(new PointUnselectEvent(this, this, category, point));
    }

    private void firePointRemove(String category, Point point) {
        fireEvent(new PointRemoveEvent(this, this, category, point));
    }

    private void fireLegendItemClick(Point point) {
        fireEvent(new PieChartLegendItemClickEvent(this, this, point));
    }

    private PointEventData getPointEventData(Map<String, Object> eventData) {
        String seriesName = (String) eventData.get("seriesName");
        String category = (String) eventData.get("category");
        double pointX = Double.valueOf((String) eventData.get("pointX"));
        double pointY = Double.valueOf((String) eventData.get("pointY"));
        return new PointEventData(seriesName, category, pointX, pointY);
    }

    private MousePosition getClickPosition(Map<String, Object> eventData) {
        Integer mouseX = null;
        if (eventData.get("mouseX") instanceof Integer) {
            mouseX = (Integer) eventData.get("mouseX");
        }
        Integer mouseY = null;
        if (eventData.get("mouseY") instanceof Integer) {
            mouseY = (Integer) eventData.get("mouseY");
        }
        if (mouseX != null && mouseY != null) {
            return new MousePosition(mouseX, mouseY);
        }
        return null;
    }

    /**
     * This class contain mouse coordinates when a click event occurs on a
     * chart, a series or a point.
     * <p/>
     * The mouse coordinates are in pixels.
     *
     * @author Invient
     */
    public final class MousePosition implements Serializable {
        private int mouseX;
        private int mouseY;

        /**
         * Creates this object with given arguments.
         *
         * @param mouseX x position of mouse when a click event occurred, in pixel
         * @param mouseY y position of mouse when a click event occurred, in pixel
         */
        public MousePosition(int mouseX, int mouseY) {
            this.mouseX = mouseX;
            this.mouseY = mouseY;
        }

        /**
         * @return Returns x position of mouse when a click event occurred, in
         *         pixel
         */
        public int getMouseX() {
            return mouseX;
        }

        /**
         * @return Returns y position of mouse when a click event occurred, in
         *         pixel
         */
        public int getMouseY() {
            return mouseY;
        }

        @Override
        public String toString() {
            return "MousePosition [mouseX=" + mouseX + ", mouseY=" + mouseY + "]";
        }

    }

    private final class PointEventData implements Serializable {
        private String seriesName;
        private String category;
        private double pointX;
        private double pointY;

        public PointEventData(String seriesName, String category, double pointX, double pointY) {
            super();
            this.seriesName = seriesName;
            this.category = category;
            this.pointX = pointX;
            this.pointY = pointY;
        }

        public String getSeriesName() {
            return seriesName;
        }

        public String getCategory() {
            return category;
        }

        public double getPointX() {
            return pointX;
        }

        public double getPointY() {
            return pointY;
        }

        @Override
        public String toString() {
            return "PointEventData [seriesName=" + seriesName + ", category=" + category + ", pointX=" + pointX
                    + ", pointY=" + pointY + "]";
        }

    }

    /***************************** POINT CLICK EVENT *****************************/
    /**
     * Click event. This event is thrown, when any point of this chart is
     * clicked and the point marker is enabled. The point marker is enabled by
     * default.
     *
     * @author Invient
     */
    public class PointClickEvent extends Component.Event {

        private String category;
        private Point point;
        private InvientCharts chart;
        private MousePosition mousePosition;

        /**
         * New instance of the point click event.
         *
         * @param source        the chart object itself
         * @param chart         the chart object itself
         * @param category      a category to which point is associated in case of
         *                      categorized axis,
         * @param point         the point on which the click event occurred
         * @param mousePosition the position of a mouse when the click event occurred
         */
        public PointClickEvent(Component source, InvientCharts chart, String category, Point point,
                MousePosition mousePosition) {
            super(source);
            this.chart = chart;
            this.category = category;
            this.point = point;
            this.mousePosition = mousePosition;
        }

        /**
         * @return Returns a category to which point is associated in case of
         *         categorized axis only.
         */
        public String getCategory() {
            return category;
        }

        /**
         * @return Returns the chart object associated with the point
         */
        public InvientCharts getChart() {
            return chart;
        }

        /**
         * @return Returns the point on which the click event occurred
         */
        public Point getPoint() {
            return this.point;
        }

        /**
         * @return Returns the position of a mouse when the click event occurred
         */
        public MousePosition getMousePosition() {
            return mousePosition;
        }

    }

    /**
     * Interface for listening for a {@link PointClickEvent} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface PointClickListener extends Serializable {
        public void pointClick(PointClickEvent pointClickEvent);
    }

    private Map<SeriesType, Set<PointClickListener>> pointClickListeners = new HashMap<SeriesType, Set<PointClickListener>>();

    /**
     * Adds the point click listener. If the argument seriesTypes is not
     * specified then the listener will be added for all series type otherwise
     * it will be added for a specific series type
     *
     * @param listener the Listener to be added.
     */
    public void addListener(PointClickListener listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }

        for (SeriesType seriesType : seriesTypes) {
            if (pointClickListeners.containsKey(seriesType)) {
                pointClickListeners.get(seriesType).add(listener);
            } else {
                Set<PointClickListener> listeners = new HashSet<PointClickListener>();
                listeners.add(listener);
                pointClickListeners.put(seriesType, listeners);
            }
        }
        addListener(PointClickEvent.class, listener, POINT_CLICK_METHOD);
    }

    /**
     * Removes the point click listener. If the argument seriesTypes is not
     * specified then the listener will be removed only for a series type
     * SeriesType.COMMONSERIES otherwise the listener will be removed for all
     * specified series types.
     *
     * @param listener    the listener to be removed
     * @param seriesTypes one or more series types as defined by (@link SeriesType}
     */
    public void removeListener(PointClickListener listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }

        for (SeriesType seriesType : seriesTypes) {
            if (pointClickListeners.containsKey(seriesType)) {
                pointClickListeners.get(seriesType).remove(listener);
            }
        }
        removeListener(PointClickEvent.class, listener, POINT_CLICK_METHOD);
    }

    /**
     * Point remove event. This event is thrown, when any point of this chart is
     * removed from its series.
     * <p/>
     * This event is EXPERIMENTAL ONLY.
     *
     * @author Invient
     */
    public class PointRemoveEvent extends Component.Event {

        private String category;
        private Point point;
        private InvientCharts chart;

        /**
         * New instance of the point remove event.
         *
         * @param source   the chart object itself
         * @param chart    the chart object itself
         * @param category a category to which point is associated in case of
         *                 categorized axis,
         * @param point    the point removed
         */
        public PointRemoveEvent(Component source, InvientCharts chart, String category, Point point) {
            super(source);
            this.chart = chart;
            this.category = category;
            this.point = point;
        }

        /**
         * @return Returns a category to which point is associated in case of
         *         categorized axis only.
         */
        public String getCategory() {
            return category;
        }

        /**
         * @return Returns the chart object associated with the point
         */
        public InvientCharts getChart() {
            return chart;
        }

        /**
         * @return Returns the point which has been removed
         */
        public Point getPoint() {
            return this.point;
        }

    }

    /**
     * Interface for listening for a {@link PointRemoveEvent} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface PointRemoveListener extends Serializable {
        public void pointRemove(PointRemoveEvent pointRemoveEvent);
    }

    private Map<SeriesType, Set<PointRemoveListener>> pointRemoveListeners = new HashMap<SeriesType, Set<PointRemoveListener>>();

    /**
     * Adds the point remove listener. If the argument seriesTypes is not
     * specified then the listener will be added for all series type otherwise
     * it will be added for a specific series type
     *
     * @param listener the Listener to be added.
     */
    public void addListener(PointRemoveListener listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }
        for (SeriesType seriesType : seriesTypes) {
            if (pointRemoveListeners.containsKey(seriesType)) {
                pointRemoveListeners.get(seriesType).add(listener);
            } else {
                Set<PointRemoveListener> listeners = new HashSet<PointRemoveListener>();
                listeners.add(listener);
                pointRemoveListeners.put(seriesType, listeners);
            }
        }
        addListener(PointRemoveEvent.class, listener, POINT_REMOVE_METHOD);
    }

    /**
     * Removes the point remove listener. If the argument seriesTypes is not
     * specified then the listener will be removed only for a series type
     * SeriesType.COMMONSERIES otherwise the listener will be removed for all
     * specified series types.
     *
     * @param listener    the listener to be removed
     * @param seriesTypes one or more series types as defined by (@link SeriesType}
     */
    public void removeListener(PointRemoveListener listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }
        for (SeriesType seriesType : seriesTypes) {
            if (pointRemoveListeners.containsKey(seriesType)) {
                pointRemoveListeners.get(seriesType).remove(listener);
            }
        }
        pointRemoveListeners.remove(listener);
        removeListener(PointRemoveEvent.class, listener, POINT_REMOVE_METHOD);
    }

    /**
     * Point unselect event. This event is thrown, when any point of this chart
     * is unselected and the point marker is enabled. The point marker is
     * enabled by default.
     *
     * @author Invient
     */
    public class PointUnselectEvent extends Component.Event {

        private String category;
        private Point point;
        private InvientCharts chart;

        /**
         * New instance of the point unselect event.
         *
         * @param source   the chart object itself
         * @param chart    the chart object itself
         * @param category a category to which point is associated in case of
         *                 categorized axis,
         * @param point    the point unselected as a result of this event
         */
        public PointUnselectEvent(Component source, InvientCharts chart, String category, Point point) {
            super(source);
            this.chart = chart;
            this.category = category;
            this.point = point;
        }

        /**
         * @return Returns a category to which point is associated in case of
         *         categorized axis only.
         */
        public String getCategory() {
            return category;
        }

        /**
         * @return Returns the chart object associated with the point
         */
        public InvientCharts getChart() {
            return chart;
        }

        /**
         * @return Returns the unselected point
         */
        public Point getPoint() {
            return this.point;
        }

    }

    /**
     * Interface for listening for a {@link PointUnselectEvent} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface PointUnselectListener extends Serializable {
        public void pointUnSelect(PointUnselectEvent pointUnSelectEvent);
    }

    private Map<SeriesType, Set<PointUnselectListener>> pointUnselectListeners = new HashMap<SeriesType, Set<PointUnselectListener>>();

    /**
     * Adds the point unselect listener. If the argument seriesTypes is not
     * specified then the listener will be added for all series type otherwise
     * it will be added for a specific series type
     *
     * @param listener the Listener to be added.
     */
    public void addListener(PointUnselectListener listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }
        for (SeriesType seriesType : seriesTypes) {
            if (pointUnselectListeners.containsKey(seriesType)) {
                pointUnselectListeners.get(seriesType).add(listener);
            } else {
                Set<PointUnselectListener> listeners = new HashSet<PointUnselectListener>();
                listeners.add(listener);
                pointUnselectListeners.put(seriesType, listeners);
            }
        }
        addListener(PointUnselectEvent.class, listener, POINT_UNSELECT_METHOD);
    }

    /**
     * Removes the point unselect listener. If the argument seriesTypes is not
     * specified then the listener will be removed only for a series type
     * SeriesType.COMMONSERIES otherwise the listener will be removed for all
     * specified series types.
     *
     * @param listener    the listener to be removed
     * @param seriesTypes one or more series types as defined by (@link SeriesType}
     */
    public void removeListener(PointUnselectListener listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }
        for (SeriesType seriesType : seriesTypes) {
            if (pointUnselectListeners.containsKey(seriesType)) {
                pointUnselectListeners.get(seriesType).remove(listener);
            }
        }
        removeListener(PointUnselectEvent.class, listener, POINT_UNSELECT_METHOD);
    }

    /**
     * Point select event. This event is thrown, when any point of this chart is
     * selected and the point marker is enabled. The point marker is enabled by
     * default.
     *
     * @author Invient
     */
    public class PointSelectEvent extends Component.Event {

        private String category;
        private Point point;
        private InvientCharts chart;

        /**
         * New instance of the point select event.
         *
         * @param source   the chart object itself
         * @param chart    the chart object itself
         * @param category a category to which point is associated in case of
         *                 categorized axis,
         * @param point    the point selected as a result of this event
         */
        public PointSelectEvent(Component source, InvientCharts chart, String category, Point point) {
            super(source);
            this.chart = chart;
            this.category = category;
            this.point = point;
        }

        /**
         * @return Returns a category to which point is associated in case of
         *         categorized axis only.
         */
        public String getCategory() {
            return category;
        }

        /**
         * @return Returns the chart object associated with the point
         */
        public InvientCharts getChart() {
            return chart;
        }

        /**
         * @return Returns the selected point
         */
        public Point getPoint() {
            return this.point;
        }

    }

    /**
     * Interface for listening for a {@link PointSelectListener} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface PointSelectListener extends Serializable {
        public void pointSelected(PointSelectEvent pointSelectEvent);
    }

    private Map<SeriesType, Set<PointSelectListener>> pointSelectListeners = new HashMap<SeriesType, Set<PointSelectListener>>();

    /**
     * Adds the point select listener. If the argument seriesTypes is not
     * specified then the listener will be added for all series type otherwise
     * it will be added for a specific series type
     *
     * @param listener the Listener to be added.
     */
    public void addListener(PointSelectListener listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }

        for (SeriesType seriesType : seriesTypes) {
            if (pointSelectListeners.containsKey(seriesType)) {
                pointSelectListeners.get(seriesType).add(listener);
            } else {
                Set<PointSelectListener> listeners = new HashSet<PointSelectListener>();
                listeners.add(listener);
                pointSelectListeners.put(seriesType, listeners);
            }
        }
        addListener(PointSelectEvent.class, listener, POINT_SELECT_METHOD);
    }

    /**
     * Removes the point select listener. If the argument seriesTypes is not
     * specified then the listener will be removed only for a series type
     * SeriesType.COMMONSERIES otherwise the listener will be removed for all
     * specified series types.
     *
     * @param listener    the listener to be removed
     * @param seriesTypes one or more series types as defined by (@link SeriesType}
     */
    public void removeListener(PointSelectListener listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }
        for (SeriesType seriesType : seriesTypes) {
            if (pointSelectListeners.containsKey(seriesType)) {
                pointSelectListeners.get(seriesType).remove(listener);
            }
        }
        removeListener(PointSelectEvent.class, listener, POINT_SELECT_METHOD);
    }

    private static final Method POINT_CLICK_METHOD;
    private static final Method POINT_REMOVE_METHOD;
    private static final Method POINT_SELECT_METHOD;
    private static final Method POINT_UNSELECT_METHOD;

    static {
        try {
            POINT_CLICK_METHOD = PointClickListener.class.getDeclaredMethod("pointClick",
                    new Class[] { PointClickEvent.class });
            POINT_REMOVE_METHOD = PointRemoveListener.class.getDeclaredMethod("pointRemove",
                    new Class[] { PointRemoveEvent.class });
            POINT_SELECT_METHOD = PointSelectListener.class.getDeclaredMethod("pointSelected",
                    new Class[] { PointSelectEvent.class });
            POINT_UNSELECT_METHOD = PointUnselectListener.class.getDeclaredMethod("pointUnSelect",
                    new Class[] { PointUnselectEvent.class });
        } catch (final java.lang.NoSuchMethodException e) {
            // This should not happen
            throw new java.lang.RuntimeException("Internal error finding methods in Button");
        }
    }

    // ***************************** Series Events ****************************/

    /**
     * Series click event. This event is thrown, when any series of this chart
     * is clicked.
     *
     * @author Invient
     */
    public class SeriesClickEvent extends Component.Event {
        private Point point;
        private Series series;
        private InvientCharts chart;
        private MousePosition mousePosition;

        /**
         * New instance of the series click event.
         *
         * @param source        the chart object itself
         * @param chart         the chart object itself
         * @param series        the series on which click event occurred
         * @param point         the closest point of a series
         * @param mousePosition the position of a mouse when the click event occurred
         */
        public SeriesClickEvent(Component source, InvientCharts chart, Series series, Point point,
                MousePosition mousePosition) {
            super(source);
            this.chart = chart;
            this.series = series;
            this.point = point;
            this.mousePosition = mousePosition;
        }

        /**
         * @return Returns the chart object associated with the point
         */
        public InvientCharts getChart() {
            return this.chart;
        }

        /**
         * @return Returns the series object on which the click event occurred
         */
        public Series getSeries() {
            return this.series;
        }

        /**
         * @return Returns the point of a series closest to the position where
         *         mouse click event occurred.
         */
        public Point getNearestPoint() {
            return this.point;
        }

        /**
         * @return Returns the position of a mouse when the click event occurred
         */
        public MousePosition getMousePosition() {
            return mousePosition;
        }

    }

    /**
     * Interface for listening for a {@link SeriesClickListerner} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface SeriesClickListerner extends Serializable {
        public void seriesClick(SeriesClickEvent seriesClickEvent);
    }

    private Map<SeriesType, Set<SeriesClickListerner>> seriesClickListeners = new HashMap<SeriesType, Set<SeriesClickListerner>>();

    /**
     * Adds the series click listener. If the argument seriesTypes is not
     * specified then the listener will be added for all series type otherwise
     * it will be added for a specific series type
     *
     * @param listener the Listener to be added.
     */
    public void addListener(SeriesClickListerner listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }

        for (SeriesType seriesType : seriesTypes) {
            if (seriesClickListeners.containsKey(seriesType)) {
                seriesClickListeners.get(seriesType).add(listener);
            } else {
                Set<SeriesClickListerner> listeners = new HashSet<SeriesClickListerner>();
                listeners.add(listener);
                seriesClickListeners.put(seriesType, listeners);
            }
        }
        addListener(SeriesClickEvent.class, listener, SERIES_CLICK_METHOD);
    }

    /**
     * Removes the series click listener. If the argument seriesTypes is not
     * specified then the listener will be removed only for a series type
     * SeriesType.COMMONSERIES otherwise the listener will be removed for all
     * specified series types.
     *
     * @param listener    the listener to be removed
     * @param seriesTypes one or more series types as defined by (@link SeriesType}
     */
    public void removeListener(SeriesClickListerner listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }

        for (SeriesType seriesType : seriesTypes) {
            if (seriesClickListeners.containsKey(seriesType)) {
                seriesClickListeners.get(seriesType).remove(listener);
            }
        }
        removeListener(SeriesClickEvent.class, listener, SERIES_CLICK_METHOD);
    }

    /**
     * Series Hide event. This event is thrown, when any series of this chart is
     * hidden.
     *
     * @author Invient
     */
    public class SeriesHideEvent extends Component.Event {
        private Series series;
        private InvientCharts chart;

        /**
         * @param source the chart object itself
         * @param chart  the chart object itself
         * @param series the series which got hidden
         */
        public SeriesHideEvent(Component source, InvientCharts chart, Series series) {
            super(source);
            this.chart = chart;
            this.series = series;
        }

        /**
         * @return Returns the chart object associated with the point
         */
        public InvientCharts getChart() {
            return this.chart;
        }

        /**
         * @return Returns the series which got hidden
         */
        public Series getSeries() {
            return this.series;
        }
    }

    /**
     * Interface for listening for a {@link SeriesHideEvent} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface SeriesHideListerner extends Serializable {
        public void seriesHide(SeriesHideEvent seriesHideEvent);
    }

    private Map<SeriesType, Set<SeriesHideListerner>> seriesHideListeners = new HashMap<SeriesType, Set<SeriesHideListerner>>();

    /**
     * Adds the series hide listener. If the argument seriesTypes is not
     * specified then the listener will be added for all series type otherwise
     * it will be added for a specific series type
     *
     * @param listener the Listener to be added.
     */
    public void addListener(SeriesHideListerner listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }

        for (SeriesType seriesType : seriesTypes) {
            if (seriesHideListeners.containsKey(seriesType)) {
                seriesHideListeners.get(seriesType).add(listener);
            } else {
                Set<SeriesHideListerner> listeners = new HashSet<SeriesHideListerner>();
                listeners.add(listener);
                seriesHideListeners.put(seriesType, listeners);
            }
        }
        addListener(SeriesHideEvent.class, listener, SERIES_HIDE_METHOD);
    }

    /**
     * Removes the series hide listener. If the argument seriesTypes is not
     * specified then the listener will be removed only for a series type
     * SeriesType.COMMONSERIES otherwise the listener will be removed for all
     * specified series types.
     *
     * @param listener    the listener to be removed
     * @param seriesTypes one or more series types as defined by (@link SeriesType}
     */
    public void removeListener(SeriesHideListerner listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }

        for (SeriesType seriesType : seriesTypes) {
            if (seriesHideListeners.containsKey(seriesType)) {
                seriesHideListeners.get(seriesType).remove(listener);
            }
        }
        removeListener(SeriesHideEvent.class, listener, SERIES_HIDE_METHOD);
    }

    /**
     * Series show event. This event is thrown, when any series of this chart is
     * displayed after a chart is created.
     *
     * @author Invient
     */
    public class SeriesShowEvent extends Component.Event {
        private Series series;
        private InvientCharts chart;

        /**
         * New instance of the series show event.
         *
         * @param source the chart object itself
         * @param chart  the chart object itself
         * @param series the series which got displayed
         */
        public SeriesShowEvent(Component source, InvientCharts chart, Series series) {
            super(source);
            this.chart = chart;
            this.series = series;
        }

        /**
         * @return Returns the chart object associated with the series
         */
        public InvientCharts getChart() {
            return this.chart;
        }

        /**
         * @return Returns the series which got displayed
         */
        public Series getSeries() {
            return this.series;
        }
    }

    /**
     * Interface for listening for a {@link SeriesShowEvent} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface SeriesShowListerner extends Serializable {
        public void seriesShow(SeriesShowEvent seriesShowEvent);
    }

    private Map<SeriesType, Set<SeriesShowListerner>> seriesShowListeners = new HashMap<SeriesType, Set<SeriesShowListerner>>();

    /**
     * Adds the series show listener. If the argument seriesTypes is not
     * specified then the listener will be added for all series type otherwise
     * it will be added for a specific series type
     *
     * @param listener the Listener to be added.
     */
    public void addListener(SeriesShowListerner listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }

        for (SeriesType seriesType : seriesTypes) {
            if (seriesShowListeners.containsKey(seriesType)) {
                seriesShowListeners.get(seriesType).add(listener);
            } else {
                Set<SeriesShowListerner> listeners = new HashSet<SeriesShowListerner>();
                listeners.add(listener);
                seriesShowListeners.put(seriesType, listeners);
            }
        }
        addListener(SeriesShowEvent.class, listener, SERIES_SHOW_METHOD);
    }

    /**
     * Removes the series show listener. If the argument seriesTypes is not
     * specified then the listener will be removed only for a series type
     * SeriesType.COMMONSERIES otherwise the listener will be removed for all
     * specified series types.
     *
     * @param listener    the listener to be removed
     * @param seriesTypes one or more series types as defined by (@link SeriesType}
     */
    public void removeListener(SeriesShowListerner listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }

        for (SeriesType seriesType : seriesTypes) {
            if (seriesShowListeners.containsKey(seriesType)) {
                seriesShowListeners.get(seriesType).remove(listener);
            }
        }
        removeListener(SeriesShowEvent.class, listener, SERIES_SHOW_METHOD);
    }

    /**
     * Series legend item click event. This event is thrown, when legend item is
     * clicked. This event is not applicable for PieChart instead use {@link PieChartLegendItemClickEvent}.
     *
     * @author Invient
     */
    public class SeriesLegendItemClickEvent extends Component.Event {
        private Series series;
        private InvientCharts chart;

        /**
         * New instance of the point click event.
         *
         * @param source the chart object itself
         * @param chart  the chart object itself
         * @param series the series associated with the legend item
         */
        public SeriesLegendItemClickEvent(Component source, InvientCharts chart, Series series) {
            super(source);
            this.chart = chart;
            this.series = series;
        }

        /**
         * @return Returns the chart object associated with the series
         */
        public InvientCharts getChart() {
            return this.chart;
        }

        /**
         * @return Returns the series associated with the legend item
         */
        public Series getSeries() {
            return this.series;
        }
    }

    /**
     * Interface for listening for a {@link SeriesLegendItemClickEvent}
     * triggered by {@link InvientCharts}
     *
     * @author Invient
     */
    public interface SeriesLegendItemClickListerner extends Serializable {
        public void seriesLegendItemClick(SeriesLegendItemClickEvent seriesLegendItemClickEvent);
    }

    private Map<SeriesType, Set<SeriesLegendItemClickListerner>> seriesLegendItemClickListeners = new HashMap<SeriesType, Set<SeriesLegendItemClickListerner>>();

    /**
     * Adds the series legend item click listener. If the argument seriesTypes
     * is not specified then the listener will be added for all series type
     * otherwise it will be added for a specific series type
     *
     * @param listener the Listener to be added.
     */
    public void addListener(SeriesLegendItemClickListerner listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }

        for (SeriesType seriesType : seriesTypes) {
            if (seriesLegendItemClickListeners.containsKey(seriesType)) {
                seriesLegendItemClickListeners.get(seriesType).add(listener);
            } else {
                Set<SeriesLegendItemClickListerner> listeners = new HashSet<SeriesLegendItemClickListerner>();
                listeners.add(listener);
                seriesLegendItemClickListeners.put(seriesType, listeners);
            }
        }
        addListener(SeriesLegendItemClickEvent.class, listener, SERIES_LEGENDITEM_CLICK_METHOD);
    }

    /**
     * Removes the series legend item click listener. If the argument
     * seriesTypes is not specified then the listener will be removed only for a
     * series type SeriesType.COMMONSERIES otherwise the listener will be
     * removed for all specified series types.
     *
     * @param listener    the listener to be removed
     * @param seriesTypes one or more series types as defined by (@link SeriesType}
     */
    public void removeListener(SeriesLegendItemClickListerner listener, SeriesType... seriesTypes) {
        if (seriesTypes.length == 0) {
            seriesTypes = new SeriesType[] { SeriesType.COMMONSERIES };
        }
        for (SeriesType seriesType : seriesTypes) {
            if (seriesLegendItemClickListeners.containsKey(seriesType)) {
                seriesLegendItemClickListeners.get(seriesType).remove(listener);
            }
        }
        removeListener(SeriesLegendItemClickEvent.class, listener, SERIES_LEGENDITEM_CLICK_METHOD);
    }

    private static final Method SERIES_CLICK_METHOD;
    // private static final Method SERIES_CHECKBOX_CLICK_METHOD;
    private static final Method SERIES_HIDE_METHOD;
    private static final Method SERIES_SHOW_METHOD;
    private static final Method SERIES_LEGENDITEM_CLICK_METHOD;

    static {
        try {
            SERIES_CLICK_METHOD = SeriesClickListerner.class.getDeclaredMethod("seriesClick",
                    new Class[] { SeriesClickEvent.class });
            // SERIES_CHECKBOX_CLICK_METHOD = SeriesCheckboxClickListerner.class
            // .getDeclaredMethod("seriesCheckboxClick",
            // new Class[] { SeriesCheckboxClickEvent.class });
            SERIES_HIDE_METHOD = SeriesHideListerner.class.getDeclaredMethod("seriesHide",
                    new Class[] { SeriesHideEvent.class });
            SERIES_SHOW_METHOD = SeriesShowListerner.class.getDeclaredMethod("seriesShow",
                    new Class[] { SeriesShowEvent.class });
            SERIES_LEGENDITEM_CLICK_METHOD = SeriesLegendItemClickListerner.class
                    .getDeclaredMethod("seriesLegendItemClick", new Class[] { SeriesLegendItemClickEvent.class });
        } catch (final java.lang.NoSuchMethodException e) {
            // This should never happen
            throw new java.lang.RuntimeException("Internal error finding methods in Button");
        }
    }

    /**************************** PieChart related events ****************************/
    // PieChart LEGENDITEMCLICK
    // This event occurs when a point of a PieChart is clicked

    /**
     * PieChart legend item click event. This event is thrown, when the legend
     * item belonging to the pie point (slice) is clicked.
     *
     * @author Invient
     */
    public class PieChartLegendItemClickEvent extends Component.Event {

        private InvientCharts chart;
        private Point point;

        /**
         * New instance of the piechart legend item click event
         *
         * @param source the chart object itself
         * @param chart  the chart object itself
         * @param point  the pie point (slice) associated with the legend item
         */
        public PieChartLegendItemClickEvent(Component source, InvientCharts chart, Point point) {
            super(source);
            this.chart = chart;
            this.point = point;
        }

        /**
         * @return Returns the chart object associated with the point
         */
        public InvientCharts getChart() {
            return this.chart;
        }

        /**
         * @return Returns the pie point (slice) associated with the legend item
         */
        public Point getPoint() {
            return this.point;
        }
    }

    /**
     * Interface for listening for a {@link PieChartLegendItemClickEvent}
     * triggered by {@link InvientCharts}
     *
     * @author Invient
     */
    public interface PieChartLegendItemClickListener extends Serializable {
        public void legendItemClick(PieChartLegendItemClickEvent legendItemClickEvent);
    }

    private Set<PieChartLegendItemClickListener> pieChartLegendItemClickListener = new HashSet<PieChartLegendItemClickListener>();

    /**
     * Adds the piechart legend item click listener.
     *
     * @param listener the Listener to be added.
     */
    public void addListener(PieChartLegendItemClickListener listener) {
        pieChartLegendItemClickListener.add(listener);
        addListener(PieChartLegendItemClickEvent.class, listener, LEGENDITEM_CLICK_METHOD);
    }

    /**
     * Removes the piechart legend item click listener.
     *
     * @param listener the listener to be removed
     */
    public void removeListener(PieChartLegendItemClickListener listener) {
        pieChartLegendItemClickListener.remove(listener);
        removeListener(PieChartLegendItemClickEvent.class, listener, LEGENDITEM_CLICK_METHOD);
    }

    private static final Method LEGENDITEM_CLICK_METHOD;

    static {
        try {
            LEGENDITEM_CLICK_METHOD = PieChartLegendItemClickListener.class.getDeclaredMethod("legendItemClick",
                    new Class[] { PieChartLegendItemClickEvent.class });
        } catch (final java.lang.NoSuchMethodException e) {
            // This should never happen
            throw new java.lang.RuntimeException("Internal error finding methods in Button");
        }
    }

    /***************************** Chart Events *****************************/
    /**
     * Chart Click event. This event is thrown, when this chart is clicked.
     *
     * @author Invient
     */
    public class ChartClickEvent extends Component.Event {
        private InvientCharts chart;
        private Point point;
        private MousePosition mousePosition;

        /**
         * New instance of the chart click event.
         *
         * @param source        the chart object itself
         * @param chart         the chart object itself
         * @param point         the position where the click event occurred in axes units
         * @param mousePosition the coordinate of mouse where the click event occurred in
         *                      pixels
         */
        public ChartClickEvent(Component source, InvientCharts chart, Point point, MousePosition mousePosition) {
            super(source);
            this.chart = chart;
            this.point = point;
            this.mousePosition = mousePosition;
        }

        /**
         * Returns the chart object on which the click event occurred
         *
         * @return Returns the chart object on which the click event occurred
         * @see InvientCharts
         */
        public InvientCharts getChart() {
            return this.chart;
        }

        /**
         * Returns the point representing the position where the click event
         * occurred in axes units
         *
         * @return Returns the point representing the position where the click
         *         event occurred in axes units
         * @see Point
         */
        public Point getPoint() {
            return this.point;
        }

        /**
         * Returns the position of a mouse when the click event occurred
         *
         * @return Returns the position of a mouse when the click event occurred
         * @see MousePosition
         */
        public MousePosition getMousePosition() {
            return mousePosition;
        }

        @Override
        public String toString() {
            return "ChartClickEvent [point=" + point + ", mousePosition=" + mousePosition + "]";
        }

    }

    /**
     * Interface for listening for a {@link ChartClickEvent} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface ChartClickListener extends Serializable {
        public void chartClick(ChartClickEvent chartClickEvent);
    }

    private Set<ChartClickListener> chartClickListener = new HashSet<ChartClickListener>();

    /**
     * Adds the chart click listener.
     *
     * @param listener the Listener to be added.
     */
    public void addListener(ChartClickListener listener) {
        chartClickListener.add(listener);
        addListener(ChartClickEvent.class, listener, CHART_CLICK_METHOD);
    }

    /**
     * Removes the chart click listener.
     *
     * @param listener the listener to be removed
     */
    public void removeListener(ChartClickListener listener) {
        chartClickListener.remove(listener);
        removeListener(ChartClickEvent.class, listener, CHART_CLICK_METHOD);
    }

    /**
     * Add series event. This event is thrown, when a series is added to the
     * chart.
     *
     * @author Invient
     */
    public class ChartAddSeriesEvent extends Component.Event {
        private InvientCharts chart;

        /**
         * New instance of the chart add series event.
         *
         * @param source
         * @param chart
         */
        public ChartAddSeriesEvent(Component source, InvientCharts chart) {
            super(source);
            this.chart = chart;
        }

        /**
         * Returns the chart object to which a series is added
         *
         * @return Returns the chart object to which a series has been added.
         * @see InvientCharts
         */
        public InvientCharts getChart() {
            return this.chart;
        }
    }

    /**
     * Interface for listening for a {@link ChartAddSeriesEvent} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface ChartAddSeriesListener extends Serializable {
        public void chartAddSeries(ChartAddSeriesEvent chartAddSeriesEvent);
    }

    private Set<ChartAddSeriesListener> chartAddSeriesListener = new HashSet<ChartAddSeriesListener>();

    /**
     * Adds the series add listener.
     *
     * @param listener the Listener to be added.
     */
    public void addListener(ChartAddSeriesListener listener) {
        chartAddSeriesListener.add(listener);
        addListener(ChartAddSeriesEvent.class, listener, CHART_ADD_SERIES_METHOD);
    }

    /**
     * Removes the series add listener.
     *
     * @param listener the listener to be removed
     */
    public void removeListener(ChartAddSeriesListener listener) {
        chartAddSeriesListener.remove(listener);
        removeListener(ChartAddSeriesEvent.class, listener, CHART_ADD_SERIES_METHOD);
    }

    /**
     * Defines information on the selected area.
     *
     * @author Invient
     */
    public final class ChartArea implements Serializable {
        private double xAxisMin;
        private double xAxisMax;
        private double yAxisMin;
        private double yAxisMax;

        public ChartArea(double xAxisMin, double xAxisMax, double yAxisMin, double yAxisMax) {
            this.xAxisMin = xAxisMin;
            this.xAxisMax = xAxisMax;
            this.yAxisMin = yAxisMin;
            this.yAxisMax = yAxisMax;
        }

        public double getxAxisMin() {
            return xAxisMin;
        }

        public double getxAxisMax() {
            return xAxisMax;
        }

        public double getyAxisMin() {
            return yAxisMin;
        }

        public double getyAxisMax() {
            return yAxisMax;
        }

        @Override
        public String toString() {
            return "ChartSelectedArea [xAxisMin=" + xAxisMin + ", xAxisMax=" + xAxisMax + ", yAxisMin=" + yAxisMin
                    + ", yAxisMax=" + yAxisMax + "]";
        }

    }

    /**
     * Chart zoom event. This event is thrown, when an area of the chart has
     * been selected.
     *
     * @author Invient
     */
    public class ChartZoomEvent extends Component.Event {
        private InvientCharts chart;
        private ChartArea chartArea;

        /**
         * New instance of the chart zoom event.
         *
         * @param source    the chart object itself
         * @param chart     the chart object itself
         * @param chartArea the chartArea object containing dimensions of zoomed area
         *                  of the chart
         */
        public ChartZoomEvent(Component source, InvientCharts chart, ChartArea chartArea) {
            super(source);
            this.chart = chart;
            this.chartArea = chartArea;
        }

        /**
         * Returns the chart object for which the zoom event has occurred
         *
         * @return Returns the chart object for which the zoom event has
         *         occurred
         */
        public InvientCharts getChart() {
            return this.chart;
        }

        /**
         * Returns the chartArea object containing dimensions of zoomed area of
         * the chart
         *
         * @return Returns the chartArea object containing dimensions of zoomed
         *         area of the chart
         */
        public ChartArea getChartArea() {
            return this.chartArea;
        }
    }

    /**
     * Interface for listening for a {@link ChartZoomEvent} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface ChartZoomListener extends Serializable {
        public void chartZoom(ChartZoomEvent chartZoomEvent);
    }

    private Set<ChartZoomListener> chartZoomListener = new HashSet<ChartZoomListener>();

    /**
     * Adds the chart zoom listener.
     *
     * @param listener the Listener to be added.
     */
    public void addListener(ChartZoomListener listener) {
        chartZoomListener.add(listener);
        addListener(ChartZoomEvent.class, listener, CHART_ZOOM_METHOD);
    }

    /**
     * Removes the chart zoom listener.
     *
     * @param listener the listener to be removed
     */
    public void removeListener(ChartZoomListener listener) {
        chartZoomListener.remove(listener);
        removeListener(ChartZoomEvent.class, listener, CHART_ZOOM_METHOD);
    }

    /**
     * Chart reset zoom event. This event is thrown, when a chart is reset by
     * setting its zoom level to normal.
     *
     * @author Invient
     */
    public class ChartResetZoomEvent extends Component.Event {
        private InvientCharts chart;

        /**
         * New instance of the chart reset zoom event
         *
         * @param source the chart object itself
         * @param chart  the chart object itself
         */
        public ChartResetZoomEvent(Component source, InvientCharts chart) {
            super(source);
            this.chart = chart;
        }

        /**
         * Returns the chart object for which zoom has been reset to normal
         *
         * @return Returns the chart object for which zoom has been reset to
         *         normal
         */
        public InvientCharts getChart() {
            return this.chart;
        }
    }

    /**
     * Interface for listening for a {@link ChartResetZoomEvent} triggered by
     * {@link InvientCharts}
     *
     * @author Invient
     */
    public interface ChartResetZoomListener extends Serializable {
        public void chartResetZoom(ChartResetZoomEvent chartResetZoomEvent);
    }

    private Set<ChartResetZoomListener> chartResetZoomListener = new HashSet<ChartResetZoomListener>();

    /**
     * Adds the chart reset zoom listener.
     *
     * @param listener the Listener to be added.
     */
    public void addListener(ChartResetZoomListener listener) {
        chartResetZoomListener.add(listener);
        addListener(ChartResetZoomEvent.class, listener, CHART_RESET_ZOOM_METHOD);
    }

    /**
     * Removes the chart reset zoom listener.
     *
     * @param listener the listener to be removed
     */
    public void removeListener(ChartResetZoomListener listener) {
        chartResetZoomListener.remove(listener);
        removeListener(ChartResetZoomEvent.class, listener, CHART_RESET_ZOOM_METHOD);
    }

    /**
     * Chart SVG event. This event is thrown, when an SVG string representing
     * the chart is received or ready.
     * <p/>
     * Note that this event is thrown only once after a
     * {@link ChartSVGAvailableListener} is registered.
     *
     * @author Invient
     */
    public class ChartSVGAvailableEvent extends Component.Event {
        private InvientCharts chart;
        private String svg;

        /**
         * New instance of the chart svg available event.
         *
         * @param source the chart object itself
         * @param chart  the chart object itself
         * @param svg    an svg string representing the chart object
         */
        public ChartSVGAvailableEvent(Component source, InvientCharts chart, String svg) {
            super(source);
            this.chart = chart;
            this.svg = svg;
        }

        /**
         * Returns the chart object for which an svg string representation is
         * available
         *
         * @return Returns the chart object for which an svg string
         *         representation is available
         */
        public InvientCharts getChart() {
            return this.chart;
        }

        /**
         * @return Returns an SVG string representing the chart
         */
        public String getSVG() {
            return this.svg;
        }

    }

    /**
     * Interface for listening for a {@link ChartSVGAvailableEvent} triggered by
     * {@link InvientCharts}.
     * <p/>
     * The chart can have only one listener of this type registered at any time.
     * If a listener has already been registered and an attempt is made to
     * register another listener then the previously registered listener will be
     * unregistered and the new listener will be registered.
     * <p/>
     * A listener will be called only once after it has been registered though
     * it will be called again if the same listener is registered again.
     *
     * @author Invient
     */
    public interface ChartSVGAvailableListener extends Serializable {
        public void svgAvailable(ChartSVGAvailableEvent chartSVGAvailableEvent);
    }

    private ChartSVGAvailableListener svgAvailableListener;

    /**
     * Adds the chart svg available listener for this chart. If the chart
     * already has a listener of this type then the existing listener will be
     * removed and the argument listener will be registered.
     *
     * @param listener the Listener to be added or registered.
     */
    public void addListener(ChartSVGAvailableListener listener) {
        if (svgAvailableListener != null && svgAvailableListener != listener) {
            // remove earlier listener
            removeListener(svgAvailableListener);
        }
        svgAvailableListener = listener;
        addListener(ChartSVGAvailableEvent.class, svgAvailableListener, CHART_SVG_AVAILABLE_METHOD);
        isRetrieveSVG = true;
        requestRepaint();
    }

    /**
     * Removes the chart svg available listener for this chart.
     *
     * @param listener the listener to be removed or unregistered.
     */
    public void removeListener(ChartSVGAvailableListener listener) {
        if (svgAvailableListener == listener) {
            removeListener(ChartSVGAvailableEvent.class, listener, CHART_SVG_AVAILABLE_METHOD);
            isRetrieveSVG = false;
            svgAvailableListener = null;
        }
    }

    private static final Method CHART_CLICK_METHOD;
    private static final Method CHART_ADD_SERIES_METHOD;
    private static final Method CHART_ZOOM_METHOD;
    private static final Method CHART_RESET_ZOOM_METHOD;
    private static final Method CHART_SVG_AVAILABLE_METHOD;

    static {
        try {
            CHART_CLICK_METHOD = ChartClickListener.class.getDeclaredMethod("chartClick",
                    new Class[] { ChartClickEvent.class });
            CHART_ADD_SERIES_METHOD = ChartAddSeriesListener.class.getDeclaredMethod("chartAddSeries",
                    new Class[] { ChartAddSeriesEvent.class });
            CHART_ZOOM_METHOD = ChartZoomListener.class.getDeclaredMethod("chartZoom",
                    new Class[] { ChartZoomEvent.class });
            CHART_RESET_ZOOM_METHOD = ChartResetZoomListener.class.getDeclaredMethod("chartResetZoom",
                    new Class[] { ChartResetZoomEvent.class });
            CHART_SVG_AVAILABLE_METHOD = ChartSVGAvailableListener.class.getDeclaredMethod("svgAvailable",
                    new Class[] { ChartSVGAvailableEvent.class });
        } catch (final java.lang.NoSuchMethodException e) {
            // This should never happen unless there is a typo!
            throw new java.lang.RuntimeException("Internal error finding methods in Button");
        }
    }

    // *************************************************************************//
    // **************************** Chart Container
    // ****************************//
    // *************************************************************************//
    private LinkedHashSet<Series> chartSeries = new LinkedHashSet<Series>();
    private boolean reloadChartSeries = false;

    /**
     * The data of a chart is defined in terms of {@link Series}. This method
     * removes all previously set series of this chart and adds the argument
     * series. If the argument series is null then no actions are taken.
     *
     * @param series A collection of series to set as chart's data
     */
    public void setSeries(LinkedHashSet<Series> series) {
        if (series != null) {
            reloadChartSeries = true;
            this.chartSeries.clear();
            this.seriesCURMap.clear();
            for (Series seriesData : series) {
                addSeries(seriesData);
            }
        }
    }

    /**
     * Returns a series whose name matches the argument name.
     *
     * @param name the name of the series
     * @return Returns a series with the given name
     */
    public Series getSeries(String name) {
        for (Series series : this.chartSeries) {
            if (series.getName().equals(name)) {
                return series;
            }
        }
        return null;
    }

    /**
     * Returns all series associated with this chart.
     *
     * @return returns all series associated with this chart.
     */
    public LinkedHashSet<Series> getAllSeries() {
        return this.chartSeries;
    }

    /**
     * Adds the argument series to this chart.
     *
     * @param seriesData the series to be added
     */
    public void addSeries(Series seriesData) {
        if (this.chartSeries.add(seriesData)) {
            this.setAxisInSeriesIfNotSetAlready(seriesData);
            seriesData.setInvientCharts(this);
            addSeriesCUROperation(new SeriesCUR(SeriesCURType.ADD, seriesData.getName()));
            requestRepaint();
        }
    }

    // Before sending data to the client, this method sets 
    // axis in all series associated with the chart
    private void setAxisInAllSeriesIfNotSetAlready() {
        for (Series series : this.chartSeries) {
            setAxisInSeriesIfNotSetAlready(series);
        }
    }

    private void setAxisInSeriesIfNotSetAlready(Series series) {
        if (this.getConfig() != null) {
            if (series.getXAxis() == null && this.getConfig().getXAxes() != null
                    && this.getConfig().getXAxes().size() > 0) {
                series.setXAxis(this.getConfig().getXAxes().iterator().next());
            }
            if (series.getYAxis() == null && this.getConfig().getYAxes() != null
                    && this.getConfig().getYAxes().size() > 0) {
                series.setYAxis(this.getConfig().getYAxes().iterator().next());
            }
        }
    }

    /**
     * Removes a series whose name matches the argument name.
     *
     * @param name the name of the series
     */
    public void removeSeries(String name) {
        for (Iterator<Series> seriesItr = this.chartSeries.iterator(); seriesItr.hasNext();) {
            Series series = seriesItr.next();
            if (series.getName().equals(name)) {
                seriesItr.remove();
                series.setInvientCharts(null);
                addSeriesCUROperation(new SeriesCUR(SeriesCURType.REMOVE, series.getName()));
                requestRepaint();
            }
        }
    }

    /**
     * Removes the argument seriesData from this chart.
     *
     * @param seriesData the series object to be removed
     */
    public void removeSeries(Series seriesData) {
        if (this.chartSeries.remove(seriesData)) {
            seriesData.setInvientCharts(null);
            addSeriesCUROperation(new SeriesCUR(SeriesCURType.REMOVE, seriesData.getName()));
            requestRepaint();
        }
    }

    /**
     * This class represents a point of the chart's series. A series can have
     * one or more points. A point has (X, Y) coordinates. None of the
     * coordinates are mandatory. The name of a point can be displayed in a
     * tooltip.
     * <p/>
     * To represent no activity or missing points in the chart, create a point
     * with both X and Y as null or just Y as null.
     * <p/>
     * It is possible to specify custom configuration for each point. e.g. If a
     * highest point can be marked in a chart with a different color using this
     * configuration.
     * <p/>
     * A point cannot be created without a series. It must belong to a series.
     * However, the point must be added to a series by calling Series.addPoint()
     * or Series.setPoints() to permanently add point to the series.
     *
     * @author Invient
     * @see DecimalPoint
     * @see DateTimePoint
     * @see PointConfig
     */
    public static abstract class Point implements Serializable {
        private String id;
        private String name;
        private Series series;
        private PointConfig config;
        private boolean isAutosetX;
        private boolean shift;

        /**
         * Creates a point with given arguments.
         *
         * @param series The series to which the point must be associated.
         * @throws IllegalArgumentException If the argument series is null
         */
        public Point(Series series) {
            if (series == null) {
                throw new IllegalArgumentException("A point cannot be created without a series.");
            }
            this.series = series;
        }

        /**
         * To allow creation of a point from inside of InvientCharts component
         */
        private Point() {
            // FIXME this is not a correct way of doing it.
        }

        /**
         * Creates a point with given arguments.
         *
         * @param series The series to which the point must be associated.
         * @param config The configuration for this point, if any
         * @throws IllegalArgumentException If the argument series is null
         */
        public Point(Series series, PointConfig config) {
            this(series);
            this.config = config;
        }

        /**
         * Creates a point with given arguments.
         *
         * @param series The series to which the point must be associated.
         * @param name   name of this point
         * @throws IllegalArgumentException If the argument series is null
         */
        public Point(Series series, String name) {
            this(series);
            this.name = name;
        }

        /**
         * Creates a point with given arguments.
         *
         * @param series The series to which the point must be associated.
         * @param name   name of this point
         * @param config The configuration for this point, if any
         * @throws IllegalArgumentException If the argument series is null
         */
        public Point(Series series, String name, PointConfig config) {
            this(series, name);
            this.config = config;
        }

        String getId() {
            return id;
        }

        /**
         * @return Returns name of this point
         */
        public String getName() {
            return name;
        }

        /**
         * Sets name of this point
         *
         * @param name name of this point
         */
        public void setName(String name) {
            this.name = name;
        }

        /**
         * @return Returns {@link Series} associated with this point
         */
        public Series getSeries() {
            return series;
        }

        /**
         * @return Returns {@link PointConfig} for this point
         */
        public PointConfig getConfig() {
            return config;
        }

        /**
         * Sets {@link PointConfig} for this point
         *
         * @param config configuration of this point
         * @see PointConfig
         */
        public void setConfig(PointConfig config) {
            this.config = config;
        }

        /**
         * @return Returns true if X value of this point is set programmatically
         */
        boolean isAutosetX() {
            return isAutosetX;
        }

        /**
         * If the argument is true it indicates that the X value of this point
         * is set programmatically and user has not specified it.
         *
         * @return
         */
        void setAutosetX(boolean isAutosetX) {
            this.isAutosetX = isAutosetX;
        }

        /**
         * @return Returns true if a point at the start of the series should be
         *         shifted off when this point is appended otherwise false.
         */
        boolean isShift() {
            return shift;
        }

        /**
         * A value of true means one point is shifted off the start of the
         * series as one is appended to the end.
         *
         * @param shift
         */
        void setShift(boolean shift) {
            this.shift = shift;
        }

        /**
         * @return Returns X value of this point
         */
        public abstract Object getX();

        /**
         * @return Returns Y value of this point
         */
        public abstract Object getY();

        @Override
        public String toString() {
            return "Point [id=" + id + ", name=" + name + ", series=" + series.getName() + ", config=" + config
                    + "]";
        }

    }

    /**
     * This class represent a point with (X, Y) both as number. It should be
     * used to add points to {@link XYSeries}
     *
     * @author Invient
     */
    public static final class DecimalPoint extends Point {
        private Double x;
        private Double y;

        /**
         * @param series the series to which this belongs to
         */
        public DecimalPoint(Series series) {
            super(series);
        }

        /**
         * @param series the series to which this point belongs to
         * @param y      the y value of this point
         */
        public DecimalPoint(Series series, double y) {
            super(series);
            this.y = y;
        }

        /**
         * @param series the series to which this belongs to
         * @param name   the name of this point
         * @param y      the y value of this point
         */
        public DecimalPoint(Series series, String name, double y) {
            super(series, name);
            this.y = y;
        }

        /**
         * To allow creation of a point within the InvientChart.
         *
         * @param x the x value of this point
         * @param y the y value of this point
         */
        private DecimalPoint(double x, double y) {
            // FIXME this is not a correct way of doing it.
            super();
            this.x = x;
            this.y = y;
        }

        /**
         * @param series the series to which this belongs to
         * @param name   the name for this point
         * @param y      the y value of this point
         * @param config
         */
        public DecimalPoint(Series series, String name, double y, PointConfig config) {
            super(series, name, config);
            this.y = y;
        }

        /**
         * @param series the series to which this belongs to
         * @param y      the y value of this point
         * @param config the configuration for this point
         */
        public DecimalPoint(Series series, double y, PointConfig config) {
            super(series, config);
            this.y = y;
        }

        /**
         * @param series the series to which this belongs to
         * @param x      the x value of this point
         * @param y      the y value of this point
         */
        public DecimalPoint(Series series, double x, double y) {
            this(series, x, y, null);
        }

        /**
         * @param series the series to which this belongs to
         * @param x      the x value of this point
         * @param y      the y value of this point
         */
        public DecimalPoint(Series series, Double x, Double y) {
            this(series, x, y, null);
        }

        /**
         * @param series the series to which this belongs to
         * @param x      the x value of this point
         * @param y      the y value of this point
         * @param config the configuration of this point
         */
        public DecimalPoint(Series series, double x, double y, PointConfig config) {
            super(series, config);
            this.x = x;
            this.y = y;
        }

        /**
         * @param series the series to which this belongs to
         * @param x      the x value of this point
         * @param y      the y value of this point
         * @param config the configuration of this point
         */
        public DecimalPoint(Series series, Double x, Double y, PointConfig config) {
            super(series, config);
            this.x = x;
            this.y = y;
        }

        /*
         * (non-Javadoc)
         * 
         * @see com.invient.vaadin.chart.InvientChart.Point#getX()
         */
        @Override
        public Double getX() {
            return x;
        }

        /**
         * Sets the x value of this point
         *
         * @param x
         */
        private void setX(Double x) {
            this.x = x;
        }

        /*
         * (non-Javadoc)
         * 
         * @see com.invient.vaadin.chart.InvientChart.Point#getY()
         */
        @Override
        public Double getY() {
            return y;
        }

        /**
         * Sets the y value of this point
         *
         * @param y
         */
        private void setY(Double y) {
            this.y = y;
        }

        @Override
        public String toString() {
            return "DecimalPoint [x=" + x + ", y=" + y + ", id=" + getId() + ", name=" + getName() + ", seriesName="
                    + (getSeries() != null ? getSeries().getName() : "") + "]";
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((y == null) ? 0 : y.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            DecimalPoint other = (DecimalPoint) obj;
            // If x is null then return always false as x is calculated if not
            // specified
            if (x == null || other.x == null) {
                return false;
            }
            if (!x.equals(other.x))
                return false;
            if (y == null) {
                if (other.y != null)
                    return false;
            } else if (other.y == null) {
                return false;
            } else if (y.compareTo(other.y) != 0)
                return false;
            return true;
        }

    }

    /**
     * This class represent a point with (X, Y) both as number. It should be
     * used to add points to {@link DateTimeSeries}
     *
     * @author Invient
     */
    public static final class DateTimePoint extends Point {
        private Date x;
        private Double y;

        /**
         * @param series the series to which this belongs to
         */
        public DateTimePoint(Series series) {
            super(series);
        }

        /**
         * @param series the series to which this belongs to
         * @param y      the y value of this point
         */
        public DateTimePoint(Series series, double y) {
            this(series, "", y);
        }

        /**
         * @param series the series to which this belongs to
         * @param name   the name of this point
         * @param y      the y value of this point
         */
        public DateTimePoint(Series series, String name, double y) {
            super(series, name);
            this.y = y;
        }

        /**
         * @param series the series to which this belongs to
         * @param name   the name of this point
         * @param y      the y value of this point
         * @param config
         */
        public DateTimePoint(Series series, String name, double y, PointConfig config) {
            super(series, name, config);
            this.y = y;
        }

        /**
         * @param series the series to which this belongs to
         * @param x      the x value of this point
         * @param y      the y value of this point
         */
        public DateTimePoint(Series series, Date x, double y) {
            this(series, y);
            this.x = x;
        }

        /*
         * (non-Javadoc)
         * 
         * @see com.invient.vaadin.chart.InvientChart.Point#getX()
         */
        public Date getX() {
            return x;
        }

        /**
         * Sets the x value of this point
         *
         * @param x
         */
        private void setX(Date x) {
            this.x = x;
        }

        /*
         * (non-Javadoc)
         * 
         * @see com.invient.vaadin.chart.InvientChart.Point#getY()
         */
        public Double getY() {
            return y;
        }

        /**
         * Sets the y value of this point
         *
         * @param y
         */
        private void setY(Double y) {
            this.y = y;
        }

        @Override
        public String toString() {
            return "DateTimePoint [x="
                    + getDateInMilliseconds(x,
                            (getSeries() != null ? ((DateTimeSeries) getSeries()).isIncludeTime() : false))
                    + ", y=" + y + ", id=" + getId() + ", name=" + getName() + ", seriesName="
                    + (getSeries() != null ? getSeries().getName() : "") + "]";
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((y == null) ? 0 : y.hashCode());
            result = prime * result + ((x == null) ? 0 : (int) x.getTime());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            DateTimePoint other = (DateTimePoint) obj;
            // If x is null then return always false as x is calculated if not
            // specified
            if (x == null || other.x == null) {
                return false;
            }

            boolean pointIncludeTime = (this.getSeries() instanceof DateTimeSeries
                    ? ((DateTimeSeries) this.getSeries()).isIncludeTime()
                    : false);
            boolean pointOtherIncludeTime = (other.getSeries() instanceof DateTimeSeries
                    ? ((DateTimeSeries) other.getSeries()).isIncludeTime()
                    : false);
            Long pointX = getDateInMilliseconds(x, pointIncludeTime);
            Long pointOtherX = getDateInMilliseconds(other.x, pointOtherIncludeTime);

            if (pointX.compareTo(pointOtherX) != 0)
                return false;

            if (y == null) {
                if (other.y != null)
                    return false;
            } else if (other.y == null) {
                return false;
            } else if (y.compareTo(other.y) != 0)
                return false;
            return true;
        }
    }

    /**
     * This class defines a series of the chart. A series contains a collection
     * of points. Series can be one of types defined by {@link SeriesType}.
     * <p/>
     * Each series must have unique name. If an attempt is made to add two
     * series with same then only the first added series will be in effect.
     * <p/>
     * If the series type is not specified, it defaults to chart type and the
     * default chart type is SeriesType.LINE. A series has unique xAxis and
     * yAxis object associated with it. There is no need to set xAxis and yAxis
     * unless the chart has more than one one axis of any type and the series
     * must belong to any of the secondary axis.
     * <p/>
     * It is also possible to specify configuration for individual series and
     * not just series type.
     *
     * @author Invient
     */
    public static abstract class Series<T extends Point> implements Serializable {

        private LinkedHashSet<T> points = new LinkedHashSet<T>();
        private String name = "";
        private SeriesType type;
        private String stack;
        private XAxis xAxis;
        private YAxis yAxis;
        private SeriesConfig config;
        private InvientCharts invientCharts;

        /**
         * Creates a series with given name
         *
         * @param name the name of this series
         */
        public Series(String name) {
            this.name = name;
        }

        /**
         * Creates a series with given name and type
         *
         * @param name       the name of this series
         * @param seriesType the type of this series
         */
        public Series(String name, SeriesType seriesType) {
            this(name);
            this.type = seriesType;
        }

        /**
         * Creates a series with given name and configuration
         *
         * @param name   the name of this series
         * @param config the configuration for this series
         */
        public Series(String name, SeriesConfig config) {
            this(name);
            this.config = config;
        }

        /**
         * Creates a series with given name, type and configuration
         *
         * @param name       the name of this series
         * @param seriesType the type of this series
         * @param config     the configuration for this series
         */
        public Series(String name, SeriesType seriesType, SeriesConfig config) {
            this(name, config);
            this.type = seriesType;
        }

        /**
         * @return Returns the configuration object associated with this series
         */
        public SeriesConfig getConfig() {
            return config;
        }

        /**
         * @return Returns name of this series
         */
        public String getName() {
            return name;
        }

        /**
         * Sets name of this series
         *
         * @param name
         */
        public void setName(String name) {
            this.name = name;
        }

        /**
         * @return
         */
        public SeriesType getType() {
            return type;
        }

        /**
         * Sets type of this series
         *
         * @param type
         */
        public void setType(SeriesType type) {
            this.type = type;
        }

        /**
         * @return Returns stack of this series
         */
        public String getStack() {
            return stack;
        }

        /**
         * By using this stack property, it is possible to group series in a
         * stacked chart. Sets stack for this series. If two series belongs to
         * the same stack then the resultant chart will be stacked chart
         *
         * @param stack
         */
        public void setStack(String stack) {
            this.stack = stack;
        }

        /**
         * @return Returns x-axis associated with this series.
         * @see Axis
         */
        public XAxis getXAxis() {
            return xAxis;
        }

        /**
         * Sets x-axis of this series. A series can be associated with at most
         * one x-axis.
         *
         * @param xAxis
         */
        public void setXAxis(XAxis xAxis) {
            this.xAxis = xAxis;
        }

        /**
         * @return Returns y-axis of this series.
         */
        public YAxis getYAxis() {
            return yAxis;
        }

        /**
         * Sets y-axis of this series. A series can be associated with at most
         * one y-axis.
         *
         * @param yAxis
         */
        public void setYAxis(YAxis yAxis) {
            this.yAxis = yAxis;
        }

        /**
         * @param points
         */
        protected void removePoint(Point... points) {
            List<Point> pointsRemovedList = new ArrayList<Point>();
            for (Point point : points) {
                if (this.points.remove(point)) {
                    pointsRemovedList.add(point);
                }
            }
            updatePointXValuesIfNotPresent();
            for (Point point : pointsRemovedList) {
                if (this.invientCharts != null) {
                    this.invientCharts.addSeriesPointRemovedOperation(point.getSeries().getName(), point);
                    this.invientCharts.requestRepaint();
                }
            }
        }

        /**
         * Removes all points in this series
         */
        protected void removeAllPoints() {
            this.points.clear();
            if (this.invientCharts != null) {
                this.invientCharts.addSeriesCUROperation(new SeriesCUR(SeriesCURType.UPDATE, this.getName(), true));
                this.invientCharts.requestRepaint();
            }
        }

        /**
         * Adds one or more points into this series, specified as an argument to
         * this method
         *
         * @param points
         * @return Returns null if the argument is null otherwise returns a
         *         collection of points which are added in this series. If a
         *         point has same (x, y) value as any other point in the
         *         argument points then it will not be added.
         */
        protected LinkedHashSet<T> addPoint(boolean shift, T... points) {
            if (shift) {
                // Remove first point as other points gets appended at the end
                Iterator<T> pointsItr = this.points.iterator();
                if (pointsItr.hasNext()) {
                    pointsItr.next();
                    pointsItr.remove();
                }
            }
            List<T> pointsAddedList = new ArrayList<T>();
            for (T point : points) {
                if (this.points.add(point)) {
                    pointsAddedList.add(point);
                }
            }
            updatePointXValuesIfNotPresent();
            // Now record point add event as we need to know x value of a point
            for (Point point : pointsAddedList) {
                if (this.invientCharts != null) {
                    this.invientCharts.addSeriesPointAddedOperation(point.getSeries().getName(), point);
                    this.invientCharts.requestRepaint();
                }
            }
            return new LinkedHashSet<T>(pointsAddedList);
        }

        private void addPointsInternal(LinkedHashSet<T> points) {
            for (T point : points) {
                this.points.add(point);
            }
        }

        /**
         * @return Returns all points of this series. Adding or removing any
         *         point to or from the returned collection will not impact the
         *         chart. To add a point or points, use addPoint() or
         *         removePoint() method.
         */
        protected LinkedHashSet<T> getPoints() {
            return new LinkedHashSet<T>(this.points);
        }

        /**
         * Sets points into this series
         *
         * @param points The points to set.
         * @return Returns {@code null} if the argument is {@code null} otherwise returns a
         *         collection of points which are set in this series. If a point
         *         has same (x, y) value as any other point in the argument
         *         points then it will not be added.
         */
        protected LinkedHashSet<T> setPoints(LinkedHashSet<T> points) {
            if (points != null) {
                this.points.clear();
                addPointsInternal(points);
                updatePointXValuesIfNotPresent();
                if (this.invientCharts != null) {
                    this.invientCharts
                            .addSeriesCUROperation(new SeriesCUR(SeriesCURType.UPDATE, this.getName(), true));
                    this.invientCharts.requestRepaint();
                }
                return getPoints();
            }
            return null;
        }

        /**
         * Each of the subclass needs to implement this method to ensure that
         * each point has appropriate X value even if it is not specified.
         */
        protected abstract void updatePointXValuesIfNotPresent();

        /**
         * Show this series
         */
        public void show() {
            this.config = (this.config == null ? new SeriesConfig() : this.config);
            this.config.setVisible(true);
            if (this.invientCharts != null) {
                this.invientCharts.addSeriesCUROperation(new SeriesCUR(SeriesCURType.UPDATE, this.getName()));
                this.invientCharts.requestRepaint();
            }
        }

        /**
         * Hide this series
         */
        public void hide() {
            this.config = (this.config == null ? new SeriesConfig() : this.config);
            this.config.setVisible(false);
            if (this.invientCharts != null) {
                this.invientCharts.addSeriesCUROperation(new SeriesCUR(SeriesCURType.UPDATE, this.getName()));
                this.invientCharts.requestRepaint();
            }
        }

        void setInvientCharts(InvientCharts invientCharts) {
            this.invientCharts = invientCharts;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Series other = (Series) obj;
            if (name == null) {
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return "Series [points=" + points + ", name=" + name + ", type=" + type + ", stack=" + stack
                    + ", xAxis=" + xAxis + ", yAxis=" + yAxis + ", config=" + config + "]";
        }
    }

    /**
     * This class defines a number series. In this series both X and Y values
     * must be number. To use date values, use {@link DateTimeSeries}
     *
     * @author Invient
     * @see DateTimeSeries
     */
    public static class XYSeries extends Series<DecimalPoint> {

        /**
         * Creates a series with given name
         *
         * @param name the name of this series
         */
        public XYSeries(String name) {
            super(name);
        }

        /**
         * Creates a series with given name and configuration
         *
         * @param name   the name of this series
         * @param config the configuration for this series
         */
        public XYSeries(String name, SeriesConfig config) {
            super(name, config);
        }

        /**
         * Creates a series with given name and type
         *
         * @param name       the name of this series
         * @param seriesType the type of this series
         */
        public XYSeries(String name, SeriesType seriesType) {
            super(name, seriesType);
        }

        /**
         * Creates a series with given name, type and configuration
         *
         * @param name       the name of this series
         * @param seriesType the type of this series
         * @param config     the configuration for this series
         */
        public XYSeries(String name, SeriesType seriesType, SeriesConfig config) {
            super(name, seriesType, config);
        }

        /**
         * Removes the specified point from the series
         *
         * @param points Points to be removed.
         */
        public void removePoint(DecimalPoint... points) {
            super.removePoint(points);
        }

        /*
         * (non-Javadoc)
         * 
         * @see com.invient.vaadin.chart.InvientChart.Series#removeAllPoints()
         */
        public void removeAllPoints() {
            super.removeAllPoints();
        }

        /**
         * Appends the specified point into the series if they do not exists in
         * this series. The points which already exists will not be appended. A
         * collection of points appended to this series will be returned.
         *
         * @param points Points to be added.
         * @return Returns a collection of points which are added in this
         *         series. If a point has same (x, y) value as any other point
         *         in the input argument points then it will not be added in
         *         this series.
         */
        public LinkedHashSet<DecimalPoint> addPoint(DecimalPoint... points) {
            return super.addPoint(false, points);
        }

        /**
         * Append the specified point into this series. If the argument shift is
         * true then one point is shifted off the start of this series as one is
         * appended to the end.
         *
         * @param point Point to add.
         * @param shift If true then one point is shifted off the start of this
         *              series as one is appended to the end.
         * @return Returns a collection of points which are added in this
         *         series. If a point has same (x, y) value as any other point
         *         in the input argument points then it will not be added in
         *         this series.
         */
        public LinkedHashSet<DecimalPoint> addPoint(DecimalPoint point, boolean shift) {
            point.setShift(shift);
            return super.addPoint(shift, point);
        }

        /**
         * {@inheritDoc}
         */
        public LinkedHashSet<DecimalPoint> getPoints() {
            return super.getPoints();
        }

        /**
         * Sets points into this series. This method removes all of its points
         * and then add points specified in the method argument. If the argument
         * is null then no actions are taken.
         *
         * @param points the collection of points to set into this series.
         * @return Returns a collection of points which are set in this series.
         *         If a point has same (x, y) value as any other point in the
         *         argument points then it will not be added.
         */
        public LinkedHashSet<DecimalPoint> setSeriesPoints(LinkedHashSet<DecimalPoint> points) {
            return super.setPoints(points);
        }

        @Override
        protected void updatePointXValuesIfNotPresent() {
            double pointStart = 0;
            double pointInterval = 1;
            if (super.getConfig() instanceof BaseLineConfig) {
                BaseLineConfig config = (BaseLineConfig) super.getConfig();
                if (config.getPointStart() != null) {
                    pointStart = config.getPointStart();
                }
                if (config.getPointInterval() != null) {
                    pointInterval = config.getPointInterval();
                }
            }
            int count = 0;
            for (Point point : getPoints()) {
                if ((point.getX() == null || (point.getX() != null && point.isAutosetX()))) {
                    DecimalPoint decimalPoint = (DecimalPoint) point;
                    point.setAutosetX(true);
                    if (count == 0) {
                        decimalPoint.setX(pointStart);
                        count++;
                    } else {
                        pointStart = pointStart + pointInterval;
                        decimalPoint.setX(pointStart);
                    }
                }
            }
        }

    }

    /**
     * This class defines a datetime series. In this series, the X value must be
     * date and Y values must be number. To use number values, use
     * {@link XYSeries}
     * <p/>
     * By default, the time of a day is not included in the X value. In order to
     * include time, use a constructor with argument isIncludeTime and pass true
     * value for the argument.
     *
     * @author Invient
     * @see XYSeries
     */
    public static class DateTimeSeries extends Series<DateTimePoint> {
        private boolean includeTime;

        /**
         * Creates a series with given name. This series will not consider time
         * in the X property of {@link DateTimePoint}. To include time, use any
         * constructor having isIncludeTime as part of the arguments.
         *
         * @param name the name of this series
         */
        public DateTimeSeries(String name) {
            this(name, false);
        }

        /**
         * Creates a series with given name and boolean value.
         *
         * @param name          the name of this series
         * @param isIncludeTime If true then the time in the X property of
         *                      {@link DateTimePoint} will be considered when drawing the
         *                      chart. Defaults to false.
         */
        public DateTimeSeries(String name, boolean isIncludeTime) {
            super(name);
            this.includeTime = isIncludeTime;
        }

        /**
         * Creates a series with given name and configuration.
         *
         * @param name   the name of this series
         * @param config the configuration for this series
         */
        public DateTimeSeries(String name, SeriesConfig config) {
            this(name, config, false);
        }

        /**
         * Creates a series with given name, configuration and boolean value.
         *
         * @param name          the name of this series
         * @param config        the configuration for this series
         * @param isIncludeTime If true then the time in the X property of
         *                      {@link DateTimePoint} will be considered when drawing the
         *                      chart. Defaults to false.
         */
        public DateTimeSeries(String name, SeriesConfig config, boolean isIncludeTime) {
            super(name, config);
            this.includeTime = isIncludeTime;
        }

        /**
         * Creates a series with given name and type.
         *
         * @param name       the name of this series
         * @param seriesType the type of this series
         */
        public DateTimeSeries(String name, SeriesType seriesType) {
            this(name, seriesType, false);
        }

        /**
         * Creates a series with given name, type and boolean value.
         *
         * @param name          the name of this series
         * @param seriesType    the type of this series
         * @param isIncludeTime If true then the time in the X property of
         *                      {@link DateTimePoint} will be considered when drawing the
         *                      chart. Defaults to false.
         */
        public DateTimeSeries(String name, SeriesType seriesType, boolean isIncludeTime) {
            super(name, seriesType);
            this.includeTime = isIncludeTime;
        }

        /**
         * Creates a series with given name, type and configuration.
         *
         * @param name       the name of this series
         * @param seriesType the type of this series
         * @param config     the configuration for this series
         */
        public DateTimeSeries(String name, SeriesType seriesType, SeriesConfig config) {
            this(name, seriesType, config, false);
        }

        /**
         * Creates a series with given name, type, configuration and boolean
         * value.
         *
         * @param name          the name of this series
         * @param seriesType    the type of this series
         * @param config        the configuration for this series
         * @param isIncludeTime If true then the time in the X property of
         *                      {@link DateTimePoint} will be considered when drawing the
         *                      chart. Defaults to false.
         */
        public DateTimeSeries(String name, SeriesType seriesType, SeriesConfig config, boolean isIncludeTime) {
            super(name, seriesType, config);
            this.includeTime = isIncludeTime;
        }

        /**
         * Removes all points specified as method argument into this series
         *
         * @param points The points to be removed.
         */
        public void removePoint(DateTimePoint... points) {
            super.removePoint(points);
        }

        /*
         * (non-Javadoc)
         * 
         * @see com.invient.vaadin.chart.InvientChart.Series#removeAllPoints()
         */
        public void removeAllPoints() {
            super.removeAllPoints();
        }

        /**
         * Appends the specified point into the series if they do not exists in
         * this series. The points which already exists will not be appended. A
         * collection of points appended to this series will be returned.
         *
         * @param points The points to be removed.
         *
         * @return Returns a collection of points which are added in this
         *         series. If a point has same (x, y) value as any other point
         *         in the input argument points then it will not be added in
         *         this series.
         */
        public LinkedHashSet<DateTimePoint> addPoint(DateTimePoint... points) {
            return super.addPoint(false, points);
        }

        /**
         * Append the specified point into this series. If the argument shift is
         * true then one point is shifted off the start of this series as one is
         * appended to the end.
         *
         * @param point A point to be added at the end of this series
         * @param shift If true then one point is shifted off the start of this
         *              series as one is appended to the end.
         * @return Returns a collection of points which are added in this
         *         series. If a point has same (x, y) value as any other point
         *         in the input argument points then it will not be added in
         *         this series.
         */
        public LinkedHashSet<DateTimePoint> addPoint(DateTimePoint point, boolean shift) {
            point.setShift(shift);
            return super.addPoint(shift, point);
        }

        /**
         * @return Returns true if the time in the X property of
         *         {@link DateTimePoint} will be considered when drawing the
         *         chart otherwise false.
         */
        public boolean isIncludeTime() {
            return includeTime;
        }

        /**
         * {@inheritDoc}
         */
        public LinkedHashSet<DateTimePoint> getPoints() {
            return super.getPoints();
        }

        /**
         * Sets points into this series. This method removes all of its points
         * and then add points specified in the method argument. If the argument
         * is null then no actions are taken.
         *
         * @param points the collection of points to set into this series.
         * @return Returns a collection of points which are added in this
         *         series. If a point has same (x, y) value as any other point
         *         in the input argument points then it will not be added in
         *         this series.
         */
        public LinkedHashSet<DateTimePoint> setSeriesPoints(LinkedHashSet<DateTimePoint> points) {
            return super.setPoints(points);
        }

        @Override
        protected void updatePointXValuesIfNotPresent() {
            double pointStart = (double) getDefPointStart();
            double pointInterval = 3600000; // 1 hour
            if (super.getConfig() instanceof BaseLineConfig) {
                BaseLineConfig config = (BaseLineConfig) super.getConfig();
                if (config.getPointStart() != null) {
                    pointStart = config.getPointStart();
                }
                if (config.getPointInterval() != null) {
                    pointInterval = config.getPointInterval();
                }
            }
            Date prevDate = new Date((long) pointStart);
            int count = 0;
            for (Point point : getPoints()) {
                DateTimePoint dateTimePoint = (DateTimePoint) point;
                if ((point.getX() == null || (point.getX() != null && point.isAutosetX()))) {
                    point.setAutosetX(true);
                    if (count == 0) {
                        dateTimePoint.setX(prevDate);
                        count++;
                    } else {
                        dateTimePoint.setX(getUpdatedDate(prevDate, (long) pointInterval));
                        prevDate = dateTimePoint.getX();
                    }
                }
            }
        }

        private static long getDefPointStart() {
            Calendar cal = GregorianCalendar.getInstance();
            cal.set(Calendar.YEAR, 1970);
            cal.set(Calendar.MONTH, Calendar.JANUARY);
            cal.set(Calendar.DAY_OF_MONTH, 1);
            cal.set(Calendar.HOUR, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            return cal.getTimeInMillis();
        }

        private static Date getUpdatedDate(Date dt, long milliseconds) {
            Calendar cal = Calendar.getInstance();
            cal.setTimeInMillis(dt.getTime() + milliseconds);
            return cal.getTime();
        }

        @Override
        public String toString() {
            return "DateTimeSeries [includeTime=" + includeTime + ", getConfig()=" + getConfig() + ", getName()="
                    + getName() + ", getType()=" + getType() + ", getStack()=" + getStack() + ", getXAxis()="
                    + getXAxis() + ", getYAxis()=" + getYAxis() + "]";
        }

    }

    public static enum SeriesType {

        COMMONSERIES("series"), LINE("line"), SPLINE("spline"), SCATTER("scatter"), AREA("area"), AREASPLINE(
                "areaspline"), BAR("bar"), COLUMN("column"), PIE("pie");

        private String type;

        private SeriesType(String type) {
            this.type = type;
        }

        public String getName() {
            return this.type;
        }

    }

    static class SeriesCUR implements Serializable {

        private SeriesCURType type;
        private String name;
        private boolean reloadPoints = false;
        private LinkedHashSet<Point> pointsAdded = new LinkedHashSet<InvientCharts.Point>();
        private LinkedHashSet<Point> pointsRemoved = new LinkedHashSet<InvientCharts.Point>();

        public SeriesCURType getType() {
            return type;
        }

        public String getName() {
            return name;
        }

        public SeriesCUR(SeriesCURType type, String name) {
            super();
            this.type = type;
            this.name = name;
        }

        public SeriesCUR(SeriesCURType type, String name, boolean reloadPoints) {
            super();
            this.type = type;
            this.name = name;
            this.reloadPoints = reloadPoints;
        }

        /**
         * Indicates whether the client/terminal should update series by setting
         * all data of a series instead of adding or removing individual points
         *
         * @return Returns true if the data of the series must be reloaded
         *         otherwise false.
         */
        boolean isReloadPoints() {
            return reloadPoints;
        }

        void setReloadPoints(boolean reloadPoints) {
            this.reloadPoints = reloadPoints;
        }

        void trackPointAdded(Point point) {
            pointsAdded.add(point);
        }

        void trackPointRemoved(Point point) {
            // If the point was added earlier and now removed
            // then there is no need to record its add/remove operation
            // as add of a point is nullified by remove of a point
            if (!removePointIfTrackedAlready(point)) {
                pointsRemoved.add(point);
            }
        }

        boolean removePointIfTrackedAlready(Point point) {
            return pointsAdded.remove(point);
        }

        // Used to clear all points added/removed when
        // series data is set/cleared using series.setPoints() or
        // series.removeAllPoints()
        void clearTrackedPoints() {
            pointsAdded.clear();
            pointsRemoved.clear();
        }

        public LinkedHashSet<Point> getPointsAdded() {
            return pointsAdded;
        }

        public LinkedHashSet<Point> getPointsRemoved() {
            return pointsRemoved;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            result = prime * result + ((type == null) ? 0 : type.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            SeriesCUR other = (SeriesCUR) obj;
            if (name == null) {
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))
                return false;
            if (type == null) {
                if (other.type != null)
                    return false;
            } else if (!type.equals(other.type))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return "SeriesCUR [type=" + type + ", name=" + name + ", reloadPoints=" + reloadPoints
                    + ", pointsAdded=" + pointsAdded + ", pointsRemoved=" + pointsRemoved + "]";
        }

        static enum SeriesCURType {
            ADD("Add"), UPDATE("Update"), REMOVE("Remove");
            private String name;

            private SeriesCURType(String name) {
                this.name = name;
            }

            public String getName() {
                return this.name;
            }
        }
    }

    private LinkedHashMap<String, LinkedHashSet<SeriesCUR>> seriesCURMap = new LinkedHashMap<String, LinkedHashSet<SeriesCUR>>();

    boolean addSeriesCUROperation(SeriesCUR newSeriesCUR) {
        //
        if (seriesCURMap.keySet().contains(newSeriesCUR.getName())) {
            LinkedHashSet<SeriesCUR> seriesCURSet = seriesCURMap.get(newSeriesCUR.getName());
            // If for a series, no operation is found
            if (seriesCURSet == null || seriesCURSet.size() == 0) {
                seriesCURSet = new LinkedHashSet<InvientCharts.SeriesCUR>();
                seriesCURSet.add(newSeriesCUR);
                seriesCURMap.put(newSeriesCUR.getName(), seriesCURSet);
            } else if (seriesCURSet.contains(newSeriesCUR)) {
                SeriesCUR seriesCUR = getMatchedSeriesCUR(seriesCURSet, newSeriesCUR);
                // In case of series update (due to series.show/hide or
                // series.setPoints or series.removeAllPoints)
                // we need to check if all points of a series are set afresh. If
                // so then
                // set a flag to indicate that instead of adding and removing
                // points to and from series, set series data in full.
                if (seriesCUR.getType().equals(SeriesCURType.UPDATE)) {
                    seriesCUR.setReloadPoints(newSeriesCUR.isReloadPoints());
                    if (newSeriesCUR.isReloadPoints()) {
                        seriesCUR.clearTrackedPoints();
                    }
                    return true;
                }
                // Operation on a series has already been recorded
                return false;
            } else {
                Iterator<SeriesCUR> seriesCURItr = seriesCURSet.iterator();
                while (seriesCURItr.hasNext()) {
                    SeriesCUR seriesCUR = seriesCURItr.next();
                    if (seriesCUR.getName().equals(newSeriesCUR.getName())) {
                        if (SeriesCURType.REMOVE.equals(newSeriesCUR.getType())
                                && SeriesCURType.ADD.equals(seriesCUR.getType())) {
                            // Remove addition of a series as there is no reason
                            // to add
                            // a series and
                            // then remove it. E.g. If a new series is added and
                            // then
                            // removed then
                            // actually there is nothing to be done
                            seriesCURItr.remove();
                            return false;
                        }
                        if (SeriesCURType.UPDATE.equals(newSeriesCUR.getType())
                                && SeriesCURType.ADD.equals(seriesCUR.getType())) {
                            // There is no need for update as adding a series
                            // will
                            // take care of applying any update to the series
                            // attributes
                            // specifically visibility
                            return false;
                        }
                        if (SeriesCURType.REMOVE.equals(newSeriesCUR.getType())
                                && SeriesCURType.UPDATE.equals(seriesCUR.getType())) {
                            // Remove update of a series as there is no reason
                            // to update
                            // a series
                            // and then remove it. E.g. If an existing series
                            // was
                            // updated (for show/hide) and
                            // then removed then series need not be updated
                            // after all it
                            // is going to be
                            // removed. Hover, the remove operation must be
                            // captured.
                            seriesCURItr.remove();
                            break;
                        }
                    }
                }
            }
            seriesCURSet.add(newSeriesCUR);
            return true;
        } else {
            LinkedHashSet<SeriesCUR> seriesCURSet = new LinkedHashSet<InvientCharts.SeriesCUR>();
            seriesCURSet.add(newSeriesCUR);
            seriesCURMap.put(newSeriesCUR.getName(), seriesCURSet);
            return true;
        }
    }

    void addSeriesPointAddedOperation(String seriesName, Point point) {
        LinkedHashSet<SeriesCUR> seriesCURSet = seriesCURMap.get(seriesName);
        if (seriesCURSet == null || seriesCURSet.size() == 0) {
            SeriesCUR seriesCUR = new SeriesCUR(SeriesCURType.UPDATE, seriesName);
            seriesCUR.trackPointAdded(point);
            seriesCURSet = new LinkedHashSet<InvientCharts.SeriesCUR>();
            seriesCURSet.add(seriesCUR);
            seriesCURMap.put(seriesName, seriesCURSet);
        } else {
            SeriesCUR lastSeriesCur = getLastSeriesCUR(seriesCURSet);
            // Track points only if series is updated.
            // Tracking point is useless in following cases
            // 1. A new series is added : In this case, a series will be added
            // with all points so no need to track
            // 2. A series is removed : In this case, a series will be removed
            // and hence any point added to the series doesn't carry any
            // meaning.
            if (lastSeriesCur.getType().equals(SeriesCURType.UPDATE) && !lastSeriesCur.isReloadPoints()) {
                lastSeriesCur.trackPointAdded(point);
            }
        }
    }

    private SeriesCUR getLastSeriesCUR(LinkedHashSet<SeriesCUR> seriesCURSet) {
        if (seriesCURSet == null || seriesCURSet.size() == 0) {
            return null;
        }
        SeriesCUR lastSeriesCur = null;
        for (SeriesCUR seriesCur : seriesCURSet) {
            lastSeriesCur = seriesCur;
        }
        return lastSeriesCur;
    }

    private SeriesCUR getMatchedSeriesCUR(LinkedHashSet<SeriesCUR> seriesCURSet, SeriesCUR matchAgainstSeriesCUR) {
        for (SeriesCUR seriesCur : seriesCURSet) {
            if (matchAgainstSeriesCUR.equals(seriesCur)) {
                return seriesCur;
            }
        }
        return null;
    }

    void addSeriesPointRemovedOperation(String seriesName, Point point) {
        LinkedHashSet<SeriesCUR> seriesCURSet = seriesCURMap.get(seriesName);
        if (seriesCURSet == null || seriesCURSet.size() == 0) {
            SeriesCUR seriesCUR = new SeriesCUR(SeriesCURType.UPDATE, seriesName);
            seriesCUR.trackPointRemoved(point);
            seriesCURSet = new LinkedHashSet<InvientCharts.SeriesCUR>();
            seriesCURSet.add(seriesCUR);
            seriesCURMap.put(seriesName, seriesCURSet);
        } else {
            SeriesCUR lastSeriesCur = getLastSeriesCUR(seriesCURSet);
            // Track points only if series is updated.
            // Tracking point is useless in following cases
            // 1. A new series is added : In this case, a series will be added
            // with all points so no need to track
            // 2. A series is removed : In this case, a series will be removed
            // and hence any point removed from the series
            // doesn't carry any meaning.
            if (lastSeriesCur.getType().equals(SeriesCURType.UPDATE) && !lastSeriesCur.isReloadPoints()) {
                lastSeriesCur.trackPointRemoved(point);
            }
        }
    }

    /**
     * After a series is added or removed, there is no need to call this method
     * as it is handled implicitly. This method will send updates to the client.
     * This method should be called after adding/removing plotbands and
     * plotlines. This inconsistency will be fixed in next revision.
     */
    public void refresh() {
        super.requestRepaint();
    }

    /**
     * Displays a Print dialog of the Webkit to print this chart. Invoking this
     * method causes the Webkit to hide other widgets on the screen and only
     * this chart widget will be visible. Also it prints this chart widget as it
     * is displayed.
     */
    public void print() {
        isPrint = true;
        requestRepaint();
    }

}