/******************************************************************************* * Copyright (c) 2008-2011 SWTChart project. All rights reserved. * * This code is distributed under the terms of the Eclipse Public License v1.0 * which is available at http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.swtchart.internal; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.swt.SWT; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.swtchart.Chart; import org.swtchart.Constants; import org.swtchart.IBarSeries; import org.swtchart.ILegend; import org.swtchart.ILineSeries; import org.swtchart.ISeries; import org.swtchart.internal.series.LineSeries; import org.swtchart.internal.series.Series; /** * A legend for chart. */ public class Legend extends Composite implements ILegend, PaintListener { /** the plot chart */ private Chart chart; /** the state indicating the legend visibility */ private boolean visible; /** the position of legend */ private int position; /** the margin */ private static final int MARGIN = 5; /** the width of area to draw symbol */ private static final int SYMBOL_WIDTH = 20; /** the line width */ private static final int LINE_WIDTH = 2; /** the default foreground */ private static final Color DEFAULT_FOREGROUND = Display.getDefault() .getSystemColor(SWT.COLOR_BLACK); /** the default background */ private static final Color DEFAULT_BACKGROUND = Display.getDefault() .getSystemColor(SWT.COLOR_WHITE); /** the default font */ private Font defaultFont; /** the default font size */ private static final int DEFAULT_FONT_SIZE = Constants.SMALL_FONT_SIZE; /** the default position */ private static final int DEFAULT_POSITION = SWT.RIGHT; /** the map between series id and cell bounds */ private Map<String, Rectangle> cellBounds; /** * Constructor. * * @param chart * the chart * @param style * the style */ public Legend(Chart chart, int style) { super(chart, style); this.chart = chart; visible = true; position = DEFAULT_POSITION; cellBounds = new HashMap<String, Rectangle>(); defaultFont = new Font(Display.getDefault(), "Tahoma", DEFAULT_FONT_SIZE, SWT.NORMAL); setFont(defaultFont); setForeground(DEFAULT_FOREGROUND); setBackground(DEFAULT_BACKGROUND); addPaintListener(this); } /* * @see Control#setVisible(boolean) */ @Override public void setVisible(boolean visible) { if (this.visible == visible) { return; } this.visible = visible; chart.updateLayout(); } /* * @see Control#isVisible() */ @Override public boolean isVisible() { return visible; } /* * @see Canvas#setFont(Font) */ @Override public void setFont(Font font) { if (font == null) { super.setFont(defaultFont); } else { super.setFont(font); } chart.updateLayout(); } /* * @see Control#setForeground(Color) */ @Override public void setForeground(Color color) { if (color == null) { super.setForeground(DEFAULT_FOREGROUND); } else { super.setForeground(color); } } /* * @see Control#setBackground(Color) */ @Override public void setBackground(Color color) { if (color == null) { super.setBackground(DEFAULT_BACKGROUND); } else { super.setBackground(color); } } /* * @see ILegend#getPosition() */ public int getPosition() { return position; } /* * @see ILegend#setPosition(int) */ public void setPosition(int value) { if (value == SWT.LEFT || value == SWT.RIGHT || value == SWT.TOP || value == SWT.BOTTOM) { position = value; } else { position = DEFAULT_POSITION; } chart.updateLayout(); } /* * @see ILegend#getBounds(String) */ public Rectangle getBounds(String seriesId) { return cellBounds.get(seriesId); } /* * @see Widget#dispose() */ @Override public void dispose() { super.dispose(); if (!defaultFont.isDisposed()) { defaultFont.dispose(); } } /** * Sorts the given series array. For instance, if there are two stack series * in horizontal orientation, the top of stack series should appear at top * of legend. * <p> * If there are multiple x axes, the given series array will be sorted with * x axis first. And then, the series in each x axis will be sorted with * {@link Legend#sort(List, boolean, boolean)}. * * @param seriesArray * the series array * @return the sorted series array */ private ISeries[] sort(ISeries[] seriesArray) { // create a map between axis id and series list Map<Integer, List<ISeries>> map = new HashMap<Integer, List<ISeries>>(); for (ISeries series : seriesArray) { int axisId = series.getXAxisId(); List<ISeries> list = map.get(axisId); if (list == null) { list = new ArrayList<ISeries>(); } list.add(series); map.put(axisId, list); } // sort an each series list List<ISeries> sortedArray = new ArrayList<ISeries>(); boolean isVertical = chart.getOrientation() == SWT.VERTICAL; for (Entry<Integer, List<ISeries>> entry : map.entrySet()) { boolean isCategoryEnabled = chart.getAxisSet().getXAxis( entry.getKey()).isCategoryEnabled(); sortedArray.addAll(sort(entry.getValue(), isCategoryEnabled, isVertical)); } return sortedArray.toArray(new ISeries[0]); } /** * Sorts the given series list which belongs to a certain x axis. * <ul> * <li>The stacked series will be gathered, and the order of stack series * will be reversed.</li> <li>In the case of vertical orientation, the order * of whole series will be reversed.</li> * </ul> * * @param seriesList * the series list which belongs to a certain x axis * @param isCategoryEnabled * true if category is enabled * @param isVertical * true in the case of vertical orientation * @return the sorted series array */ private List<ISeries> sort(List<ISeries> seriesList, boolean isCategoryEnabled, boolean isVertical) { List<ISeries> sortedArray = new ArrayList<ISeries>(); // gather the stacked series reversing the order of stack series int insertIndex = -1; for (int i = 0; i < seriesList.size(); i++) { if (isCategoryEnabled && ((Series) seriesList.get(i)).isValidStackSeries()) { if (insertIndex == -1) { insertIndex = i; } else { sortedArray.add(insertIndex, seriesList.get(i)); continue; } } sortedArray.add(seriesList.get(i)); } // reverse the order of whole series in the case of vertical orientation if (isVertical) { Collections.reverse(sortedArray); } return sortedArray; } /** * Update the layout data. */ public void updateLayoutData() { if (!visible) { setLayoutData(new ChartLayoutData(0, 0)); return; } int width = 0; int height = 0; ISeries[] seriesArray = sort(chart.getSeriesSet().getSeries()); Rectangle r = chart.getClientArea(); int titleHeight = ((Composite) chart.getTitle()).getSize().y; int cellHeight = Util.getExtentInGC(getFont(), "dummy").y; if (position == SWT.RIGHT || position == SWT.LEFT) { int columns = 1; int yPosition = MARGIN; int maxCellWidth = 0; for (ISeries series : seriesArray) { int textWidth = Util.getExtentInGC(getFont(), series.getId()).x; int cellWidth = textWidth + SYMBOL_WIDTH + MARGIN * 3; maxCellWidth = Math.max(maxCellWidth, cellWidth); if (yPosition + cellHeight < r.height - titleHeight || yPosition == MARGIN) { yPosition += cellHeight + MARGIN; } else { columns++; yPosition = cellHeight + MARGIN * 2; } cellBounds.put(series.getId(), new Rectangle(maxCellWidth * (columns - 1), yPosition - cellHeight - MARGIN, cellWidth, cellHeight)); height = Math.max(yPosition, height); } width = maxCellWidth * columns; } else if (position == SWT.TOP || position == SWT.BOTTOM) { int rows = 1; int xPosition = 0; for (ISeries series : seriesArray) { int textWidth = Util.getExtentInGC(getFont(), series.getId()).x; int cellWidth = textWidth + SYMBOL_WIDTH + MARGIN * 3; if (xPosition + cellWidth < r.width || xPosition == 0) { xPosition += cellWidth; } else { rows++; xPosition = cellWidth; } cellBounds.put(series.getId(), new Rectangle(xPosition - cellWidth, (cellHeight + MARGIN) * (rows - 1) + MARGIN, cellWidth, cellHeight)); width = Math.max(xPosition, width); } height = (cellHeight + MARGIN) * rows + MARGIN; } setLayoutData(new ChartLayoutData(width, height)); } /** * Draws the symbol of series. * * @param gc * the graphics context * @param series * the series * @param r * the rectangle to draw the symbol of series */ protected void drawSymbol(GC gc, Series series, Rectangle r) { if (!visible) { return; } if (series instanceof ILineSeries) { // draw plot line gc.setForeground(((ILineSeries) series).getLineColor()); gc.setLineWidth(LINE_WIDTH); int lineStyle = Util.getIndexDefinedInSWT(((ILineSeries) series) .getLineStyle()); int x = r.x; int y = r.y + r.height / 2; if (lineStyle != SWT.NONE) { gc.setLineStyle(lineStyle); gc.drawLine(x, y, x + SYMBOL_WIDTH, y); } // draw series symbol Color color = ((ILineSeries) series).getSymbolColor(); Color[] colors = ((ILineSeries) series).getSymbolColors(); if (colors != null && colors.length > 0) { color = colors[0]; } ((LineSeries) series).drawSeriesSymbol(gc, x + SYMBOL_WIDTH / 2, y, color); } else if (series instanceof IBarSeries) { // draw riser gc.setBackground(((IBarSeries) series).getBarColor()); int size = SYMBOL_WIDTH / 2; int x = r.x + size / 2; int y = (int) (r.y - size / 2d + r.height / 2d); gc.fillRectangle(x, y, size, size); } } /* * @see PaintListener#paintControl(PaintEvent) */ public void paintControl(PaintEvent e) { if (!visible) { return; } GC gc = e.gc; gc.setFont(getFont()); ISeries[] seriesArray = chart.getSeriesSet().getSeries(); if (seriesArray.length == 0) { return; } // draw frame gc.setLineStyle(SWT.LINE_SOLID); gc.setLineWidth(1); gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_GRAY)); gc.drawRectangle(0, 0, getSize().x - 1, getSize().y - 1); // draw content for (int i = 0; i < seriesArray.length; i++) { // draw plot line, symbol etc String id = seriesArray[i].getId(); Rectangle r = cellBounds.get(id); drawSymbol(gc, (Series) seriesArray[i], new Rectangle(r.x + MARGIN, r.y + MARGIN, SYMBOL_WIDTH, r.height - MARGIN * 2)); // draw plot id gc.setBackground(getBackground()); gc.setForeground(getForeground()); gc.drawText(id, r.x + SYMBOL_WIDTH + MARGIN * 2, r.y, true); } } }