I have implemented jstockchart as a plugin to jfreechart.
I have altered their JStockChartGettingStarted by implementing the Yahoo Finance API to grab stock quotes.
I run the following specs:
I also use all packages found in the jstockchart 0.4.3 package.
Now my result looks as follows:
Now what I like so much about this plugin is that the volume and the price are seperated but the two displays are still linked. So if I zoom in one display, the other display also zooms.
I was wondering how I am able to add another display below the current two displays, which also interacts with the other two as I explained above.
I know the used plot is a combinedDomainXYPlot and that I can simply add to it as follows:
if (timeseriesArea.getVolumeWeight() > 0) {
XYPlot volumePlot = createVolumePlot();
combinedDomainXYPlot.add(volumePlot, timeseriesArea
.getVolumeWeight());
}
But how do I add another display?
So what I want to do is add an additional set of axes/an additional panel like visualised below:
So I know how to add additional plots to any panel but not how to add an extra (third) panel to the whole. Where this third panel also uses the same CombinedDomainXYPlot
as the first two panels.
I know the code for adding an additional panel should be in the code below somewhere. But where?
package org.jstockchart.plot;
import java.awt.BasicStroke;
import org.jfree.chart.axis.SegmentedTimeline;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.ValueMarker;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.TimeSeriesCollection;
import org.jstockchart.area.PriceArea;
import org.jstockchart.area.TimeseriesArea;
import org.jstockchart.area.VolumeArea;
import org.jstockchart.axis.TimeseriesDateAxis;
import org.jstockchart.axis.TimeseriesNumberAxis;
import org.jstockchart.axis.logic.CentralValueAxis;
import org.jstockchart.axis.logic.LogicDateAxis;
import org.jstockchart.axis.logic.LogicNumberAxis;
import org.jstockchart.dataset.TimeseriesDataset;
/**
* Creates <code>CombinedDomainXYPlot</code> and <code>XYPlot</code> for the
* timeseries chart.
*
* @author Sha Jiang
*/
public class TimeseriesPlot {
private static final long serialVersionUID = 8799771872991017065L;
private TimeseriesDataset dataset = null;
private SegmentedTimeline timeline = null;
private TimeseriesArea timeseriesArea = null;
/**
* Creates a new <code>TimeseriesPlot</code> instance.
*
* @param dataset
* timeseries data set(<code>null</code> not permitted).
* @param timeline
* a "segmented" timeline.
* @param timeseriesArea
* timeseries area.
*/
public TimeseriesPlot(TimeseriesDataset dataset,
SegmentedTimeline timeline, TimeseriesArea timeseriesArea) {
if (dataset == null) {
throw new IllegalArgumentException("Null 'dataset' argument.");
}
this.dataset = dataset;
this.timeline = timeline;
if (timeseriesArea == null) {
throw new IllegalArgumentException(
"Null 'timeseriesArea' argument.");
}
this.timeseriesArea = timeseriesArea;
}
private CombinedDomainXYPlot createCombinedXYPlot() {
LogicDateAxis logicDateAxis = timeseriesArea.getlogicDateAxis();
TimeseriesDateAxis dateAxis = new TimeseriesDateAxis(logicDateAxis
.getLogicTicks());
if (timeline != null) {
dateAxis.setTimeline(timeline);
}
CombinedDomainXYPlot combinedDomainXYPlot = new CombinedDomainXYPlot(
dateAxis);
combinedDomainXYPlot.setGap(timeseriesArea.getGap());
combinedDomainXYPlot.setOrientation(timeseriesArea.getOrientation());
combinedDomainXYPlot.setDomainAxis(dateAxis);
combinedDomainXYPlot.setDomainAxisLocation(timeseriesArea
.getDateAxisLocation());
combinedDomainXYPlot.setDomainPannable(true);
combinedDomainXYPlot.setRangePannable(true);
if (timeseriesArea.getPriceWeight() <= 0
&& timeseriesArea.getVolumeWeight() <= 0) {
throw new IllegalArgumentException(
"Illegal weight value: priceWeight="
+ timeseriesArea.getPriceWeight()
+ ", volumeWeight="
+ timeseriesArea.getVolumeWeight());
}
if (timeseriesArea.getPriceWeight() > 0) {
XYPlot pricePlot = createPricePlot();
combinedDomainXYPlot
.add(pricePlot, timeseriesArea.getPriceWeight());
}
if (timeseriesArea.getVolumeWeight() > 0) {
XYPlot volumePlot = createVolumePlot();
combinedDomainXYPlot.add(volumePlot, timeseriesArea
.getVolumeWeight());
}
return combinedDomainXYPlot;
}
private XYPlot createPricePlot() {
PriceArea priceArea = timeseriesArea.getPriceArea();
TimeSeriesCollection priceDataset = new TimeSeriesCollection();
priceDataset.addSeries(dataset.getPriceTimeSeries().getTimeSeries());
if (priceArea.isAverageVisible()) {
priceDataset.addSeries(dataset.getAverageTimeSeries()
.getTimeSeries());
}
CentralValueAxis logicPriceAxis = priceArea.getLogicPriceAxis();
TimeseriesNumberAxis priceAxis = new TimeseriesNumberAxis(
logicPriceAxis.getLogicTicks());
XYLineAndShapeRenderer priceRenderer = new XYLineAndShapeRenderer(true,
false);
priceAxis.setUpperBound(logicPriceAxis.getUpperBound());
priceAxis.setLowerBound(logicPriceAxis.getLowerBound());
priceRenderer.setSeriesPaint(0, priceArea.getPriceColor());
priceRenderer.setSeriesPaint(1, priceArea.getAverageColor());
TimeseriesNumberAxis rateAxis = new TimeseriesNumberAxis(logicPriceAxis
.getRatelogicTicks());
rateAxis.setUpperBound(logicPriceAxis.getUpperBound());
rateAxis.setLowerBound(logicPriceAxis.getLowerBound());
XYPlot plot = new XYPlot(priceDataset, null, priceAxis, priceRenderer);
plot.setBackgroundPaint(priceArea.getBackgroudColor());
plot.setOrientation(priceArea.getOrientation());
plot.setRangeAxisLocation(priceArea.getPriceAxisLocation());
if (priceArea.isRateVisible()) {
plot.setRangeAxis(1, rateAxis);
plot.setRangeAxisLocation(1, priceArea.getRateAxisLocation());
plot.setDataset(1, null);
plot.mapDatasetToRangeAxis(1, 1);
}
if (priceArea.isMarkCentralValue()) {
Number centralPrice = logicPriceAxis.getCentralValue();
if (centralPrice != null) {
plot.addRangeMarker(new ValueMarker(centralPrice.doubleValue(),
priceArea.getCentralPriceColor(), new BasicStroke()));
}
}
return plot;
}
private XYPlot createVolumePlot() {
VolumeArea volumeArea = timeseriesArea.getVolumeArea();
LogicNumberAxis logicVolumeAxis = volumeArea.getLogicVolumeAxis();
TimeseriesNumberAxis volumeAxis = new TimeseriesNumberAxis(
logicVolumeAxis.getLogicTicks());
volumeAxis.setUpperBound(logicVolumeAxis.getUpperBound());
volumeAxis.setLowerBound(logicVolumeAxis.getLowerBound());
volumeAxis.setAutoRangeIncludesZero(false);
XYBarRenderer volumeRenderer = new XYBarRenderer();
volumeRenderer.setSeriesPaint(0, volumeArea.getVolumeColor());
volumeRenderer.setShadowVisible(false);
XYPlot plot = new XYPlot(new TimeSeriesCollection(dataset
.getVolumeTimeSeries()), null, volumeAxis, volumeRenderer);
plot.setBackgroundPaint(volumeArea.getBackgroudColor());
plot.setOrientation(volumeArea.getOrientation());
plot.setRangeAxisLocation(volumeArea.getVolumeAxisLocation());
return plot;
}
public CombinedDomainXYPlot getTimeseriesPlot() {
return createCombinedXYPlot();
}
public TimeseriesDataset getDataset() {
return dataset;
}
public void setDataset(TimeseriesDataset dataset) {
if (dataset == null) {
throw new IllegalArgumentException("Null 'dataset' argument.");
}
this.dataset = dataset;
}
public SegmentedTimeline getTimeline() {
return timeline;
}
public void setTimeline(SegmentedTimeline timeline) {
this.timeline = timeline;
}
public TimeseriesArea getTimeseriesArea() {
return timeseriesArea;
}
public void setTimeseriesArea(TimeseriesArea timeseriesArea) {
if (timeseriesArea == null) {
throw new IllegalArgumentException(
"Null 'timeseriesArea' argument.");
}
this.timeseriesArea = timeseriesArea;
}
}
The code for the frontpanel display is as follows:
package gui;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import javax.swing.JFrame;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.SegmentedTimeline;
import org.jfree.data.Range;
import org.jfree.data.time.Minute;
import org.jfree.data.xy.OHLCDataItem;
import org.jstockchart.JStockChartFactory;
import org.jstockchart.area.PriceArea;
import org.jstockchart.area.TimeseriesArea;
import org.jstockchart.area.VolumeArea;
import org.jstockchart.axis.TickAlignment;
import org.jstockchart.axis.logic.CentralValueAxis;
import org.jstockchart.axis.logic.LogicDateAxis;
import org.jstockchart.axis.logic.LogicNumberAxis;
import org.jstockchart.dataset.TimeseriesDataset;
import org.jstockchart.model.TimeseriesItem;
import org.jstockchart.util.DateUtils;
/**
* Demo application for JStockChart timeseries.
*
* @author Sha Jiang
*/
public class TimeseriesChartDemo {
public static int period = 400;
public static void main(String[] args) throws IOException {
String imageDir = "./images";
File images = new File(imageDir);
if (!images.exists()) {
images.mkdir();
}
String imageFile = imageDir + "/jstockchart-timeseries.png";
Date startTime = DateUtils.createDate(2008, 1, 1, 9, 30, 0);
Date endTime = DateUtils.createDate(2008, 1, 1, 15, 0, 0);
// 'data' is a list of TimeseriesItem instances.
List<TimeseriesItem> data = getData("AAPL", period, "d");
// the 'timeline' indicates the segmented time range '00:00-11:30, 13:00-24:00'.
SegmentedTimeline timeline = new SegmentedTimeline(
SegmentedTimeline.DAY_SEGMENT_SIZE, 1351, 89);
timeline.setStartTime(SegmentedTimeline.firstMondayAfter1900() + 780
* SegmentedTimeline.DAY_SEGMENT_SIZE);
// Creates timeseries data set.
TimeseriesDataset dataset = new TimeseriesDataset(Minute.class, 1,
timeline, true);
dataset.addDataItems(data);
DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(Locale.US);
otherSymbols.setDecimalSeparator('.');
otherSymbols.setGroupingSeparator(',');
DecimalFormat df = new DecimalFormat(".##", otherSymbols);
// Creates logic price axis.
CentralValueAxis logicPriceAxis = new CentralValueAxis(
dataset.getPriceTimeSeries().getTimeSeries().getValue(data.size()-1).doubleValue(), new Range(
dataset.getMinPrice().doubleValue(), dataset
.getMaxPrice().doubleValue()), 9,
df);
PriceArea priceArea = new PriceArea(logicPriceAxis);
// Creates logic volume axis.
LogicNumberAxis logicVolumeAxis = new LogicNumberAxis(new Range(dataset
.getMinVolume().doubleValue(), dataset.getMaxVolume()
.doubleValue()), 5, new DecimalFormat("0"));
VolumeArea volumeArea = new VolumeArea(logicVolumeAxis);
TimeseriesArea timeseriesArea = new TimeseriesArea(priceArea,
volumeArea, createlogicDateAxis(DateUtils
.createDate(2008, 1, 1)));
JFreeChart jfreechart = JStockChartFactory.createTimeseriesChart(
"Stock chart test with two seperate displays", dataset, timeline, timeseriesArea,
false);
JFrame outside = new JFrame();
ChartPanel chartPanel = new ChartPanel(jfreechart, false);
chartPanel.setMouseWheelEnabled(true);
outside.add(chartPanel);
outside.setVisible(true);
ChartUtilities
.saveChartAsPNG(new File(imageFile), jfreechart, 545, 300);
}
// Specifies date axis ticks.
private static LogicDateAxis createlogicDateAxis(Date baseDate) {
LogicDateAxis logicDateAxis = new LogicDateAxis(baseDate,
new SimpleDateFormat("HH:mm"));
logicDateAxis.addDateTick("09:30", TickAlignment.START);
logicDateAxis.addDateTick("10:00");
logicDateAxis.addDateTick("10:30");
logicDateAxis.addDateTick("11:00");
logicDateAxis.addDateTick("11:30", TickAlignment.END);
logicDateAxis.addDateTick("13:00", TickAlignment.START);
logicDateAxis.addDateTick("13:30");
logicDateAxis.addDateTick("14:00");
logicDateAxis.addDateTick("14:30", TickAlignment.END);
logicDateAxis.addDateTick("15:00", TickAlignment.END);
return logicDateAxis;
}
static List<TimeseriesItem> dataItems;
static boolean TodayAdded = true;
static ArrayList<Double> prices;
static ArrayList<Date> dates;
static List<TimeseriesItem> getData(String stockSymbol, int periodToLoad, String periodUnit) {
TodayAdded = true;
dataItems = new ArrayList<TimeseriesItem>();
Date today = new Date();
today = addDays(today, 1);
Date beginDate = addDays(today, -periodToLoad);
GregorianCalendar BEGIN = (GregorianCalendar) DateToCalendar(beginDate);
GregorianCalendar END = (GregorianCalendar) DateToCalendar(today);
String QUOTE = constructURL(stockSymbol, BEGIN, END, periodUnit);
try {
String strUrl = QUOTE;
URL url = new URL(strUrl);
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
DateFormat df = new SimpleDateFormat("y-M-d");
dates = new ArrayList<Date>();
prices = new ArrayList<Double>();
String inputLine;
in.readLine();
int counter = 0;
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() );
double price = close;
dataItems.add(new TimeseriesItem(date, close, volume));
System.out.println(close);
dates.add(date);
prices.add(close);
}
in.close();
}
catch (Exception e) {
e.printStackTrace();
}
//Reversal of dates
Collections.reverse(dates);
Collections.reverse(prices);
//Data from Yahoo is from newest to oldest. Reverse so it is oldest to newest
return dataItems;
}
public static Date addDays(Date date, int days)
{
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DATE, days); //minus number would decrement the days
return cal.getTime();
}
public static String constructURL(String symbol, Calendar start, Calendar end, String periodUnit) {
return "http://ichart.finance.yahoo.com/table.csv" +
"?s=" +
symbol +
"&a=" +
Integer.toString(start.get(Calendar.MONTH)) +
"&b=" +
start.get(Calendar.DAY_OF_MONTH) +
"&c=" +
Integer.toString(start.get(Calendar.YEAR)) +
"&d=" +
Integer.toString(end.get(Calendar.MONTH)) +
"&e=" +
Integer.toString(end.get(Calendar.DAY_OF_MONTH)) +
"&f=" +
Integer.toString(end.get(Calendar.YEAR)) +
"&g=" +
periodUnit +
"&ignore=.csv";
}
public static Calendar DateToCalendar(Date date){
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
}
}
Hope someone can help me out. Thanks in advance.
trashgod was right again. He truly is the god of jfreechart.
I found out that the CombinedDomainXYPlot
in the jstockchart
package is built from a package called org.jstockchart.axis.logic
which ensures every new plot gets its own frame.
Or in other words, the package behaves as follows:
CombinedDomainXYPlot
In coding terms, this means that the following statement:
if (timeseriesArea.getPriceWeight() > 0) {
XYPlot pricePlot = createPricePlot();
combinedDomainXYPlot
.add(pricePlot, timeseriesArea.getPriceWeight());
}
if (timeseriesArea.getVolumeWeight() > 0) {
XYPlot volumePlot = createVolumePlot();
combinedDomainXYPlot.add(volumePlot, timeseriesArea
.getVolumeWeight());
}
if (timeseriesArea.getPriceWeight() > 0) {
XYPlot pricePlot2 = createPricePlot();
combinedDomainXYPlot
.add(pricePlot2, timeseriesArea.getPriceWeight());
}
Creates the following output:
Which is exactly what I wanted to achieve (or wanted to show).
So thank you very much trashgod, I didn't know the jstockchart
package worked this way.