/* * 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.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.RGB; import org.eclipse.swt.widgets.Composite; import org.eclipsetrader.core.charts.IDataSeries; /** * Draw an historgram bar chart. * * @since 1.0 */ public class HistogramBarChart implements IChartObject, ISummaryBarDecorator, IAdaptable { private IDataSeries dataSeries; private int width = 5; private RGB positiveColor = new RGB(0, 254, 0); private RGB negativeColor = new RGB(254, 0, 0); private IAdaptable[] values; private List<Bar> pointArray = new ArrayList<Bar>(2048); private boolean valid; private boolean hasFocus; private SummaryNumberItem numberItem; private NumberFormat numberFormat = NumberFormat.getInstance(); public HistogramBarChart(IDataSeries dataSeries) { this.dataSeries = dataSeries; numberFormat.setGroupingUsed(true); numberFormat.setMinimumIntegerDigits(1); numberFormat.setMinimumFractionDigits(0); numberFormat.setMaximumFractionDigits(4); } /* (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 - 1; 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 || pointArray == null) && values != null) { int zero = graphics.mapToVerticalAxis(0.0); RGB positiveOutlineColor = blend(positiveColor, graphics.getForegroundColor(), 75); RGB negativeOutlineColor = blend(negativeColor, graphics.getForegroundColor(), 75); for (int i = 0; i < values.length; i++) { Date date = (Date) values[i].getAdapter(Date.class); Number value = (Number) values[i].getAdapter(Number.class); Number previousValue = i > 0 ? (Number) values[i - 1].getAdapter(Number.class) : null; int x = graphics.mapToHorizontalAxis(date) - width / 2; int y = graphics.mapToVerticalAxis(value); Bar bar; if (i < pointArray.size()) { bar = pointArray.get(i); } else { bar = new Bar(); pointArray.add(bar); } bar.setBounds(x, y, Math.abs(y - zero)); bar.setValue(value); if (previousValue != null) { bar.setColor(previousValue.doubleValue() > value.doubleValue() ? negativeColor : positiveColor); bar.setOutlineColor(previousValue.doubleValue() > value.doubleValue() ? negativeOutlineColor : positiveOutlineColor); } else { bar.setColor(positiveColor); bar.setOutlineColor(positiveOutlineColor); } } while (pointArray.size() > values.length) { pointArray.remove(pointArray.size() - 1); } valid = true; } graphics.pushState(); graphics.setLineWidth(hasFocus() ? 2 : 1); for (Bar 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 (Bar 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) { 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 (Bar 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) { 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#accept(org.eclipsetrader.ui.charts.IChartObjectVisitor) */ @Override public void accept(IChartObjectVisitor visitor) { visitor.visit(this); } private RGB blend(RGB c1, RGB c2, int ratio) { int r = blend(c1.red, c2.red, ratio); int g = blend(c1.green, c2.green, ratio); int b = blend(c1.blue, c2.blue, ratio); return new RGB(r, g, b); } private int blend(int v1, int v2, int ratio) { return (ratio * v1 + (100 - ratio) * v2) / 100; } /* (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(); 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$ } /* (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)) { Number value = (Number) values[i].getAdapter(Number.class); if (value != null) { numberItem.setValue(dataSeries.getName() + ": ", value); //$NON-NLS-1$ } } } } } private class Bar { int x; int y; int height; Number value; RGB color; RGB outlineColor; public Bar() { } public void setBounds(int x, int y, int height) { this.x = x; this.y = y; this.height = height; if (height < 0) { this.y += height; this.height = -height; } } public void setValue(Number value) { this.value = value; } public void setColor(RGB color) { this.color = color; } public void setOutlineColor(RGB outlineColor) { this.outlineColor = outlineColor; } public void paint(IGraphics graphics) { graphics.setForegroundColor(outlineColor); graphics.setBackgroundColor(color); graphics.fillRectangle(x, y, width, height); graphics.drawRectangle(x, y, width - 1, height - 1); } public boolean containsPoint(int x, int y) { if (y == SWT.DEFAULT) { return x >= this.x && x <= this.x + width; } if (x >= this.x && x <= this.x + width) { return y >= this.y && y <= this.y + height; } return false; } public String getToolTip() { return dataSeries.getName() + ": " + numberFormat.format(value); //$NON-NLS-1$ } } }