package org.jactr.eclipse.runtime.ui.probe.components; /* * default logging */ import java.util.ArrayList; import java.util.Collection; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import javolution.util.FastSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.birt.chart.device.IDeviceRenderer; import org.eclipse.birt.chart.factory.GeneratedChartState; import org.eclipse.birt.chart.factory.Generator; import org.eclipse.birt.chart.model.ChartWithAxes; import org.eclipse.birt.chart.model.attribute.AxisType; import org.eclipse.birt.chart.model.attribute.Bounds; import org.eclipse.birt.chart.model.attribute.IntersectionType; import org.eclipse.birt.chart.model.attribute.LineStyle; import org.eclipse.birt.chart.model.attribute.Marker; import org.eclipse.birt.chart.model.attribute.MarkerType; import org.eclipse.birt.chart.model.attribute.NumberFormatSpecifier; import org.eclipse.birt.chart.model.attribute.Position; import org.eclipse.birt.chart.model.attribute.TickStyle; import org.eclipse.birt.chart.model.attribute.impl.ColorDefinitionImpl; import org.eclipse.birt.chart.model.attribute.impl.NumberFormatSpecifierImpl; import org.eclipse.birt.chart.model.component.Axis; import org.eclipse.birt.chart.model.component.CurveFitting; import org.eclipse.birt.chart.model.component.Series; import org.eclipse.birt.chart.model.component.impl.CurveFittingImpl; import org.eclipse.birt.chart.model.component.impl.SeriesImpl; import org.eclipse.birt.chart.model.data.SeriesDefinition; import org.eclipse.birt.chart.model.data.impl.NumberDataElementImpl; import org.eclipse.birt.chart.model.data.impl.NumberDataSetImpl; import org.eclipse.birt.chart.model.data.impl.SeriesDefinitionImpl; import org.eclipse.birt.chart.model.impl.ChartWithAxesImpl; import org.eclipse.birt.chart.model.layout.Plot; import org.eclipse.birt.chart.model.type.LineSeries; import org.eclipse.birt.chart.model.type.impl.LineSeriesImpl; import org.eclipse.birt.chart.model.type.impl.ScatterSeriesImpl; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; 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.Image; import org.eclipse.swt.widgets.Composite; import org.jactr.eclipse.core.concurrent.QueueingJob; import org.jactr.eclipse.runtime.RuntimePlugin; import org.jactr.eclipse.runtime.probe2.ModelProbeData; import org.jactr.eclipse.runtime.ui.probe.TimeFormatSpecifier; public abstract class AbstractBIRTProbeContainer extends AbstractProbeContainer<ModelProbeData, Series> { static final transient Log LOGGER = LogFactory .getLog(AbstractBIRTProbeContainer.class); private long MINIMUM_RESPONSIVENESS = 500; // ms protected ChartWithAxes _chart; protected volatile GeneratedChartState _chartState; protected IDeviceRenderer _chartRenderer; protected Bounds _chartBounds; protected double[] _timeSpan = new double[2]; protected double[] _scale = new double[] { -1, 1 }; protected QueueingJob _updateJob; protected ReentrantLock _contextLock = new ReentrantLock(); boolean _useScatter = false; private Collection<IChartUpdateListener> _chartUpdateListeners = new ArrayList<IChartUpdateListener>(); public AbstractBIRTProbeContainer(Composite parent, ModelProbeData mpd) { super(parent, SWT.BORDER_SOLID); _probeData = mpd; setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WHITE)); initializeChart(); addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { try { _contextLock.lock(); Image uiImage = getUIImage(); if (uiImage != null && !uiImage.isDisposed()) e.gc.drawImage(uiImage, 0, 0); } finally { _contextLock.unlock(); } } }); _updateJob = new QueueingJob("Probe Render") { @Override protected IStatus run(final IProgressMonitor monitor) { final boolean updated = updateChart(monitor); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Chart updated. RefreshRequired : %s", updated)); if (!isDisposed() && updated) getDisplay().asyncExec(new Runnable() { /* * signal that a repaint is needed */ public void run() { // done drawing, swap the image buffers if (isDisposed()) return; swapImage(); getParent().redraw(); monitor.done(); _updateJob.schedule(MINIMUM_RESPONSIVENESS); } }); else monitor.done(); return Status.OK_STATUS; } }; parent.addControlListener(new ControlListener() { public void controlMoved(ControlEvent e) { } public void controlResized(ControlEvent e) { updateRenderContext(); refresh(); } }); updateRenderContext(); _updateJob.schedule(MINIMUM_RESPONSIVENESS); } public ChartWithAxes getChart() { return _chart; } @Override public void setFilteredProbes(Set<String> probeNames) { synchronized (_filteredOut) { _filteredOut.clear(); _filteredOut.addAll(probeNames); } FastSet<String> allKnown = FastSet.newInstance(); allKnown.addAll(_probeSeries.keySet()); for (String probeName : allKnown) { Series series = _probeSeries.get(probeName); if (series != null) { boolean filterOut = probeNames.contains(probeName); series.setVisible(!filterOut); } } FastSet.recycle(allKnown); } /** * swap or copy the render image into the uiimage */ abstract protected void swapImage(); /** * this is the image that is rendered to the component * * @return */ abstract protected Image getUIImage(); /** * update the context that the chart will be rendered to */ abstract protected void updateRenderContext(); /** * returns the required renderer * * @return */ abstract protected IDeviceRenderer getDeviceRenderer(); protected void initializeChart() { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("initializing chart")); try { // config.setProperty("STANDALONE", "true"); _chartRenderer = getDeviceRenderer(); _chart = ChartWithAxesImpl.create(); // Plot _chart.getBlock().setBackground(ColorDefinitionImpl.WHITE()); _chart.getTitle().getLabel().getCaption().setValue(""); Plot p = _chart.getPlot(); p.getClientArea() .setBackground(ColorDefinitionImpl.create(255, 255, 225)); // Legend // // Legend lg = _chart.getLegend(); // LineAttributes lia = lg.getOutline(); // lg.getText().getFont().setSize(16); // lia.setStyle(LineStyle.SOLID_LITERAL); // lg.getInsets().setLeft(10); // lg.getInsets().setRight(10); Axis xAxisPrimary = _chart.getPrimaryBaseAxes()[0]; // xAxisPrimary.setType(AxisType.TEXT_LITERAL); xAxisPrimary.setType(AxisType.LINEAR_LITERAL); xAxisPrimary.getOrigin().setType(IntersectionType.VALUE_LITERAL); xAxisPrimary.getOrigin().setType(IntersectionType.MIN_LITERAL); xAxisPrimary.getTitle().getCaption().setValue("Time");//$NON-NLS-1$ xAxisPrimary.getTitle().setVisible(true); xAxisPrimary.setTitlePosition(Position.BELOW_LITERAL); // NumberFormatSpecifier spec = NumberFormatSpecifierImpl.create(); // spec.setFractionDigits(2); xAxisPrimary.setFormatSpecifier(new TimeFormatSpecifier()); xAxisPrimary.getLabel().getCaption().getFont().setRotation(75); xAxisPrimary.setLabelPosition(Position.BELOW_LITERAL); xAxisPrimary.getMajorGrid().setTickStyle(TickStyle.BELOW_LITERAL); xAxisPrimary.getMajorGrid().getLineAttributes() .setStyle(LineStyle.DOTTED_LITERAL); xAxisPrimary.getMajorGrid().getLineAttributes() .setColor(ColorDefinitionImpl.create(64, 64, 64)); // xAxisPrimary.getMajorGrid().getLineAttributes().setVisible(true); // Y-Axis Axis yAxisPrimary = _chart.getPrimaryOrthogonalAxis(xAxisPrimary); yAxisPrimary.getLabel().getCaption().setValue("");//$NON-NLS-1$ yAxisPrimary.getLabel().getCaption().getFont().setRotation(37); NumberFormatSpecifier spec = NumberFormatSpecifierImpl.create(); spec.setFractionDigits(2); yAxisPrimary.setFormatSpecifier(spec); yAxisPrimary.setLabelPosition(Position.LEFT_LITERAL); yAxisPrimary.setTitlePosition(Position.LEFT_LITERAL); yAxisPrimary.getTitle().getCaption().setValue("");//$NON-NLS-1$ yAxisPrimary.setType(AxisType.LINEAR_LITERAL); yAxisPrimary.getMajorGrid().setTickStyle(TickStyle.LEFT_LITERAL); yAxisPrimary.getMajorGrid().getLineAttributes() .setStyle(LineStyle.DOTTED_LITERAL); // yAxisPrimary.getMajorGrid().getLineAttributes().setColor( // ColorDefinitionImpl.RED()); // yAxisPrimary.getMajorGrid().getLineAttributes().setVisible(true); // X-Series Series seCategory = SeriesImpl.create(); SeriesDefinition sdX = SeriesDefinitionImpl.create(); xAxisPrimary.getSeriesDefinitions().add(sdX); sdX.getSeries().add(seCategory); SeriesDefinition sdY = SeriesDefinitionImpl.create(); yAxisPrimary.getSeriesDefinitions().add(sdY); sdY.getSeriesPalette().shift(-1); // sdY.getSeriesPalette().getEntries().clear(); } catch (Exception e) { RuntimePlugin.error("Could not create chart ", e); LOGGER.error("Could not create chart ", e); } } /** * renders the chart into the image displayed by the component * * @param newData * @param scaleHasChanged * @param timeSpanHasChanged */ private void renderBackingImage(boolean timeSpanHasChanged, boolean scaleHasChanged, boolean newData) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format( "renderingChart timeChange:%s scaleChange:%s newData:%s", timeSpanHasChanged, scaleHasChanged, newData)); try { _contextLock.lock(); Generator gr = Generator.instance(); try { if (timeSpanHasChanged || scaleHasChanged) _chartState = gr.build(_chartRenderer.getDisplayServer(), _chart, _chartBounds, null, null, null); else gr.refresh(_chartState); gr.render(_chartRenderer, _chartState); } catch (Exception ce) { LOGGER.error("Failed to render", ce); RuntimePlugin.error(String.format("Failed to render chart."), ce); } } finally { _contextLock.unlock(); } } public void addListener(IChartUpdateListener listener) { synchronized (_chartUpdateListeners) { _chartUpdateListeners.add(listener); } } public void removeListener(IChartUpdateListener listener) { synchronized (_chartUpdateListeners) { _chartUpdateListeners.remove(listener); } } /** * signal that the contents have changed and trigger a potential redial. */ public void refresh() { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Refresh queued")); _updateJob.queue(MINIMUM_RESPONSIVENESS); } /** * update the chart data, possibly invalidating caches, for the chart. then * rendering to the back image * * @return true if any changes have been made */ protected boolean updateChart(IProgressMonitor monitor) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Updating chart")); /* * has the size of the canvas changed? */ /* * first we see if either the max/min or the time scale has changed. */ boolean timeSpanHasChanged = hasTimeSpanChanged(); boolean scaleHasChanged = hasDataRangeChanged(); boolean newData = false; if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("timeSpanChanged:%s scaleHasChanged:%s. %s", timeSpanHasChanged, scaleHasChanged, timeSpanHasChanged || scaleHasChanged ? "chart rebuild required." : "")); if (timeSpanHasChanged && LOGGER.isDebugEnabled()) LOGGER.debug(String.format("time span (%.2f, %.2f) : axis (%.2f, %.2f)", _probeData.getStartTime(), _probeData.getEndTime(), _timeSpan[0], _timeSpan[1])); if (scaleHasChanged && LOGGER.isDebugEnabled()) LOGGER.debug(String.format("scale span (%.2f, %.2f) : axis (%.2f, %.2f)", _probeData.getMinimumValue(), _probeData.getMaximumValue(), _scale[0], _scale[1])); FastSet<String> probeNames = FastSet.newInstance(); _probeData.getProbeNames(probeNames); probeNames.removeAll(_probeSeries.keySet()); /* * remaining are new. */ monitor.beginTask("Updating Chart", 5); monitor.subTask("Processing new data"); for (String probeName : probeNames) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("adding series for probe %s", probeName)); Series series = addSeries(probeName); _probeSeries.put(probeName, series); double[] data = _probeData.getProbeData(probeName, null); series.setDataSet(NumberDataSetImpl.create(data)); newData = true; } monitor.worked(1); /* * zip through all of our probes. for (String probeName : * _probeData.getProbeNames(null)) { Series series = * _probeSeries.get(probeName); if (series == null) { if * (LOGGER.isDebugEnabled()) * LOGGER.debug(String.format("adding series for probe %s", probeName)); * series = addSeries(probeName); _probeSeries.put(probeName, series); * double[] data = _probeData.getProbeData(probeName, null); * series.setDataSet(NumberDataSetImpl.create(data)); newData = true; } * boolean shouldHide = _filteredOut.contains(probeName); if (shouldHide) { * if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Should hide %s", * probeName)); newData |= series.isVisible(); } * series.setVisible(!shouldHide); } */ FastSet.recycle(probeNames); /* * update the axis. */ monitor.subTask("Adjusting time scale"); if (timeSpanHasChanged) adjustTimeScale(); monitor.worked(1); /* * update the scales */ monitor.subTask("Updating scale"); if (scaleHasChanged) { double low = _probeData.getMinimumValue(); double high = _probeData.getMaximumValue() * 1.1; if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Updating value scale (%.2f - %.2f)", low, high)); Axis xAxisPrimary = _chart.getPrimaryBaseAxes()[0]; Axis yAxisPrimary = _chart.getPrimaryOrthogonalAxis(xAxisPrimary); yAxisPrimary.getScale().setMax(NumberDataElementImpl.create(high)); yAxisPrimary.getScale().setMin(NumberDataElementImpl.create(low)); _scale[0] = low; _scale[1] = high; } monitor.worked(1); monitor.subTask("Notifying listeners"); synchronized (_chartUpdateListeners) { for (IChartUpdateListener listener : _chartUpdateListeners) listener.chartUpdated(timeSpanHasChanged, scaleHasChanged, newData); } monitor.worked(1); monitor.subTask("Rendering"); renderBackingImage(timeSpanHasChanged, scaleHasChanged, newData); monitor.worked(1); return timeSpanHasChanged || scaleHasChanged || newData; } private void adjustTimeScale() { double[] ranges = new double[(int) Math.max(_probeData.getTimeWindow(), 15) / 5 + 1]; ranges[0] = 1; for (int i = 1; i < ranges.length; i++) ranges[i] = i * 5; double[] sampleTimes = _probeData.getSampleTimes(null); double delta = _probeData.getEndTime() - _probeData.getStartTime(); int rangeIndex = ranges.length - 1; for (int i = 0; i < ranges.length; i++) if (delta < ranges[i]) { rangeIndex = i; break; } _timeSpan[1] = _probeData.getEndTime(); _timeSpan[0] = _timeSpan[1] - ranges[rangeIndex]; if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format( "Updating time scale w/ %d samples, using range [%.2f, %.2f] (%.2f)", sampleTimes.length, _timeSpan[0], _timeSpan[1], _probeData.getEndTime())); Axis xAxisPrimary = _chart.getPrimaryBaseAxes()[0]; xAxisPrimary.getSeriesDefinitions().get(0).getSeries().get(0) .setDataSet(NumberDataSetImpl.create(sampleTimes)); xAxisPrimary.getScale().setMax(NumberDataElementImpl.create(_timeSpan[1])); xAxisPrimary.getScale().setMin(NumberDataElementImpl.create(_timeSpan[0])); } private boolean hasTimeSpanChanged() { return _probeData.getEndTime() > _timeSpan[1]; } private boolean hasDataRangeChanged() { return _probeData.getMinimumValue() < _scale[0] || _probeData.getMaximumValue() > _scale[1]; } protected Series addSeries(String probeName) { LineSeries series = null; if (_useScatter) series = (LineSeries) ScatterSeriesImpl.create(); else series = (LineSeries) LineSeriesImpl.create(); series.setPaletteLineColor(true); series.setSeriesIdentifier(probeName); series.setConnectMissingValue(true); if (_useScatter) { CurveFitting curve = CurveFittingImpl.create(); series.setCurve(true); series.setCurveFitting(curve); } MarkerType type = MarkerType.get(Math.abs(probeName.hashCode()) % MarkerType.VALUES.size()); for (Object m : series.getMarkers()) ((Marker) m).setType(type); // series.setDataSet(NumberDataSetImpl.create(new double[] { 0 })); SeriesDefinition seriesDef = _chart .getPrimaryOrthogonalAxis(_chart.getPrimaryBaseAxes()[0]) .getSeriesDefinitions().get(0); seriesDef.getSeries().add(series); return series; } @Override public boolean setFocus() { refresh(); return super.setFocus(); } public interface IChartUpdateListener { public void chartUpdated(boolean timeSpanHasChange, boolean scaleHasChanged, boolean newData); } }