package name.abuchen.portfolio.ui.views.taxonomy; import java.util.Iterator; import javax.inject.Inject; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Transform; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import de.engehausen.treemap.IColorProvider; import de.engehausen.treemap.ILabelProvider; import de.engehausen.treemap.IRectangle; import de.engehausen.treemap.IRectangleRenderer; import de.engehausen.treemap.ITreeModel; import de.engehausen.treemap.IWeightedTreeModel; import de.engehausen.treemap.impl.SquarifiedLayout; import de.engehausen.treemap.swt.TreeMap; import name.abuchen.portfolio.money.Values; import name.abuchen.portfolio.ui.util.Colors; import name.abuchen.portfolio.ui.util.SWTHelper; import name.abuchen.portfolio.ui.views.SecurityDetailsViewer; /* package */class TreeMapViewer extends AbstractChartPage { private TreeMap<TaxonomyNode> treeMap; private TreeMapLegend legend; @Inject public TreeMapViewer(TaxonomyModel model, TaxonomyNodeRenderer renderer) { super(model, renderer); } @Override public void beforePage() {} @Override public void afterPage() {} @Override public void nodeChange(TaxonomyNode node) { onConfigChanged(); } @Override public void onConfigChanged() { treeMap.setTreeModel(new Model(getModel())); legend.setRootItem(getModel().getVirtualRootNode()); } @Override public Control createControl(Composite parent) { SashForm sash = new SashForm(parent, SWT.HORIZONTAL); sash.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); Composite container = new Composite(sash, SWT.NONE); container.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); treeMap = new TreeMap<>(container); treeMap.setTreeMapLayout(new SquarifiedLayout<TaxonomyNode>(10)); treeMap.setLabelProvider((model, rectangle) -> rectangle.getNode().getName()); legend = new TreeMapLegend(container, treeMap, getModel(), getRenderer()); final SecurityDetailsViewer details = new SecurityDetailsViewer(sash, SWT.NONE, getModel().getClient(), true); treeMap.addSelectionChangeListener((model, rectangle, label) -> { TaxonomyNode node = rectangle.getNode(); details.setInput(node.getBackingSecurity()); }); // layout tree map + legend GridLayoutFactory.fillDefaults().numColumns(1).margins(10, 10).applyTo(container); GridDataFactory.fillDefaults().grab(true, true).applyTo(treeMap); GridDataFactory.fillDefaults().grab(true, false).applyTo(legend); // layout sash SWTHelper.setSashWeights(sash, parent.getParent().getParent(), details.getControl()); treeMap.setRectangleRenderer(new ClassificationRectangleRenderer(getModel(), getRenderer())); treeMap.setTreeModel(new Model(getModel())); legend.setRootItem(getModel().getChartRenderingRootNode()); return sash; } private static class Model implements IWeightedTreeModel<TaxonomyNode> { private TaxonomyModel model; public Model(TaxonomyModel model) { this.model = model; } @Override public Iterator<TaxonomyNode> getChildren(TaxonomyNode item) { return item.getChildren().iterator(); } @Override public TaxonomyNode getParent(TaxonomyNode item) { if (getRoot().equals(item)) return null; else return item.getParent(); } @Override public TaxonomyNode getRoot() { return model.getChartRenderingRootNode(); } @Override public boolean hasChildren(TaxonomyNode item) { return !item.getChildren().isEmpty(); } @Override public long getWeight(TaxonomyNode item) { return item.getActual().getAmount(); } } private static class ClassificationRectangleRenderer implements IRectangleRenderer<TaxonomyNode, PaintEvent, Color> { private TaxonomyModel model; private TaxonomyNodeRenderer colorProvider; public ClassificationRectangleRenderer(TaxonomyModel model, TaxonomyNodeRenderer colorProvider) { this.model = model; this.colorProvider = colorProvider; } @Override public void render(final PaintEvent event, final ITreeModel<IRectangle<TaxonomyNode>> model, final IRectangle<TaxonomyNode> rectangle, final IColorProvider<TaxonomyNode, Color> colorProvider, final ILabelProvider<TaxonomyNode> labelProvider) { TaxonomyNode item = rectangle.getNode(); if (item.getClassification() != null) return; Color oldForeground = event.gc.getForeground(); Color oldBackground = event.gc.getBackground(); Rectangle r = new Rectangle(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight()); this.colorProvider.drawRectangle(model.getRoot().getNode(), item, event.gc, r); String label = item.getName(); double total = this.model.getChartRenderingRootNode().getActual().getAmount(); String info = String.format("%s (%s%%)", Values.Money.format(item.getActual()), //$NON-NLS-1$ Values.Percent.format(item.getActual().getAmount() / total)); event.gc.setForeground(Colors.getTextColor(event.gc.getBackground())); Point labelExtend = event.gc.textExtent(label); Point infoExtend = event.gc.textExtent(info); int width = Math.max(labelExtend.x, infoExtend.x); if (width <= rectangle.getWidth() || rectangle.getWidth() > rectangle.getHeight()) { event.gc.drawText(label, r.x + 2, r.y + 2, true); event.gc.drawText(info, r.x + 2, r.y + 2 + labelExtend.y, true); } else { final Transform transform = new Transform(event.display); try { transform.translate(r.x, r.y); transform.rotate(-90); event.gc.setTransform(transform); event.gc.drawString(label, -labelExtend.x - 2, 2, true); event.gc.drawString(info, -infoExtend.x - 2, 2 + labelExtend.y, true); } finally { transform.dispose(); } } event.gc.setForeground(oldForeground); event.gc.setBackground(oldBackground); } @Override public void highlight(final PaintEvent event, final ITreeModel<IRectangle<TaxonomyNode>> model, final IRectangle<TaxonomyNode> rectangle, final IColorProvider<TaxonomyNode, Color> colorProvider, final ILabelProvider<TaxonomyNode> labelProvider) { event.gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); event.gc.drawRectangle(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight()); } } }