package name.abuchen.portfolio.ui.util; import java.util.Comparator; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; public class PieChart extends Composite implements Listener { public static class Slice { public static class ByValue implements Comparator<Slice> { @Override public int compare(Slice o1, Slice o2) { return o1.value > o2.value ? -1 : o1.value == o2.value ? 0 : 1; } } private long value; private String label; private Color color; public Slice(long value, String label, Color color) { this.value = value; this.label = label; this.color = color; } public Slice(long value, String label) { this(value, label, null); } public long getValue() { return value; } public String getLabel() { return label; } public Color getColor() { return color; } public void setColor(Color color) { this.color = color; } } private static final int PADDING = 40; private Image image; private boolean updateImage; private List<Slice> slices; public PieChart(Composite parent, int style) { super(parent, style); setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WHITE)); addListener(SWT.Paint, this); addListener(SWT.Resize, this); } public void setSlices(List<Slice> slices) { this.slices = slices; this.updateImage = true; } public void handleEvent(Event event) { switch (event.type) { case SWT.Paint: paintControl(event); break; case SWT.Resize: redraw(); break; default: break; } } @Override public void update() { super.update(); updateImage = true; } @Override public void redraw() { super.redraw(); updateImage = true; } @Override public void dispose() { super.dispose(); if (image != null && !image.isDisposed()) image.dispose(); } private void paintControl(Event e) { if (updateImage) { Point size = getSize(); if (image != null && !image.isDisposed()) image.dispose(); image = new Image(Display.getCurrent(), size.x, size.y); GC gc = new GC(image); gc.setAntialias(SWT.ON); int total = 0; for (Slice slice : slices) total += slice.getValue(); final int centerX = size.x / 2; final int centerY = size.y / 2; final int diameter = Math.min(size.x, size.y) - 2 * PADDING; final int radius = diameter / 2; // background gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); gc.fillRectangle(0, 0, size.x, size.y); // slices int startAngle = 0; for (Slice slice : slices) { int arcAngle = (int) ((double) slice.getValue() * 360 / total + 0.5d); if (slice == slices.get(slices.size() - 1)) arcAngle = 360 - startAngle; gc.setBackground(slice.getColor()); gc.fillArc(centerX - radius, centerY - radius, diameter, diameter, startAngle, arcAngle); startAngle += arcAngle; } // circle gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_GRAY)); gc.drawOval(centerX - radius, centerY - radius, diameter, diameter); // edges startAngle = 0; for (Slice slice : slices) { int arcAngle = (int) ((double) slice.getValue() * 360 / total + 0.5d); if (slice == slices.get(slices.size() - 1)) arcAngle = 360 - startAngle; double angleRad = startAngle * Math.PI / 180.0d; int x = centerX + (int) (radius * Math.cos(-angleRad)); int y = centerY + (int) (radius * Math.sin(-angleRad)); gc.drawLine(centerX, centerY, x, y); startAngle += arcAngle; } // labels startAngle = 0; for (Slice slice : slices) { int arcAngle = (int) ((double) slice.getValue() * 360 / total + 0.5d); if (slice == slices.get(slices.size() - 1)) arcAngle = 360 - startAngle; if (arcAngle > 1) { // percentage double angleRad = (startAngle + arcAngle / 2) * Math.PI / 180.0d; int x = (size.x / 2) + (int) (radius / 1.2 * Math.cos(-angleRad)); int y = (size.y / 2) + (int) (radius / 1.2 * Math.sin(-angleRad)); String label = String.format("%,.2f%%", (double) slice.getValue() / total * 100); //$NON-NLS-1$ Point extend = gc.stringExtent(label); gc.setForeground(Colors.getTextColor(slice.getColor())); gc.drawString(label, x - extend.x / 2, y - extend.y / 2, true); // label x = (size.x / 2) + (int) ((radius + 10) * Math.cos(-angleRad)); y = (size.y / 2) + (int) ((radius + 10) * Math.sin(-angleRad)); extend = gc.stringExtent(slice.getLabel()); if (x < centerX) x -= extend.x; gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK)); gc.drawString(slice.getLabel(), x, y - extend.y / 2, true); } startAngle += arcAngle; } updateImage = false; } e.gc.drawImage(image, 0, 0); } }