Mercurial > dive4elements > river
view artifacts/src/main/java/org/dive4elements/river/exports/LegendAggregator.java @ 9555:ef5754ba5573
Implemented legend aggregation based on type of themes.
Added theme-editor style configuration for aggregated legend entries.
Only configured themes get aggregated.
author | gernotbelger |
---|---|
date | Tue, 23 Oct 2018 16:26:48 +0200 |
parents | |
children | f8308db94634 |
line wrap: on
line source
/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde * Software engineering by * Björnsen Beratende Ingenieure GmbH * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt * * This file is Free Software under the GNU AGPL (>=v3) * and comes with ABSOLUTELY NO WARRANTY! Check out the * documentation coming with Dive4Elements River for details. */ package org.dive4elements.river.exports; import java.awt.Font; import java.awt.Paint; import java.awt.Shape; import java.awt.Stroke; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.dive4elements.artifacts.CallContext; import org.dive4elements.river.artifacts.context.RiverContext; import org.dive4elements.river.artifacts.resources.Resources; import org.dive4elements.river.jfree.StyledSeries; import org.dive4elements.river.jfree.StyledXYDataset; import org.dive4elements.river.themes.Theme; import org.dive4elements.river.themes.ThemeDocument; import org.dive4elements.river.themes.ThemeFactory; import org.jfree.chart.LegendItem; import org.jfree.chart.LegendItemCollection; import org.jfree.chart.plot.XYPlot; import org.jfree.data.general.Dataset; import org.jfree.data.xy.XYSeries; import org.jfree.data.xy.XYSeriesCollection; import org.w3c.dom.Document; /** * Builds and adds legend entries to the plot. * * @author Gernot Belger */ public class LegendAggregator { private static final String NULL_THEME_TYPE = ""; //$NON-NLS-1$ private static final String I10N_MERGED = "legend.aggregator.merged"; //$NON-NLS-1$ private final List<Map.Entry<LegendItem, String>> legendItems = new ArrayList<>(); private final Map<String, List<LegendItem>> itemsPerThemeType = new HashMap<>(); private final Font labelFont; private final int aggregationThreshold; public LegendAggregator(final int aggregationThreshold, final Font labelFont) { this.aggregationThreshold = aggregationThreshold; this.labelFont = labelFont; } public void addLegendItem(final String themeType, final LegendItem item) { item.setLabelFont(this.labelFont); this.legendItems.add(new SimpleEntry<>(item, themeType)); final List<LegendItem> perThemeType = getItemsPerThemeType(themeType); perThemeType.add(item); } private List<LegendItem> getItemsPerThemeType(final String themeType) { final String key = themeType == null ? NULL_THEME_TYPE : themeType; if (!this.itemsPerThemeType.containsKey(key)) this.itemsPerThemeType.put(key, new ArrayList<LegendItem>()); return this.itemsPerThemeType.get(key); } /** * Apply the gathered items to the plot. Use only once. */ public void apply(final CallContext context, final XYPlot plot) { final LegendItemCollection aggregatedItems = new LegendItemCollection(); /* marker set for types that are already aggregated */ final Set<String> aggregatedTypes = new HashSet<>(); for (final Entry<LegendItem, String> entry : this.legendItems) { final LegendItem item = entry.getKey(); final String themeType = entry.getValue(); /* ignore already aggregated items */ if (aggregatedTypes.contains(themeType)) continue; /* aggregate known types if count over threshold */ final Theme legendTheme = getLegendTheme(context, themeType); if (legendTheme != null && getItemsPerThemeType(themeType).size() > this.aggregationThreshold) { final String labelDescription = Resources.getMsg(context.getMeta(), legendTheme.getDescription()); final String labelMerged = Resources.getMsg(context.getMeta(), I10N_MERGED); final String label = String.format("%s (%s)", labelDescription, labelMerged); final List<LegendItem> items = findDistinctItems(getItemsPerThemeType(themeType)); /* add items for each distinct shape, only the last one gets the label */ for (final Iterator<LegendItem> iterator = items.iterator(); iterator.hasNext();) { final LegendItem legendItem = iterator.next(); final String itemLabel = iterator.hasNext() ? "," : label; /* create and add aggregated item(s) */ final LegendItem aggregatedItem = createAggregatedItem(legendItem, legendTheme, itemLabel); aggregatedItems.add(aggregatedItem); } /* mark as handles */ aggregatedTypes.add(themeType); } else { /* simply add normal items */ aggregatedItems.add(item); } } plot.setFixedLegendItems(aggregatedItems); this.itemsPerThemeType.clear(); this.legendItems.clear(); } /** * Extract distinct items, curently only those that are different regarding their shape */ private List<LegendItem> findDistinctItems(final List<LegendItem> items) { // HACKY: we hash by unique shape, because we know that the used shapes are cashed in a static cache. final Map<Shape, LegendItem> shapeMap = new IdentityHashMap<>(); for (final LegendItem item : items) { final Shape shape = item.isShapeVisible() ? item.getShape() : null; if (!shapeMap.containsKey(shape)) shapeMap.put(shape, item); } return new ArrayList<>(shapeMap.values()); } private LegendItem createAggregatedItem(final LegendItem item, final Theme legendTheme, final String label) { /* clone properties from current item */ final String description = item.getDescription(); final String tooltipText = item.getToolTipText(); final String urlText = item.getURLText(); final boolean shapeVisible = item.isShapeVisible(); final Shape shape = item.getShape(); final boolean shapeFilled = item.isShapeFilled(); final Paint fillPaint = item.getFillPaint(); final boolean shapeOutlineVisible = item.isShapeOutlineVisible(); final Paint outlinePaint = item.getOutlinePaint(); final Stroke outlineStroke = item.getOutlineStroke(); final boolean lineVisible = item.isLineVisible(); final Shape line = item.getLine(); final Stroke lineStroke = item.getLineStroke(); final Paint linePaint = item.getLinePaint(); final LegendItem aggregatedItem = new LegendItem(label, description, tooltipText, urlText, shapeVisible, shape, shapeFilled, fillPaint, shapeOutlineVisible, outlinePaint, outlineStroke, lineVisible, line, lineStroke, linePaint); aggregatedItem.setDataset(item.getDataset()); aggregatedItem.setDatasetIndex(item.getDatasetIndex()); aggregatedItem.setFillPaintTransformer(item.getFillPaintTransformer()); aggregatedItem.setLabelFont(item.getLabelFont()); aggregatedItem.setLabelPaint(item.getLabelPaint()); aggregatedItem.setSeriesIndex(item.getSeriesIndex()); /* let styled dataset apply specific theme configuration */ applyThemeToLegend(aggregatedItem, legendTheme); return aggregatedItem; } private void applyThemeToLegend(final LegendItem item, final Theme legendTheme) { final Dataset dataset = item.getDataset(); if (dataset == null) return; final Document xml = legendTheme.toXML(); final ThemeDocument themeDocument = new ThemeDocument(xml); if (dataset instanceof StyledXYDataset) { final StyledXYDataset styledDataset = (StyledXYDataset) dataset; styledDataset.applyAggregatedLegendTheme(item, themeDocument); return; } if (dataset instanceof XYSeriesCollection) { final int seriesIndex = item.getSeriesIndex(); final XYSeriesCollection seriesCollection = (XYSeriesCollection) dataset; if (seriesIndex >= 0 && seriesIndex < seriesCollection.getSeriesCount()) { final XYSeries series = seriesCollection.getSeries(seriesIndex); if (series instanceof StyledSeries) { ((StyledSeries) series).applyAggregatedLegendTheme(item, themeDocument); } } } } private Theme getLegendTheme(final CallContext context, final String themeType) { final RiverContext flysContext = context instanceof RiverContext ? (RiverContext) context : (RiverContext) context.globalContext(); return ThemeFactory.getLegendTheme(flysContext, themeType); } }