/*
* 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.internal.charts.views;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
import org.eclipsetrader.core.charts.DataSeriesSubsetVisitor;
import org.eclipsetrader.core.charts.IDataSeries;
import org.eclipsetrader.core.charts.NumericDataSeries;
import org.eclipsetrader.core.charts.OHLCDataSeries;
import org.eclipsetrader.core.feed.IOHLC;
import org.eclipsetrader.ui.charts.AdaptableWrapper;
import org.eclipsetrader.ui.charts.IBarDecorator;
import org.eclipsetrader.ui.charts.IChartRenderer;
import org.eclipsetrader.ui.charts.IColorRegistry;
import org.eclipsetrader.ui.charts.ILineDecorator;
import org.eclipsetrader.ui.charts.IObjectRenderer;
import org.eclipsetrader.ui.charts.IScaleRenderer;
import org.eclipsetrader.ui.charts.RenderTarget;
import org.eclipsetrader.ui.internal.UIActivator;
public class ChartDocumentRenderer implements IChartRenderer, IScaleRenderer, IColorRegistry {
private NumberFormat nf = NumberFormat.getInstance();
private SimpleDateFormat monthYearFormatter = new SimpleDateFormat("MMM, yyyy"); //$NON-NLS-1$
private SimpleDateFormat monthFormatter = new SimpleDateFormat("MMM"); //$NON-NLS-1$
private Map<RGB, Color> colorRegistry = new HashMap<RGB, Color>();
private class CacheData {
Object cachedFirstValue;
Object cachedLastValue;
IDataSeries cachedSeries;
}
private Map<Object, CacheData> cacheMap = new HashMap<Object, CacheData>();
public ChartDocumentRenderer() {
}
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IChartRenderer#dispose()
*/
@Override
public void dispose() {
}
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IChartRenderer#renderBackground(org.eclipsetrader.ui.charts.RenderTarget)
*/
@Override
public void renderBackground(RenderTarget graphics) {
graphics.gc.fillRectangle(0, 0, graphics.width, graphics.height);
IDataSeries series = getVisibleSeries(graphics.input, graphics.firstValue, graphics.lastValue);
if (series == null) {
return;
}
if (series.getFirst() == null || series.getLast() == null) {
return;
}
graphics.verticalAxis.clear();
graphics.verticalAxis.addValues(new Object[] {
series.getHighest(), series.getLowest()
});
graphics.verticalAxis.computeSize(graphics.height);
int dashes[] = {
3, 3
};
graphics.gc.setLineDash(dashes);
graphics.gc.setForeground(getColor(blend(graphics.gc.getForeground().getRGB(), graphics.gc.getBackground().getRGB(), 15)));
Date firstDate = (Date) series.getFirst().getAdapter(Date.class);
Date lastDate = (Date) series.getLast().getAdapter(Date.class);
if (firstDate != null && lastDate != null) {
Calendar oldDate = null;
Calendar currentDate = Calendar.getInstance();
Object[] scaleArray = graphics.horizontalAxis.getValues();
int index = Collections.binarySearch(Arrays.asList(scaleArray), firstDate, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
return ((Date) o1).compareTo((Date) o2);
}
});
index = index < 0 ? -index : index;
for (int i = index; i < scaleArray.length; i++) {
Date date = (Date) scaleArray[i];
if (date.before(firstDate)) {
continue;
}
if (date.after(lastDate)) {
continue;
}
boolean tick = false;
currentDate.setTime(date);
if (oldDate != null) {
if (currentDate.get(Calendar.YEAR) != oldDate.get(Calendar.YEAR)) {
tick = true;
}
else if (currentDate.get(Calendar.MONTH) != oldDate.get(Calendar.MONTH)) {
tick = true;
}
oldDate.setTime(date);
}
else {
oldDate = Calendar.getInstance();
oldDate.setTime(date);
}
if (tick) {
int x = graphics.horizontalAxis.mapToAxis(date) + graphics.x;
graphics.gc.drawLine(x, 0, x, graphics.height);
}
}
}
Object[] scaleArray = graphics.verticalAxis.getValues();
for (int loop = 0; loop < scaleArray.length; loop++) {
int y = graphics.verticalAxis.mapToAxis(scaleArray[loop]);
graphics.gc.drawLine(0, y, graphics.width, y);
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IChartRenderer#renderElement(org.eclipsetrader.ui.charts.RenderTarget, java.lang.Object)
*/
@Override
public void renderElement(RenderTarget graphics, Object element) {
try {
IAdaptable adaptableElement = (IAdaptable) element;
IDataSeries series = getVisibleSeries(element, graphics.firstValue, graphics.lastValue);
IObjectRenderer renderer = (IObjectRenderer) adaptableElement.getAdapter(IObjectRenderer.class);
if (renderer != null) {
graphics.registry = this;
renderer.renderObject(graphics, series);
}
else {
if (series != null) {
graphics.verticalAxis.clear();
graphics.verticalAxis.addValues(new Object[] {
series.getHighest(), series.getLowest()
});
IAdaptable[] values = series.getValues();
if (series instanceof OHLCDataSeries) {
IBarDecorator decorator = (IBarDecorator) adaptableElement.getAdapter(IBarDecorator.class);
Color positiveColor = getColor(decorator != null ? decorator.getPositiveBarColor() : new RGB(0, 255, 0));
Color negativeColor = getColor(decorator != null ? decorator.getNegativeBarColor() : new RGB(255, 0, 0));
renderBars(graphics, values, 3, positiveColor, negativeColor);
}
else if (series instanceof NumericDataSeries) {
ILineDecorator decorator = (ILineDecorator) adaptableElement.getAdapter(ILineDecorator.class);
Color color = getColor(decorator != null ? decorator.getColor() : new RGB(0, 0, 0));
renderLine(graphics, values, color);
}
}
}
} catch (Error e) {
if (UIActivator.getDefault() == null) {
throw e;
}
UIActivator.log(new Status(IStatus.ERROR, UIActivator.PLUGIN_ID, Messages.ChartDocumentRenderer_RenderingErrorMessage));
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IScaleRenderer#renderHorizontalScale(org.eclipsetrader.ui.charts.RenderTarget)
*/
@Override
public void renderHorizontalScale(RenderTarget graphics) {
graphics.gc.fillRectangle(0, 0, graphics.width, graphics.height);
Color color = graphics.gc.getForeground();
Color highLightColor = getColor(new RGB(255, 0, 0));
Date firstDate = (Date) (graphics.firstValue instanceof IAdaptable ? ((IAdaptable) graphics.firstValue).getAdapter(Date.class) : graphics.firstValue);
Date lastDate = (Date) (graphics.lastValue instanceof IAdaptable ? ((IAdaptable) graphics.lastValue).getAdapter(Date.class) : graphics.lastValue);
if (firstDate != null && lastDate != null) {
Calendar oldDate = null;
Calendar currentDate = Calendar.getInstance();
Object[] scaleArray = graphics.horizontalAxis.getValues();
int index = Collections.binarySearch(Arrays.asList(scaleArray), firstDate, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
return ((Date) o1).compareTo((Date) o2);
}
});
index = index < 0 ? -index : index;
for (int i = index; i < scaleArray.length; i++) {
Date date = (Date) scaleArray[i];
if (date.before(firstDate)) {
continue;
}
if (date.after(lastDate)) {
continue;
}
boolean tick = false; // Draw a longer tick
boolean highlight = false; // Highlight the tick
String text = ""; //$NON-NLS-1$
currentDate.setTime(date);
if (oldDate != null) {
if (currentDate.get(Calendar.YEAR) != oldDate.get(Calendar.YEAR)) {
tick = true;
highlight = true;
text = monthYearFormatter.format(currentDate.getTime());
}
else if (currentDate.get(Calendar.MONTH) != oldDate.get(Calendar.MONTH)) {
tick = true;
highlight = false;
text = monthFormatter.format(currentDate.getTime());
}
oldDate.setTime(date);
}
else {
oldDate = Calendar.getInstance();
oldDate.setTime(date);
}
int x = graphics.horizontalAxis.mapToAxis(date) + graphics.x;
graphics.gc.setForeground(highlight ? highLightColor : color);
graphics.gc.drawLine(x, 0, x, tick ? 6 : 3);
if (tick) {
graphics.gc.drawString(text, x - 1, 7, true);
}
}
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.IScaleRenderer#renderVerticalScale(org.eclipsetrader.ui.charts.RenderTarget)
*/
@Override
public void renderVerticalScale(RenderTarget graphics) {
graphics.gc.fillRectangle(0, 0, graphics.width, graphics.height);
IDataSeries series = getVisibleSeries(graphics.input, graphics.firstValue, graphics.lastValue);
if (series == null) {
return;
}
graphics.verticalAxis.clear();
graphics.verticalAxis.addValues(new Object[] {
series.getHighest(), series.getLowest()
});
graphics.verticalAxis.computeSize(graphics.height);
Object[] scaleArray = graphics.verticalAxis.getValues();
for (int loop = 0; loop < scaleArray.length; loop++) {
int y = graphics.verticalAxis.mapToAxis(scaleArray[loop]);
String s = nf.format(scaleArray[loop]);
int h = graphics.gc.stringExtent(s).y / 2;
graphics.gc.drawLine(0, y, 4, y);
graphics.gc.drawString(s, 7, y - h);
}
}
protected IDataSeries getVisibleSeries(Object element, Object first, Object last) {
CacheData cache = cacheMap.get(element);
if (cache != null) {
if (cache.cachedFirstValue == first && cache.cachedLastValue == last) {
return cache.cachedSeries;
}
}
IDataSeries dataSeries = (IDataSeries) ((IAdaptable) element).getAdapter(IDataSeries.class);
if (dataSeries == null) {
return null;
}
IAdaptable firstValue = first instanceof IAdaptable ? (IAdaptable) first : new AdaptableWrapper(first);
IAdaptable lastValue = last instanceof IAdaptable ? (IAdaptable) last : new AdaptableWrapper(last);
cache = new CacheData();
cache.cachedFirstValue = first;
cache.cachedLastValue = last;
DataSeriesSubsetVisitor visitor = new DataSeriesSubsetVisitor(firstValue, lastValue);
visitor.visit(dataSeries);
cache.cachedSeries = visitor.getSubset();
cacheMap.put(dataSeries, cache);
return cache.cachedSeries;
}
public void clearCache() {
cacheMap.clear();
}
/* (non-Javadoc)
* @see org.eclipsetrader.ui.charts.renderers.IColorRegistry#getColor(org.eclipse.swt.graphics.RGB)
*/
@Override
public Color getColor(RGB rgb) {
Color color = colorRegistry.get(rgb);
if (color == null || color.isDisposed()) {
color = rgb != null ? new Color(Display.getCurrent(), rgb) : null;
if (color != null) {
colorRegistry.put(rgb, color);
}
}
return color;
}
protected void renderLine(RenderTarget graphics, IAdaptable[] values, Color color) {
int[] pointArray = new int[values.length * 2];
for (int i = 0, pa = 0; i < values.length; i++) {
Date date = (Date) values[i].getAdapter(Date.class);
Number value = (Number) values[i].getAdapter(Number.class);
pointArray[pa++] = graphics.horizontalAxis.mapToAxis(date) + graphics.x;
pointArray[pa++] = graphics.verticalAxis.mapToAxis(value);
}
graphics.gc.setLineStyle(SWT.LINE_SOLID);
graphics.gc.setForeground(color);
graphics.gc.setLineWidth(1);
graphics.gc.drawPolyline(pointArray);
}
protected void renderBars(RenderTarget event, IAdaptable[] values, int width, Color positiveColor, Color negativeColor) {
int half = width / 2;
event.gc.setLineStyle(SWT.LINE_SOLID);
event.gc.setLineWidth(1);
for (int i = 0; i < values.length; i++) {
IOHLC ohlc = (IOHLC) values[i].getAdapter(IOHLC.class);
if (ohlc == null) {
continue;
}
int h = event.verticalAxis.mapToAxis(ohlc.getHigh());
int l = event.verticalAxis.mapToAxis(ohlc.getLow());
int c = event.verticalAxis.mapToAxis(ohlc.getClose());
int o = event.verticalAxis.mapToAxis(ohlc.getOpen());
int x = event.horizontalAxis.mapToAxis(ohlc.getDate()) + event.x;
event.gc.setForeground(ohlc.getClose() >= ohlc.getOpen() ? positiveColor : negativeColor);
event.gc.drawLine(x, h, x, l);
event.gc.drawLine(x - half, o, x, o);
event.gc.drawLine(x, c, x + half + 1, c);
}
}
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;
}
}