Page 1 of 1

finance.yahoo and finance.google charting

Posted: Thu May 29, 2008 2:03 am
by jim_athome
Hello,

Both of these sites have nice charting features of "animation" when the mouse moves over the chart itself. Can jfreechart support this type of implementation/feature?

An example is here. Move the mouse over chart itself and values are updated as the mouse moves.

finance.google.com/finance?q=msft

Thanks,
Jim

Posted: Thu May 29, 2008 3:44 pm
by RoyW
As ChartPanel is a swing component you should be able to add almost any interactivity. JFreeChart does not provide this out of the box but it is possible. The best way is to extend ChartPanel and add your own animation. The example below just adds the mouse move animation (it is very rough, doesn't extend ChartPanel but should show what is possible). It is also possible to implement the mouse drag and animated zooming but thats a lot of code.

Code: Select all

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.SegmentedTimeline;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.labels.HighLowItemLabelGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.AbstractXYDataset;
import org.jfree.data.xy.DefaultOHLCDataset;
import org.jfree.data.xy.OHLCDataItem;
import org.jfree.data.xy.XYDataset;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.StringTokenizer;

public class MouseMoveDemo extends JFrame {
    JLabel descriptionLabel = new JLabel(" ");
    public MouseMoveDemo(String symbol) {
        super("MouseMoveDemo");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        DateAxis domainAxis       = new DateAxis("Date");
        NumberAxis rangeAxis        = new NumberAxis("Price");
        XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
        XYDataset dataset          = getDataSet(symbol);

        XYPlot mainPlot = new XYPlot(dataset, domainAxis, rangeAxis, renderer);

        renderer.setSeriesPaint(0, Color.BLACK);
        rangeAxis.setAutoRangeIncludesZero(false);
        domainAxis.setTimeline( SegmentedTimeline.newMondayThroughFridayTimeline() );

        JFreeChart chart = new JFreeChart(symbol, null, mainPlot, false);
        ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new Dimension(600, 300));

        this.add(chartPanel);
        this.add(descriptionLabel, BorderLayout.NORTH);
        this.pack();

        addListener(chartPanel);
    }

    protected AbstractXYDataset getDataSet(String symbol) {
        DefaultOHLCDataset result = null;
        OHLCDataItem[] data;
        data = getData(symbol);
        return new DefaultOHLCDataset(symbol, data);
    }
    //This method uses yahoo finance to get the OHLC data
    protected OHLCDataItem[] getData(String symbol) {
        java.util.List<OHLCDataItem> dataItems = new ArrayList<OHLCDataItem>();
        try {
            String strUrl= "http://ichart.finance.yahoo.com/table.csv?s="+symbol+"&a=0&b=1&c=2008&d=3&e=30&f=2008&ignore=.csv";
            URL url = new URL(strUrl);
            BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
            DateFormat df = new SimpleDateFormat("y-M-d");

            String inputLine;
            in.readLine();
            while ((inputLine = in.readLine()) != null) {
                StringTokenizer st = new StringTokenizer(inputLine, ",");

                Date date       = df.parse( st.nextToken() );
                double open     = Double.parseDouble( st.nextToken() );
                double high     = Double.parseDouble( st.nextToken() );
                double low      = Double.parseDouble( st.nextToken() );
                double close    = Double.parseDouble( st.nextToken() );
                double volume   = Double.parseDouble( st.nextToken() );
                double adjClose = Double.parseDouble( st.nextToken() );

                OHLCDataItem item = new OHLCDataItem(date, open, high, low, close, volume);
                dataItems.add(item);
            }
            in.close();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        Collections.reverse(dataItems);
        return dataItems.toArray(new OHLCDataItem[dataItems.size()]);
    }

    ////////////////////////////////////////////
    //This is the code for the animation. It is very rough, for an idea only
    //It does not take in to account chart orientation or scaling. (or repaint on zoom)
    /////////////////////////////////////////////
    protected ChartEntity prev = null;
    protected void addListener(ChartPanel chartPanel){
        chartPanel.addMouseMotionListener(new MouseMotionListener() {
            public void mouseDragged(MouseEvent e) {
            }

            public void mouseMoved(MouseEvent e) {
                ChartPanel chartPanel = (ChartPanel) e.getSource();
                Insets insets = chartPanel.getInsets();
                int x = (int) ((e.getX() - insets.left) / chartPanel.getScaleX());
                int y = (int) ((e.getY() - insets.top) / chartPanel.getScaleY());

                if (chartPanel.getChartRenderingInfo() != null) {
                    EntityCollection entities = chartPanel.getChartRenderingInfo().getEntityCollection();
                    if (entities != null) {
                        int entityCount = entities.getEntityCount();
                        for (int i = entityCount - 1; i >= 0; i--) {
                            ChartEntity entity = (ChartEntity) entities.getEntity(i);
                            if( !(entity instanceof XYItemEntity))
                                continue;

                            Rectangle r = entity.getArea().getBounds();

                            //Check to see if the x-value intersects an entity
                            if (r.getMinX() < x && x < r.getMaxX()) {
                                //Yes it intersects, make sure it is not the previously found entity
                                if(prev == null || prev != entity){
                                    Graphics2D g2 = (Graphics2D) chartPanel.getGraphics();
                                    g2.setXORMode(Color.cyan);
                                    if(prev != null)
                                        g2.fill(prev.getArea());//Erase previous marker
                                    g2.fill(entity.getArea());  //Draw new marker
                                    g2.setPaintMode();
                                    prev = entity;

                                    //This is where you extract the data from the entity and display it.
                                    displayData((XYItemEntity) entity);
                                    return;
                                }
                            }
                        }

                    }
                }
            }
        });
    }

    protected void displayData(XYItemEntity entity){
        DefaultOHLCDataset dataset = (DefaultOHLCDataset) entity.getDataset();
        int series = entity.getSeriesIndex();
        int item   = entity.getItem();

        HighLowItemLabelGenerator labelGenerator = new HighLowItemLabelGenerator();
        String description = labelGenerator.generateToolTip(dataset, series, item);
        descriptionLabel.setText(description);
    }


    public static void main(String[] args) {
        new MouseMoveDemo("MSFT").setVisible(true);
    }
}


Maximize not work.

Posted: Fri Jul 18, 2008 4:32 am
by ck
When window maximize, the mark point is not correctly showed. Any idea how to fix?

Posted: Fri Jul 18, 2008 1:55 pm
by RoyW
RoyW wrote: ////////////////////////////////////////////
//This is the code for the animation. It is very rough, for an idea only
//It does not take in to account chart orientation or scaling. (or repaint on zoom)
////////////////////////////////////////////
The key thing to note is this was for an idea only.
The ideal way to do this is extend ChartPanel then you can have more control because you will have to deal with not only resizing but repaints (due to the window being resized or the chart being zoomed or a right click menu popping up).

The way I did all the interactions for my chart project was to create my own InteractiveChartPanel that extended JComponent. That way I could have complete control over the drawing of the chart and the interaction with it.

Posted: Fri Jul 18, 2008 1:59 pm
by RoyW
This demo shows how you have to use an AffineTransform to take in to acount the scaling of ChartPanel when the window is resized. In my chart panel I took out the scaling all together.

Code: Select all

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.SegmentedTimeline;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.labels.HighLowItemLabelGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.AbstractXYDataset;
import org.jfree.data.xy.DefaultOHLCDataset;
import org.jfree.data.xy.OHLCDataItem;
import org.jfree.data.xy.XYDataset;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.StringTokenizer;

public class MouseMoveDemo extends JFrame {
    JLabel descriptionLabel = new JLabel(" ");
    public MouseMoveDemo(String symbol) {
        super("MouseMoveDemo");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        DateAxis domainAxis       = new DateAxis("Date");
        NumberAxis rangeAxis        = new NumberAxis("Price");
        XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
        XYDataset dataset          = getDataSet(symbol);

        XYPlot mainPlot = new XYPlot(dataset, domainAxis, rangeAxis, renderer);

        renderer.setSeriesPaint(0, Color.BLACK);
        rangeAxis.setAutoRangeIncludesZero(false);
        domainAxis.setTimeline( SegmentedTimeline.newMondayThroughFridayTimeline() );

        JFreeChart chart = new JFreeChart(symbol, null, mainPlot, false);
        ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new Dimension(600, 300));

        this.add(chartPanel);
        this.add(descriptionLabel, BorderLayout.NORTH);
        this.pack();

        addListener(chartPanel);
    }

    protected AbstractXYDataset getDataSet(String symbol) {
        DefaultOHLCDataset result = null;
        OHLCDataItem[] data;
        data = getData(symbol);
        return new DefaultOHLCDataset(symbol, data);
    }
    //This method uses yahoo finance to get the OHLC data
    protected OHLCDataItem[] getData(String symbol) {
        java.util.List<OHLCDataItem> dataItems = new ArrayList<OHLCDataItem>();
        try {
            String strUrl= "http://ichart.finance.yahoo.com/table.csv?s="+symbol+"&a=0&b=1&c=2008&d=3&e=30&f=2008&ignore=.csv";
            URL url = new URL(strUrl);
            BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
            DateFormat df = new SimpleDateFormat("y-M-d");

            String inputLine;
            in.readLine();
            while ((inputLine = in.readLine()) != null) {
                StringTokenizer st = new StringTokenizer(inputLine, ",");

                Date date       = df.parse( st.nextToken() );
                double open     = Double.parseDouble( st.nextToken() );
                double high     = Double.parseDouble( st.nextToken() );
                double low      = Double.parseDouble( st.nextToken() );
                double close    = Double.parseDouble( st.nextToken() );
                double volume   = Double.parseDouble( st.nextToken() );
                double adjClose = Double.parseDouble( st.nextToken() );

                OHLCDataItem item = new OHLCDataItem(date, open, high, low, close, volume);
                dataItems.add(item);
            }
            in.close();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        Collections.reverse(dataItems);
        return dataItems.toArray(new OHLCDataItem[dataItems.size()]);
    }

    ////////////////////////////////////////////
    //This is the code for the animation. It is very rough, for an idea only
    //It does not take in to account chart orientation or repaints due to scaling/resizing
    /////////////////////////////////////////////
    protected ChartEntity prev = null;
    protected void addListener(ChartPanel chartPanel){
        chartPanel.addMouseMotionListener(new MouseMotionListener() {
            public void mouseDragged(MouseEvent e) {
            }

            public void mouseMoved(MouseEvent e) {
                ChartPanel chartPanel = (ChartPanel) e.getSource();
                Insets insets = chartPanel.getInsets();
                int x = (int) ((e.getX() - insets.left) / chartPanel.getScaleX());
                int y = (int) ((e.getY() - insets.top) / chartPanel.getScaleY());

                if (chartPanel.getChartRenderingInfo() != null) {
                    EntityCollection entities = chartPanel.getChartRenderingInfo().getEntityCollection();
                    if (entities != null) {
                        int entityCount = entities.getEntityCount();
                        for (int i = entityCount - 1; i >= 0; i--) {
                            ChartEntity entity = (ChartEntity) entities.getEntity(i);
                            if( !(entity instanceof XYItemEntity))
                                continue;

                            Rectangle r = entity.getArea().getBounds();

                            //Check to see if the x-value intersects an entity
                            if (r.getMinX() < x && x < r.getMaxX()) {
                                //Yes it intersects, make sure it is not the previously found entity
                                if(prev == null || prev != entity){
                                    Graphics2D g2 = (Graphics2D) chartPanel.getGraphics();
                                    AffineTransform saved = g2.getTransform();
                                    AffineTransform st = AffineTransform.getScaleInstance(chartPanel.getScaleX(), chartPanel.getScaleY());
                                    g2.transform(st);
                                    g2.setXORMode(Color.cyan);
                                    if(prev != null)
                                        g2.fill(prev.getArea());//Erase previous marker
                                    g2.fill(entity.getArea());  //Draw new marker
                                    g2.setPaintMode();
                                    g2.setTransform(saved);
                                    prev = entity;

                                    //This is where you extract the data from the entity and display it.
                                    displayData((XYItemEntity) entity);
                                    return;
                                }
                            }
                        }

                    }
                }
            }
        });
    }

    protected void displayData(XYItemEntity entity){
        DefaultOHLCDataset dataset = (DefaultOHLCDataset) entity.getDataset();
        int series = entity.getSeriesIndex();
        int item   = entity.getItem();

        HighLowItemLabelGenerator labelGenerator = new HighLowItemLabelGenerator();
        String description = labelGenerator.generateToolTip(dataset, series, item);
        descriptionLabel.setText(description);
    }


    public static void main(String[] args) {
        new MouseMoveDemo("MSFT").setVisible(true);
    }
}