/*******************************************************************************
* Copyright (c) 2013 Luigi Sgro. 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:
* Luigi Sgro - initial API and implementation
******************************************************************************/
package com.quantcomponents.chart;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
/**
* Base class for charts.
* Implementors must:
* <ul
* <li>write the method {@link Chart#updateMetrics(IChartMetrics)}, which
* is called at each paint iteration to update the metrics</li>
* <li>add their {@link IDrawable} objects to the list returned by
* {@link Chart#getDrawables()}</li>
* </ul>
* The chart will in turn call {@link IDrawable#draw(IChartMetrics, org.eclipse.swt.graphics.GC)}
* on each of them according to their position in the list.
*
* @param <A> type of the chart abscissa
* @param <O> type of the chart ordinate
*/
public abstract class Chart<A, O> extends Composite {
private static final long REFRESH_THREAD_WAIT_QUANTUM = 500;
private static final long MIN_TIME_BETWEEN_REFRESH = 20;
private final Canvas canvas;
private final List<IDrawable<A, O>> listOfDrawables = new CopyOnWriteArrayList<IDrawable<A, O>>();
private volatile IChartMetrics<A, O> metrics;
private final Thread refreshThread;
private volatile boolean refreshScheduled;
private final class ChartRefreshLoop implements Runnable {
@Override
public void run() {
while (!isDisposed()) {
try {
synchronized (refreshThread) {
while (!refreshScheduled) {
refreshThread.wait(REFRESH_THREAD_WAIT_QUANTUM);
if (isDisposed()) {
return;
}
}
refreshScheduled = false;
getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
updateView();
}});
Thread.sleep(MIN_TIME_BETWEEN_REFRESH);
}
} catch (InterruptedException e) { /* who cares? */ }
}
}
}
public Chart(Composite parent, int style) {
super(parent, style);
setLayout(new FillLayout());
canvas = new Canvas(this, SWT.DOUBLE_BUFFERED);
canvas.addControlListener(new ControlListener() {
@Override
public void controlMoved(ControlEvent e) { /* do nothing */ }
@Override
public void controlResized(ControlEvent e) {
updateView();
}
});
canvas.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
Rectangle canvasBounds = canvas.getBounds();
canvas.setBackground(Chart.this.getBackground());
canvas.drawBackground(e.gc, 0, 0, canvasBounds.width, canvasBounds.height);
IChartMetrics<A, O> currentChartMetrics = metrics;
if (currentChartMetrics != null) {
updateMetrics(currentChartMetrics);
currentChartMetrics.setDrawingArea(canvasBounds);
for (IDrawable<A, O> drawable : listOfDrawables) {
drawable.draw(currentChartMetrics, e.gc);
}
}
}});
refreshThread = new Thread(new ChartRefreshLoop());
refreshThread.start();
}
public abstract void updateMetrics(IChartMetrics<A, O> metrics);
public void setMetrics(IChartMetrics<A, O> metrics) {
this.metrics = metrics;
}
public List<IDrawable<A, O>> getDrawables() {
return listOfDrawables;
}
public Control getControl() {
return canvas;
}
public void refresh() {
synchronized(refreshThread) {
refreshScheduled = true;
refreshThread.notify();
}
}
private void updateView() {
if (canvas != null && !canvas.isDisposed()) {
canvas.redraw();
canvas.update();
}
}
}