net.officefloor.demo.stocks.client.StockGraphWidget.java Source code

Java tutorial

Introduction

Here is the source code for net.officefloor.demo.stocks.client.StockGraphWidget.java

Source

/*
 * OfficeFloor - http://www.officefloor.net
 * Copyright (C) 2005-2013 Daniel Sagenschneider
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.officefloor.demo.stocks.client;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;

import net.officefloor.plugin.gwt.comet.api.OfficeFloorComet;

import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.google.gwt.user.client.ui.TabPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.visualization.client.AbstractDataTable.ColumnType;
import com.google.gwt.visualization.client.DataTable;
import com.google.gwt.visualization.client.VisualizationUtils;
import com.google.gwt.visualization.client.visualizations.AnnotatedTimeLine;
import com.google.gwt.visualization.client.visualizations.AnnotatedTimeLine.Options;
import com.google.gwt.visualization.client.visualizations.AnnotatedTimeLine.ScaleType;

/**
 * {@link Widget} for graphing {@link StockPrice}.
 * 
 * @author Daniel Sagenschneider
 */
public class StockGraphWidget extends VerticalPanel {

    /**
     * Initiate.
     * 
     * @param historyPeriod
     *            Time in milliseconds to display history of prices.
     * @param redrawPeriod
     *            Time in milliseconds to allow before complete redraw of the
     *            graph.
     * @param width
     *            Width.
     * @param height
     *            Height.
     * @param stocks
     *            {@link Stock} instances to graph.
     */
    public StockGraphWidget(final long historyPeriod, final long redrawPeriod, final String width,
            final String height, Stock... stocks) {

        // Create sorted copy of stocks
        final Stock[] orderedStocks = new Stock[stocks.length];
        for (int i = 0; i < orderedStocks.length; i++) {
            orderedStocks[i] = stocks[i];
        }
        Arrays.sort(orderedStocks, new Comparator<Stock>() {
            @Override
            public int compare(Stock a, Stock b) {
                return String.CASE_INSENSITIVE_ORDER.compare(a.getName(), b.getName());
            }
        });

        // Load the chart visualisation
        Runnable loadGraph = new Runnable() {
            @Override
            public void run() {

                // Provide tab panel for graphs
                TabPanel tab = new TabPanel();
                tab.setWidth(width);
                tab.getTabBar().setWidth(width);
                tab.getDeckPanel().setWidth(width);
                StockGraphWidget.this.add(tab);

                // Load each graph onto a tab
                final StockGraph[] graphs = new StockGraph[orderedStocks.length];
                for (int i = 0; i < orderedStocks.length; i++) {
                    Stock stock = orderedStocks[i];

                    // Create the stock graph
                    StockGraph graph = new StockGraph(stock, historyPeriod, redrawPeriod, width, height);
                    graphs[i] = graph;

                    // Add the stock graph
                    tab.add(graph.getChart(), stock.getName());
                }

                // Provide the selection handler
                tab.addSelectionHandler(new SelectionHandler<Integer>() {
                    @Override
                    public void onSelection(SelectionEvent<Integer> event) {

                        // Unselect all graphs
                        for (StockGraph graph : graphs) {
                            graph.setSelected(false);
                        }

                        // Select the graph for the tab
                        Integer tabIndex = event.getSelectedItem();
                        graphs[tabIndex.intValue()].setSelected(true);
                    }
                });

                // Display first tab selected (also sets up graph)
                tab.selectTab(0);
            }
        };
        VisualizationUtils.loadVisualizationApi(loadGraph, AnnotatedTimeLine.PACKAGE);
    }

    /**
     * Graph of a {@link Stock}.
     */
    private static class StockGraph {

        /**
         * {@link Stock} being graphed.
         */
        private final Stock stock;

        /**
         * Time in milliseconds to display history of prices.
         */
        private final long historyPeriod;

        /**
         * Time in milliseconds to allow before complete redraw of the graph.
         */
        private final long redrawPeriod;

        /**
         * {@link DataTable}.
         */
        private final DataTable data;

        /**
         * {@link Options}.
         */
        private final Options options;

        /**
         * {@link AnnotatedTimeLine}.
         */
        private final AnnotatedTimeLine chart;

        /**
         * Flag indicating if selected for display.
         */
        private boolean isSelected = false;

