/* * 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.Calendar; import java.util.Date; import java.util.Observable; import java.util.Observer; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.FontMetrics; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipsetrader.core.charts.IDataSeries; import org.eclipsetrader.core.feed.TimeSpan; import org.eclipsetrader.ui.internal.UIActivator; public class ChartCanvas { private Composite composite; private SummaryBar summary; private Canvas canvas; private Canvas verticalScaleCanvas; private Image image; private Image verticalScaleImage; private Label label; private IChartObject[] chartObject; private TimeSpan resolutionTimeSpan; private DoubleValuesAxis verticalAxis; private NumberFormat nf = NumberFormat.getInstance(); Date[] visibleDates; Date firstDate; Date lastDate; Point location; DateValuesAxis datesAxis; private Observer observer = new Observer() { @Override public void update(Observable o, Object arg) { canvas.getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!canvas.isDisposed()) { redraw(); } } }); } }; public ChartCanvas(Composite parent) { composite = new Composite(parent, SWT.NONE); GridLayout gridLayout = new GridLayout(2, false); gridLayout.marginWidth = gridLayout.marginHeight = 0; gridLayout.horizontalSpacing = 3; gridLayout.verticalSpacing = 0; composite.setLayout(gridLayout); composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); GC gc = new GC(composite); FontMetrics fontMetrics = gc.getFontMetrics(); gc.dispose(); summary = new SummaryBar(composite, SWT.NONE); summary.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); canvas = new Canvas(composite, SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND); canvas.setData(this); canvas.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); canvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); canvas.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { if (image != null) { image.dispose(); image = null; } redraw(); } }); canvas.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { removeObservers(); if (image != null) { image.dispose(); image = null; } } }); canvas.addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { if (chartObject != null) { onPaint(e); } } }); verticalScaleCanvas = new Canvas(composite, SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND); verticalScaleCanvas.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); verticalScaleCanvas.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, false, false)); ((GridData) verticalScaleCanvas.getLayoutData()).widthHint = Dialog.convertWidthInCharsToPixels(fontMetrics, 12); verticalScaleCanvas.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { if (verticalScaleImage != null) { verticalScaleImage.dispose(); verticalScaleImage = null; } redraw(); } }); verticalScaleCanvas.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { if (verticalScaleImage != null) { verticalScaleImage.dispose(); verticalScaleImage = null; } } }); verticalScaleCanvas.addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { if (chartObject != null) { onPaintVerticalScale(e); } } }); label = new Label(verticalScaleCanvas, SWT.NONE); label.setBackground(verticalScaleCanvas.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); label.setBounds(-200, 0, 0, 0); } public void setDatesAxis(DateValuesAxis datesAxis) { this.datesAxis = datesAxis; } public void setLocation(Point location) { this.location = location; } public void setDateRange(Date firstDate, Date lastDate) { this.firstDate = firstDate; this.lastDate = lastDate; } public void setVisibleDates(Date[] visibleDates) { this.visibleDates = visibleDates; } public void dispose() { composite.dispose(); } public boolean isDisposed() { return composite.isDisposed(); } public Control getControl() { return composite; } public Canvas getCanvas() { return canvas; } public Canvas getVerticalScaleCanvas() { return verticalScaleCanvas; } public DoubleValuesAxis getVerticalAxis() { return verticalAxis; } private void buildVerticalAxis() { if (verticalAxis == null) { verticalAxis = new DoubleValuesAxis(); } if (Boolean.TRUE.equals(verticalScaleCanvas.getData(BaseChartViewer.K_NEEDS_REDRAW))) { verticalAxis.clear(); accept(new IChartObjectVisitor() { @Override public boolean visit(IChartObject object) { if (object.getDataSeries() != null) { IDataSeries series = object.getDataSeries().getSeries(new AdaptableWrapper(firstDate), new AdaptableWrapper(lastDate)); if (series != null) { verticalAxis.addValues(new Object[] { series.getLowest(), series.getHighest() }); } } return true; } }); } } private void onPaint(PaintEvent event) { Rectangle clientArea = canvas.getClientArea(); boolean needsRedraw = Boolean.TRUE.equals(canvas.getData(BaseChartViewer.K_NEEDS_REDRAW)); if (image != null && !image.isDisposed()) { if (image.getBounds().width != clientArea.width || image.getBounds().height != clientArea.height) { image.dispose(); } } if (image == null || image.isDisposed()) { image = new Image(canvas.getDisplay(), clientArea.width, clientArea.height); needsRedraw = true; } if (needsRedraw) { buildVerticalAxis(); verticalAxis.computeSize(clientArea.height); Graphics graphics = new Graphics(image, location, datesAxis, verticalAxis); try { graphics.fillRectangle(clientArea); if (visibleDates != null) { paintBackground(graphics, clientArea); } paintObjects(graphics, clientArea); } catch (Throwable e) { Status status = new Status(IStatus.ERROR, UIActivator.PLUGIN_ID, Messages.ChartCanvas_RenderingChartError, e); UIActivator.log(status); } finally { canvas.setData(BaseChartViewer.K_NEEDS_REDRAW, Boolean.FALSE); graphics.dispose(); } } Util.paintImage(event, image); } void paintBackground(Graphics graphics, Rectangle clientArea) { graphics.pushState(); try { graphics.setLineDash(new int[] { 3, 3 }); graphics.setForegroundColor(Util.blend(graphics.getForegroundColor(), graphics.getBackgroundColor(), 15)); Calendar oldDate = null; Calendar currentDate = Calendar.getInstance(); for (int i = 0; i < visibleDates.length; i++) { Date date = visibleDates[i]; boolean tick = false; currentDate.setTime(date); if (oldDate != null) { if (resolutionTimeSpan == null || resolutionTimeSpan.getUnits() == TimeSpan.Units.Days) { if (currentDate.get(Calendar.YEAR) != oldDate.get(Calendar.YEAR)) { tick = true; } else if (currentDate.get(Calendar.MONTH) != oldDate.get(Calendar.MONTH)) { tick = true; } } else { if (currentDate.get(Calendar.MONTH) != oldDate.get(Calendar.MONTH)) { tick = true; } else if (currentDate.get(Calendar.DAY_OF_MONTH) != oldDate.get(Calendar.DAY_OF_MONTH)) { tick = true; } } oldDate.setTime(date); } else { oldDate = Calendar.getInstance(); oldDate.setTime(date); } if (tick) { int x = graphics.mapToHorizontalAxis(date); graphics.drawLine(x, 0, x, clientArea.height); } } Object[] numberArray = verticalAxis.getValues(); for (int i = 0; i < numberArray.length; i++) { int y = graphics.mapToVerticalAxis(numberArray[i]); graphics.drawLine(0, y, clientArea.width, y); } } finally { graphics.popState(); } } void paintObjects(Graphics graphics, Rectangle clientArea) { Double lowestValue = (Double) verticalAxis.getFirstValue(); Double highestValue = (Double) verticalAxis.getLastValue(); DataBounds dataBounds = new DataBounds(visibleDates, lowestValue, highestValue, clientArea, (int) datesAxis.gridSize); for (int i = 0; i < chartObject.length; i++) { graphics.pushState(); try { chartObject[i].setDataBounds(dataBounds); chartObject[i].paint(graphics); } finally { graphics.popState(); } } } private void onPaintVerticalScale(PaintEvent event) { Rectangle clientArea = verticalScaleCanvas.getClientArea(); boolean needsRedraw = Boolean.TRUE.equals(verticalScaleCanvas.getData(BaseChartViewer.K_NEEDS_REDRAW)); if (verticalScaleImage != null && !verticalScaleImage.isDisposed()) { if (verticalScaleImage.getBounds().width != clientArea.width || verticalScaleImage.getBounds().height != clientArea.height) { verticalScaleImage.dispose(); } } if (verticalScaleImage == null || verticalScaleImage.isDisposed()) { verticalScaleImage = new Image(verticalScaleCanvas.getDisplay(), clientArea.width, clientArea.height); needsRedraw = true; } if (needsRedraw) { buildVerticalAxis(); verticalAxis.computeSize(clientArea.height); Graphics graphics = new Graphics(verticalScaleImage, location, datesAxis, verticalAxis); try { graphics.fillRectangle(clientArea); Object[] scaleArray = verticalAxis.getValues(); for (int loop = 0; loop < scaleArray.length; loop++) { int y = verticalAxis.mapToAxis(scaleArray[loop]); String s; if (((Double) scaleArray[loop]).doubleValue() > 1000000) { Double value = (Double) scaleArray[loop]; s = nf.format(value / 1000000.0) + "M"; //$NON-NLS-1$ } else { s = nf.format(scaleArray[loop]); } int h = graphics.stringExtent(s).y / 2; graphics.drawLine(0, y, 4, y); graphics.drawString(s, 7, y - h); } for (int i = 0; i < chartObject.length; i++) { graphics.pushState(); try { chartObject[i].paintScale(graphics); } finally { graphics.popState(); } } } catch (Error e) { Status status = new Status(IStatus.ERROR, UIActivator.PLUGIN_ID, Messages.ChartCanvas_VerticalScaleRenderingError, e); UIActivator.log(status); } finally { verticalScaleCanvas.setData(BaseChartViewer.K_NEEDS_REDRAW, Boolean.FALSE); graphics.dispose(); } } Util.paintImage(event, verticalScaleImage); } public void setVerticalScaleVisible(boolean visible) { if (visible && !verticalScaleCanvas.getVisible()) { ((GridData) verticalScaleCanvas.getLayoutData()).exclude = false; ((GridData) canvas.getLayoutData()).horizontalSpan = 1; verticalScaleCanvas.setVisible(true); } if (!visible && verticalScaleCanvas.getVisible()) { ((GridData) verticalScaleCanvas.getLayoutData()).exclude = true; ((GridData) canvas.getLayoutData()).horizontalSpan = 2; verticalScaleCanvas.setVisible(false); } } public IChartObject[] getChartObject() { return chartObject; } public void setChartObject(IChartObject[] chartObject) { removeObservers(); this.chartObject = chartObject; addObservers(); updateSummary(); } public void redraw() { canvas.setData(BaseChartViewer.K_NEEDS_REDRAW, Boolean.TRUE); verticalScaleCanvas.setData(BaseChartViewer.K_NEEDS_REDRAW, Boolean.TRUE); canvas.redraw(); verticalScaleCanvas.redraw(); } public void hideToolTip() { label.setLocation(-200, 0); } public void showToolTip(int x, int y) { if (verticalAxis == null) { return; } Number value = (Number) verticalAxis.mapToValue(y); if (value != null) { label.setText(nf.format(value)); label.pack(); label.setLocation(0, y - label.getSize().y / 2); } } public Image getImage() { return image; } public Image getVerticalScaleImage() { return verticalScaleImage; } protected void updateSummary() { summary.removeAll(); accept(new IChartObjectVisitor() { @Override public boolean visit(IChartObject object) { ISummaryBarDecorator factory = null; if (object instanceof IAdaptable) { factory = (ISummaryBarDecorator) ((IAdaptable) object).getAdapter(ISummaryBarDecorator.class); if (factory != null) { factory.createDecorator(summary.getCompositeControl()); } } return true; } }); summary.layout(); summary.getParent().layout(); } public TimeSpan getResolutionTimeSpan() { return resolutionTimeSpan; } public void setResolutionTimeSpan(TimeSpan resolutionTimeSpan) { this.resolutionTimeSpan = resolutionTimeSpan; } public void accept(IChartObjectVisitor visitor) { if (chartObject == null) { return; } for (int i = 0; i < chartObject.length; i++) { chartObject[i].accept(visitor); } } void addObservers() { accept(new IChartObjectVisitor() { @Override public boolean visit(IChartObject object) { if (!(object instanceof IAdaptable)) { return true; } IAdaptable adaptable = (IAdaptable) object; Observable observable = (Observable) adaptable.getAdapter(Observable.class); if (observable != null) { observable.addObserver(observer); } return true; } }); } void removeObservers() { accept(new IChartObjectVisitor() { @Override public boolean visit(IChartObject object) { if (!(object instanceof IAdaptable)) { return true; } IAdaptable adaptable = (IAdaptable) object; Observable observable = (Observable) adaptable.getAdapter(Observable.class); if (observable != null) { observable.deleteObserver(observer); } return true; } }); } public SummaryBar getSummary() { return summary; } public void setSummaryVisible(boolean visible) { summary.getControl().setVisible(visible); ((GridData) summary.getControl().getLayoutData()).exclude = !visible; composite.layout(); } }