summaryrefslogtreecommitdiff
path: root/src/js/view/mxOutline.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/view/mxOutline.js')
-rw-r--r--src/js/view/mxOutline.js649
1 files changed, 649 insertions, 0 deletions
diff --git a/src/js/view/mxOutline.js b/src/js/view/mxOutline.js
new file mode 100644
index 0000000..a0d6fd3
--- /dev/null
+++ b/src/js/view/mxOutline.js
@@ -0,0 +1,649 @@
+/**
+ * $Id: mxOutline.js,v 1.81 2012-06-20 14:13:37 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxOutline
+ *
+ * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
+ * to enable updates while the source graph is panning.
+ *
+ * Example:
+ *
+ * (code)
+ * var outline = new mxOutline(graph, div);
+ * (end)
+ *
+ * If the selection border in the outline appears behind the contents of the
+ * graph, then you can use the following code. (This may happen when using a
+ * transparent container for the outline in IE.)
+ *
+ * (code)
+ * mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_EXACT;
+ * (end)
+ *
+ * To move the graph to the top, left corner the following code can be used.
+ *
+ * (code)
+ * var scale = graph.view.scale;
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
+ * (end)
+ *
+ * To toggle the suspended mode, the following can be used.
+ *
+ * (code)
+ * outline.suspended = !outln.suspended;
+ * if (!outline.suspended)
+ * {
+ * outline.update(true);
+ * }
+ * (end)
+ *
+ * Constructor: mxOutline
+ *
+ * Constructs a new outline for the specified graph inside the given
+ * container.
+ *
+ * Parameters:
+ *
+ * source - <mxGraph> to create the outline for.
+ * container - DOM node that will contain the outline.
+ */
+function mxOutline(source, container)
+{
+ this.source = source;
+
+ if (container != null)
+ {
+ this.init(container);
+ }
+};
+
+/**
+ * Function: source
+ *
+ * Reference to the source <mxGraph>.
+ */
+mxOutline.prototype.source = null;
+
+/**
+ * Function: outline
+ *
+ * Reference to the outline <mxGraph>.
+ */
+mxOutline.prototype.outline = null;
+
+/**
+ * Function: graphRenderHint
+ *
+ * Renderhint to be used for the outline graph. Default is faster.
+ */
+mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxOutline.prototype.enabled = true;
+
+/**
+ * Variable: showViewport
+ *
+ * Specifies a viewport rectangle should be shown. Default is true.
+ */
+mxOutline.prototype.showViewport = true;
+
+/**
+ * Variable: border
+ *
+ * Border to be added at the bottom and right. Default is 10.
+ */
+mxOutline.prototype.border = 10;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies the size of the sizer handler. Default is 8.
+ */
+mxOutline.prototype.sizerSize = 8;
+
+/**
+ * Variable: updateOnPan
+ *
+ * Specifies if <update> should be called for <mxEvent.PAN> in the source
+ * graph. Default is false.
+ */
+mxOutline.prototype.updateOnPan = false;
+
+/**
+ * Variable: sizerImage
+ *
+ * Optional <mxImage> to be used for the sizer. Default is null.
+ */
+mxOutline.prototype.sizerImage = null;
+
+/**
+ * Variable: suspended
+ *
+ * Optional boolean flag to suspend updates. Default is false. This flag will
+ * also suspend repaints of the outline. To toggle this switch, use the
+ * following code.
+ *
+ * (code)
+ * nav.suspended = !nav.suspended;
+ *
+ * if (!nav.suspended)
+ * {
+ * nav.update(true);
+ * }
+ * (end)
+ */
+mxOutline.prototype.suspended = false;
+
+/**
+ * Function: init
+ *
+ * Initializes the outline inside the given container.
+ */
+mxOutline.prototype.init = function(container)
+{
+ this.outline = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());
+ this.outline.foldingEnabled = false;
+ this.outline.autoScroll = false;
+
+ // Do not repaint when suspended
+ var outlineGraphModelChanged = this.outline.graphModelChanged;
+ this.outline.graphModelChanged = mxUtils.bind(this, function(changes)
+ {
+ if (!this.suspended && this.outline != null)
+ {
+ outlineGraphModelChanged.apply(this.outline, arguments);
+ }
+ });
+
+ // Enables faster painting in SVG
+ if (mxClient.IS_SVG)
+ {
+ var node = this.outline.getView().getCanvas().parentNode;
+ node.setAttribute('shape-rendering', 'optimizeSpeed');
+ node.setAttribute('image-rendering', 'optimizeSpeed');
+ }
+
+ // Hides cursors and labels
+ this.outline.labelsVisible = false;
+ this.outline.setEnabled(false);
+
+ this.updateHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (!this.suspended && !this.active)
+ {
+ this.update();
+ }
+ });
+
+ // Updates the scale of the outline after a change of the main graph
+ this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
+ this.outline.addMouseListener(this);
+
+ // Adds listeners to keep the outline in sync with the source graph
+ var view = this.source.getView();
+ view.addListener(mxEvent.SCALE, this.updateHandler);
+ view.addListener(mxEvent.TRANSLATE, this.updateHandler);
+ view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
+ view.addListener(mxEvent.DOWN, this.updateHandler);
+ view.addListener(mxEvent.UP, this.updateHandler);
+
+ // Updates blue rectangle on scroll
+ mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+
+ this.panHandler = mxUtils.bind(this, function(sender)
+ {
+ if (this.updateOnPan)
+ {
+ this.updateHandler.apply(this, arguments);
+ }
+ });
+ this.source.addListener(mxEvent.PAN, this.panHandler);
+
+ // Refreshes the graph in the outline after a refresh of the main graph
+ this.refreshHandler = mxUtils.bind(this, function(sender)
+ {
+ this.outline.setStylesheet(this.source.getStylesheet());
+ this.outline.refresh();
+ });
+ this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
+
+ // Creates the blue rectangle for the viewport
+ this.bounds = new mxRectangle(0, 0, 0, 0);
+ this.selectionBorder = new mxRectangleShape(this.bounds, null,
+ mxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH);
+ this.selectionBorder.dialect =
+ (this.outline.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.selectionBorder.crisp = true;
+ this.selectionBorder.init(this.outline.getView().getOverlayPane());
+ mxEvent.redirectMouseEvents(this.selectionBorder.node, this.outline);
+ this.selectionBorder.node.style.background = '';
+
+ // Creates a small blue rectangle for sizing (sizer handle)
+ this.sizer = this.createSizer();
+ this.sizer.init(this.outline.getView().getOverlayPane());
+
+ if (this.enabled)
+ {
+ this.sizer.node.style.cursor = 'pointer';
+ }
+
+ // Redirects all events from the sizerhandle to the outline
+ mxEvent.addListener(this.sizer.node, (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown',
+ mxUtils.bind(this, function(evt)
+ {
+ this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+ })
+ );
+
+ this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+ this.sizer.node.style.display = this.selectionBorder.node.style.display;
+ this.selectionBorder.node.style.cursor = 'move';
+
+ this.update(false);
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxOutline.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: setZoomEnabled
+ *
+ * Enables or disables the zoom handling by showing or hiding the respective
+ * handle.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setZoomEnabled = function(value)
+{
+ this.sizer.node.style.visibility = (value) ? 'visible' : 'hidden';
+};
+
+/**
+ * Function: refresh
+ *
+ * Invokes <update> and revalidate the outline. This method is deprecated.
+ */
+mxOutline.prototype.refresh = function()
+{
+ this.update(true);
+};
+
+/**
+ * Function: createSizer
+ *
+ * Creates the shape used as the sizer.
+ */
+mxOutline.prototype.createSizer = function()
+{
+ if (this.sizerImage != null)
+ {
+ var sizer = new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src);
+ sizer.dialect = this.outline.dialect;
+
+ return sizer;
+ }
+ else
+ {
+ var sizer = new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize, this.sizerSize),
+ mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR);
+ sizer.dialect = this.outline.dialect;
+ sizer.crisp = true;
+
+ return sizer;
+ }
+};
+
+/**
+ * Function: getSourceContainerSize
+ *
+ * Returns the size of the source container.
+ */
+mxOutline.prototype.getSourceContainerSize = function()
+{
+ return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight);
+};
+
+/**
+ * Function: getOutlineOffset
+ *
+ * Returns the offset for drawing the outline graph.
+ */
+mxOutline.prototype.getOutlineOffset = function(scale)
+{
+ return null;
+};
+
+/**
+ * Function: update
+ *
+ * Updates the outline.
+ */
+mxOutline.prototype.update = function(revalidate)
+{
+ if (this.source != null)
+ {
+ var sourceScale = this.source.view.scale;
+ var scaledGraphBounds = this.source.getGraphBounds();
+ var unscaledGraphBounds = new mxRectangle(scaledGraphBounds.x / sourceScale + this.source.panDx,
+ scaledGraphBounds.y / sourceScale + this.source.panDy, scaledGraphBounds.width / sourceScale,
+ scaledGraphBounds.height / sourceScale);
+
+ var unscaledFinderBounds = new mxRectangle(0, 0,
+ this.source.container.clientWidth / sourceScale,
+ this.source.container.clientHeight / sourceScale);
+
+ var union = unscaledGraphBounds.clone();
+ union.add(unscaledFinderBounds);
+
+ // Zooms to the scrollable area if that is bigger than the graph
+ var size = this.getSourceContainerSize();
+ var completeWidth = Math.max(size.width / sourceScale, union.width);
+ var completeHeight = Math.max(size.height / sourceScale, union.height);
+
+ var availableWidth = Math.max(0, this.outline.container.clientWidth - this.border);
+ var availableHeight = Math.max(0, this.outline.container.clientHeight - this.border);
+
+ var outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight);
+ var scale = outlineScale;
+
+ if (scale > 0)
+ {
+ if (this.outline.getView().scale != scale)
+ {
+ this.outline.getView().scale = scale;
+ revalidate = true;
+ }
+
+ var navView = this.outline.getView();
+
+ if (navView.currentRoot != this.source.getView().currentRoot)
+ {
+ navView.setCurrentRoot(this.source.getView().currentRoot);
+ }
+
+ var t = this.source.view.translate;
+ var tx = t.x + this.source.panDx;
+ var ty = t.y + this.source.panDy;
+
+ var off = this.getOutlineOffset(scale);
+
+ if (off != null)
+ {
+ tx += off.x;
+ ty += off.y;
+ }
+
+ if (unscaledGraphBounds.x < 0)
+ {
+ tx = tx - unscaledGraphBounds.x;
+ }
+ if (unscaledGraphBounds.y < 0)
+ {
+ ty = ty - unscaledGraphBounds.y;
+ }
+
+ if (navView.translate.x != tx || navView.translate.y != ty)
+ {
+ navView.translate.x = tx;
+ navView.translate.y = ty;
+ revalidate = true;
+ }
+
+ // Prepares local variables for computations
+ var t2 = navView.translate;
+ scale = this.source.getView().scale;
+ var scale2 = scale / navView.scale;
+ var scale3 = 1.0 / navView.scale;
+ var container = this.source.container;
+
+ // Updates the bounds of the viewrect in the navigation
+ this.bounds = new mxRectangle(
+ (t2.x - t.x - this.source.panDx) / scale3,
+ (t2.y - t.y - this.source.panDy) / scale3,
+ (container.clientWidth / scale2),
+ (container.clientHeight / scale2));
+
+ // Adds the scrollbar offset to the finder
+ this.bounds.x += this.source.container.scrollLeft * navView.scale / scale;
+ this.bounds.y += this.source.container.scrollTop * navView.scale / scale;
+
+ var b = this.selectionBorder.bounds;
+
+ if (b.x != this.bounds.x || b.y != this.bounds.y || b.width != this.bounds.width || b.height != this.bounds.height)
+ {
+ this.selectionBorder.bounds = this.bounds;
+ this.selectionBorder.redraw();
+ }
+
+ // Updates the bounds of the zoom handle at the bottom right
+ var b = this.sizer.bounds;
+ var b2 = new mxRectangle(this.bounds.x + this.bounds.width - b.width / 2,
+ this.bounds.y + this.bounds.height - b.height / 2, b.width, b.height);
+
+ if (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height)
+ {
+ this.sizer.bounds = b2;
+
+ // Avoids update of visibility in redraw for VML
+ if (this.sizer.node.style.visibility != 'hidden')
+ {
+ this.sizer.redraw();
+ }
+ }
+
+ if (revalidate)
+ {
+ this.outline.view.revalidate();
+ }
+ }
+ }
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by starting a translation or zoom.
+ */
+mxOutline.prototype.mouseDown = function(sender, me)
+{
+ if (this.enabled && this.showViewport)
+ {
+ this.zoom = me.isSource(this.sizer);
+ this.startX = me.getX();
+ this.startY = me.getY();
+ this.active = true;
+
+ if (this.source.useScrollbarsForPanning &&
+ mxUtils.hasScrollbars(this.source.container))
+ {
+ this.dx0 = this.source.container.scrollLeft;
+ this.dy0 = this.source.container.scrollTop;
+ }
+ else
+ {
+ this.dx0 = 0;
+ this.dy0 = 0;
+ }
+ }
+
+ me.consume();
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by previewing the viewrect in <graph> and updating the
+ * rectangle that represents the viewrect in the outline.
+ */
+mxOutline.prototype.mouseMove = function(sender, me)
+{
+ if (this.active)
+ {
+ this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+ this.sizer.node.style.display = this.selectionBorder.node.style.display;
+
+ var dx = me.getX() - this.startX;
+ var dy = me.getY() - this.startY;
+ var bounds = null;
+
+ if (!this.zoom)
+ {
+ // Previews the panning on the source graph
+ var scale = this.outline.getView().scale;
+ bounds = new mxRectangle(this.bounds.x + dx,
+ this.bounds.y + dy, this.bounds.width, this.bounds.height);
+ this.selectionBorder.bounds = bounds;
+ this.selectionBorder.redraw();
+ dx /= scale;
+ dx *= this.source.getView().scale;
+ dy /= scale;
+ dy *= this.source.getView().scale;
+ this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
+ }
+ else
+ {
+ // Does *not* preview zooming on the source graph
+ var container = this.source.container;
+ var viewRatio = container.clientWidth / container.clientHeight;
+ dy = dx / viewRatio;
+ bounds = new mxRectangle(this.bounds.x,
+ this.bounds.y,
+ Math.max(1, this.bounds.width + dx),
+ Math.max(1, this.bounds.height + dy));
+ this.selectionBorder.bounds = bounds;
+ this.selectionBorder.redraw();
+ }
+
+ // Updates the zoom handle
+ var b = this.sizer.bounds;
+ this.sizer.bounds = new mxRectangle(
+ bounds.x + bounds.width - b.width / 2,
+ bounds.y + bounds.height - b.height / 2,
+ b.width, b.height);
+
+ // Avoids update of visibility in redraw for VML
+ if (this.sizer.node.style.visibility != 'hidden')
+ {
+ this.sizer.redraw();
+ }
+
+ me.consume();
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by applying the translation or zoom to <graph>.
+ */
+mxOutline.prototype.mouseUp = function(sender, me)
+{
+ if (this.active)
+ {
+ var dx = me.getX() - this.startX;
+ var dy = me.getY() - this.startY;
+
+ if (Math.abs(dx) > 0 || Math.abs(dy) > 0)
+ {
+ if (!this.zoom)
+ {
+ // Applies the new translation if the source
+ // has no scrollbars
+ if (!this.source.useScrollbarsForPanning ||
+ !mxUtils.hasScrollbars(this.source.container))
+ {
+ this.source.panGraph(0, 0);
+ dx /= this.outline.getView().scale;
+ dy /= this.outline.getView().scale;
+ var t = this.source.getView().translate;
+ this.source.getView().setTranslate(t.x - dx, t.y - dy);
+ }
+ }
+ else
+ {
+ // Applies the new zoom
+ var w = this.selectionBorder.bounds.width;
+ var scale = this.source.getView().scale;
+ this.source.zoomTo(scale - (dx * scale) / w, false);
+ }
+
+ this.update();
+ me.consume();
+ }
+
+ // Resets the state of the handler
+ this.index = null;
+ this.active = false;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroy this outline and removes all listeners from <source>.
+ */
+mxOutline.prototype.destroy = function()
+{
+ if (this.source != null)
+ {
+ this.source.removeListener(this.panHandler);
+ this.source.removeListener(this.refreshHandler);
+ this.source.getModel().removeListener(this.updateHandler);
+ this.source.getView().removeListener(this.updateHandler);
+ mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+ this.source = null;
+ }
+
+ if (this.outline != null)
+ {
+ this.outline.removeMouseListener(this);
+ this.outline.destroy();
+ this.outline = null;
+ }
+
+ if (this.selectionBorder != null)
+ {
+ this.selectionBorder.destroy();
+ this.selectionBorder = null;
+ }
+
+ if (this.sizer != null)
+ {
+ this.sizer.destroy();
+ this.sizer = null;
+ }
+};