/** * */ package fr.inria.soctrace.framesoc.ui.piechart.loaders; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.core.runtime.Assert; import org.jfree.data.general.DefaultPieDataset; import org.jfree.data.general.PieDataset; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fr.inria.linuxtools.tmf.core.util.Pair; import fr.inria.soctrace.framesoc.ui.colors.FramesocColor; import fr.inria.soctrace.framesoc.ui.piechart.model.IPieChartLoader; import fr.inria.soctrace.framesoc.ui.piechart.model.MergedItem; import fr.inria.soctrace.framesoc.ui.piechart.model.StatisticsTableFolderRow; import fr.inria.soctrace.framesoc.ui.piechart.model.StatisticsTableRow; /** * Base abstract class for Pie Chart loaders. * * <pre> * It provides the following functionalities: * - aggregating items whose percentage is lower than a threshold * - merging items in a merged item * - excluding items from statistics * </pre> * * @author "Generoso Pagano <generoso.pagano@inria.fr>" */ public abstract class PieChartLoader implements IPieChartLoader { /** * Logger */ private static final Logger logger = LoggerFactory.getLogger(PieChartLoader.class); /** * Threshold for aggregating percentage values (between 0 and 1). */ protected static final Double AGGREGATION_THRESHOLD = 0.01; /** * Label for aggregated slices. */ protected static final String AGGREGATED_LABEL = "Aggregated slices"; /** * Color for aggregated slices. */ protected static final FramesocColor AGGREGATED_COLOR = FramesocColor.BLACK; /** * Merged label colors */ protected Map<String, FramesocColor> mergedItemColors = new HashMap<>(); /** * Set of used labels */ protected Set<String> usedLabels = new HashSet<>(); /** * Get the color for a real entity whose name is passed. * * By real entity we mean something that is not an aggregate, but a leaf entity. * * @param name * entity name * @return the corresponding color */ protected abstract FramesocColor getBaseColor(String name); @Override public boolean isAggregationSupported() { return true; } @Override public double getAggregationThreshold() { return AGGREGATION_THRESHOLD; } @Override public String getAggregatedLabel() { return AGGREGATED_LABEL; } @Override public String toString() { return "PieChartLoader [" + getStatName() + "]"; } @Override public PieDataset getPieDataset(Map<String, Double> values, List<String> excluded, List<MergedItem> merged) { Assert.isTrue(values != null, "Null map passed"); // set of excluded rows Set<String> excludedSet = new HashSet<>(); for (String h : excluded) { excludedSet.add(h); } // compute total and actual threshold Double tot = getTotal(excludedSet, values); Double threshold = tot * getAggregationThreshold(); // create a list of merged slices Set<String> mergedSet = new HashSet<>(); List<Pair<String, Double>> slices = new ArrayList<>(); for (MergedItem i : merged) { Double val = 0.0; int baseItems = 0; for (String s : i.getBaseItems()) { if (excluded.contains(s)) { continue; } // add the label to the merged set, in order to skip the slice after baseItems++; mergedSet.add(s); val += values.get(s); } if (baseItems > 0) { slices.add(new Pair<>(i.getLabel(), val)); } } // add the other slices to the list and sort it Iterator<Entry<String, Double>> it = values.entrySet().iterator(); while (it.hasNext()) { Entry<String, Double> entry = it.next(); if (excludedSet.contains(entry.getKey()) || mergedSet.contains(entry.getKey())) { // skip excluded and merged rows continue; } slices.add(new Pair<>(entry.getKey(), entry.getValue())); } // create sorted slices, with aggregation if supported boolean aggregate = isAggregationSupported(); boolean isThereAnyAggregate = false; Double aggregatedValue = 0.0; List<Pair<String, Double>> sortedSlices = new ArrayList<>(); for (Pair<String, Double> pair : slices) { logger.debug(pair.toString()); if (aggregate && pair.getSecond() < threshold) { aggregatedValue += pair.getSecond(); isThereAnyAggregate = true; } else { sortedSlices.add(pair); } } if (isThereAnyAggregate) { sortedSlices.add(new Pair<String, Double>(getAggregatedLabel(), aggregatedValue)); } Collections.sort(sortedSlices, new ValueComparator()); // create dataset DefaultPieDataset dataset = new DefaultPieDataset(); for (Pair<String, Double> pair : sortedSlices) { dataset.setValue(pair.getFirst(), pair.getSecond()); } return dataset; } @Override public StatisticsTableRow[] getTableDataset(Map<String, Double> values, List<String> excluded, List<MergedItem> merged) { Assert.isTrue(values != null, "Null map passed"); // set of excluded rows Set<String> excludedSet = new HashSet<>(); for (String h : excluded) { excludedSet.add(h); } // compute total and actual threshold Double tot = getTotal(excludedSet, values); Double threshold = tot * getAggregationThreshold(); // create dataset for merged rows List<StatisticsTableRow> roots = new ArrayList<>(); Set<String> mergedSet = new HashSet<>(); for (MergedItem m : merged) { // create merged rows List<StatisticsTableRow> rows = new ArrayList<>(); Double mTot = 0.0; for (String a : m.getBaseItems()) { if (excluded.contains(a)) { continue; } // add the label to the merged set, in order to skip the row after mergedSet.add(a); Double val = values.get(a); mTot += val; StatisticsTableRow ar = new StatisticsTableRow(a, val.toString(), getPercentLine( val, tot), getColor(a).getSwtColor()); rows.add(ar); } // create folder row if (!rows.isEmpty()) { StatisticsTableFolderRow folderRow = new StatisticsTableFolderRow(m.getLabel(), mTot.toString(), getPercentLine(mTot, tot), m.getColor().getSwtColor()); for (StatisticsTableRow ar : rows) { folderRow.addChild(ar); } roots.add(folderRow); } } // create dataset for all the other rows boolean aggregate = isAggregationSupported(); List<StatisticsTableRow> aggregatedRows = new ArrayList<>(); Double aggregatedValue = 0.0; Iterator<Entry<String, Double>> it = values.entrySet().iterator(); while (it.hasNext()) { Entry<String, Double> entry = it.next(); if (excludedSet.contains(entry.getKey()) || mergedSet.contains(entry.getKey())) { // skip excluded and merged rows continue; } logger.debug(entry.toString()); StatisticsTableRow row = new StatisticsTableRow(entry.getKey(), String.valueOf(entry .getValue()), getPercentLine(entry.getValue(), tot), getColor(entry.getKey()) .getSwtColor()); if (aggregate && entry.getValue() < threshold) { aggregatedRows.add(row); aggregatedValue += entry.getValue(); } else { roots.add(row); } } if (!aggregatedRows.isEmpty()) { StatisticsTableFolderRow agg = new StatisticsTableFolderRow(getAggregatedLabel(), String.valueOf(aggregatedValue), getPercentLine(aggregatedValue, tot), FramesocColor.BLACK.getSwtColor()); for (StatisticsTableRow r : aggregatedRows) { agg.addChild(r); } roots.add(agg); } return roots.toArray(new StatisticsTableRow[roots.size()]); } @Override public void updateLabels(Map<String, Double> values, List<MergedItem> merged) { // update used labels and merged item colors usedLabels = new HashSet<>(); for (String s : values.keySet()) { usedLabels.add(s); } mergedItemColors = new HashMap<>(); for (MergedItem i : merged) { usedLabels.add(i.getLabel()); mergedItemColors.put(i.getLabel(), i.getColor()); } } @Override public FramesocColor getColor(String name) { if (name.equals(AGGREGATED_LABEL)) { return AGGREGATED_COLOR; } else if (mergedItemColors.containsKey(name)) { return mergedItemColors.get(name); } return getBaseColor(name); } @Override public boolean checkLabel(String label) { if (label.equals(getAggregatedLabel())) { return false; } if (usedLabels.contains(label)) { return false; } return true; } private Double getTotal(Set<String> hiddenSet, Map<String, Double> values) { Double tot = 0.0; Iterator<Entry<String, Double>> it = values.entrySet().iterator(); while (it.hasNext()) { Entry<String, Double> entry = it.next(); if (!hiddenSet.contains(entry.getKey())) { tot += entry.getValue(); } } return tot; } private String getPercentLine(double val, double tot) { Double pValue = (val * 100) / tot; NumberFormat formatter = new DecimalFormat("#0.00"); return formatter.format(pValue) + " %"; } class ValueComparator implements Comparator<Pair<String, Double>> { @Override public int compare(Pair<String, Double> p1, Pair<String, Double> p2) { // descending order return -1 * Double.compare(p1.getSecond(), p2.getSecond()); } } }