diff options
Diffstat (limited to 'addons/batikvis/src/main/java/de/lmu/ifi/dbs/elki/result/ExportVisualizations.java')
-rw-r--r-- | addons/batikvis/src/main/java/de/lmu/ifi/dbs/elki/result/ExportVisualizations.java | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/addons/batikvis/src/main/java/de/lmu/ifi/dbs/elki/result/ExportVisualizations.java b/addons/batikvis/src/main/java/de/lmu/ifi/dbs/elki/result/ExportVisualizations.java new file mode 100644 index 00000000..150d384f --- /dev/null +++ b/addons/batikvis/src/main/java/de/lmu/ifi/dbs/elki/result/ExportVisualizations.java @@ -0,0 +1,302 @@ +package de.lmu.ifi.dbs.elki.result; + +/* + This file is part of ELKI: + Environment for Developing KDD-Applications Supported by Index-Structures + + Copyright (C) 2015 + Ludwig-Maximilians-Universität München + Lehr- und Forschungseinheit für Datenbanksysteme + ELKI Development Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.batik.util.SVGConstants; + +import de.lmu.ifi.dbs.elki.logging.Logging; +import de.lmu.ifi.dbs.elki.result.Result; +import de.lmu.ifi.dbs.elki.result.ResultHandler; +import de.lmu.ifi.dbs.elki.result.ResultHierarchy; +import de.lmu.ifi.dbs.elki.utilities.Alias; +import de.lmu.ifi.dbs.elki.utilities.datastructures.hierarchy.Hierarchy; +import de.lmu.ifi.dbs.elki.utilities.exceptions.AbortException; +import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer; +import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID; +import de.lmu.ifi.dbs.elki.utilities.optionhandling.constraints.CommonConstraints; +import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization; +import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.DoubleParameter; +import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.FileParameter; +import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.FileParameter.FileType; +import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; +import de.lmu.ifi.dbs.elki.visualization.VisualizerContext; +import de.lmu.ifi.dbs.elki.visualization.VisualizerParameterizer; +import de.lmu.ifi.dbs.elki.visualization.gui.VisualizationPlot; +import de.lmu.ifi.dbs.elki.visualization.gui.overview.PlotItem; +import de.lmu.ifi.dbs.elki.visualization.projector.Projector; +import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; + +/** + * Class that automatically generates all visualizations and exports them into + * SVG files. To configure the export, you <em>will</em> want to configure the + * {@link VisualizerParameterizer}, in particular the pattern for choosing which + * visualizers to run. + * + * @author Erich Schubert + * @since 0.5.0 + * + * @apiviz.composedOf VisualizerParameterizer + */ +@Alias("de.lmu.ifi.dbs.elki.visualization.ExportVisualizations") +public class ExportVisualizations implements ResultHandler { + /** + * Get a logger for this class. + */ + private static final Logging LOG = Logging.getLogger(ExportVisualizations.class); + + /** + * Output folder + */ + File output; + + /** + * Visualization manager. + */ + VisualizerParameterizer manager; + + /** + * Ratio for canvas + */ + double ratio; + + /** + * Base result + */ + Result baseResult = null; + + /** + * Visualizer context + */ + VisualizerContext context = null; + + /** + * Output counter. + */ + Map<String, Integer> counter = new HashMap<>(); + + /** + * Constructor. + * + * @param output Output folder + * @param manager Parameterizer + * @param ratio Canvas ratio + */ + public ExportVisualizations(File output, VisualizerParameterizer manager, double ratio) { + super(); + this.output = output; + this.manager = manager; + this.ratio = ratio; + } + + @Override + public void processNewResult(ResultHierarchy hier, Result newResult) { + if(output.isFile()) { + throw new AbortException("Output folder cannot be an existing file."); + } + if(!output.exists()) { + if(!output.mkdirs()) { + throw new AbortException("Could not create output directory."); + } + } + if(this.baseResult == null) { + this.baseResult = newResult; + context = null; + counter = new HashMap<>(); + LOG.warning("Note: Reusing visualization exporter for more than one result is untested."); + } + if(context == null) { + context = manager.newContext(hier, baseResult); + } + + // Projected visualizations + Hierarchy<Object> vistree = context.getVisHierarchy(); + for(Hierarchy.Iter<?> iter2 = vistree.iterAll(); iter2.valid(); iter2.advance()) { + if(!(iter2.get() instanceof Projector)) { + continue; + } + Projector proj = (Projector) iter2.get(); + // TODO: allow selecting individual projections only. + Collection<PlotItem> items = proj.arrange(context); + for(PlotItem item : items) { + processItem(item); + } + } + for(Hierarchy.Iter<?> iter2 = vistree.iterAll(); iter2.valid(); iter2.advance()) { + if(!(iter2.get() instanceof VisualizationTask)) { + continue; + } + VisualizationTask task = (VisualizationTask) iter2.get(); + boolean isprojected = false; + for(Hierarchy.Iter<?> iter = vistree.iterParents(task); iter.valid(); iter.advance()) { + if(iter.get() instanceof Projector) { + isprojected = true; + break; + } + } + if(isprojected) { + continue; + } + PlotItem pi = new PlotItem(ratio, 1.0, null); + pi.add(task); + processItem(pi); + } + } + + private void processItem(PlotItem item) { + // Descend into subitems + for(Iterator<PlotItem> iter = item.subitems.iterator(); iter.hasNext();) { + processItem(iter.next()); + } + if(item.taskSize() <= 0) { + return; + } + item.sort(); + final double width = item.w, height = item.h; + + VisualizationPlot svgp = new VisualizationPlot(); + svgp.getRoot().setAttribute(SVGConstants.SVG_WIDTH_ATTRIBUTE, "20cm"); + svgp.getRoot().setAttribute(SVGConstants.SVG_HEIGHT_ATTRIBUTE, (20 * height / width) + "cm"); + svgp.getRoot().setAttribute(SVGConstants.SVG_VIEW_BOX_ATTRIBUTE, "0 0 " + width + " " + height); + + ArrayList<Visualization> layers = new ArrayList<>(); + for(Iterator<VisualizationTask> iter = item.tasks.iterator(); iter.hasNext();) { + VisualizationTask task = iter.next(); + if(task.hasAnyFlags(VisualizationTask.FLAG_NO_DETAIL | VisualizationTask.FLAG_NO_EXPORT) || !task.visible) { + continue; + } + try { + Visualization v = task.getFactory().makeVisualization(task, svgp, width, height, item.proj); + layers.add(v); + } + catch(Exception e) { + if(Logging.getLogger(task.getFactory().getClass()).isDebugging()) { + LOG.exception("Visualization failed.", e); + } + else { + LOG.warning("Visualizer " + task.getFactory().getClass().getName() + " failed - enable debugging to see details."); + } + } + } + if(layers.size() <= 0) { + return; + } + for(Visualization layer : layers) { + if(layer.getLayer() == null) { + LOG.warning("NULL layer seen."); + continue; + } + svgp.getRoot().appendChild(layer.getLayer()); + } + svgp.updateStyleElement(); + + String prefix = null; + prefix = (prefix == null && item.proj != null) ? item.proj.getMenuName() : prefix; + prefix = (prefix == null && item.tasks.size() > 0) ? item.tasks.get(0).getMenuName() : prefix; + prefix = (prefix != null ? prefix : "plot"); + // TODO: generate names... + Integer count = counter.get(prefix); + counter.put(prefix, count = count == null ? 1 : (count + 1)); + File outname = new File(output, prefix + "-" + count + ".svg"); + try { + svgp.saveAsSVG(outname); + } + catch(Exception e) { + LOG.warning("Export of visualization failed.", e); + } + for(Visualization layer : layers) { + layer.destroy(); + } + } + + /** + * Parameterization class + * + * @author Erich Schubert + * + * @apiviz.exclude + */ + public static class Parameterizer extends AbstractParameterizer { + /** + * Parameter to specify the canvas ratio + * <p> + * Key: {@code -vis.ratio} + * </p> + * <p> + * Default value: 1.33 + * </p> + */ + public static final OptionID RATIO_ID = new OptionID("vis.ratio", "The width/heigh ratio of the output."); + + /** + * Parameter to specify the output folder + * <p> + * Key: {@code -vis.output} + * </p> + */ + public static final OptionID FOLDER_ID = new OptionID("vis.output", "The output folder."); + + /** + * Visualization manager. + */ + VisualizerParameterizer manager; + + /** + * Output folder + */ + File output; + + /** + * Ratio for canvas + */ + double ratio; + + @Override + protected void makeOptions(Parameterization config) { + super.makeOptions(config); + FileParameter outputP = new FileParameter(FOLDER_ID, FileType.OUTPUT_FILE); + if(config.grab(outputP)) { + output = outputP.getValue(); + } + + DoubleParameter ratioP = new DoubleParameter(RATIO_ID, 1.33); + ratioP.addConstraint(CommonConstraints.GREATER_THAN_ZERO_DOUBLE); + if(config.grab(ratioP)) { + ratio = ratioP.doubleValue(); + } + + manager = config.tryInstantiate(VisualizerParameterizer.class); + } + + @Override + protected ExportVisualizations makeInstance() { + return new ExportVisualizations(output, manager, ratio); + } + } +} |