/* * Copyright (c) 2004-2011 Marco Maccaferri and others. * 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: * Marco Maccaferri - initial API and implementation */ package org.eclipsetrader.ui.charts; import java.text.DateFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Composite; import org.eclipsetrader.core.charts.IDataSeries; import org.eclipsetrader.core.feed.IOHLC; /** * Draws a line chart. * * @since 1.0 */ public class OHLCLineChart implements IChartObject, ISummaryBarDecorator, IAdaptable { private IDataSeries dataSeries; private LineStyle style; private RGB color; private int width = 5; private IAdaptable[] values; private Point[] pointArray; private boolean valid; private boolean hasFocus; private SummaryDateItem dateItem; private SummaryOHLCItem ohlcItem; private DateFormat dateFormat = DateFormat.getDateInstance(); private NumberFormat numberFormat = NumberFormat.getInstance(); public static enum LineStyle { Solid, Dot, Dash, Invisible } public OHLCLineChart(IDataSeries dataSeries, LineStyle style, RGB color) { this.dataSeries = dataSeries; this.style = style; this.color = color; numberFormat.setGroupingUsed(true); numberFormat.setMinimumIntegerDigits(1); numberFormat.setMinimumFractionDigits(0); numberFormat.setMaximumFractionDigits(4); } public RGB getColor() { return color; } public void setColor(RGB color) { this.color = color; } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#setDataBounds(org.eclipsetrader.ui.charts.DataBounds) */ @Override public void setDataBounds(DataBounds dataBounds) { List<IAdaptable> l = new ArrayList<IAdaptable>(2048); for (IAdaptable value : dataSeries.getValues()) { Date date = (Date) value.getAdapter(Date.class); if ((dataBounds.first == null || !date.before(dataBounds.first)) && (dataBounds.last == null || !date.after(dataBounds.last))) { l.add(value); } } this.values = l.toArray(new IAdaptable[l.size()]); this.width = dataBounds.horizontalSpacing; this.valid = false; } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#handleFocusGained(org.eclipsetrader.ui.charts.ChartObjectFocusEvent) */ @Override public void handleFocusGained(ChartObjectFocusEvent event) { hasFocus = true; } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#handleFocusLost(org.eclipsetrader.ui.charts.ChartObjectFocusEvent) */ @Override public void handleFocusLost(ChartObjectFocusEvent event) { hasFocus = false; } protected boolean hasFocus() { return hasFocus; } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#invalidate() */ @Override public void invalidate() { this.valid = false; } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#paint(org.eclipsetrader.ui.charts.IGraphics) */ @Override public void paint(IGraphics graphics) { if ((!valid || pointArray == null) && values != null && style != LineStyle.Invisible) { pointArray = new Point[values.length]; for (int i = 0; i < values.length; i++) { Date date = (Date) values[i].getAdapter(Date.class); Number value = (Number) values[i].getAdapter(Number.class); pointArray[i] = graphics.mapToPoint(date, value); } valid = true; } if (pointArray != null && style != LineStyle.Invisible) { switch (style) { case Dash: graphics.setLineStyle(SWT.LINE_DASH); break; case Dot: graphics.setLineStyle(SWT.LINE_DOT); break; default: graphics.setLineStyle(SWT.LINE_SOLID); break; } graphics.pushState(); graphics.setForegroundColor(color); graphics.setLineWidth(hasFocus() ? 2 : 1); graphics.drawPolyline(pointArray); graphics.popState(); } } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#paintScale(org.eclipsetrader.ui.charts.Graphics) */ @Override public void paintScale(Graphics graphics) { } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#containsPoint(int, int) */ @Override public boolean containsPoint(int x, int y) { if (pointArray != null) { if (y == SWT.DEFAULT) { return true; } return PixelTools.isPointOnLine(x, y, pointArray); } return false; } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#getDataSeries() */ @Override public IDataSeries getDataSeries() { return dataSeries; } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#getToolTip() */ @Override public String getToolTip() { if (dataSeries.getLast() != null) { IOHLC ohlc = (IOHLC) dataSeries.getLast().getAdapter(IOHLC.class); return dateFormat.format(ohlc.getDate()) + " O:" + numberFormat.format(ohlc.getOpen()) + //$NON-NLS-1$ " H:" + numberFormat.format(ohlc.getHigh()) + //$NON-NLS-1$ " L:" + numberFormat.format(ohlc.getLow()) + //$NON-NLS-1$ " C:" + numberFormat.format(ohlc.getHigh()); //$NON-NLS-1$ } return dataSeries.getName(); } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#getToolTip(int, int) */ @Override public String getToolTip(int x, int y) { if (pointArray != null) { if (y == SWT.DEFAULT) { for (int i = 0; i < pointArray.length; i++) { if (x >= pointArray[i].x - width / 2 && x <= pointArray[i].x + width / 2) { return getTooltipAt(i); } } } else { for (int i = 1; i < pointArray.length; i++) { if (PixelTools.isPointOnLine(x, y, pointArray[i - 1].x, pointArray[i - 1].y, pointArray[i].x, pointArray[i].y)) { return getTooltipAt(i - 1); } } } } return null; } String getTooltipAt(int index) { IOHLC ohlc = (IOHLC) values[index].getAdapter(IOHLC.class); return dataSeries.getName() + "\r\nD:" + dateFormat.format(ohlc.getDate()) + //$NON-NLS-1$ "\r\nO:" + numberFormat.format(ohlc.getOpen()) + //$NON-NLS-1$ "\r\nH:" + numberFormat.format(ohlc.getHigh()) + //$NON-NLS-1$ "\r\nL:" + numberFormat.format(ohlc.getLow()) + //$NON-NLS-1$ "\r\nC:" + numberFormat.format(ohlc.getHigh()); //$NON-NLS-1$ } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#accept(org.eclipsetrader.ui.charts.IChartObjectVisitor) */ @Override public void accept(IChartObjectVisitor visitor) { visitor.visit(this); } /* (non-Javadoc) * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) */ @Override @SuppressWarnings("unchecked") public Object getAdapter(Class adapter) { if (adapter.isAssignableFrom(ISummaryBarDecorator.class)) { return this; } return null; } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.ISummaryBarDecorator#createDecorator(org.eclipse.swt.widgets.Composite) */ @Override public void createDecorator(Composite parent) { IAdaptable[] values = dataSeries.getValues(); IOHLC ohlc = (IOHLC) (values.length > 0 ? values[values.length - 1].getAdapter(IOHLC.class) : null); IOHLC previousOhlc = (IOHLC) (values.length > 1 ? values[values.length - 2].getAdapter(IOHLC.class) : null); dateItem = new SummaryDateItem(parent, SWT.DATE); dateItem.setDate(ohlc != null ? ohlc.getDate() : null); ohlcItem = new SummaryOHLCItem(parent, SWT.NONE); ohlcItem.setOHLC(ohlc, previousOhlc); } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.ISummaryBarDecorator#updateDecorator(int, int) */ @Override public void updateDecorator(int x, int y) { if (pointArray != null) { IOHLC ohlc = null; IOHLC previousOhlc = null; if (y == SWT.DEFAULT) { for (int i = 0; i < pointArray.length; i++) { if (x >= pointArray[i].x - width / 2 && x <= pointArray[i].x + width / 2) { ohlc = (IOHLC) values[i].getAdapter(IOHLC.class); if (i > 0) { previousOhlc = (IOHLC) values[i - 1].getAdapter(IOHLC.class); } } } } else { for (int i = 1; i < pointArray.length; i++) { if (PixelTools.isPointOnLine(x, y, pointArray[i - 1].x, pointArray[i - 1].y, pointArray[i].x, pointArray[i].y)) { ohlc = (IOHLC) values[i - 1].getAdapter(IOHLC.class); if (i > 1) { previousOhlc = (IOHLC) values[i - 2].getAdapter(IOHLC.class); } } } } if (ohlc != null) { dateItem.setDate(ohlc.getDate()); ohlcItem.setOHLC(ohlc, previousOhlc); } } } }