blob: bd5f3b2129ef41a3eea664ad84a680ea75851ed8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 IBM Corporation.
* 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:
* IBM Corporation - Renato Stoffalette Joao <rsjoao@br.ibm.com>
*******************************************************************************/
package org.eclipse.linuxtools.dataviewers.piechart;
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.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.swtchart.IBarSeries;
import org.swtchart.ISeries;
import org.swtchart.Range;
public class PieChartPaintListener implements PaintListener {
private PieChart chart;
private Control plotArea;
private double[][] seriesValues;
private String[] seriesNames;
private static final int X_GAP = 10;
private static final Color WHITE = Display.getDefault().getSystemColor(SWT.COLOR_WHITE);
private static final Color BLACK = Display.getDefault().getSystemColor(SWT.COLOR_BLACK);
private static final String FONT = "Arial"; //$NON-NLS-1$
private Point[] pieCenters;
private int[][] pieSliceAngles;
private int pieWidth;
/**
* Handles drawing and updating of a PieChart, with titles given to its legend and
* to each of its pies. Pies will be drawn in the given chart's plot area.
* @param chart The PieChart to draw and update.
* @since 2.0
*/
public PieChartPaintListener(PieChart chart) {
this.chart = chart;
this.plotArea = chart.getPlotArea();
}
@Override
public void paintControl(PaintEvent e) {
GC gc = e.gc;
Rectangle bounds;
this.getPieSeriesArray();
pieCenters = new Point[seriesValues.length];
pieSliceAngles = new int[seriesValues.length][];
if (seriesValues.length == 0) {
bounds = gc.getClipping();
Font font = new Font(Display.getDefault(), FONT, 15, SWT.BOLD);
gc.setForeground(BLACK);
gc.setFont(font);
String text = "No data"; //$NON-NLS-1$
Point textSize = e.gc.textExtent(text);
gc.drawText(text, (bounds.width - textSize.x) / 2, (bounds.height - textSize.y) / 2);
font.dispose();
return;
}
bounds = plotArea.getBounds();
setTitleBounds(bounds);
int width = bounds.width / seriesValues.length;
int x = bounds.x;
if (chart.getLegend().isVisible()) {
Rectangle legendBounds = ((Control) chart.getLegend()).getBounds();
Font font = new Font(Display.getDefault(), FONT, 10, SWT.BOLD);
gc.setForeground(BLACK);
gc.setFont(font);
String text = chart.getAxisSet().getXAxis(0).getTitle().getText();
Point textSize = e.gc.textExtent(text);
gc.drawText(text, legendBounds.x + (legendBounds.width - textSize.x) / 2, legendBounds.y - textSize.y);
font.dispose();
}
pieWidth = Math.min(width - X_GAP, bounds.height);
for (int i = 0; i < seriesValues.length; i++) {
drawPieChart(e, i, new Rectangle(x, bounds.y, width, bounds.height));
x += width;
}
}
private void setTitleBounds(Rectangle bounds) {
Control title = (Control) chart.getTitle();
Rectangle titleBounds = title.getBounds();
title.setLocation(new Point(bounds.x + (bounds.width - titleBounds.width) / 2, title.getLocation().y));
}
private void drawPieChart(PaintEvent e, int chartnum, Rectangle bounds) {
double series[] = seriesValues[chartnum];
int nelemSeries = series.length;
double sumTotal = 0;
pieSliceAngles[chartnum] = new int[nelemSeries - 1]; // Don't need first angle; it's always 0
for (int i = 0; i < nelemSeries; i++) {
sumTotal += series[i];
}
GC gc = e.gc;
gc.setLineWidth(1);
int pieX = bounds.x + (bounds.width - pieWidth) / 2;
int pieY = bounds.y + (bounds.height - pieWidth) / 2;
pieCenters[chartnum] = new Point(pieX + pieWidth / 2, pieY + pieWidth / 2);
if (sumTotal == 0) {
gc.drawOval(pieX, pieY, pieWidth, pieWidth);
} else {
double factor = 100 / sumTotal;
int sweepAngle = 0;
int incrementAngle = 0;
int initialAngle = 90;
for (int i = 0; i < nelemSeries; i++) {
// Stored angles increase in clockwise direction from 0 degrees at 12:00
if (i > 0) {
pieSliceAngles[chartnum][i - 1] = 90 - initialAngle;
}
gc.setBackground(((IBarSeries) chart.getSeriesSet().getSeries()[i]).getBarColor());
if (i == (nelemSeries - 1)) {
sweepAngle = 360 - incrementAngle;
} else {
double angle = series[i] * factor * 3.6;
sweepAngle = (int) Math.round(angle);
}
gc.fillArc(pieX, pieY, pieWidth, pieWidth, initialAngle, (-sweepAngle));
gc.drawArc(pieX, pieY, pieWidth, pieWidth, initialAngle, (-sweepAngle));
incrementAngle += sweepAngle;
initialAngle += (-sweepAngle);
}
gc.drawLine(pieCenters[chartnum].x, pieCenters[chartnum].y, pieCenters[chartnum].x, pieCenters[chartnum].y - pieWidth / 2);
}
Font font = new Font(Display.getDefault(), FONT, 12, SWT.BOLD);
gc.setForeground(BLACK);
gc.setBackground(WHITE);
gc.setFont(font);
String text = seriesNames[chartnum];
Point textSize = e.gc.textExtent(text);
gc.drawText(text, pieX + (pieWidth - textSize.x) / 2, pieY + pieWidth + textSize.y);
font.dispose();
}
private void getPieSeriesArray() {
ISeries series[] = this.chart.getSeriesSet().getSeries();
if (series == null || series.length == 0) {
seriesValues = new double[0][0];
seriesNames = new String[0];
return;
}
String names[] = this.chart.getAxisSet().getXAxis(0).getCategorySeries();
Range range = chart.getAxisSet().getXAxis(0).getRange();
int itemRange = (int) range.upper - (int) range.lower + 1;
int itemOffset = (int) range.lower;
seriesValues = new double[itemRange][series.length];
seriesNames = new String[itemRange];
for (int i = 0; i < seriesValues.length; i++) {
seriesNames[i] = names[i + itemOffset];
for (int j = 0; j < seriesValues[i].length; j++) {
double d[] = series[j].getXSeries();
if (d != null && d.length > 0) {
seriesValues[i][j] = d[i + itemOffset];
} else {
seriesValues[i][j] = 0;
}
}
}
return;
}
/**
* Given a set of 2D pixel coordinates (typically those of a mouse cursor), return the
* index of the given pie's slice that those coordinates reside in.
* @param x The x-coordinate to test.
* @param y The y-coordinate to test.
* @return The slice that contains the point with coordinates (x,y).
* @since 2.0
*/
public int getSliceIndexFromPosition(int chartnum, int x, int y) {
Range range = chart.getAxisSet().getXAxis(0).getRange();
chartnum -= (int) range.lower;
if (chartnum >= pieCenters.length || chartnum < 0) {
return -1;
}
// Only continue if the point is inside the pie circle
double rad = Math.sqrt(Math.pow(pieCenters[chartnum].x - x, 2) + Math.pow(pieCenters[chartnum].y - y, 2));
if (2 * rad > pieWidth) {
return -1;
}
// Angle is relative to 12:00 position, increases clockwise
double angle = Math.acos((pieCenters[chartnum].y - y) / rad) / Math.PI * 180.0;
if (x - pieCenters[chartnum].x < 0) {
angle = 360 - angle;
}
if (pieSliceAngles[chartnum].length == 0 || angle < pieSliceAngles[chartnum][0]) {
return 0;
}
for (int s = 0; s < pieSliceAngles[chartnum].length - 1; s++) {
if (pieSliceAngles[chartnum][s] <= angle && angle < pieSliceAngles[chartnum][s+1]) {
return s + 1;
}
}
return pieSliceAngles[chartnum].length;
}
}