From f41959ccb2d9d4c722fe8fc3351401d53bcf4900 Mon Sep 17 00:00:00 2001 From: Manjunath Kudlur Date: Fri, 6 Nov 2015 16:27:58 -0800 Subject: TensorFlow: Initial commit of TensorFlow library. TensorFlow is an open source software library for numerical computation using data flow graphs. Base CL: 107276108 --- .../tf-graph-common/lib/scene/minimap.ts | 269 +++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts (limited to 'tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts') diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts new file mode 100644 index 0000000000..1a34132765 --- /dev/null +++ b/tensorflow/tensorboard/components/tf-graph-common/lib/scene/minimap.ts @@ -0,0 +1,269 @@ +/// +/// + +module tf.scene { + +/** Show minimap when the viewpoint area is less than X% of the whole area. */ +const FRAC_VIEWPOINT_AREA: number = 0.8; + +export class Minimap { + /** The minimap container. */ + private minimap: HTMLElement; + /** The canvas used for drawing the mini version of the svg. */ + private canvas: HTMLCanvasElement; + /** A buffer canvas used for temporary drawing to avoid flickering. */ + private canvasBuffer: HTMLCanvasElement; + + /** The minimap svg used for holding the viewpoint rectangle. */ + private minimapSvg: SVGSVGElement; + /** The rectangle showing the current viewpoint. */ + private viewpoint: SVGRectElement; + /** + * The scale factor for the minimap. The factor is determined automatically + * so that the minimap doesn't violate the maximum width/height specified + * in the constructor. The minimap maintains the same aspect ratio as the + * original svg. + */ + private scaleMinimap: number; + /** The main svg element. */ + private svg: SVGSVGElement; + /** The svg group used for panning and zooming the main svg. */ + private zoomG: SVGGElement; + /** The zoom behavior of the main svg. */ + private mainZoom: d3.behavior.Zoom; + /** The maximum width and height for the minimap. */ + private maxWandH: number; + /** The last translation vector used in the main svg. */ + private translate: [number, number]; + /** The last scaling factor used in the main svg. */ + private scaleMain: number; + /** The coordinates of the viewpoint rectangle. */ + private viewpointCoord: {x: number, y: number}; + /** The current size of the minimap */ + private minimapSize: {width: number, height: number}; + /** Padding (px) due to the main labels of the graph. */ + private labelPadding: number; + /** + * Constructs a new minimap. + * + * @param svg The main svg element. + * @param zoomG The svg group used for panning and zooming the main svg. + * @param mainZoom The main zoom behavior. + * @param minimap The minimap container. + * @param maxWandH The maximum width/height for the minimap. + * @param labelPadding Padding in pixels due to the main graph labels. + */ + constructor(svg: SVGSVGElement, zoomG: SVGGElement, + mainZoom: d3.behavior.Zoom, minimap: HTMLElement, + maxWandH: number, labelPadding: number) { + this.svg = svg; + this.labelPadding = labelPadding; + this.zoomG = zoomG; + this.mainZoom = mainZoom; + this.maxWandH = maxWandH; + let $minimap = d3.select(minimap); + // The minimap will have 2 main components: the canvas showing the content + // and an svg showing a rectangle of the currently zoomed/panned viewpoint. + let $minimapSvg = $minimap.select("svg"); + + // Make the viewpoint rectangle draggable. + let $viewpoint = $minimapSvg.select("rect"); + let dragmove = (d) => { + this.viewpointCoord.x = (d3.event).x; + this.viewpointCoord.y = (d3.event).y; + this.updateViewpoint(); + }; + this.viewpointCoord = {x: 0, y: 0}; + let drag = d3.behavior.drag().origin(Object).on("drag", dragmove); + $viewpoint.datum(this.viewpointCoord).call(drag); + + // Make the minimap clickable. + $minimapSvg.on("click", () => { + if ((d3.event).defaultPrevented) { + // This click was part of a drag event, so suppress it. + return; + } + // Update the coordinates of the viewpoint. + let width = Number($viewpoint.attr("width")); + let height = Number($viewpoint.attr("height")); + let clickCoords = d3.mouse($minimapSvg.node()); + this.viewpointCoord.x = clickCoords[0] - width / 2; + this.viewpointCoord.y = clickCoords[1] - height / 2; + this.updateViewpoint(); + }); + this.viewpoint = $viewpoint.node(); + this.minimapSvg = $minimapSvg.node(); + this.minimap = minimap; + this.canvas = $minimap.select("canvas.first").node(); + this.canvasBuffer = + $minimap.select("canvas.second").node(); + } + + /** + * Updates the position and the size of the viewpoint rectangle. + * It also notifies the main svg about the new panned position. + */ + private updateViewpoint(): void { + // Update the coordinates of the viewpoint rectangle. + d3.select(this.viewpoint) + .attr("x", this.viewpointCoord.x) + .attr("y", this.viewpointCoord.y); + // Update the translation vector of the main svg to reflect the + // new viewpoint. + let mainX = - this.viewpointCoord.x * this.scaleMain / this.scaleMinimap; + let mainY = - this.viewpointCoord.y * this.scaleMain / this.scaleMinimap; + let zoomEvent = this.mainZoom.translate([mainX, mainY]).event; + d3.select(this.zoomG).call(zoomEvent); + } + + /** + * Redraws the minimap. Should be called whenever the main svg + * was updated (e.g. when a node was expanded). + */ + update(): void { + let $svg = d3.select(this.svg); + // Read all the style rules in the document and embed them into the svg. + // The svg needs to be self contained, i.e. all the style rules need to be + // embedded so the canvas output matches the origin. + let stylesText = ""; + for (let k = 0; k < document.styleSheets.length; k++) { + try { + let cssRules = (document.styleSheets[k]).cssRules || + (document.styleSheets[k]).rules; + if (cssRules == null) { + continue; + } + for (let i = 0; i < cssRules.length; i++) { + stylesText += cssRules[i].cssText + "\n"; + } + } catch (e) { + if (e.name !== "SecurityError") { + throw e; + } + } + } + + // Temporarily add the css rules to the main svg. + let svgStyle = $svg.append("style"); + svgStyle.text(stylesText); + + // Temporarily remove the zoom/pan transform from the main svg since we + // want the minimap to show a zoomed-out and centered view. + let $zoomG = d3.select(this.zoomG); + let zoomTransform = $zoomG.attr("transform"); + $zoomG.attr("transform", null); + + // Get the size of the entire scene. + let sceneSize = this.zoomG.getBBox(); + // Since we add padding, account for that here. + sceneSize.height += this.labelPadding; + + // Temporarily assign an explicit width/height to the main svg, since + // it doesn't have one (uses flex-box), but we need it for the canvas + // to work. + $svg.attr({ + width: sceneSize.width, + height: sceneSize.height, + }); + + // Since the content inside the svg changed (e.g. a node was expanded), + // the aspect ratio have also changed. Thus, we need to update the scale + // factor of the minimap. The scale factor is determined such that both + // the width and height of the minimap are <= maximum specified w/h. + this.scaleMinimap = + this.maxWandH / Math.max(sceneSize.width, sceneSize.height); + + this.minimapSize = { + width: sceneSize.width * this.scaleMinimap, + height: sceneSize.height * this.scaleMinimap + }; + + // Update the size of the minimap's svg, the buffer canvas and the + // viewpoint rect. + d3.select(this.minimapSvg).attr(this.minimapSize); + d3.select(this.canvasBuffer).attr(this.minimapSize); + if (this.translate != null && this.zoom != null) { + // Update the viewpoint rectangle shape since the aspect ratio of the + // map has changed. + requestAnimationFrame(() => this.zoom()); + } + + // Serialize the main svg to a string which will be used as the rendering + // content for the canvas. + let svgXml = (new XMLSerializer()).serializeToString(this.svg); + + // Now that the svg is serialized for rendering, remove the temporarily + // assigned styles, explicit width and height and bring back the pan/zoom + // transform. + svgStyle.remove(); + $svg.attr({ + width: null, + height: null + }); + $zoomG.attr("transform", zoomTransform); + let image = new Image(); + image.onload = () => { + // Draw the svg content onto the buffer canvas. + let context = this.canvasBuffer.getContext("2d"); + context.clearRect(0, 0, this.canvasBuffer.width, + this.canvasBuffer.height); + context.drawImage(image, 0, 0, + this.minimapSize.width, this.minimapSize.height); + requestAnimationFrame(() => { + // Hide the old canvas and show the new buffer canvas. + d3.select(this.canvasBuffer).style("display", null); + d3.select(this.canvas).style("display", "none"); + // Swap the two canvases. + [this.canvas, this.canvasBuffer] = [this.canvasBuffer, this.canvas]; + }); + }; + image.src = "data:image/svg+xml;base64," + btoa(svgXml); + } + + /** + * Handles changes in zooming/panning. Should be called from the main svg + * to notify that a zoom/pan was performed and this minimap will update it's + * viewpoint rectangle. + * + * @param translate The translate vector, or none to use the last used one. + * @param scale The scaling factor, or none to use the last used one. + */ + zoom(translate?: [number, number], scale?: number): void { + // Update the new translate and scale params, only if specified. + this.translate = translate || this.translate; + this.scaleMain = scale || this.scaleMain; + // Update the location of the viewpoint rectangle. + let svgRect = this.svg.getBoundingClientRect(); + let $viewpoint = d3.select(this.viewpoint); + this.viewpointCoord.x = -this.translate[0] * this.scaleMinimap / + this.scaleMain; + this.viewpointCoord.y = -this.translate[1] * this.scaleMinimap / + this.scaleMain; + let viewpointWidth = svgRect.width * this.scaleMinimap / this.scaleMain; + let viewpointHeight = svgRect.height * this.scaleMinimap / this.scaleMain; + $viewpoint.attr({ + x: this.viewpointCoord.x, + y: this.viewpointCoord.y, + width: viewpointWidth, + height: viewpointHeight + }); + // Show/hide the minimap depending on the viewpoint area as fraction of the + // whole minimap. + let mapWidth = this.minimapSize.width; + let mapHeight = this.minimapSize.height; + let x = this.viewpointCoord.x; + let y = this.viewpointCoord.y; + let w = Math.min(Math.max(0, x + viewpointWidth), mapWidth) - + Math.min(Math.max(0, x), mapWidth); + let h = Math.min(Math.max(0, y + viewpointHeight), mapHeight) - + Math.min(Math.max(0, y), mapHeight); + let fracIntersect = (w * h) / (mapWidth * mapHeight); + if (fracIntersect < FRAC_VIEWPOINT_AREA) { + this.minimap.classList.remove("hidden"); + } else { + this.minimap.classList.add("hidden"); + } + } +} + +} // close module tf.scene -- cgit v1.2.3