        /**
         * Initiate.
         * 
         * @param stock
         *            {@link Stock} to graph.
         * @param historyPeriod
         *            Time in milliseconds to display history of prices.
         * @param redrawPeriod
         *            Time in milliseconds to allow before complete redraw of
         *            the graph.
         * @param width
         *            Width.
         * @param height
         *            Height.
         */
        public StockGraph(Stock stock, long historyPeriod, long redrawPeriod, String width, String height) {
            this.stock = stock;
            this.historyPeriod = historyPeriod;
            this.redrawPeriod = redrawPeriod;

            // Initiate the data for the stocks
            this.data = DataTable.create();
            this.data.addColumn(ColumnType.DATETIME);
            this.data.addColumn(ColumnType.NUMBER, this.stock.getName());

            // Configure the chart
            this.options = Options.create();
            this.options.setDisplayAnnotations(false);
            this.options.setDisplayZoomButtons(false);
            this.options.setOption("dateFormat", "d MMM yyyy");
            this.options.setOption("displayRangeSelector", false);
            this.options.setOption("displayLegendDots", false);
            this.options.setScaleType(ScaleType.MAXIMIZE);
            this.options.setOption("allowRedraw", true);

            // Add the chart
            this.chart = new AnnotatedTimeLine(this.data, this.options, width, height);

            // Listen for stock price events
            OfficeFloorComet.subscribe(StockPriceEvents.class, new StockPriceEvents() {
                @Override
                public void event(StockPrice stockPrice, Stock stock) {
                    StockGraph.this.addPrice(stockPrice.getTimestamp(), stockPrice);
                }
            }, new StockFilter(this.stock));
        }

        /**
         * Flags whether selected.
         * 
         * @param isSelected
         *            Indicates if selected.
         */
        public void setSelected(boolean isSelected) {
            this.isSelected = isSelected;

            // If selected, draw the graph fresh
            if (this.isSelected) {
                this.options.setOption("allowRedraw", false);
                this.chart.draw(this.data, this.options);
            }
        }

        /**
         * Obtains the {@link AnnotatedTimeLine}.
         * 
         * @return {@link AnnotatedTimeLine}.
         */
        public AnnotatedTimeLine getChart() {
            return this.chart;
        }

        /**
         * Adds a price to the display.
         * 
         * @param priceTimestamp
         *            {@link StockPrice} time stamp.
         * @param price
         *            {@link StockPrice}. May be <code>null</code> if no price
         *            to allow moving graph to time stamp.
         */
        private void addPrice(long priceTimestamp, StockPrice price) {

            // Only add price when chart available
            if (this.chart == null) {
                return;
            }

            // By default allow to redraw graph (for dynamic values)
            boolean isAllowRedraw = true;

            // Only truncate if have rows (no rows on first price)
            if (this.data.getNumberOfRows() > 0) {

                // Determine the start time
                long startTime = priceTimestamp - this.historyPeriod;

                // Determine redraw hard time
                long redrawTime = startTime - this.redrawPeriod;

                // Determine if need to truncate display
                Date oldestPriceTimestamp = this.data.getValueDate(0, 0);
                if (oldestPriceTimestamp.getTime() < redrawTime) {

                    // Determine number of rows before period of time
                    double firstPrice = -1.00;
                    int numberOfRowsToRemove = 0;
                    NO_FURTHER_ROWS: for (int i = 0; i < this.data.getNumberOfRows(); i++) {
                        Date rowTimestamp = this.data.getValueDate(i, 0);
                        if (rowTimestamp.getTime() < startTime) {

                            // Flag to remove this row
                            numberOfRowsToRemove++;

                            // Keep track of first price for graph
                            if (!(this.data.isValueNull(i, 1))) {
                                firstPrice = this.data.getValueDouble(i, 1);
                            }

                        } else {
                            break NO_FURTHER_ROWS;
                        }
                    }

                    // Remove rows before period of time
                    if (numberOfRowsToRemove > 0) {

                        // Remove the rows
                        this.data.removeRows(0, numberOfRowsToRemove);

                        // Have clean draw of the graph
                        isAllowRedraw = false;

                        // Ensure has a row for continuous graph appearance
                        if (firstPrice >= 0) {
                            if (this.data.getNumberOfRows() == 0) {
                                this.data.addRow(); // ensure row
                                this.data.setValue(0, 0, new Date(priceTimestamp));
                            }
                            this.data.setValue(0, 1, firstPrice);
                        }
                    }
                }
            }

            // Specify whether dynamically display added price
            this.options.setOption("allowRedraw", isAllowRedraw);

            // Add the price for the stock
            int rowIndex = this.data.addRow();
            this.data.setValue(rowIndex, 0, new Date(priceTimestamp));
            if (price != null) {
                this.data.setValue(rowIndex, 1, price.getAskPrice());
            }

            // Only draw if selected
            if (this.isSelected) {
                // Draw graph for added price
                this.chart.draw(this.data, this.options);
            }
        }
    }

    /**
     * Filter on the {@link Stock}.
     */
    public static class StockFilter implements IsSerializable {

        /**
         * Market identifier for the {@link Stock}.
         */
        private String marketId;

        /**
         * Initiate.
         * 
         * @param stock
         *            {@link Stock} of interest.
         */
        public StockFilter(Stock stock) {
            this.marketId = stock.getMarketId();
        }

        /**
         * Initiate for {@link IsSerializable}.
         */
        public StockFilter() {
        }

        /*
         * ======================== Object =======================
         */

        @Override
        public boolean equals(Object obj) {

            // Ensure match on stock
            if (!(obj instanceof Stock)) {
                return false;
            }
            Stock that = (Stock) obj;

            // Include if matching stock
            return this.marketId.equals(that.getMarketId());
        }
    }

}