package de.lmu.ifi.dbs.elki.visualization.visualizers.thumbs; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures Copyright (C) 2011 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 . */ import java.io.File; import org.apache.batik.util.SVGConstants; import org.w3c.dom.Element; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreEvent; import de.lmu.ifi.dbs.elki.database.datastore.DataStoreListener; import de.lmu.ifi.dbs.elki.logging.Logging; import de.lmu.ifi.dbs.elki.logging.LoggingUtil; import de.lmu.ifi.dbs.elki.result.Result; import de.lmu.ifi.dbs.elki.result.SelectionResult; import de.lmu.ifi.dbs.elki.visualization.VisualizationTask; import de.lmu.ifi.dbs.elki.visualization.svg.SVGPlot; import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil; import de.lmu.ifi.dbs.elki.visualization.visualizers.AbstractVisualization; import de.lmu.ifi.dbs.elki.visualization.visualizers.VisFactory; import de.lmu.ifi.dbs.elki.visualization.visualizers.Visualization; import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ContextChangedEvent; import de.lmu.ifi.dbs.elki.visualization.visualizers.events.ResizedEvent; /** * Thumbnail visualization. * * @author Erich Schubert * * @apiviz.uses Thumbnailer * @apiviz.uses ThumbnailThread */ public class ThumbnailVisualization extends AbstractVisualization implements ThumbnailThread.Listener, DataStoreListener { /** * Constant to listen for data changes */ public static final int ON_DATA = 1; /** * Constant to listen for selection changes */ public static final int ON_SELECTION = 2; /** * Visualizer factory */ protected final VisFactory visFactory; /** * The thumbnail file. */ protected File thumb = null; /** * Pending redraw */ protected ThumbnailThread.Task pendingThumbnail = null; /** * Thumbnail resolution */ protected int tresolution; /** * The event mask. See {@link #ON_DATA}, {@link #ON_SELECTION} */ private int mask; /** * Constructor. * * @param visFactory Visualizer Factory to use * @param task Task to use * @param mask Event mask (for auto-updating) */ public ThumbnailVisualization(VisFactory visFactory, VisualizationTask task, int mask) { super(task); this.visFactory = visFactory; Integer tres = task.getGenerics(VisualizationTask.THUMBNAIL_RESOLUTION, Integer.class); this.tresolution = tres; this.layer = task.getPlot().svgElement(SVGConstants.SVG_G_TAG); this.thumb = null; this.mask = mask; // Listen for database events only when needed. if((mask & ON_DATA) == ON_DATA) { context.addDataStoreListener(this); } // Always listen for context changes, in particular resize. context.addContextChangeListener(this); // Listen for result changes, including the one we monitor context.addResultListener(this); } @Override public void destroy() { if(pendingThumbnail != null) { ThumbnailThread.UNQUEUE(pendingThumbnail); } context.removeResultListener(this); context.removeContextChangeListener(this); context.removeDataStoreListener(this); } @Override public Element getLayer() { if(thumb == null) { synchronizedRedraw(); } return layer; } @Override public void contextChanged(ContextChangedEvent e) { if(testRedraw(e)) { refreshThumbnail(); } } /** * Override this method to add additional redraw triggers! * * @param e Event * @return Test result */ @Override protected boolean testRedraw(ContextChangedEvent e) { if(e instanceof ResizedEvent) { return true; } return false; } @Override public void contentChanged(DataStoreEvent e) { refreshThumbnail(); } /** * Redraw the visualization (maybe incremental). * * Optional - by default, it will do a full redraw, which often is faster! */ @Override protected void incrementalRedraw() { final Element oldcontainer; if(layer.hasChildNodes()) { oldcontainer = layer; layer = (Element) layer.cloneNode(false); } else { oldcontainer = null; } redraw(); if(oldcontainer != null && oldcontainer.getParentNode() != null) { oldcontainer.getParentNode().replaceChild(layer, oldcontainer); } } /** * Perform a full redraw. */ @Override protected void redraw() { if(thumb == null) { // LoggingUtil.warning("Generating new thumbnail " + this); layer.appendChild(SVGUtil.svgWaitIcon(task.getPlot().getDocument(), 0, 0, task.getWidth(), task.getHeight())); if(pendingThumbnail == null) { pendingThumbnail = ThumbnailThread.QUEUE(this); } } else { // LoggingUtil.warning("Injecting Thumbnail " + this); Element i = task.getPlot().svgElement(SVGConstants.SVG_IMAGE_TAG); SVGUtil.setAtt(i, SVGConstants.SVG_X_ATTRIBUTE, 0); SVGUtil.setAtt(i, SVGConstants.SVG_Y_ATTRIBUTE, 0); SVGUtil.setAtt(i, SVGConstants.SVG_WIDTH_ATTRIBUTE, task.getWidth()); SVGUtil.setAtt(i, SVGConstants.SVG_HEIGHT_ATTRIBUTE, task.getHeight()); i.setAttributeNS(SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.XLINK_HREF_QNAME, thumb.toURI().toString()); layer.appendChild(i); } } @Override public synchronized void doThumbnail(Thumbnailer t) { pendingThumbnail = null; try { SVGPlot plot = new SVGPlot(); plot.getRoot().setAttribute(SVGConstants.SVG_VIEW_BOX_ATTRIBUTE, "0 0 " + task.getWidth() + " " + task.getHeight()); // Work on a clone VisualizationTask clone = task.clone(plot, context); clone.put(VisualizationTask.THUMBNAIL, false); Visualization vis = visFactory.makeVisualization(clone); plot.getRoot().appendChild(vis.getLayer()); plot.updateStyleElement(); final int tw = (int) (task.getWidth() * tresolution); final int th = (int) (task.getHeight() * tresolution); thumb = t.thumbnail(plot, tw, th); // The visualization will not be used anymore. vis.destroy(); synchronizedRedraw(); } catch(Exception e) { final Logging logger = Logging.getLogger(task.getFactory().getClass()); if(logger != null && logger.isDebugging()) { logger.exception("Thumbnail failed.", e); } else { LoggingUtil.warning("Thumbnail for " + task.getFactory().getClass().getName() + " failed - enable debugging to see details."); } // TODO: hide the failed image? } } protected void refreshThumbnail() { // Discard an existing thumbnail thumb = null; synchronizedRedraw(); } @Override public void resultChanged(Result current) { if((mask & ON_SELECTION) == ON_SELECTION && current instanceof SelectionResult) { refreshThumbnail(); return; } super.resultChanged(current); } }