import $ from "jquery"; import "jquery-contextmenu"; import "jquery.ui.position"; import _ from "underscore"; // Use lighter weight 'core' version of paper since we don't need paperscript import paper from "../node_modules/paper/dist/paper-core.js"; const CommandManager = (function() { function CommandManager() {} CommandManager.executed = []; CommandManager.unexecuted = []; CommandManager.execute = function execute(cmd) { cmd.execute(); CommandManager.executed.push(cmd); }; CommandManager.undo = function undo() { const cmd1 = CommandManager.executed.pop(); if (cmd1 !== undefined) { if (cmd1.unexecute !== undefined) { cmd1.unexecute(); } CommandManager.unexecuted.push(cmd1); } }; CommandManager.redo = function redo() { let cmd2 = CommandManager.unexecuted.pop(); if (cmd2 === undefined) { cmd2 = CommandManager.executed.pop(); CommandManager.executed.push(cmd2); CommandManager.executed.push(cmd2); } if (cmd2 !== undefined) { cmd2.execute(); CommandManager.executed.push(cmd2); } }; return CommandManager; })(); const generateUUID = function() { let d = new Date().getTime(); const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { const r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == "x" ? r : (r & 0x7) | 0x8).toString(16); }); return uuid; }; window.bundleEntries = window.bundleEntries || {}; window.bundleEntries.load = function (opt) { const chart = opt.chart; const dataset = opt.dataset; const defaults = { color: "red", width: 4, opacity: 0.5 }; $.fn.createCanvas = function(options) { let settings = $.extend({}, defaults, options || {}); const self = this; this.setOptions = function(options) { settings = $.extend(settings, options); }; $(document).ready(function() { $(self).each(function(eachIndex, eachItem) { self.paths = []; const img = eachItem; // Get a reference to the canvas object const canvas = $("") .attr({ width: options.img_width + "px", height: options.img_height + "px" }) .addClass("image-canvas") .css({ position: "absolute", top: "0px", left: "0px" }); $(img).after(canvas); $(img).data("paths", []); // Create an empty project and a view for the canvas paper.setup(canvas[0]); canvas[0].width = options.img_width; canvas[0].height = options.img_height; $(canvas).mouseenter(function() { paper.projects[eachIndex].activate(); }); // Create a simple drawing tool: const tool = new paper.Tool(); tool.onMouseMove = function(event) { if (!$(".context-menu-list").is(":visible")) { paper.project.activeLayer.selected = false; self.setPenColor(settings.color); if (event.item) { event.item.selected = true; selectedItem = event.item; self.setCursorHandOpen(); } else { selectedItem = null; } } }; tool.onMouseDown = function(event) { switch (event.event.button) { // leftclick case 0: // If we produced a path before, deselect it: if (path) { path.selected = false; } path = new paper.Path(); path.data.id = generateUUID(); path.strokeColor = settings.color; path.strokeWidth = settings.width; path.opacity = settings.opacity; break; // rightclick case 2: break; } }; tool.onMouseDrag = function(event) { switch (event.event.button) { // leftclick case 0: // Every drag event, add a point to the path at the current // position of the mouse: if (selectedItem) { if (!mouseDownPoint) { mouseDownPoint = selectedItem.position; } self.setCursorHandClose(); selectedItem.position = new paper.Point( selectedItem.position.x + event.delta.x, selectedItem.position.y + event.delta.y ); } else if (path) { path.add(event.point); } break; // rightclick case 2: break; } }; tool.onMouseUp = function(event) { switch (event.event.button) { // leftclick case 0: if (selectedItem) { if (mouseDownPoint) { const selectedItemId = selectedItem.id; const draggingStartPoint = { x: mouseDownPoint.x, y: mouseDownPoint.y }; CommandManager.execute({ execute: function() { //item was already moved, so do nothing }, unexecute: function() { $(paper.project.activeLayer.children).each(function(index, item) { if (item.id == selectedItemId) { if (item.segments) { new paper.Point( (item.segments[item.segments.length - 1].point.x - item.segments[0].point.x) / 2, (item.segments[item.segments.length - 1].point.y - item.segments[0].point.y) / 2 ); item.position = new paper.Point( draggingStartPoint.x, draggingStartPoint.y ); } else { item.position = draggingStartPoint; } return false; } }); } }); mouseDownPoint = null; } } else { // When the mouse is released, simplify it: path.simplify(); path.remove(); const strPath = path.exportJSON({ asString: true }); const uid = generateUUID(); CommandManager.execute({ execute: function() { path = new paper.Path(); path.importJSON(strPath); path.data.uid = uid; }, unexecute: function() { $(paper.project.activeLayer.children).each(function(index, item) { if (item.data && item.data.uid) { if (item.data.uid == uid) { item.remove(); } } }); } }); } break; // rightclick case 2: contextPoint = event.point; //Unused? //contextSelectedItemId = selectedItem ? selectedItem.data.id : ""; break; } }; tool.onKeyUp = function(event) { if (selectedItem) { // When a key is released, set the content of the text item: if (selectedItem.content) { if (event.key == "backspace") selectedItem.content = selectedItem.content.slice(0, -1); } else { selectedItem.content = selectedItem.content.replace("", ""); if (event.key == "space") selectedItem.content += " "; else if (event.key.length == 1) selectedItem.content += event.key; } } }; paper.view.draw(); }); }); let path; let contextPoint; //let contextSelectedItemId; let selectedItem; let mouseDownPoint; this.downloadCanvas = function(canvas, filename) { /// create an "off-screen" anchor tag const lnk = document.createElement("a"); /// the key here is to set the download attribute of the a tag lnk.download = filename; /// convert canvas content to data-uri for link. When download /// attribute is set the content pointed to by link will be /// pushed as "download" in HTML5 capable browsers lnk.href = canvas.toDataURL(); /// create a "fake" click-event to trigger the download if (document.createEvent) { const e = document.createEvent("MouseEvents"); e.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); lnk.dispatchEvent(e); } else if (lnk.fireEvent) { lnk.fireEvent("onclick"); } }; this.download = function() { const canvas = paper.project.activeLayer.view.element; const img = $(canvas)[0]; const mergeCanvas = $("").attr({ width: img.width, height: img.height }); const mergedContext = mergeCanvas[0].getContext("2d"); mergedContext.clearRect(0, 0, img.width, img.height); mergedContext.drawImage(img, 0, 0); mergedContext.drawImage(canvas, 0, 0); self.downloadCanvas(mergeCanvas[0], "only-annotations.png"); // create canvas for original and annotations const annotated_img = $(canvas) .parent() .find("img")[0]; const mergeCanvasAnnotated = $("").attr({ width: $(annotated_img).width(), height: $(annotated_img).height() }); const mergedContextAnnotated = mergeCanvasAnnotated[0].getContext("2d"); mergedContextAnnotated.clearRect(0, 0, $(annotated_img).width(), $(annotated_img).height()); mergedContextAnnotated.drawImage(annotated_img, 0, 0); mergedContextAnnotated.drawImage(canvas, 0, 0); self.downloadCanvas(mergeCanvasAnnotated[0], "original-with-annotations.png"); }; this.setText = function() { const uid = generateUUID(); const pos = contextPoint; CommandManager.execute({ execute: function() { const TXT_DBL_CLICK = "<>"; const txt = TXT_DBL_CLICK; const text = new paper.PointText(pos); text.content = txt; text.fillColor = settings.color; text.fontSize = 14; text.fontFamily = "sans-serif"; text.data.uid = uid; text.opacity = settings.opacity; text.onDoubleClick = function(event) { if (this.className == "PointText") { const txt = window.prompt("Type in your text", this.content.replace(TXT_DBL_CLICK, "")); if (txt.length > 0) this.content = txt; } }; }, unexecute: function() { $(paper.project.activeLayer.children).each(function(index, item) { if (item.data && item.data.uid) { if (item.data.uid == uid) { item.remove(); } } }); } }); }; this.setPenColor = function(color) { self.setOptions({ color: color }); $(".image-canvas").css( "cursor", "url(/static/plugins/visualizations/annotate_image/static/images/" + color + "-pen.png) 14 50, auto" ); }; this.setCursorHandOpen = function() { $(".image-canvas").css( "cursor", "url(/static/plugins/visualizations/annotate_image/static/images/hand-open.png) 25 25, auto" ); }; this.setCursorHandClose = function() { $(".image-canvas").css( "cursor", "url(/static/plugins/visualizations/annotate_image/static/images/hand-close.png) 25 25, auto" ); }; $.contextMenu({ selector: ".image-canvas", callback: function(key, options) { switch (key) { //COMMANDS case "undo": CommandManager.undo(); break; case "redo": CommandManager.redo(); break; case "download": self.download(); break; //TOOLS case "text": self.setText(); break; //PENS case "blackPen": self.setPenColor("black"); break; case "redPen": self.setPenColor("red"); break; case "greenPen": self.setPenColor("green"); break; case "bluePen": self.setPenColor("blue"); break; case "yellowPen": self.setPenColor("yellow"); break; } }, items: { undo: { name: "Undo", icon: "undo" }, redo: { name: "Redo", icon: "redo" }, download: { name: "Download", icon: "download" }, sep1: "---------", text: { name: "Text", icon: "text" }, sep2: "---------", blackPen: { name: "Black Pen", icon: "blackpen" }, redPen: { name: "Red Pen", icon: "redpen" }, greenPen: { name: "Green Pen", icon: "greenpen" }, bluePen: { name: "Blue Pen", icon: "bluepen" }, yellowPen: { name: "Yellow Pen", icon: "yellowpen" } } }); const $menuList = $(".context-menu-list"); $menuList.find(".context-menu-icon-text").css({ "background-image": "url(/static/plugins/visualizations/annotate_image/static/images/text.png)", "background-repeat": "no-repeat", "background-position": "right 96% bottom 45%" }); $menuList.find(".context-menu-icon-blackpen").css({ "background-image": "url(/static/plugins/visualizations/annotate_image/static/images/blackpen.png)", "background-repeat": "no-repeat", "background-position": "right 97% bottom 48%" }); $menuList.find(".context-menu-icon-redpen").css({ "background-image": "url(/static/plugins/visualizations/annotate_image/static/images/redpen.png)", "background-repeat": "no-repeat", "background-position": "right 97% bottom 48%" }); $menuList.find(".context-menu-icon-greenpen").css({ "background-image": "url(/static/plugins/visualizations/annotate_image/static/images/greenpen.png)", "background-repeat": "no-repeat", "background-position": "right 97% bottom 48%" }); $menuList.find(".context-menu-icon-bluepen").css({ "background-image": "url(/static/plugins/visualizations/annotate_image/static/images/bluepen.png)", "background-repeat": "no-repeat", "background-position": "right 97% bottom 48%" }); $menuList.find(".context-menu-icon-yellowpen").css({ "background-image": "url(/static/plugins/visualizations/annotate_image/static/images/yellowpen.png)", "background-repeat": "no-repeat", "background-position": "right 97% bottom 48%" }); }; $.ajax({ url: dataset.download_url, success: function(content) { const $chartViewer = $("#" + opt.target); $chartViewer.html(""); $chartViewer.css("overflow", "auto"); $chartViewer.css("position", "relative"); const $image = $chartViewer.find("img"); $image.on("load", function() { const width = $(this).width(); const height = $(this).height(); $image.width(width); $image.height(height); $image.createCanvas({ color: "red", width: 2, opacity: 0.5, img_width: width, img_height: height }); }); chart.state("ok", "Chart drawn."); opt.process.resolve(); }, error: function() { chart.state("failed", "Failed to access dataset."); opt.process.resolve(); } }); }