package org.openlca.app.results.analysis.sankey; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Queue; import java.util.Set; import java.util.Stack; import org.openlca.core.matrix.ProcessLinkSearchMap; import org.openlca.core.model.ProcessLink; /** * Calculates the processes that are visible in the Sankey diagram. */ class SankeyProcessList { private SankeyResult sankeyResult; private long refProcess; private double cutoff; private ProcessLinkSearchMap linkSearchMap; private SankeyProcessList(SankeyResult result, long refProcess, double cutoff, ProcessLinkSearchMap linkSearchMap) { this.sankeyResult = result; this.refProcess = refProcess; this.cutoff = cutoff; this.linkSearchMap = linkSearchMap; } public static Set<Long> calculate(SankeyResult result, long refProcess, double cutoff, ProcessLinkSearchMap linkSearchMap) { return new SankeyProcessList(result, refProcess, cutoff, linkSearchMap) .calculate(); } private Set<Long> calculate() { List<Long> initial = sankeyResult.getProcesseIdsAboveCutoff(cutoff); Set<Long> processesToDraw = new HashSet<Long>(initial); processesToDraw.add(refProcess); fillUp(processesToDraw); return processesToDraw; } /** * Checks if each process has a path to the reference process, if not it * searches a way to the reference or another connected node and adds the * missing nodes. */ private void fillUp(Set<Long> processIds) { Set<Long> unconnected = new HashSet<>(processIds); Set<Long> connected = new HashSet<>(); Queue<Long> queue = new ArrayDeque<>(); queue.add(refProcess); while (!queue.isEmpty()) { Long recipient = queue.poll(); unconnected.remove(recipient); connected.add(recipient); for (ProcessLink link : linkSearchMap.getIncomingLinks(recipient)) { Long provider = link.providerId; if (!processIds.contains(provider)) continue; if (!queue.contains(provider) && !connected.contains(provider)) queue.add(provider); } } for (Long processId : unconnected) { Stack<Long> path = searchPathFor(processId, connected); for (Long id : path) { processIds.add(id); connected.add(id); } } } /** * Find a way from the given process to the connected graph following the * path with biggest weight and return the list of new processes that */ private Stack<Long> searchPathFor(long processToConnect, Set<Long> connectedGraph) { Stack<Long> path = new Stack<>(); path.push(processToConnect); HashSet<Long> visited = new HashSet<>(); visited.add(processToConnect); Stack<List<Long>> candidateStack = new Stack<>(); candidateStack.push(getWeightedRecipients(processToConnect)); while (!candidateStack.isEmpty()) { List<Long> candidates = candidateStack.peek(); if (candidates.isEmpty()) { candidateStack.pop(); Long v = path.pop(); visited.add(v); continue; } for (Long candidate : candidates) { if (connectedGraph.contains(candidate)) return path; // found a way to the connected graph } Long next = null; for (int i = 0; i < candidates.size(); i++) { Long candidate = candidates.remove(0); // take the first = 0 if (!path.contains(candidate)) { next = candidate; break; } } if (next != null) { path.push(next); List<Long> nextCandidates = new ArrayList<>(); for (Long nextCandidate : getWeightedRecipients(next)) { if (!visited.contains(nextCandidate) && !path.contains(nextCandidate)) { nextCandidates.add(nextCandidate); } } candidateStack.push(nextCandidates); } } return path; } private List<Long> getWeightedRecipients(long processId) { List<WeightedProcess> recipients = new ArrayList<>(); for (ProcessLink link : linkSearchMap.getOutgoingLinks(processId)) { WeightedProcess wp = new WeightedProcess(); wp.id = link.processId; wp.weight = Math.abs(sankeyResult.getLinkContribution(link)); recipients.add(wp); } Collections.sort(recipients); List<Long> ids = new ArrayList<>(); for (WeightedProcess recipient : recipients) ids.add(recipient.id); return ids; } private class WeightedProcess implements Comparable<WeightedProcess> { private long id; private double weight; @Override public int compareTo(final WeightedProcess o) { return -Double.compare(weight, o.weight); } } }