summaryrefslogtreecommitdiff
path: root/addons/batikvis/src/main/java/de/lmu/ifi/dbs/elki/result/ExportVisualizations.java
diff options
context:
space:
mode:
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.java302
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);
+ }
+ }
+}