added new map screenshot capture method

This commit is contained in:
Connor Turland 2014-11-09 22:10:13 -05:00
parent d73fa37f57
commit 44923eb660
9 changed files with 197 additions and 26 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -20,7 +20,13 @@ Metamaps.JIT = {
$(".zoomIn").click(self.zoomIn); $(".zoomIn").click(self.zoomIn);
$(".zoomOut").click(self.zoomOut); $(".zoomOut").click(self.zoomOut);
$(".zoomExtents").click(self.zoomExtents);
var zoomExtents = function (event) {
self.zoomExtents(event, Metamaps.Visualize.mGraph.canvas);
};
$(".zoomExtents").click(zoomExtents);
$(".takeScreenshot").click(Metamaps.Map.exportImage);
self.synapseStarImage = new Image(); self.synapseStarImage = new Image();
self.synapseStarImage.src = '/assets/synapsestar.png'; self.synapseStarImage.src = '/assets/synapsestar.png';
@ -123,7 +129,11 @@ Metamaps.JIT = {
var directionCat = synapse.get("category"); var directionCat = synapse.get("category");
//label placement on edges //label placement on edges
Metamaps.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse); if (canvas.denySelected) {
var color = Metamaps.Settings.colors.synapses.normal;
canvas.getCtx().fillStyle = canvas.getCtx().strokeStyle = color;
}
Metamaps.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse, canvas);
//check for edge label in data //check for edge label in data
var desc = synapse.get("desc"); var desc = synapse.get("desc");
@ -140,7 +150,7 @@ Metamaps.JIT = {
} }
}; };
if (desc != "" && showDesc) { if (!canvas.denySelected && desc != "" && showDesc) {
// '&' to '&' // '&' to '&'
desc = Metamaps.Util.decodeEntities(desc); desc = Metamaps.Util.decodeEntities(desc);
@ -188,7 +198,7 @@ Metamaps.JIT = {
drawStar(ctx, x + width, y); drawStar(ctx, x + width, y);
} }
} }
else if (showDesc) { else if (!canvas.denySelected && showDesc) {
if (adj.getData("synapses").length > 1) { if (adj.getData("synapses").length > 1) {
var ctx = canvas.getCtx(); var ctx = canvas.getCtx();
var x = (pos.x + posChild.x) / 2; var x = (pos.x + posChild.x) / 2;
@ -400,7 +410,7 @@ Metamaps.JIT = {
ctx = canvas.getCtx(); ctx = canvas.getCtx();
// if the topic is selected draw a circle around it // if the topic is selected draw a circle around it
if (node.selected) { if (!canvas.denySelected && node.selected) {
ctx.beginPath(); ctx.beginPath();
ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false); ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false);
ctx.strokeStyle = Metamaps.Settings.colors.topics.selected; ctx.strokeStyle = Metamaps.Settings.colors.topics.selected;
@ -1595,12 +1605,10 @@ Metamaps.JIT = {
ctx.lineTo(v2.x, v2.y); ctx.lineTo(v2.x, v2.y);
ctx.stroke(); ctx.stroke();
}, // renderMidArrow }, // renderMidArrow
renderEdgeArrows: function (edgeHelper, adj, synapse) { renderEdgeArrows: function (edgeHelper, adj, synapse, canvas) {
var self = Metamaps.JIT; var self = Metamaps.JIT;
var canvas = Metamaps.Visualize.mGraph.canvas;
var directionCat = synapse.get('category'); var directionCat = synapse.get('category');
var direction = synapse.getDirection(); var direction = synapse.getDirection();
@ -1657,8 +1665,7 @@ Metamaps.JIT = {
Metamaps.Visualize.mGraph.canvas.scale(0.8,0.8); Metamaps.Visualize.mGraph.canvas.scale(0.8,0.8);
$(document).trigger(Metamaps.JIT.events.zoom, [event]); $(document).trigger(Metamaps.JIT.events.zoom, [event]);
}, },
centerMap: function () { centerMap: function (canvas) {
var canvas = Metamaps.Visualize.mGraph.canvas;
var offsetScale = canvas.scaleOffsetX; var offsetScale = canvas.scaleOffsetX;
canvas.scale(1/offsetScale,1/offsetScale); canvas.scale(1/offsetScale,1/offsetScale);
@ -1674,7 +1681,8 @@ Metamaps.JIT = {
eX = Metamaps.Mouse.boxEndCoordinates.x, eX = Metamaps.Mouse.boxEndCoordinates.x,
eY = Metamaps.Mouse.boxEndCoordinates.y; eY = Metamaps.Mouse.boxEndCoordinates.y;
Metamaps.JIT.centerMap(); var canvas = Metamaps.Visualize.mGraph.canvas;
Metamaps.JIT.centerMap(canvas);
var height = $(document).height(), var height = $(document).height(),
width = $(document).width(); width = $(document).width();
@ -1686,8 +1694,6 @@ Metamaps.JIT = {
var newRatio = Math.min(ratioX,ratioY); var newRatio = Math.min(ratioX,ratioY);
var canvas = Metamaps.Visualize.mGraph.canvas;
if(canvas.scaleOffsetX *newRatio<= 5 && canvas.scaleOffsetX*newRatio >= 0.2){ if(canvas.scaleOffsetX *newRatio<= 5 && canvas.scaleOffsetX*newRatio >= 0.2){
canvas.scale(newRatio,newRatio); canvas.scale(newRatio,newRatio);
} }
@ -1711,15 +1717,14 @@ Metamaps.JIT = {
Metamaps.Visualize.mGraph.plot(); Metamaps.Visualize.mGraph.plot();
}, },
zoomExtents: function (event) { zoomExtents: function (event, canvas, denySelected) {
Metamaps.JIT.centerMap(); Metamaps.JIT.centerMap(canvas);
var height = $(document).height(), var height = canvas.getSize().height,
width = $(document).width(), width = canvas.getSize().width,
maxX, minX, maxY, minY, counter = 0; maxX, minX, maxY, minY, counter = 0;
var canvas = Metamaps.Visualize.mGraph.canvas;
if (Metamaps.Selected.Nodes.length > 0) { if (!denySelected && Metamaps.Selected.Nodes.length > 0) {
var nodes = Metamaps.Selected.Nodes; var nodes = Metamaps.Selected.Nodes;
} }
else { else {

View file

@ -125,7 +125,7 @@
if (Metamaps.Visualize.mGraph) { if (Metamaps.Visualize.mGraph) {
Metamaps.Visualize.mGraph.graph.empty(); Metamaps.Visualize.mGraph.graph.empty();
Metamaps.Visualize.mGraph.plot(); Metamaps.Visualize.mGraph.plot();
Metamaps.JIT.centerMap(); Metamaps.JIT.centerMap(Metamaps.Visualize.mGraph.canvas);
} }
Metamaps.Famous.viz.show(); Metamaps.Famous.viz.show();
Metamaps.Topic.end(); Metamaps.Topic.end();
@ -156,7 +156,7 @@
if (Metamaps.Visualize.mGraph) { if (Metamaps.Visualize.mGraph) {
Metamaps.Visualize.mGraph.graph.empty(); Metamaps.Visualize.mGraph.graph.empty();
Metamaps.Visualize.mGraph.plot(); Metamaps.Visualize.mGraph.plot();
Metamaps.JIT.centerMap(); Metamaps.JIT.centerMap(Metamaps.Visualize.mGraph.canvas);
} }
Metamaps.Famous.viz.show(); Metamaps.Famous.viz.show();
Metamaps.Map.end(); Metamaps.Map.end();

View file

@ -3384,7 +3384,7 @@ Metamaps.Listeners = {
case 69: //if e or E is pressed case 69: //if e or E is pressed
if (e.ctrlKey){ if (e.ctrlKey){
e.preventDefault(); e.preventDefault();
Metamaps.JIT.zoomExtents(); Metamaps.JIT.zoomExtents(null, Metamaps.Visualize.mGraph.canvas);
} }
break; break;
case 77: //if m or M is pressed case 77: //if m or M is pressed
@ -4203,6 +4203,130 @@ Metamaps.Map = {
Metamaps.Map.sideLength = 1; Metamaps.Map.sideLength = 1;
Metamaps.Map.timeToTurn = 0; Metamaps.Map.timeToTurn = 0;
Metamaps.Map.turnCount = 0; Metamaps.Map.turnCount = 0;
},
exportImage: function() {
var canvas = {};
canvas.canvas = document.createElement("canvas");
canvas.canvas.width = 1880; // 960;
canvas.canvas.height = 1260; // 630
canvas.scaleOffsetX = 1;
canvas.scaleOffsetY = 1;
canvas.translateOffsetY = 0;
canvas.translateOffsetX = 0;
canvas.denySelected = true;
canvas.getSize = function() {
if(this.size) return this.size;
var canvas = this.canvas;
return this.size = {
width: canvas.width,
height: canvas.height
};
};
canvas.scale = function(x, y) {
var px = this.scaleOffsetX * x,
py = this.scaleOffsetY * y;
var dx = this.translateOffsetX * (x -1) / px,
dy = this.translateOffsetY * (y -1) / py;
this.scaleOffsetX = px;
this.scaleOffsetY = py;
this.getCtx().scale(x, y);
this.translate(dx, dy);
};
canvas.translate = function(x, y) {
var sx = this.scaleOffsetX,
sy = this.scaleOffsetY;
this.translateOffsetX += x*sx;
this.translateOffsetY += y*sy;
this.getCtx().translate(x, y);
};
canvas.getCtx = function() {
return this.canvas.getContext("2d");
};
// center it
canvas.getCtx().translate(1880/2, 1260/2);
var mGraph = Metamaps.Visualize.mGraph;
var id = mGraph.root;
var root = mGraph.graph.getNode(id);
var T = !!root.visited;
// pass true to avoid basing it on a selection
Metamaps.JIT.zoomExtents(null, canvas, true);
var c = canvas.canvas,
ctx = canvas.getCtx(),
scale = canvas.scaleOffsetX;
// draw a grey background
ctx.fillStyle = '#d8d9da';
var xPoint = (-(c.width/scale)/2) - (canvas.translateOffsetX/scale),
yPoint = (-(c.height/scale)/2) - (canvas.translateOffsetY/scale);
ctx.fillRect(xPoint,yPoint,c.width/scale,c.height/scale);
// draw the graph
mGraph.graph.eachNode(function(node) {
var nodeAlpha = node.getData('alpha');
node.eachAdjacency(function(adj) {
var nodeTo = adj.nodeTo;
if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
mGraph.fx.plotLine(adj, canvas);
}
});
if(node.drawn) {
mGraph.fx.plotNode(node, canvas);
}
if(!mGraph.labelsHidden) {
if(node.drawn && nodeAlpha >= 0.95) {
mGraph.labels.plotLabel(canvas, node);
} else {
mGraph.labels.hideLabel(node, false);
}
}
node.visited = !T;
});
var imageData = {
encoded_image: canvas.canvas.toDataURL()
};
console.log(imageData.encoded_image);
var map = Metamaps.Active.Map;
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth()+1; //January is 0!
var yyyy = today.getFullYear();
if(dd<10) {
dd='0'+dd
}
if(mm<10) {
mm='0'+mm
}
today = mm+'/'+dd+'/'+yyyy;
var downloadMessage = "";
downloadMessage += "Captured map screenshot! ";
downloadMessage += "<a href='" + imageData.encoded_image + "' ";
downloadMessage += "download='map-" + map.id + "-screenshot-" + today + ".png'>DOWNLOAD</a>";
Metamaps.GlobalUI.notifyUser(downloadMessage);
$.ajax({
type: "POST",
dataType: 'json',
url: "/maps/" + Metamaps.Active.Map.id + "/upload_screenshot",
data: imageData,
success: function (data) {
console.log('successfully uploaded map screenshot');
},
error: function () {
console.log('failed to save map screenshot');
}
});
} }
}; };

View file

@ -763,6 +763,18 @@
background-position: 0 0; background-position: 0 0;
cursor:pointer; cursor:pointer;
} }
.takeScreenshot {
margin-bottom: 5px;
border-radius: 2px;
background-image: url(screenshot_sprite.png);
display: none;
}
.takeScreenshot:hover {
background-position: -32px 0;
}
.canEditMap .takeScreenshot {
display: block;
}
.zoomExtents { .zoomExtents {
margin-bottom:5px; margin-bottom:5px;
border-radius: 2px; border-radius: 2px;
@ -883,6 +895,10 @@
line-height:14px; line-height:14px;
} }
.toast a {
color: #4fc059;
}
/* end toast */ /* end toast */
/* feedback */ /* feedback */

View file

@ -1,6 +1,6 @@
class MapsController < ApplicationController class MapsController < ApplicationController
before_filter :require_user, only: [:create, :update, :destroy] before_filter :require_user, only: [:create, :update, :screenshot, :destroy]
respond_to :html, :json respond_to :html, :json
@ -183,6 +183,30 @@ class MapsController < ApplicationController
end end
end end
# POST maps/:id/upload_screenshot
def screenshot
@current = current_user
@map = Map.find(params[:id]).authorize_to_edit(@current)
if @map
png = Base64.decode64(params[:encoded_image]['data:image/png;base64,'.length .. -1])
StringIO.open(png) do |data|
data.class.class_eval { attr_accessor :original_filename, :content_type }
data.original_filename = "map-" + @map.id.to_s + "-screenshot.png"
data.content_type = "image/png"
@map.screenshot = data
end
if @map.save
render :json => {:message => "Successfully uploaded the map screenshot."}
else
render :json => {:message => "Failed to upload image."}
end
else
render :json => {:message => "Unauthorized to set map screenshot."}
end
end
# DELETE maps/:id # DELETE maps/:id
def destroy def destroy
@current = current_user @current = current_user

View file

@ -8,12 +8,12 @@ class Map < ActiveRecord::Base
has_many :topics, :through => :topicmappings has_many :topics, :through => :topicmappings
has_many :synapses, :through => :synapsemappings has_many :synapses, :through => :synapsemappings
after_touch :save_screenshot #after_touch :save_screenshot
# This method associates the attribute ":image" with a file attachment # This method associates the attribute ":image" with a file attachment
has_attached_file :screenshot, :styles => { has_attached_file :screenshot, :styles => {
:thumb => ['188x126#', :png], :thumb => ['188x126#', :png]
:full => ['940x630#', :png] #:full => ['940x630#', :png]
}, },
:default_url => "/assets/missing-map.png" :default_url => "/assets/missing-map.png"

View file

@ -1,4 +1,5 @@
<div class="mapControls mapElement"> <div class="mapControls mapElement">
<div class="takeScreenshot mapControl"></div>
<div class="zoomExtents mapControl"></div> <div class="zoomExtents mapControl"></div>
<div class="zoomIn mapControl"></div> <div class="zoomIn mapControl"></div>
<div class="zoomOut mapControl"></div> <div class="zoomOut mapControl"></div>

View file

@ -30,6 +30,7 @@ Metamaps::Application.routes.draw do
match 'maps/topics/:id', to: 'maps#index', via: :get, as: :topicmaps match 'maps/topics/:id', to: 'maps#index', via: :get, as: :topicmaps
resources :maps, except: [:new, :edit] resources :maps, except: [:new, :edit]
match 'maps/:id/contains', to: 'maps#contains', via: :get, as: :contains match 'maps/:id/contains', to: 'maps#contains', via: :get, as: :contains
match 'maps/:id/upload_screenshot', to: 'maps#screenshot', via: :post, as: :screenshot
devise_for :users, controllers: { registrations: 'users/registrations', passwords: 'users/passwords', sessions: 'devise/sessions' }, :skip => [:sessions] devise_for :users, controllers: { registrations: 'users/registrations', passwords: 'users/passwords', sessions: 'devise/sessions' }, :skip => [:sessions]