/* * 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 an histogram area chart. * * @since 1.0 */ public class HistogramAreaChart implements IChartObject, ISummaryBarDecorator, IAdaptable { private IDataSeries dataSeries; private OHLCField field; private IAdaptable[] values; private List<Polygon> pointArray = new ArrayList<Polygon>(2048); private boolean valid; private boolean focus; private RGB color = new RGB(0, 0, 0); private RGB fillColor; private SummaryDateItem dateItem; private SummaryOHLCItem ohlcItem; private SummaryNumberItem numberItem; private DateFormat dateFormat = DateFormat.getDateInstance(); private NumberFormat numberFormat = NumberFormat.getInstance(); public HistogramAreaChart(IDataSeries dataSeries, RGB color) { this(dataSeries, OHLCField.Close, color); } public HistogramAreaChart(IDataSeries dataSeries, OHLCField field, RGB color) { this.dataSeries = dataSeries; this.field = field; if (color != null) { 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.valid = false; } /* (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 && values != null) { int zero = graphics.mapToVerticalAxis(0.0); double[] inReal = Util.getValuesForField(values, field); for (int i = 0; i < values.length - 1; i++) { Date date1 = (Date) values[i].getAdapter(Date.class); Date date2 = (Date) values[i + 1].getAdapter(Date.class); int x1 = graphics.mapToHorizontalAxis(date1); int y1 = graphics.mapToVerticalAxis(inReal[i]); int x2 = graphics.mapToHorizontalAxis(date2); int y2 = graphics.mapToVerticalAxis(inReal[i + 1]); Polygon candle; if (i < pointArray.size()) { candle = pointArray.get(i); } else { candle = new Polygon(); pointArray.add(candle); } candle.setBounds(zero, x1, y1, x2, y2); candle.setValue(values[i]); } while (pointArray.size() > values.length - 1) { pointArray.remove(pointArray.size() - 1); } } if (fillColor == null) { fillColor = Util.blend(color, graphics.getBackgroundColor(), 25); } graphics.pushState(); graphics.setLineWidth(hasFocus() ? 2 : 1); for (Polygon c : pointArray) { c.paint(graphics); } 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) { for (Polygon c : pointArray) { if (c.containsPoint(x, y)) { return true; } } 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); if (ohlc != null) { 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() + ": " + numberFormat.format(dataSeries.getLast().getAdapter(Number.class)); //$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) { for (Polygon c : pointArray) { if (c.containsPoint(x, y)) { return c.getToolTip(); } } return null; } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#handleFocusGained(org.eclipsetrader.ui.charts.ChartObjectFocusEvent) */ @Override public void handleFocusGained(ChartObjectFocusEvent event) { focus = true; } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.IChartObject#handleFocusLost(org.eclipsetrader.ui.charts.ChartObjectFocusEvent) */ @Override public void handleFocusLost(ChartObjectFocusEvent event) { focus = false; } protected boolean hasFocus() { return focus; } /* (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(); if (values.length == 0) { return; } IOHLC ohlc = (IOHLC) (values.length > 0 ? values[values.length - 1].getAdapter(IOHLC.class) : null); if (ohlc != 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); } else { Number value = (Number) (values.length > 0 ? values[values.length - 1].getAdapter(Number.class) : null); numberItem = new SummaryNumberItem(parent, SWT.NONE); numberItem.setValue(dataSeries.getName() + ": ", value); //$NON-NLS-1$ if (color != null) { numberItem.setForeground(color); } } } /* (non-Javadoc) * @see org.eclipsetrader.ui.charts.ISummaryBarDecorator#updateDecorator(int, int) */ @Override public void updateDecorator(int x, int y) { if (pointArray != null) { for (int i = 0; i < pointArray.size(); i++) { if (pointArray.get(i).containsPoint(x, y)) { if (numberItem != null) { Number value = (Number) values[i].getAdapter(Number.class); if (value != null) { numberItem.setValue(dataSeries.getName() + ": ", value); //$NON-NLS-1$ } } if (dateItem != null && ohlcItem != null) { IOHLC ohlc = (IOHLC) values[i].getAdapter(IOHLC.class); IOHLC previousOhlc = (IOHLC) (i >= 1 ? values[i - 1].getAdapter(IOHLC.class) : null); dateItem.setDate(ohlc.getDate()); ohlcItem.setOHLC(ohlc, previousOhlc); } } } } } public class Polygon { int x1; int x2; int y1; int y2; int yZero; int[] polygon = new int[8]; Point[] points = new Point[5]; IAdaptable value; public Polygon() { } public void setBounds(int yZero, int x1, int y1, int x2, int y2) { this.yZero = yZero; this.x1 = x1; this.x2 = x2; this.y1 = y1; this.y2 = y2; points[0] = new Point(x1, yZero); points[1] = new Point(x1, y1); points[2] = new Point(x2, y2); points[3] = new Point(x2, yZero); points[4] = points[0]; polygon[0] = x1; polygon[1] = yZero; polygon[2] = x1; polygon[3] = y1; polygon[4] = x2; polygon[5] = y2; polygon[6] = x2; polygon[7] = yZero; } public void setValue(IAdaptable value) { this.value = value; } public void paint(IGraphics graphics) { graphics.setBackgroundColor(fillColor); graphics.fillPolygon(polygon); graphics.setForegroundColor(color); graphics.drawLine(x1, y1, x2, y2); graphics.drawLine(x1, yZero, x2, yZero); } public boolean containsPoint(int x, int y) { if (y == SWT.DEFAULT) { return x >= x1 && x < x2; } int crossings = 0; for (int i = 0; i < points.length - 1; i++) { int div = points[i + 1].y - points[i].y; if (div == 0) { div = 1; } double slope = (points[i + 1].x - points[i].x) / div; boolean cond1 = points[i].y <= y && y < points[i + 1].y; boolean cond2 = points[i + 1].y <= y && y < points[i].y; boolean cond3 = x < slope * (y - points[i].y) + points[i].x; if ((cond1 || cond2) && cond3) { crossings++; } } return crossings % 2 != 0; } public String getToolTip() { IOHLC ohlc = (IOHLC) value.getAdapter(IOHLC.class); if (ohlc != null) { 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$ } return dataSeries.getName() + ": " + numberFormat.format(value.getAdapter(Number.class)); //$NON-NLS-1$ } } }