
org.eclipse.swtchart.support.CircularLegend Maven / Gradle / Ivy
Show all versions of rwtchart Show documentation
/*******************************************************************************
* Copyright (c) 2020 SWTChart project.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Himanshu Balasamanta: Orignal API and implementation
*******************************************************************************/
package org.eclipse.swtchart.support;
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.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swtchart.Chart;
import org.eclipse.swtchart.Constants;
import org.eclipse.swtchart.ICircularSeries;
import org.eclipse.swtchart.ILegend;
import org.eclipse.swtchart.ISeries;
import org.eclipse.swtchart.internal.ChartLayoutData;
import org.eclipse.swtchart.internal.Legend;
import org.eclipse.swtchart.internal.Title;
import org.eclipse.swtchart.internal.Util;
import org.eclipse.swtchart.internal.series.Series;
/**
* A legend for chart.
*/
public class CircularLegend extends Canvas implements ILegend, PaintListener {
/** the plot chart */
private Composite composite;
private ISeries>[] series;
private Title title;
/** 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; // it's not used yet.
/** 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 cellBounds;
/**
* Constructor.
*
* @param composite
* the chart
* @param style
* the style
*/
public CircularLegend(Composite composite, int style) {
super(composite, style | SWT.DOUBLE_BUFFERED);
this.composite = composite;
visible = true;
position = DEFAULT_POSITION;
cellBounds = new HashMap();
defaultFont = new Font(Display.getDefault(), "Tahoma", DEFAULT_FONT_SIZE, SWT.NORMAL); //$NON-NLS-1$
setFont(defaultFont);
setForeground(DEFAULT_FOREGROUND);
setBackground(DEFAULT_BACKGROUND);
addPaintListener(this);
}
@Override
public void setVisible(boolean visible) {
if(this.visible == visible) {
return;
}
this.visible = visible;
}
@Override
public boolean isVisible() {
return visible;
}
/**
* @see org.eclipse.swtchart.ILegend#isExtended()
*/
@Override
public boolean isExtended()
{
return false;
}
/**
* @see org.eclipse.swtchart.ILegend#setExtended(boolean)
*/
@Override
public void setExtended(boolean extended)
{
// do nothing - extended mode not supported for this legend type
}
@Override
public void setFont(Font font) {
if(font == null) {
super.setFont(defaultFont);
} else {
super.setFont(font);
}
}
@Override
public void setForeground(Color color) {
if(color == null) {
super.setForeground(DEFAULT_FOREGROUND);
} else {
super.setForeground(color);
}
}
@Override
public void setBackground(Color color) {
if(color == null) {
super.setBackground(DEFAULT_BACKGROUND);
} else {
super.setBackground(color);
}
}
@Override
public int getPosition() {
return position;
}
@Override
public void setPosition(int value) {
if(value == SWT.LEFT || value == SWT.RIGHT || value == SWT.TOP || value == SWT.BOTTOM) {
position = value;
} else {
position = DEFAULT_POSITION;
}
}
@Override
public Rectangle getBounds(String seriesId) {
if(seriesId == null) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
return cellBounds.get(seriesId.trim());
}
@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.
*
* 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>> map = new HashMap>>();
for(ISeries> series : seriesArray) {
int axisId = series.getXAxisId();
List> list = map.get(axisId);
if(list == null) {
list = new ArrayList>();
}
list.add(series);
map.put(axisId, list);
}
// sort an each series list
List> sortedArray = new ArrayList>();
for(Entry>> entry : map.entrySet()) {
sortedArray.addAll(sort(entry.getValue(), false, true));
}
return sortedArray.toArray(new ISeries[sortedArray.size()]);
}
/**
* Sorts the given series list which belongs to a certain x axis.
*
* - The stacked series will be gathered, and the order of stack series
* will be reversed.
* - In the case of vertical orientation, the order of whole series will
* be reversed.
*
*
* @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 static List> sort(List> seriesList, boolean isCategoryEnabled, boolean isVertical) {
List> sortedArray = new ArrayList>();
// 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(series);
Rectangle r = composite.getClientArea();
Rectangle titleBounds = (title).getBounds();
int titleHeight = titleBounds.y + titleBounds.height;
int cellHeight = Util.getExtentInGC(getFont(), null).y;
if(position == SWT.RIGHT || position == SWT.LEFT) {
int columns = 1;
int yPosition = MARGIN;
int maxCellWidth = 0;
for(ISeries> series : seriesArray) {
if(!series.isVisibleInLegend()) {
continue;
}
if(series instanceof ICircularSeries) {
if(((ICircularSeries>)series).getLabels() != null) {
String[] labels = ((ICircularSeries>)series).getLabels();
for(int i = 0; i != labels.length; i++) {
int textWidth = Util.getExtentInGC(getFont(), labels[i]).x;
int cellWidth = textWidth + SYMBOL_WIDTH + MARGIN * 3;
maxCellWidth = Math.max(maxCellWidth, cellWidth);
if(yPosition + cellHeight < r.height - titleHeight - MARGIN || yPosition == MARGIN) {
yPosition += cellHeight + MARGIN;
} else {
columns++;
yPosition = cellHeight + MARGIN * 2;
}
cellBounds.put(labels[i], new Rectangle(maxCellWidth * (columns - 1), yPosition - cellHeight - MARGIN, cellWidth, cellHeight));
height = Math.max(yPosition, height);
}
width = maxCellWidth * columns;
continue;
}
}
}
width = maxCellWidth * columns;
} else if(position == SWT.TOP || position == SWT.BOTTOM) {
int rows = 1;
int xPosition = 0;
for(ISeries> series : seriesArray) {
if(!series.isVisibleInLegend()) {
continue;
}
if(series instanceof ICircularSeries) {
if(((ICircularSeries>)series).getLabels() != null) {
String[] labels = ((ICircularSeries>)series).getLabels();
for(int i = 0; i != labels.length; i++) {
int textWidth = Util.getExtentInGC(getFont(), labels[i]).x;
int cellWidth = textWidth + SYMBOL_WIDTH + MARGIN * 3;
if(xPosition + cellWidth < r.width || xPosition == 0) {
xPosition += cellWidth;
} else {
rows++;
xPosition = cellWidth;
}
cellBounds.put(labels[i], new Rectangle(xPosition - cellWidth, (cellHeight + MARGIN) * (rows - 1) + MARGIN, cellWidth, cellHeight));
width = Math.max(xPosition, width);
}
height = (cellHeight + MARGIN) * rows + MARGIN;
continue;
}
}
}
height = (cellHeight + MARGIN) * rows + MARGIN;
}
setLayoutData(new ChartLayoutData(width, height));
}
/**
* Gets the legend label.
*
* @param series
* the series
* @return the legend label
*/
private static String getLegendLabel(ISeries> series) {
String description = series.getDescription();
if(description == null) {
return series.getId();
}
return description;
}
/**
* 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) {
return;
}
@Override
public void paintControl(PaintEvent e) {
if(!visible) {
return;
}
GC gc = e.gc;
gc.setFont(getFont());
ISeries>[] seriesArray = series;
if(seriesArray.length == 0) {
return;
}
// draw frame
gc.fillRectangle(0, 0, getSize().x - 1, getSize().y - 1);
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++) {
if(!seriesArray[i].isVisibleInLegend()) {
continue;
}
//
if(seriesArray[i] instanceof ICircularSeries) {
ICircularSeries> pieSeries = (ICircularSeries>)seriesArray[i];
String[] labels = pieSeries.getLabels();
Color[] color = pieSeries.getColors();
for(int j = 0; j != labels.length; j++) {
Rectangle r = cellBounds.get(labels[j]);
if(r != null) {
String labelPie = labels[j];
Color colorPie = color[j];
if(labelPie != null && colorPie != null) {
drawPieSymbol(gc, labelPie, colorPie, new Rectangle(r.x + MARGIN, r.y + MARGIN, SYMBOL_WIDTH, r.height - MARGIN * 2));
gc.setBackground(getBackground());
gc.setForeground(getForeground());
gc.drawText(labelPie, r.x + SYMBOL_WIDTH + MARGIN * 2, r.y, true);
}
}
}
} else {
// 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 label
String label = getLegendLabel(seriesArray[i]);
gc.setBackground(getBackground());
gc.setForeground(getForeground());
gc.drawText(label, r.x + SYMBOL_WIDTH + MARGIN * 2, r.y, true);
}
}
}
private void drawPieSymbol(GC gc, String string, Color color, Rectangle r) {
gc.setBackground(color);
int size = SYMBOL_WIDTH / 2;
int x = r.x + size / 2;
int y = (int)(r.y - size / 2d + r.height / 2d);
gc.fillArc(x, y, size, size, 0, 360);
}
public void setChart(Chart chart) {
this.series = chart.getSeriesSet().getSeries();
this.title = (Title)chart.getTitle();
}
}