/*******************************************************************************
* Copyright (c) 2013 Luigi Sgro. All rights reserved. This
* program and the accompanying materials are made available under the terms of
* the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Luigi Sgro - initial API and implementation
******************************************************************************/
package com.quantcomponents.chart.series;
import java.util.Date;
import java.util.TimeZone;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import com.quantcomponents.chart.ArrayScaleSelector;
import com.quantcomponents.chart.Chart;
import com.quantcomponents.chart.DataRange;
import com.quantcomponents.chart.IChartMetrics;
import com.quantcomponents.chart.IMarkScale;
import com.quantcomponents.chart.series.scale.DayScale;
import com.quantcomponents.chart.series.scale.FiveMinutesScale;
import com.quantcomponents.chart.series.scale.FourHoursScale;
import com.quantcomponents.chart.series.scale.HourScale;
import com.quantcomponents.chart.series.scale.MinuteScale;
import com.quantcomponents.chart.series.scale.MonthScale;
import com.quantcomponents.chart.series.scale.QuarterScale;
import com.quantcomponents.chart.series.scale.TenMinutesScale;
import com.quantcomponents.chart.series.scale.ThirtyMinutesScale;
import com.quantcomponents.chart.series.scale.TwoHoursScale;
import com.quantcomponents.chart.series.scale.TwoMinutesScale;
import com.quantcomponents.chart.series.scale.WeekScale;
import com.quantcomponents.chart.series.scale.YearScale;
import com.quantcomponents.core.calendar.FlatCalendar;
import com.quantcomponents.core.calendar.ITradingCalendar;
import com.quantcomponents.core.model.ISeries;
import com.quantcomponents.core.model.ISeriesPoint;
/**
* SWT component displaying a time series chart
*/
public class TimeSeriesChart extends Composite {
private static final long DEFAULT_POINT_INTERVAL = 5L * 60 * 60 * 1000;
private static final int DEFAULT_MARGIN_LEFT = 10;
private static final int DEFAULT_MARGIN_TOP = 10;
private static final int DEFAULT_MARGIN_RIGHT = 50;
private static final int DEFAULT_MARGIN_BOTTOM = 30;
private static final int AVERAGE_Y_LABEL_CHAR_WIDTH = 12;
private final Chart<Date, Double> chart;
private final TimeRulerX timeRuler;
private final DoubleRulerY priceRuler;
private final DoubleCurrentValueY curValueMark;
private ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>> series;
private ITimeSeriesRenderer timeSeriesRenderer;
private DataRange<Date, Double> seriesRange;
private ITradingCalendar tradingCalendar = new FlatCalendar();
private int marginLeft = DEFAULT_MARGIN_LEFT;
private int marginTop = DEFAULT_MARGIN_TOP;
private int marginRight = DEFAULT_MARGIN_RIGHT;
private int marginBottom = DEFAULT_MARGIN_BOTTOM;
private long pointInterval = DEFAULT_POINT_INTERVAL;
private boolean recalcNeeded;
public TimeSeriesChart(Composite parent, int style) {
super(parent, style);
setLayout(new FillLayout());
chart = new Chart<Date, Double>(this, SWT.NONE) {
@Override
public void updateMetrics(IChartMetrics<Date, Double> metrics) {
metrics.setDataRange(seriesRange);
}};
chart.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_BLACK));
timeRuler = new TimeRulerX();
chart.getDrawables().add(timeRuler);
priceRuler = new DoubleRulerY();
chart.getDrawables().add(priceRuler);
chart.getDrawables().add(null); // placeholder for the time-series renderer
curValueMark = new DoubleCurrentValueY();
chart.getDrawables().add(curValueMark);
}
public synchronized void setSeriesRenderer(ITimeSeriesRenderer timeSeriesRenderer) {
this.timeSeriesRenderer = timeSeriesRenderer;
curValueMark.setCurrentValueProvider(timeSeriesRenderer);
chart.getDrawables().set(2, this.timeSeriesRenderer);
recalcNeeded = true;
}
public synchronized void setTradingCalendar(ITradingCalendar tradingCalendar) {
this.tradingCalendar = tradingCalendar;
timeRuler.setTimeZone(tradingCalendar.getTimeZone());
recalcNeeded = true;
}
public synchronized void setMargins(int marginLeft, int marginTop, int marginRight, int marginBottom) {
this.marginLeft = marginLeft;
this.marginTop = marginTop;
this.marginRight = marginRight;
this.marginBottom = marginBottom;
recalcNeeded = true;
}
public synchronized void setPointInterval(long pointInterval) {
this.pointInterval = pointInterval;
recalcNeeded = true;
}
public synchronized void setSeries(ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>> series) {
this.series = series;
recalcNeeded = true;
}
public synchronized void refresh() {
recalculateIfNeeded();
chart.refresh();
}
public synchronized Control getControl() {
return chart.getControl();
}
private void recalculateIfNeeded() {
if (recalcNeeded) {
if (series == null || series.isEmpty()) {
chart.setMetrics(null);
} else {
TimeChartMetrics metrics = new TimeChartMetrics(tradingCalendar, new ArrayScaleSelector<Date>(buildTimeGranularities(tradingCalendar.getTimeZone())), new DoubleScaleSelector(new double[] { 1.0, 2.0, 5.0 }));
seriesRange = new DataRange<Date, Double>(series.getFirst().getIndex(), series.getLast().getIndex(), series.getMinimum().getBottomValue(), series.getMaximum().getTopValue());
metrics.setDataRange(seriesRange);
int minMarginRight = (curValueMark.integerDigits(seriesRange.getHighY()) + curValueMark.fractionDigits(seriesRange.getLowY(), seriesRange.getHighY()) + 1) * AVERAGE_Y_LABEL_CHAR_WIDTH;
marginRight = Math.max(marginRight, minMarginRight);
metrics.setMargins(marginLeft, marginTop, marginRight, marginBottom);
metrics.setPointInterval(pointInterval);
chart.setMetrics(metrics);
if (timeSeriesRenderer != null) {
timeSeriesRenderer.setSeries(series);
}
}
recalcNeeded = false;
}
}
@SuppressWarnings("unchecked")
private IMarkScale<Date>[] buildTimeGranularities(TimeZone timeZone) {
IMarkScale<Date> MINUTE_G = new MinuteScale(timeZone);
IMarkScale<Date> TWO_MINUTES_G = new TwoMinutesScale(timeZone);
IMarkScale<Date> FIVE_MINUTES_G = new FiveMinutesScale(timeZone);
IMarkScale<Date> TEN_MINUTES_G = new TenMinutesScale(timeZone);
IMarkScale<Date> THIRTY_MINUTES_G = new ThirtyMinutesScale(timeZone);
IMarkScale<Date> HOUR_G = new HourScale(timeZone);
IMarkScale<Date> TWO_HOURS_G = new TwoHoursScale(timeZone);
IMarkScale<Date> FOUR_HOURS_G = new FourHoursScale(timeZone);
IMarkScale<Date> DAY_G = new DayScale(timeZone);
IMarkScale<Date> WEEK_G = new WeekScale(timeZone);
IMarkScale<Date> MONTH_G = new MonthScale(timeZone);
IMarkScale<Date> QUARTER_G = new QuarterScale(timeZone);
IMarkScale<Date> YEAR_G = new YearScale(timeZone);
return new IMarkScale[] { DAY_G, FIVE_MINUTES_G, FOUR_HOURS_G, HOUR_G, MINUTE_G,
MONTH_G, QUARTER_G, TEN_MINUTES_G, THIRTY_MINUTES_G, TWO_HOURS_G, TWO_MINUTES_G, WEEK_G, YEAR_G
};
}
}