/******************************************************************************* * Copyright (c) 2010 Oak Ridge National Laboratory. * 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 ******************************************************************************/ package org.eclipse.nebula.visualization.xygraph.figures; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.eclipse.draw2d.Figure; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.nebula.visualization.internal.xygraph.undo.OperationsManager; import org.eclipse.nebula.visualization.internal.xygraph.undo.XYGraphMemento; import org.eclipse.nebula.visualization.internal.xygraph.undo.ZoomCommand; import org.eclipse.nebula.visualization.xygraph.linearscale.AbstractScale.LabelSide; import org.eclipse.nebula.visualization.xygraph.linearscale.Range; import org.eclipse.nebula.visualization.xygraph.util.Log10; import org.eclipse.nebula.visualization.xygraph.util.SingleSourceHelper2; import org.eclipse.nebula.visualization.xygraph.util.XYGraphMediaFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Display; /** * This class is the main figure for the plotting. It contains a PlotArea, which * contains a space to plot traces, and the axes, title and legend of the graph. * * * XY-Graph Figure. * * @author Xihui Chen * @author Kay Kasemir (performStagger) * @author Laurent PHILIPPE (property change support) * @author Alex Clayton (added {@link IAxesFactory} factory) */ public class XYGraph extends Figure implements IXYGraph { /** * Use {@link IXYGraph#PROPERTY_CONFIG} instead */ @Deprecated public static final String PROPERTY_CONFIG = "config"; //$NON-NLS-1$ /** * Use {@link IXYGraph#PROPERTY_XY_GRAPH_MEM} instead */ @Deprecated public static final String PROPERTY_XY_GRAPH_MEM = "xyGraphMem"; //$NON-NLS-1$ /** * Use {@link IXYGraph#PROPERTY_ZOOMTYPE} instead */ @Deprecated public static final String PROPERTY_ZOOMTYPE = "zoomType"; //$NON-NLS-1$ /** * Add property change support to XYGraph Use for inform listener of * xyGraphMem property changed * * @author L.PHILIPPE (GANIL) */ private PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); @Override public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } @Override public void addPropertyChangeListener(String property, PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(property, listener); } @Override public void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } @Override public void removePropertyChangeListener(String property, PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(property, listener); } public void fireConfigChanged() { changeSupport.firePropertyChange(IXYGraph.PROPERTY_CONFIG, null, this); } /** * Save the Graph settings Send a property changed event when changed * * @author L.PHILIPPE (GANIL) */ private XYGraphMemento xyGraphMem; public XYGraphMemento getXyGraphMem() { return xyGraphMem; } public void setXyGraphMem(XYGraphMemento xyGraphMem) { XYGraphMemento old = this.xyGraphMem; this.xyGraphMem = xyGraphMem; changeSupport.firePropertyChange(IXYGraph.PROPERTY_XY_GRAPH_MEM, old, this.xyGraphMem); } private static final int GAP = 2; // public final static Color WHITE_COLOR = ColorConstants.white; // public final static Color BLACK_COLOR = ColorConstants.black; /** * Default colors for newly added item, used over when reaching the end. * <p> * Very hard to find a long list of distinct colors. This list is definitely * too short... */ final public static RGB[] DEFAULT_TRACES_COLOR = { new RGB(21, 21, 196), // blue new RGB(242, 26, 26), // red new RGB(33, 179, 33), // green new RGB(0, 0, 0), // black new RGB(128, 0, 255), // violett new RGB(255, 170, 0), // (darkish) yellow new RGB(255, 0, 240), // pink new RGB(243, 132, 132), // peachy new RGB(0, 255, 11), // neon green new RGB(0, 214, 255), // neon blue new RGB(114, 40, 3), // brown new RGB(219, 128, 4), // orange }; private int traceNum = 0; protected boolean transparent = false; private boolean showLegend = true; protected Map<Axis, Legend> legendMap; /** * Graph title. Should never be <code>null</code> because otherwise the * ToolbarArmedXYGraph's GraphConfigPage can crash. */ private String title = ""; private Color titleColor; protected Label titleLabel; // ADD BECAUSE OF SWT invalid Thread acess on getTitleColor() private FontData titleFontData; private RGB titleColorRgb; protected List<Axis> xAxisList; protected List<Axis> yAxisList; protected PlotArea plotArea; /** * Use {@link #getPrimaryXAxis()} instead */ @Deprecated final public Axis primaryXAxis; /** * Use {@link #getPrimaryYAxis()} instead */ @Deprecated final public Axis primaryYAxis; protected OperationsManager operationsManager; private ZoomType zoomType = ZoomType.NONE; /** * Constructor */ public XYGraph() { this(new DefaultAxesFactory()); } /** * Constructor. * * @param axesFactory * The {@link IAxesFactory} to use to create the primary axes for * the graph. Should not be {@code null} */ public XYGraph(IAxesFactory axesFactory) { setOpaque(!transparent); legendMap = new LinkedHashMap<Axis, Legend>(); titleLabel = new Label(); String sysFontName = Display.getCurrent().getSystemFont().getFontData()[0].getName(); setTitleFont(XYGraphMediaFactory.getInstance().getFont(new FontData(sysFontName, 12, SWT.BOLD))); setFont(Display.getCurrent().getSystemFont()); // titleLabel.setVisible(false); xAxisList = new ArrayList<Axis>(); yAxisList = new ArrayList<Axis>(); plotArea = new PlotArea((IXYGraph) this); getPlotArea().setOpaque(!transparent); add(titleLabel); add(plotArea); primaryYAxis = axesFactory.createYAxis(); addAxis(primaryYAxis); primaryXAxis = axesFactory.createXAxis(); addAxis(primaryXAxis); operationsManager = new OperationsManager(); } @Override public boolean isOpaque() { return false; } @Override protected void layout() { Rectangle clientArea = getClientArea().getCopy(); boolean hasRightYAxis = false; boolean hasTopXAxis = false; boolean hasLeftYAxis = false; boolean hasBottomXAxis = false; if (titleLabel != null && titleLabel.isVisible() && !(titleLabel.getText().length() <= 0)) { Dimension titleSize = titleLabel.getPreferredSize(); titleLabel.setBounds(new Rectangle(clientArea.x + clientArea.width / 2 - titleSize.width / 2, clientArea.y, titleSize.width, titleSize.height)); clientArea.y += titleSize.height + GAP; clientArea.height -= titleSize.height + GAP; } if (showLegend) { List<Integer> rowHPosList = new ArrayList<Integer>(); List<Dimension> legendSizeList = new ArrayList<Dimension>(); List<Integer> rowLegendNumList = new ArrayList<Integer>(); List<Legend> legendList = new ArrayList<Legend>(); Object[] yAxes = legendMap.keySet().toArray(); int hPos = 0; int rowLegendNum = 0; for (int i = 0; i < yAxes.length; i++) { Legend legend = legendMap.get(yAxes[i]); if (legend != null && legend.isVisible()) { legendList.add(legend); Dimension legendSize = legend.getPreferredSize(clientArea.width, clientArea.height); legendSizeList.add(legendSize); if ((hPos + legendSize.width + GAP) > clientArea.width) { if (rowLegendNum == 0) break; rowHPosList.add(clientArea.x + (clientArea.width - hPos) / 2); rowLegendNumList.add(rowLegendNum); rowLegendNum = 1; hPos = legendSize.width + GAP; clientArea.height -= legendSize.height + GAP; if (i == yAxes.length - 1) { hPos = legendSize.width + GAP; rowLegendNum = 1; rowHPosList.add(clientArea.x + (clientArea.width - hPos) / 2); rowLegendNumList.add(rowLegendNum); clientArea.height -= legendSize.height + GAP; } } else { hPos += legendSize.width + GAP; rowLegendNum++; if (i == yAxes.length - 1) { rowHPosList.add(clientArea.x + (clientArea.width - hPos) / 2); rowLegendNumList.add(rowLegendNum); clientArea.height -= legendSize.height + GAP; } } } } int lm = 0; int vPos = clientArea.y + clientArea.height + GAP; for (int i = 0; i < rowLegendNumList.size(); i++) { hPos = rowHPosList.get(i); for (int j = 0; j < rowLegendNumList.get(i); j++) { legendList.get(lm).setBounds( new Rectangle(hPos, vPos, legendSizeList.get(lm).width, legendSizeList.get(lm).height)); hPos += legendSizeList.get(lm).width + GAP; lm++; } vPos += legendSizeList.get(lm - 1).height + GAP; } } for (int i = xAxisList.size() - 1; i >= 0; i--) { Axis xAxis = xAxisList.get(i); Dimension xAxisSize = xAxis.getPreferredSize(clientArea.width, clientArea.height); if (xAxis.getTickLabelSide() == LabelSide.Primary) { if (xAxis.isVisible()) hasBottomXAxis = true; xAxis.setBounds(new Rectangle(clientArea.x, clientArea.y + clientArea.height - xAxisSize.height, xAxisSize.width, xAxisSize.height)); clientArea.height -= xAxisSize.height; } else { if (xAxis.isVisible()) hasTopXAxis = true; xAxis.setBounds(new Rectangle(clientArea.x, clientArea.y + 1, xAxisSize.width, xAxisSize.height)); clientArea.y += xAxisSize.height; clientArea.height -= xAxisSize.height; } } for (int i = yAxisList.size() - 1; i >= 0; i--) { Axis yAxis = yAxisList.get(i); int hintHeight = clientArea.height + (hasTopXAxis ? 1 : 0) * yAxis.getMargin() + (hasBottomXAxis ? 1 : 0) * yAxis.getMargin(); if (hintHeight > getClientArea().height) hintHeight = clientArea.height; Dimension yAxisSize = yAxis.getPreferredSize(clientArea.width, hintHeight); if (yAxis.getTickLabelSide() == LabelSide.Primary) { // on the left if (yAxis.isVisible()) hasLeftYAxis = true; yAxis.setBounds(new Rectangle(clientArea.x, clientArea.y - (hasTopXAxis ? yAxis.getMargin() : 0), yAxisSize.width, yAxisSize.height)); clientArea.x += yAxisSize.width; clientArea.width -= yAxisSize.width; } else { // on the right if (yAxis.isVisible()) hasRightYAxis = true; yAxis.setBounds(new Rectangle(clientArea.x + clientArea.width - yAxisSize.width - 1, clientArea.y - (hasTopXAxis ? yAxis.getMargin() : 0), yAxisSize.width, yAxisSize.height)); clientArea.width -= yAxisSize.width; } } // re-adjust xAxis boundss for (int i = xAxisList.size() - 1; i >= 0; i--) { Axis xAxis = xAxisList.get(i); Rectangle r = xAxis.getBounds().getCopy(); if (hasLeftYAxis) r.x = clientArea.x - xAxis.getMargin() - 1; r.width = clientArea.width + (hasLeftYAxis ? xAxis.getMargin() : -1) + (hasRightYAxis ? xAxis.getMargin() : 0); xAxis.setBounds(r); } if (plotArea != null && plotArea.isVisible()) { Rectangle plotAreaBound = new Rectangle(primaryXAxis.getBounds().x + primaryXAxis.getMargin() + 1, primaryYAxis.getBounds().y + primaryYAxis.getMargin(), primaryXAxis.getBounds().width - 2 * primaryXAxis.getMargin(), primaryYAxis.getBounds().height - 2 * primaryYAxis.getMargin()); plotArea.setBounds(plotAreaBound); } super.layout(); } /** * @param zoomType * the zoomType to set */ public void setZoomType(ZoomType zoomType) { if (this.zoomType == zoomType) { return; } plotArea.setZoomType(zoomType); for (Axis axis : xAxisList) axis.setZoomType(zoomType); for (Axis axis : yAxisList) axis.setZoomType(zoomType); changeSupport.firePropertyChange(PROPERTY_ZOOMTYPE, this.zoomType, this.zoomType = zoomType); } /** * @return the zoomType */ public ZoomType getZoomType() { return zoomType; } /** * @param title * the title to set */ public void setTitle(String title) { this.title = title.trim(); titleLabel.setText(title); } /** * @param showTitle * true if title should be shown; false otherwise. */ public void setShowTitle(boolean showTitle) { titleLabel.setVisible(showTitle); revalidate(); } /** * @return true if title should be shown; false otherwise. */ public boolean isShowTitle() { return titleLabel.isVisible(); } /** * @param showLegend * true if legend should be shown; false otherwise. */ public void setShowLegend(boolean showLegend) { this.showLegend = showLegend; for (Axis yAxis : legendMap.keySet()) { Legend legend = legendMap.get(yAxis); legend.setVisible(showLegend); } revalidate(); } /** * @return the showLegend */ public boolean isShowLegend() { return showLegend; } /** * Add an axis to the graph * * @param axis */ public void addAxis(Axis axis) { if (axis.isHorizontal()) xAxisList.add(axis); else yAxisList.add(axis); plotArea.addGrid(new Grid(axis)); add(axis); axis.setXyGraph((IXYGraph) this); revalidate(); } /** * Remove an axis from the graph * * @param axis * @return true if this axis exists. */ public boolean removeAxis(Axis axis) { remove(axis); plotArea.removeGrid(axis.getGrid()); revalidate(); if (axis.isHorizontal()) return xAxisList.remove(axis); else return yAxisList.remove(axis); } /** * Add a trace * * @param trace */ public void addTrace(Trace trace) { if (trace.getTraceColor() == null) { // Cycle through default colors trace.setTraceColor(XYGraphMediaFactory.getInstance() .getColor(DEFAULT_TRACES_COLOR[traceNum % DEFAULT_TRACES_COLOR.length])); ++traceNum; } if (legendMap.containsKey(trace.getYAxis())) legendMap.get(trace.getYAxis()).addTrace(trace); else { legendMap.put(trace.getYAxis(), new Legend((IXYGraph) this)); legendMap.get(trace.getYAxis()).addTrace(trace); add(legendMap.get(trace.getYAxis())); } plotArea.addTrace(trace); trace.setXYGraph((IXYGraph) this); trace.dataChanged(null); revalidate(); repaint(); } /** * Remove a trace. * * @param trace */ public void removeTrace(Trace trace) { if (legendMap.containsKey(trace.getYAxis())) { legendMap.get(trace.getYAxis()).removeTrace(trace); if (legendMap.get(trace.getYAxis()).getTraceList().size() <= 0) { remove(legendMap.remove(trace.getYAxis())); } } plotArea.removeTrace(trace); trace.getXAxis().removeTrace(trace); trace.getYAxis().removeTrace(trace); revalidate(); repaint(); } /** * Add an annotation * * @param annotation */ public void addAnnotation(Annotation annotation) { plotArea.addAnnotation(annotation); } /** * Remove an annotation * * @param annotation */ public void removeAnnotation(Annotation annotation) { plotArea.removeAnnotation(annotation); } /** * @param titleFont * the titleFont to set */ public void setTitleFont(Font titleFont) { titleLabel.setFont(titleFont); titleFontData = titleFont.getFontData()[0]; } /** * @return the title font. */ public Font getTitleFont() { return titleLabel.getFont(); } public FontData getTitleFontData() { return titleFontData; } /** * @param titleColor * the titleColor to set */ public void setTitleColor(Color titleColor) { this.titleColor = titleColor; titleLabel.setForegroundColor(titleColor); this.titleColorRgb = titleColor.getRGB(); } /** * {@inheritDoc} */ public void paintFigure(final Graphics graphics) { if (!transparent) { graphics.fillRectangle(getClientArea()); } super.paintFigure(graphics); } /** * @param transparent * the transparent to set */ public void setTransparent(boolean transparent) { this.transparent = transparent; getPlotArea().setOpaque(!transparent); repaint(); } /** * @return the transparent */ public boolean isTransparent() { return transparent; } /** * @return the plotArea, which contains all the elements drawn inside it. */ public PlotArea getPlotArea() { return plotArea; } /** @return Image of the XYFigure. Receiver must dispose. */ public Image getImage() { return SingleSourceHelper2.getXYGraphSnapShot(this); } /** * @return the titleColor */ public Color getTitleColor() { if (titleColor == null) return getForegroundColor(); return titleColor; } public RGB getTitleColorRgb() { return titleColorRgb; } /** * @return the title */ public String getTitle() { return title; } /** * @return the operationsManager */ public OperationsManager getOperationsManager() { return operationsManager; } /** * @return the xAxisList */ public List<Axis> getXAxisList() { return xAxisList; } /** * @return the yAxisList */ public List<Axis> getYAxisList() { return yAxisList; } /** * @return the all the axis include xAxes and yAxes. yAxisList is appended * to xAxisList in the returned list. */ public List<Axis> getAxisList() { List<Axis> list = new ArrayList<Axis>(); list.addAll(xAxisList); list.addAll(yAxisList); return list; } /** * @return the legendMap */ public Map<Axis, Legend> getLegendMap() { return legendMap; } /** * Perform forced autoscale to all axes. */ public void performAutoScale() { final ZoomCommand command = new ZoomCommand("Auto Scale", xAxisList, yAxisList); for (Axis axis : xAxisList) { axis.performAutoScale(true); } for (Axis axis : yAxisList) { axis.performAutoScale(true); } command.saveState(); operationsManager.addCommand(command); } /** * Stagger all axes: Autoscale each axis so that traces on various axes * don't overlap */ public void performStagger() { final double GAP = 0.1; final ZoomCommand command = new ZoomCommand("Stagger Axes", null, yAxisList); // Arrange all axes so they don't overlap by assigning 1/Nth of // the vertical range to each one final int N = yAxisList.size(); for (int i = 0; i < N; ++i) { final Axis yaxis = yAxisList.get(i); // Does axis handle itself in another way? if (yaxis.isAutoScale()) continue; // Determine range of values on this axis final Range axis_range = yaxis.getTraceDataRange(); // Skip axis which for some reason cannot determine its range if (axis_range == null) continue; double low = axis_range.getLower(); double high = axis_range.getUpper(); if (low == high) { // Center trace with constant value (empty range) final double half = Math.abs(low / 2); low -= half; high += half; } if (yaxis.isLogScaleEnabled()) { // Transition into log space low = Log10.log10(low); high = Log10.log10(high); } double span = high - low; // Make some extra space low -= GAP * span; high += GAP * span; span = high - low; // With N axes, assign 1/Nth of the vertical plot space to this axis // by shifting the span down according to the axis index, // using a total of N*range. low -= (N - i - 1) * span; high += i * span; if (yaxis.isLogScaleEnabled()) { // Revert from log space low = Log10.pow10(low); high = Log10.pow10(high); } // Sanity check for empty traces if (low < high && !Double.isInfinite(low) && !Double.isInfinite(high)) yaxis.setRange(low, high); } command.saveState(); operationsManager.addCommand(command); } public Axis getPrimaryXAxis() { if (xAxisList.size() > 0) { return xAxisList.get(0); } return null; } public Axis getPrimaryYAxis() { if (yAxisList.size() > 0) { return yAxisList.get(0); } return null; } }