diff --git a/demos/minimap/icon.png b/demos/minimap/icon.png
new file mode 100644
index 000000000..870caa070
Binary files /dev/null and b/demos/minimap/icon.png differ
diff --git a/demos/minimap/index.html b/demos/minimap/index.html
new file mode 100644
index 000000000..2e495fdb1
--- /dev/null
+++ b/demos/minimap/index.html
@@ -0,0 +1,90 @@
+
+
+
+
+ Blockly Demo: Minimap
+
+
+
+
+
+
+
+
+
+ This is a simple demo showing how a minimap can be implemented.
+
+
+
+
+
+
+
+
+
+
+
+ i
+ j
+ k
+
+
+
+
+
+
+
diff --git a/demos/minimap/minimap.js b/demos/minimap/minimap.js
new file mode 100644
index 000000000..4ac687e39
--- /dev/null
+++ b/demos/minimap/minimap.js
@@ -0,0 +1,306 @@
+/**
+* Blockly Demos: Code
+*
+* Copyright 2017 Google Inc.
+* https://developers.google.com/blockly/
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/**
+* @fileoverview JavaScript for Blockly's Minimap demo.
+* @author karnpurohit@gmail.com (Karan Purohit)
+*/
+'use strict';
+
+/**
+* Creating a seperate namespace for minimap.
+*/
+var Minimap = {};
+
+/**
+* Initilize the workspace and minimap.
+* @param {Workspace} workspace The main workspace of the user.
+* @param {Workspace} minimap The workspace that will be used as a minimap.
+*/
+Minimap.init = function(workspace, minimap){
+ this.workspace = workspace;
+ this.minimap = minimap;
+
+ //Adding scroll callback functionlity to vScroll and hScroll just for this demo.
+ //IMPORTANT: This should be changed when there is proper UI event handling
+ // api available and should be handled by workspace's event listeners.
+ this.workspace.scrollbar.vScroll.setHandlePosition = function(newPosition){
+ this.handlePosition_ = newPosition;
+ this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_);
+
+ // Code above is same as the original setHandlePosition function in core/scrollbar.js.
+ // New code starts from here.
+
+ // Get the absolutePosition.
+ var absolutePosition = (this.handlePosition_ / this.ratio_);
+
+ // Firing the scroll change listener.
+ Minimap.onScrollChange(absolutePosition, this.horizontal_);
+ };
+
+ // Adding call back for horizontal scroll.
+ this.workspace.scrollbar.hScroll.setHandlePosition = function(newPosition){
+ this.handlePosition_ = newPosition;
+ this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_);
+
+ // Code above is same as the original setHandlePosition function in core/scrollbar.js.
+ // New code starts from here.
+
+ // Get the absolutePosition.
+ var absolutePosition = (this.handlePosition_ / this.ratio_);
+
+ // Firing the scroll change listener.
+ Minimap.onScrollChange(absolutePosition, this.horizontal_);
+ };
+
+
+ // Required to stop a positive feedback loop when user clicks minimap
+ // and the scroll changes, which inturn may change minimap.
+ this.disableScrollChange = false;
+
+ // Listen to events on the main workspace.
+ this.workspace.addChangeListener(Minimap.mirrorEvent);
+
+ //Get rectangle bounding the minimap div.
+ this.rect = document.getElementById('mapDiv').getBoundingClientRect();
+
+ // Create a svg overlay on the top of mapDiv for the minimap.
+ this.svg = Blockly.utils.createSvgElement('svg', {
+ 'xmlns': 'http://www.w3.org/2000/svg',
+ 'xmlns:html': 'http://www.w3.org/1999/xhtml',
+ 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
+ 'version': '1.1',
+ 'height': this.rect.bottom-this.rect.top,
+ 'width': this.rect.right-this.rect.left,
+ 'class': 'minimap',
+ }, document.getElementById('mapDiv'));
+ this.svg.style.top = this.rect.top + 'px';
+ this.svg.style.left = this.rect.left + 'px';
+
+ // Creating a rectangle in the minimap that represents current view.
+ Blockly.utils.createSvgElement('rect', {
+ 'width':100,
+ 'height':100,
+ 'class':'mapDragger'
+ }, this.svg);
+
+ // Rectangle in the minimap that represents current view.
+ this.mapDragger = this.svg.childNodes[0];
+
+ // Adding mouse events to the rectangle, to make it Draggable.
+ // Using Blockly.bindEvent_ to attach mouse/touch listeners.
+ Blockly.bindEvent_(this.mapDragger, "mousedown", null, Minimap.mousedown);
+
+ //When the window change, we need to resize the minimap window.
+ window.addEventListener('resize', Minimap.repositionMinimap);
+
+ // Mouse up event for the minimap.
+ this.svg.addEventListener('mouseup', Minimap.updateMapDragger);
+
+ //Boolen to check whether I am dragging the surface or not.
+ this.isDragging = false;
+};
+
+Minimap.mousedown = function(e){
+ // Using Blockly.bindEvent_ to attach mouse/touch listeners.
+ Minimap.mouseMoveBindData = Blockly.bindEvent_(document,"mousemove", null, Minimap.mousemove);
+ Minimap.mouseUpBindData = Blockly.bindEvent_(document,"mouseup", null, Minimap.mouseup);
+
+ Minimap.isDragging=true;
+ e.stopPropagation();
+};
+
+Minimap.mouseup = function(e){
+ Minimap.isDragging = false;
+ // Removing listeners.
+ Blockly.unbindEvent_(Minimap.mouseUpBindData);
+ Blockly.unbindEvent_(Minimap.mouseMoveBindData);
+ Minimap.updateMapDragger(e);
+ e.stopPropagation();
+};
+
+Minimap.mousemove = function(e){
+ if(Minimap.isDragging){
+ Minimap.updateMapDragger(e);
+ e.stopPropagation();
+ }
+};
+
+/**
+* Initilize the workspace and minimap.
+* @param {Event} event Event that triggered in the main workspace.
+*/
+Minimap.mirrorEvent = function(event){
+ if (event.type == Blockly.Events.UI) {
+ return; // Don't mirror UI events.
+ }
+ // Convert event to JSON. This could then be transmitted across the net.
+ var json = event.toJson();
+ // Convert JSON back into an event, then execute it.
+ var minimapEvent = Blockly.Events.fromJson(json, Minimap.minimap);
+ minimapEvent.run(true);
+ Minimap.scaleMinimap();
+ Minimap.setDraggerHeight();
+ Minimap.setDraggerWidth();
+};
+
+/**
+* Called when window is resized. Repositions the minimap overlay.
+*/
+Minimap.repositionMinimap = function(){
+ Minimap.rect = document.getElementById('mapDiv').getBoundingClientRect();
+ Minimap.svg.style.top = Minimap.rect.top + 'px';
+ Minimap.svg.style.left = Minimap.rect.left + 'px';
+};
+
+/**
+* Updates the rectangle's height .
+*/
+Minimap.setDraggerHeight = function(){
+ var workspaceMetrics = Minimap.workspace.getMetrics();
+ var draggerHeight = (workspaceMetrics.viewHeight / Minimap.workspace.scale) * Minimap.minimap.scale;
+ // It's zero when first block is placed.
+ if(draggerHeight == 0){
+ return;
+ }
+ Minimap.mapDragger.setAttribute("height", draggerHeight);
+};
+
+/**
+* Updates the rectangle's width.
+*/
+Minimap.setDraggerWidth = function(){
+ var workspaceMetrics = Minimap.workspace.getMetrics();
+ var draggerWidth = (workspaceMetrics.viewWidth / Minimap.workspace.scale) * Minimap.minimap.scale;
+ // It's zero when first block is placed.
+ if(draggerWidth == 0){
+ return;
+ }
+ Minimap.mapDragger.setAttribute("width", draggerWidth);
+};
+
+
+/**
+* Updates the overall position of the viewport of the minimap by appropriately
+* using translate functions.
+*/
+Minimap.scaleMinimap = function(){
+ var minimapBoundingBox = Minimap.minimap.getBlocksBoundingBox();
+ var workspaceBoundingBox = Minimap.workspace.getBlocksBoundingBox();
+ var workspaceMetrics = Minimap.workspace.getMetrics();
+ var minimapMetrics = Minimap.minimap.getMetrics();
+
+ //Scaling the mimimap such that all the blocks can be seen in the viewport.
+ //This padding is default because this is how to scrollbar(in main workspace) is implemented.
+ var topPadding = (workspaceMetrics.viewHeight) * Minimap.minimap.scale / (2 * Minimap.workspace.scale);
+ var sidePadding = (workspaceMetrics.viewWidth) * Minimap.minimap.scale / (2 * Minimap.workspace.scale);
+
+ // If actual padding is more than half view ports height, change it to actual padding.
+ if((workspaceBoundingBox.y * Minimap.workspace.scale - workspaceMetrics.contentTop)
+ * Minimap.minimap.scale / Minimap.workspace.scale > topPadding){
+ topPadding = (workspaceBoundingBox.y * Minimap.workspace.scale - workspaceMetrics.contentTop)
+ * Minimap.minimap.scale / Minimap.workspace.scale;
+ }
+
+ // If actual padding is more than half view ports height, change it to actual padding.
+ if((workspaceBoundingBox.x * Minimap.workspace.scale - workspaceMetrics.contentLeft)
+ * Minimap.minimap.scale / Minimap.workspace.scale > sidePadding){
+ sidePadding = (workspaceBoundingBox.x * Minimap.workspace.scale - workspaceMetrics.contentLeft)
+ * Minimap.minimap.scale / Minimap.workspace.scale;
+ }
+
+ var scalex = (minimapMetrics.viewWidth - 2 * sidePadding) / minimapBoundingBox.width;
+ var scaley = (minimapMetrics.viewHeight - 2 * topPadding) / minimapBoundingBox.height;
+ Minimap.minimap.setScale(Math.min(scalex, scaley));
+
+ // Translating the minimap.
+ Minimap.minimap.translate( - minimapMetrics.contentLeft * Minimap.minimap.scale + sidePadding,
+ - minimapMetrics.contentTop * Minimap.minimap.scale + topPadding);
+};
+
+/**
+* Handles the onclick event on the minimapBoundingBox. Changes mapDraggers position.
+* @param {Event} e Event from the mouse click.
+*/
+Minimap.updateMapDragger = function(e){
+ var y = e.clientY;
+ var x = e.clientX;
+ var draggerHeight = Minimap.mapDragger.getAttribute("height");
+ var draggerWidth = Minimap.mapDragger.getAttribute("width");
+
+ var finalY = y - Minimap.rect.top - draggerHeight / 2;
+ var finalX = x - Minimap.rect.left - draggerWidth / 2;
+
+ var maxValidY = (Minimap.workspace.getMetrics().contentHeight - Minimap.workspace.getMetrics().viewHeight)
+ * Minimap.minimap.scale;
+ var maxValidX = (Minimap.workspace.getMetrics().contentWidth - Minimap.workspace.getMetrics().viewWidth)
+ * Minimap.minimap.scale;
+
+ if(y + draggerHeight / 2 > Minimap.rect.bottom){
+ finalY = Minimap.rect.bottom - Minimap.rect.top - draggerHeight;
+ }else if(y < Minimap.rect.top + draggerHeight / 2){
+ finalY = 0;
+ }
+
+ if(x + draggerWidth / 2 > Minimap.rect.right){
+ finalX = Minimap.rect.right - Minimap.rect.left - draggerWidth;
+ }else if(x < Minimap.rect.left + draggerWidth / 2){
+ finalX = 0;
+ }
+
+ // Do not go below lower bound of scrollbar.
+ if(finalY > maxValidY){
+ finalY = maxValidY;
+ }
+
+ if(finalX > maxValidX){
+ finalX = maxValidX;
+ }
+ Minimap.mapDragger.setAttribute("y", finalY);
+ Minimap.mapDragger.setAttribute("x", finalX);
+ // Required, otherwise creates a feedback loop.
+ Minimap.disableScrollChange = true;
+ Minimap.workspace.scrollbar.vScroll.set((finalY * Minimap.workspace.scale) / Minimap.minimap.scale);
+ Minimap.workspace.scrollbar.hScroll.set((finalX * Minimap.workspace.scale) / Minimap.minimap.scale);
+ Minimap.disableScrollChange = false;
+};
+
+/**
+* Handles the onclick event on the minimapBoundingBox, paramaters are passed by
+* the event handler.
+* @param {Float} position This is the absolute postion of the scrollbar.
+* @param {boolean} horizontal Informs if the change event if for horizontal(true)
+* scrollbar or vertical(false) scrollbar.
+*/
+Minimap.onScrollChange = function(position, horizontal){
+
+ if(Minimap.disableScrollChange){
+ return;
+ }
+
+ var newDraggerPosition = (position * Minimap.minimap.scale / Minimap.workspace.scale);
+ if(horizontal){
+ // Change the horizontal position of dragger.
+ Minimap.mapDragger.setAttribute("x", newDraggerPosition);
+ }
+ else{
+ // Change the vertical position of dragger.
+ Minimap.mapDragger.setAttribute("y", newDraggerPosition);
+ }
+};