Multiple instances of InfoVis RGraph visualisations - javascript

I am developing an application that has multiple tabs (using jQuery UI). These can be opened and closed by the user. Within each tab is an rGraph visualisation from the JavaScript InfoVis Toolkit (http://philogb.github.io/jit/static/v20/Jit/Examples/RGraph/example1.html). (The code for both the tabs and the visualisations are very similar to the examples on their respective websites)
Whenever I create a new tab, the rGraph is generated correctly. This seems to scale quite nicely and I can click back and forth through multiple tabs without any problems. However, if I interact with an rGraph in any way (e.g. by clicking on a node to move it around), the existing rGraphs in the other tabs stop working. When I click on any of the existing rGraphs, it produces the error: TypeError: node is undefined. In other words, new instances work fine but clicking on the newest one breaks the previous ones.
Is there anything that can be done about this?
Note: There does appear to be some allowance for multiple instances in the docs - you can create each instance with a different variable name, e.g. var graph1 = new ... var graph2 = new ... etc. This does indeed work, but as the tabs (and therefore the graphs) are dynamically generated, I cannot assign specific variable names like this. I tried doing the following:
var graphs = {};
graphs[unique_id_i_pass_in] = new Graph();
...but this didn't seem to make any difference.
EDIT: Here is the code I am using to call the rGraphs (document_id is the unique variable I am passing in for each graph):
var doc_rgraph = {};
//init RGraph
doc_rgraph[document_id] = new $jit.RGraph({
//Where to append the visualization
injectInto: 'document_vis_'+document_id,
//Optional: create a background canvas that plots
//concentric circles.
background: {
CanvasStyles: {
//Colour of the background circles
strokeStyle: '#C5BE94'
}
},
//Add navigation capabilities:
//zooming by scrolling and panning.
Navigation: {
enable: true,
panning: true,
zooming: 10
},
//Set Node and Edge styles.
Node: {
//Colour of the actual nodes
color: '#660000'
},
Edge: {
//Color of lines between nodes
color: '#660000',
lineWidth:1.5
},
onBeforeCompute: function(node){
Log.write("Centering " + node.name + "...");
},
//Add the name of the node in the correponding label
//and a click handler to move the graph.
//This method is called once, on label creation.
onCreateLabel: function(domElement, node){
domElement.innerHTML = node.name;
domElement.onclick = function(){
doc_rgraph[document_id].onClick(node.id, {
onComplete: function() {
Log.write("Done");
}
});
};
},
//Change some label dom properties.
//This method is called each time a label is plotted.
onPlaceLabel: function(domElement, node){
var style = domElement.style;
style.display = '';
style.cursor = 'pointer';
if (node._depth == 0) {
style.fontSize = "0.8em";
//Text colour of ring 1 nodes
style.color = "#000000";
style.backgroundColor = "#F05322";
style.padding = "1px 3px";
} else if (node._depth == 1) {
style.fontSize = "0.8em";
//Text colour of ring 1 nodes
style.color = "#FFFFFF";
style.backgroundColor = "#164557";
style.padding = "1px 3px";
} else if(node._depth == 2){
style.fontSize = "0.7em";
//Text colour of ring 2 nodes
style.color = "#333333";
style.backgroundColor = "#CAC399";
} else {
style.display = 'none';
}
var left = parseInt(style.left);
var w = domElement.offsetWidth;
style.left = (left - w / 2) + 'px';
},
onComplete: function(){
Log.write("Done");
}
});
//load JSON data
doc_rgraph[document_id].loadJSON(document_data[document_id]);
//trigger small animation
doc_rgraph[document_id].graph.eachNode(function(n) {
var pos = n.getPos();
pos.setc(-200, -200);
});
doc_rgraph[document_id].compute('end');
doc_rgraph[document_id].fx.animate({
modes:['polar'],
duration: 1000
});
//end

Related

Can I "freeze" fixed elements to take a screenshot with getDisplayMedia?

I'm implementing a chrome extension with javascript to take a screenshot of a full page, so far I've managed to take the screenshot and make it into a canvas in a new tab, it shows the content of a tweet perfectly, but as you can see, the sidebars repeat all the time (Ignore the red button, that's part of my extension and I know how to delete it from the screenshots) screenshot of a tweet
This is the code I'm using to take the screenshot:
async function capture(){
navigator.mediaDevices.getDisplayMedia({preferCurrentTab:true}).then(mediaStream=>{
scrollTo(0,0);
var defaultOverflow = document.body.style.overflow;
//document.body.style.overflow = 'hidden';
var totalHeight = document.body.scrollHeight;
var actualHeight = document.documentElement.clientHeight;
var leftHeight = totalHeight-actualHeight;
var scroll = 200;
var blob = new Blob([document.documentElement.innerHTML],{ type: "text/plain;charset=utf-8" });
console.log('total Height:'+totalHeight+'client height:'+actualHeight+'Left Height:'+leftHeight);
var numScreenshots = Math.ceil(leftHeight/scroll);
var arrayImg = new Array();
var i = 0;
function myLoop() {
setTimeout(function() {
var track = mediaStream.getVideoTracks()[0];
let imgCapture = new ImageCapture(track);
imgCapture.grabFrame().then(bitmap=>{
arrayImg[i] = bitmap;
window.scrollBy(0,scroll);
console.log(i);
i++;
});
if (i <= numScreenshots) {
myLoop();
}else{
document.body.style.overflow = defaultOverflow;
saveAs(blob, "static.txt");
printBitMaps(arrayImg, numScreenshots, totalHeight);
}
}, 250)
}
myLoop();
})
}
async function printBitMaps(arrayImg, numScreenshots, totalHeight){
var win = window.open('about:blank', '_blank');
win.document.write('<canvas id="myCanvas" width="'+arrayImg[0].width+'px" height="'+totalHeight+'px" style="border:5px solid #000000;"></canvas>');
var e = numScreenshots+1;
function printToCanvas(){
setTimeout(function(){
var canvas = win.document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(arrayImg[e], 0, 200*e);
e--;
if(e>=0){
printToCanvas();
}
},10);
}
printToCanvas();
}
Do you know any way by CSS or javascript that I can use to make the sidebars stay at the top of the page so they don't keep coming down with the scroll?
It's not really a case of "the sidebars ... coming down with the scroll" - the code you're using is taking a first screenshot, scrolling the page, taking another screenshot and then stitching it onto the last one and iterating to the bottom of the page. Thus it's inevitable you're seeing what you see on the screen at the point you take the subsequent screenshots.
To resolve your issue, after your first screenshot you would need to set the div element for the side bar to be display=None by use of CSS. You can find details of the side bar by using the browser Dev Tools, right clicking an using "Inspect" in Chrome.
From what I can see, Twitter seems to use fairly cryptic class names, so it might be easiest and more robust to identify the div for the side bar with some other attribute. It appears they set data-testid="sidebarColumn" on it so give using that a go (but YMMV).

(Vis.js network) Incorrect node coordinates with getPositions?

In my vis.js network, I want to make a popup appear at the position of a node when I click on the node.
I used the getPositions method but the popup appears in the wrong place (too much on the left and top corner), as if the coordinates were incorrect.
network.on("click", function (params) {
// Get the node ID
var nodeId = params.nodes[0];
if (nodeId) {
// Get the node title to show in the popup
var popup = this.body.nodes[nodeId].options.title;
// Get the node coordinates
var nodePosition = network.getPositions(nodeId);
var nodeX = nodePosition[nodeId].x;
var nodeY = nodePosition[nodeId].y;
// Show the tooltip in a div
document.getElementById('popup').innerHTML = popup;
document.getElementById('popup').style.display = "block";
// Place the div
document.getElementById('popup').style.position = "absolute";
document.getElementById('popup').style.top = nodeY+'px';
document.getElementById('popup').style.left = nodeX+'px';
}
});
// Empty and hide the div when click elsewhere
network.on("deselectNode", function (params) {
document.getElementById('popup').innerHTML = null;
document.getElementById('popup').style.display = "none";
});
I got some help on the vis support section of github.
Turns out the trick was to use canvasToDOM().
Here's how it applied to my code:
network.on("click", function(params) {
// Get the node ID
var nodeId = params.nodes[0];
if (nodeId) {
// Get the node title to show in the popup
var popup = this.body.nodes[nodeId].options.title;
// Get the node coordinates
var { x: nodeX, y: nodeY } = network.canvasToDOM(
network.getPositions([nodeId])[nodeId]
);
// Show the tooltip in a div
document.getElementById("popup").style.display = "block";
// Place the div
document.getElementById("popup").style.position = "absolute";
document.getElementById("popup").style.top = nodeY + "px";
document.getElementById("popup").style.left = nodeX + "px";
}
});
It works when the network stays put, but in my case I want to fit the network and zoom on the clicked node, so the popup doesn't follow, but since this is a separate issue I will post another question.
You are using network.getPositions(params.nodes[0]), but since the nodes can change a lot when zooming in and out and moving the network on the canvas somehow the positions do not match the real position of the node. I do not know if this is a bug in visjs or there is some other reason for it. The docs say they return the position of the node in the canvas space. But thats clearly not the case in your example.
Looking at the docs [ https://visjs.github.io/vis-network/docs/network/#Events ] the click event argument contains:
{
nodes: [Array of selected nodeIds],
edges: [Array of selected edgeIds],
event: [Object] original click event,
pointer: {
DOM: {x:pointer_x, y:pointer_y}, // << Try using this values
canvas: {x:canvas_x, y:canvas_y}
}
}
Try to use the params.pointer.DOM or params.pointer.canvas positions X and Y to position your popup. This should be the location of the cursor. This will be the same position as the node, since you clicked on it.
So try something like:
document.getElementById('popup').style.top = params.pointer.DOM.y +'px';
document.getElementById('popup').style.left = params.pointer.DOM.x +'px';
-- Untested
use the click event and paint a div over the canvas.
network.on("click", function(params) {
// Get the node ID
var nodeId = params.nodes[0];
if (nodeId) {
// Get the node title to show in the popup
var popup = this.body.nodes[nodeId].options.title;
//use JQUERY to see where the canvas is on the page.
var canvasPosition = $('.vis-network').position();
//the params give x & y relative to the edge of the canvas, not to the
//whole document.
var clickX = params.pointer.DOM.x + canvasPosition.top;
var clickY = params.pointer.DOM.y + canvasPosition.left;
// Show the tooltip in a div
document.getElementById("popup").style.display = "block";
// Place the div
document.getElementById("popup").style.position = "absolute";
document.getElementById("popup").style.top = clickY + "px";
document.getElementById("popup").style.left = clickX + "px";
}
});
fixed position for tooltip/popup example

How to add an event handler function to a prototype in javascript?

Thanks to all who helped me out earlier tonight with my little project.
I am attempting to re-write a simple procedural program I wrote a few weeks ago using OOP javascript. The program is a reaction tester that presents a random shape to the user and measures how quickly the user clicks the shape and presents the speed. Earlier I finally managed to actually get a randomly sized and colored square to appear on the page. Now I am trying to write an event handler function that will set the css display property to none when the shape is clicked so that the shape disappears. However, the event handler function doesn't work and I have tried it a few different ways so far. See my entire code below:
function Shape () {
this.x = Math.floor(Math.random()*1200);
this.y = Math.floor(Math.random()*500);
this.draw();
}
Shape.prototype.draw = function() {
var shapeHtml = '<div id="shape-div"></div>';
var widthAndHeight = Math.floor(Math.random()*400);
this.shapeElement = $(shapeHtml);
this.shapeElement.css({
'width': widthAndHeight,
'height': widthAndHeight,
position: "relative",
left: this.x,
top: this.y
});
this.shapeElement.css({
display: "block"
});
//Just below is where I am trying to create a function to make the shape disappear when clicked
this.shapeElement.click(function() {
this.shapeElement.css("display", "none");
});
$("body").append(this.shapeElement);
}
"use strict";
Shape.prototype.colour = function() {
var colours = '0123456789ABCDEF'.split('');
var randomColour = "#";
for (i = 0; i < 6; i++) {
randomColour+=colours[Math.floor(Math.random()*16)];
};
this.shapeElement.css({backgroundColor: randomColour});
}
$(document).ready(function() {
var square = new Shape();
square.draw();
square.colour();
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This just won't work. I am making the transition to OOP and finding it really difficult to do things that were a cinch using procedural programming. Is this typical? Thanks again for the help.
You could try replasing:
this.shapeElement.click(function() {...
for
this.shapeElement.on("click",function() {...
You are adding this element to the DOM after it loads.
Also, check your console, because inside the event listener this.shapeElement.css("display", "none"); probably gives you an error, this in that context is the element calling the event... I believe you could use:
$(this).css({"display": "none"});
Whenever you use function(){, the inside of that function acquires a new calling context (a new this) depending on how it was called. But in your handler, you don't want a new this value - you want to inherit the this of the instantiated Shape. You can use an arrow function instead, or you can remember that this on a handler references the clicked element:
function Shape () {
this.x = Math.floor(Math.random()*1200);
this.y = Math.floor(Math.random()*500);
this.draw();
}
Shape.prototype.draw = function() {
var shapeHtml = '<div id="shape-div"></div>';
var widthAndHeight = Math.floor(Math.random()*400);
this.shapeElement = $(shapeHtml);
this.shapeElement.css({
'width': widthAndHeight,
'height': widthAndHeight,
position: "relative",
left: this.x,
top: this.y
});
this.shapeElement.css({
display: "block"
});
//Just below is where I am trying to create a function to make the shape disappear when clicked
this.shapeElement.click(function() {
$(this).css("display", "none");
});
$("body").append(this.shapeElement);
}
"use strict";
Shape.prototype.colour = function() {
var colours = '0123456789ABCDEF'.split('');
var randomColour = "#";
for (i = 0; i < 6; i++) {
randomColour+=colours[Math.floor(Math.random()*16)];
};
this.shapeElement.css({backgroundColor: randomColour});
}
$(document).ready(function() {
var square = new Shape();
square.draw();
square.colour();
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Transform (Move/Scale/Rotate) shapes with KineticJS

I'm trying to build a transform manager for KineticJS that would build a bounding box and allow users to scale, move, and rotate an image on their canvas. I'm getting tripped up with the logic for the anchor points.
http://jsfiddle.net/mharrisn/whK2M/
I just want to allow a user to scale their image proportionally from any corner, and also rotate as the hold-drag an anchor point.
Can anyone help point me in the right direction?
Thank you!
Here is a proof of concept of a rotational control I've made:
http://codepen.io/ArtemGr/pen/ociAD
While the control is dragged around, the dragBoundFunc is used to rotate the content alongside it:
controlGroup.setDragBoundFunc (function (pos) {
var groupPos = group.getPosition()
var rotation = degrees (angle (groupPos.x, groupPos.y, pos.x, pos.y))
status.setText ('x: ' + pos.x + '; y: ' + pos.y + '; rotation: ' + rotation); layer.draw()
group.setRotationDeg (rotation); layer.draw()
return pos
})
I am doing the same thing, and I've posted a question which is allmoast the same, but I found a link where you have the resize and move tool ready developed. So I have used the same. It does not contain the rotate tool however, but this can be a good start for you too, it is very simple and logical. Here is the link: http://www.html5canvastutorials.com/labs/html5-canvas-drag-and-drop-resize-and-invert-images/
I will come back with the rotation tool as well if I manage to get it working perfectly.
I hope I am not late yet for posting this code snippet that I made. I had the same problem with you guys dealing with this kind of task. Its been 3 days since I tried so many workarounds to mimic the fabricjs framework capability when dealing with images and objects. I could use Fabricjs though but it seems that Kineticjs is more faster/consistent to deal with html5.
Luckily, we already have existing plugin/tool that we could easily implement together with kineticjs and this is jQuery Transform tool. SUPER THANKS TO THE AUTHOR OF THIS! Just search this on google and download it.
I hope the code below that I created would help lots of developers out there who is pulling their hair off to solve this kind of assignment.
$(function() {
//Declare components STAGE, LAYER and TEXT
var _stage = null;
var _layer = null;
var simpleText = null;
_stage = new Kinetic.Stage({
container: 'canvas',
width: 640,
height: 480
});
_layer = new Kinetic.Layer();
simpleText = new Kinetic.Text({
x: 60,
y: 55,
text: 'Simple Text',
fontSize: 30,
fontFamily: 'Calbiri',
draggable: false,
name:'objectInCanvas',
id:'objectCanvas',
fill: 'green'
});
//ADD LAYER AND TEXT ON STAGE
_layer.add(simpleText);
_stage.add(_layer);
_stage.draw();
//Add onclick event listener to the Stage to remove and add transform tool to the object
_stage.on('click', function(evt) {
//Remove all objects' transform tool inside the stage
removeTransformToolSelection();
// get the shape that was clicked on
ishape = evt.targetNode;
//Add and show again the transform tool to the selected object and update the stage layer
$(ishape).transformTool('show');
ishape.getParent().moveToTop();
_layer.draw();
});
function removeTransformToolSelection(){
//Search all objects inside the stage or layer who has the name of "objectInCanvas" using jQuery iterator and hide the transform tool.
$.each(_stage.find('.objectInCanvas'), function( i, child ) {
$(child).transformTool('hide');
});
}
//Event listener/Callback when selecting image using file upload element
function handleFileSelect(evt) {
//Remove all objects' transform tool inside the stage
removeTransformToolSelection();
//Create image object for selected file
var imageObj = new Image();
imageObj.onload = function() {
var myImage = new Kinetic.Image({
x: 0,
y: 0,
image: imageObj,
name:'objectInCanvas',
draggable:false,
id:'id_'
});
//Add to layer and add transform tool
_layer.add(myImage);
$(myImage).transformTool();
_layer.draw();
}
//Adding source to Image object.
var f = document.getElementById('files').files[0];
var name = f.name;
var url = window.URL;
var src = url.createObjectURL(f);
imageObj.src = src;
}
//Attach event listener to FILE element
document.getElementById('files').addEventListener('change', handleFileSelect, false);
});

Tabindex for new Objects in Canvas?

Creating a new object on mouseclick as a way for users to create reference points (which I call 'crumbs') when reading large web documents. I've got this working with a new Image() function, however, that won't let me assign a tabindex to each new image created by mouseclick (posX, posY). 'crumbtoggle' simply acknowledges that the crumb dropping tool has been selected.
working new Image() function:
function draw_crumb()
{
var b_canvas = document.getElementById("b");
var b_context = b_canvas.getContext("2d");
var crumb = new Image();
crumb.src = "crumb.gif";
if(crumbtoggle.className == "on")
{
b_context.drawImage(crumb, posX-20, posY-20, 50, 75);
}
}
non-working new Object () function:
function draw_crumb()
{
var b_canvas = document.getElementById("b");
var b_context = b_canvas.getContext("2d");
var crumb = new Object();
crumb.type = "button";
crumb.src = "crumb.gif";
crumb.tabindex = 1;
if(crumbtoggle.className == "on")
{
b_context.drawObject(crumb, posX-20, posY-20, 50, 75);
}
}
I've looked in to applying focus to the new Image objects, but that doesn't seem to be a good alternative to tabindex attributes. Any ideas would be greatly appreciated.
An HTML5 Canvas is like a real-world canvas with instantly-drying paint. When you paint a rectangle or line or image on the canvas, it becomes part of the canvas. You cannot later re-order the items, or move them relative to each other. It is not a separate entity that can get focus.
Any sort of focus management integrated with the browser's handling of focus will have to be done through form inputs or anchors recognized by the browser.
It's not clear to me why you need a canvas, or if you need one at all.

Categories