draw canvas lines to all divs with the same class name - javascript

I am making a document that will build a tree type graph through user input. I am trying to connect styled divs to the relative div they branched from with canvas lines.
I have been using .getBoundingClientRect() to get the positions, but the divs are static with inline-block, so every time a new one is added, the whole structure changes.
So, here is my attempt at a 'for loop' that is called every time a new branch is made, to re-draw all of the canvas lines.
var lines = function(){
var blocks=document.getElementsByClassName('block');
for (i=1;i<blocks.length-1;i++){
var blockDiv = blocks[i]
var offset = blockDiv.getBoundingClientRect();
var xa = offset.left+40;
var ya = offset.top+40;
var blockFrom = blockDiv.parentNode.parentNode.previousSibling;
var offsets = blockFrom.getBoundingClientRect();
var yb = offsets.top+40;
var xb = offsets.left+40;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.moveTo(xa,ya);
ctx.lineTo(xb,yb);
ctx.stroke();
}
}
Here is a jsfiddle so you can see the general structure of the divs.
When the function is called, I get no canvas lines and a console error of
166 Uncaught TypeError: blockDiv.parentNode.parentNode.previousSibling.getBoundingClientRect is not a function
I am stumped on this one and would really appreciate the help.
I am new to canvas, javascript, and coding in general so any other constructive criticism would also be greatly appreciated. :)
Vanilla js only please!

The problem is this:
Gecko-based browsers insert text nodes into a document to represent
whitespace in the source markup. Therefore a node obtained, for
example, using Node.firstChild or Node.previousSibling may refer to a
whitespace text node rather than the actual element the author
intended to get.
https://developer.mozilla.org/en-US/docs/Web/API/Node/previousSibling
Therefore, change this line:
var blockFrom = blockDiv.parentNode.parentNode.previousSibling;
to this:
var blockFrom = blockDiv.parentNode.parentNode.previousSibling.previousSibling;

Related

Using Javascript getBoundingClientRect to Snap Items to Grid

EDIT: I've simplified the code (below and in fiddle) down to the major problem needed to be solved in hope of creating more readability.
I've implemented Bader's solution for correctly using getBoundingClientRect value and using document.querySelector for getting both the class name and the html tag needed for the function. I'd now like to move on to the last five lines of the code beginning with var = style.
I've now corrected the math for the final two variables.
→ I'm trying to achieve creating a snapping function for use alongside Plumber, a baseline-grid Sass plugin.
Basically, I have a vertically centered flex item that needs to -- instead of being perfectly centered -- snap in an upward direction to the closest grid line. This will allow me to have a consistent vertical rhythm between slides in a custom mobile-based experience.
I'm using getBoundingClientRect to calculate the distance between the bottom of an object, and the top of the window.
Then I use Math.floor to round down to the nearest multiple of my rem value.
Then I use this new value to create a CSS bottom margin on the flex-centered container for the alignment fix.
(Then to finish, I'd like to have this function load on $(document).ready and on window resize.)
function() {
var box = document.querySelector('.box-1');
var rect = box.getBoundingClientRect();
var bottomOrig = rect.bottom;
var htmlRoot = document.querySelector('html');
var style = getComputedStyle(htmlRoot);
var remValue = style.getPropertyValue('font-size');
var bottomNew = Math.floor(bottomOrig / remValue) * remValue;
var fix = bottomOrig - bottomNew;
$('.container-2').css("margin-bottom", "fix + 'px'");
}
Here's the fiddle.
I most likely have a syntax problem here, and would greatly appreciate help.
Thanks!
Here are some errors / corrections.
GetBoundingClientRect() is a JS function, not jQuery, so it must be used on a javascript element, not a jquery selector. Using the [0] accessor on the jquery selector (if that's how you want to get it) will give you the JS element.
Also noticed that you were trying to select the "html" tag by id, but it doesn't have any Id. Changed it to getElementsByTagName.
var offsetYOrig = $('.box-1')[0].getBoundingClientRect().bottom;
// or, without jQuery:
// var offsetYOrig = document.getElementsByClassName('box-1')[0].getBoundingClientRect().bottom;
var html = document.getElementsByTagName("html")[0];
var style = window.getComputedStyle(html);
var remValue = style.getPropertyValue('font-size');
Edit: Regarding your edit, if you need to call the javascript to recompute on window resize, you may want to try something like this. I'm not sure if it achieves what you want fully (I don't completely understand your 'snapping' requirements, but this will at least call the code again. You may still have to edit the code in the snapFunction if it doesn't suit your needs.
I added some console logs that might help you check your math as it seemed a bit problematic to me, though I was unsure how to fix it because I don't understand your goal.
function snapFunction ()
{
var box = document.querySelector('.box-1');
var rect = box.getBoundingClientRect();
var bottomOrig = rect.bottom;
var htmlRoot = document.querySelector('html');
var style = getComputedStyle(htmlRoot);
var remValue = style.getPropertyValue('font-size');
var bottomNew = Math.floor(bottomOrig / remValue) * remValue;
var fix = bottomOrig - bottomNew;
// open your browser console and check the value of these to check your math and what values you're getting
console.log("bottomOrig: " + bottomOrig )
console.log("remValue: " + remValue)
console.log("bottomNew: " + bottomNew )
// note: no quotes around your variable name fix here
$('.container-2').css("margin-bottom", fix + "px");
};
// call on load
(function() {
snapFunction();
})();
// call on resize
$( window ).resize(function() {
snapFunction();
});
I did notice that the value of your bottomNew variable was logging as "NaN" (Not a Number) so I think something is going wrong there.
I think you're getting a font-size like "36px" instead of just "36". Maybe you could try
var remValue = parseInt(style.getPropertyValue('font-size'), 10);
The 10 in that parseInt function is just specifying we want to use base 10 numbers.
I hope this will help you
Here's the edited fiddle
jsfiddle.net/ztf64mwg/82/
I just edited some variables and fixed some of the errors
I ended up jumping on HackHands and, with help, came up with a great working solution.
This will snap any vertically flex-centered object to a grid with its size set as 1rem.
All you need to do is give the object that is being measured for distance the id attribute "measure", making sure that this object is aligned correctly with a 1rem grid from the top of its own container.
Then give the parent container (or any container higher in the DOM tree) that you'd like to snap to the grid the class of "snap".
If anyone ever finds a use for this and needs further explanation, just let me know.
function snap(object){
var rect = object.getBoundingClientRect();
var bottomOrig = rect.bottom;
var htmlRoot = document.querySelector('html');
var style = getComputedStyle(htmlRoot);
var remValue = parseInt(style.getPropertyValue('font-size'));
var bottomNew = Math.floor(bottomOrig / remValue) * remValue;
var topFixPositive = bottomNew - bottomOrig;
var topFixNegative = -Math.abs(topFixPositive);
$(object).closest('.snap').css("margin-top", topFixNegative);
}
function snapClear(object){
$(object).closest('.snap').css("margin-top", "0");
}
var measureHome = document.querySelector('#measure');
snap(measureHome);
$(window).on('resize', function() {
snapClear(measureHome);
snap(measureHome);
});

Cytoscape.js zigzag like edges

I am trying to create a zigzag like edges in cytoscape.js. I have found zigzag edges on github but I guess it's not yet implemented or it's abandoned, so I am trying to calculate the points but I am having some trouble.
My graph looks like this:
I assume that there are 3 cases here. One when the source is on the left of the target, other when the source and target have same x value and the third case when the source is on the right of the target.
The second case is basic and I have calculated it (just a straight line from source to the target) but I am having trouble calculating the other two cases.
I am trying to make the edges look like this:
I am using the following function to try to calculate them:
cy.elements('edge').css({'segment-distances':
function(ele){
var target = ele.target();
var source = ele.source();
var source_id = source.data().id;
var target_id = target.data().id;
var sou_pos_x = (cy.$('#' + source_id).position('x'));
var tar_pos_x = (cy.$('#' + target_id).position('x'));
var sou_pos_y = (cy.$('#' + source_id).position('y'));
var tar_pos_y = (cy.$('#' + target_id).position('y'));
var sou_tar_distance_x = (sou_pos_x - tar_pos_x);
var sou_tar_distance_y = (sou_pos_y - tar_pos_y);
// source on the left of the target
if (sou_pos_x < tar_pos_x){
return 0;
}
// source on same position as target
else if (sou_pos_x == tar_pos_x){
return 0;
}
// source on the right of the target
else{
return 0;
}
}
});
From what I have read from the documentation on segments-edges, I should have 2 points.
The calculation would have been a lot easier if you could specify coordinates for each segment point. With the way that is right now it's hard to calculate the values perpendicular to the line formed from the source to the target because those values might vary depending how far apart is the source from the target node
Can anyone help me with this issue or at least give me a guidance on how to achieve what I am looking for? I have been stuck with this problem for quite some time.
EDIT
I have added:
'source-endpoint': '180deg',
'target-endpoint': '0deg'
so now the all the edges start and end from same position, which would simplify the calculation.
EDIT 2:
This has been implemented by taxi edges like this

How to get pen position on html canvas?

I think an older version of canvas had a canvas.PenPos property that let you see where the current pen location is. I'm mostly going to use this for debugging purposes and to draw relatively (e.g. a line 50px long to the right of the current position). Is there any way to do this currently? If not, why did they remove it...?
The pen position I'm referring to is the virtual "pen" that a drawing context (that you would get for example by calling var ctx = canvas.getContext('2d')) uses, rather than a physical pen or mouse.
I am not trying to draw with a mouse. Again, this is not a physical pen I'm looking for, it's the virtual pen that is moved with ctx.moveTo(x,y)
According to w3.org, there isn't a method that returns the pen position.
Because the .getContext("2d") method returns an object, you can store the pen position as properties and call them as needed:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
//Start position
ctx.x = 100;
ctx.y = 100;
ctx.moveTo(ctx.x, ctx.y);
ctx.x = ctx.x + 100;
ctx.y = ctx.y + 300;
ctx.lineTo(ctx.x, ctx.y);
ctx.stroke();
When creating an arc though, you would need to calculate the pen's new position afterward.

how to get the html file of an edited canvas in html5

I am trying to make a website which helps its users to create a page by dragging and dropping elements on the canvas. The user should be able to save the html file of the edited canvas. I cannot figure how to convert the changes made to the canvas to an html file.
I don't think it's possible to get Markup out of canvas. I've searched it for a month but can't find a valid solution. but may be some experts may know. Best of luck buddy.
Canvas is basically just a bit-map image. Whatever you draw on the canvas is stored as pixels not as elements. So changes to the canvas are just changes in pixel values. To do what you wish you would need to store your 'elements' as 'objects' within your code where each 'object' stores all the required data for your 'element'.
it would then be possible to open a new window and export code into it using document.writeln
The code below may give you an idea of what sort of thing would be needed
newwindow=window.open('','_blank');
newwindow.document.writeln('<!DOCTYPE HTML>');
newwindow.document.writeln('<html>');
newwindow.document.writeln('<head>');
newwindow.document.writeln('<style>');
newwindow.document.writeln('#element0 {');
newwindow.document.writeln('background:'+ obj0.background+';');
newwindow.document.writeln('width:'+ obj0.width+';');
newwindow.document.writeln ('}');
newwindow.document.writeln('</style>');
newwindow.document.writeln('</head>');
newwindow.document.writeln('<body>');
newwindow.document.writeln('<div id="element0"></div>');
newwindow.document.writeln('</body>');
newwindow.document.writeln('</html>');
newwindow.document.writeln('<html>');
newwindow.document.close();
Hope this helps
Canvas won't help you here for anything other than to visualize the objects you have dropped onto it.
You need to record the objects you drop in a "shadow" structure behind the scene sort of. That is to say: build a object list internally which you then can use as source data to render:
Canvas visualization of it
Raw HTML code from it.
You can for example drop an image to the canvas and your code will record a new object (intention with the following code is to show the principle not to provide a full working solution):
var myObjects = [];
/// a drop occurred
var o = new myElement(x, y, width, height, id, type);
myElement is a pre-defined object that you set up in advance to hold the given arguments.
Then push the object to your object stack and render it to canvas:
myObjects.push(o);
for(var i = 0, o; o = myObjects[i]; i++) {
/// draw the look of this object here to canvas
}
When you then need a HTML version of it you do the same:
for(var i = 0, o; o = myObjects[i]; i++) {
var el = '<' + o.type + ' id="' + o.id + ' .... other things here
}
This way you can produce canvas graphics, HTML, send data over a socket etc.
The key in these sort things is to keep raw base data available. In this case it would be the element type you want to drop, its position and dimension. For HTML you also have to consider things as nesting etc. but that would require a bit more code than shown here.

Generic: Naming structure and drawing KineticJS

This one takes a bit more to describe, sorry for the shorter title.
I currently do not have my code in front of me but might update this with the full details of how it works and where the problem is located.
Basically I notice that without doing a more or less global name (window.whatever) for the shapes/groups it will never draw them. I have done logs of the objects to see if they are proper and have seen nothing wrong with them.
The full scope is a layer first, that is passed to a function that i then create shapes and groups in a logical way, without passing the groups back and instead sending the layer as a parameter to add it from within the function. When I do it this way it seems that I can never get it to draw to the container (stage).
To add to it, I am looping to create the shapes, as I am making them variable and more percentage based then exact pixels, so I am holding the objects in an array that is generated through the loop.
I have done this flat first and it worked, however after moving it to the function it stopped working, is there any reason for this I may be missing?
Another note if it is relevant, I am using Adobe AIR.
If anyone has any ideas let me know, I will post the actual code when I can (few hours from this posting).
UPDATE:
Basically the issue I am having is that when i separate the shapes/groups into their own function it does not want to draw them at all. I have tried to call the draw function inline and also just add them to the stage later, both to my understanding will trigger the draw.
here is the code
var forms = {
selector:function(params,bind){
//globals
var parent,obj,lastWidth=0,items=[];
//setup defaults
var params = params || {};
params.x = params.x || 0;
params.y = params.y || 0;
params.active = params.active || 0;
params.height = params.height || 200;
params.children = params.children || [{
fill:'yellow'
}];
params.width = params.width || bind.getWidth();
params.margins = params.margins || 5;
//container for selector
parent = new Kinetic.Group({
x:params.x,
y:params.y,
height:params.height,
width:params.width
});
air.Introspector.Console.log(params);
var totalMargins = params.margins*(params.children.length+1),
activeWidth = (params.width-totalMargins)/2,
subItems = params.children.length-1,
subWidth = activeWidth/subItems,
itemHeight = (params.height-params.margins)/2;
//loop all children
for(var i=0;i<params.children.length;i++){
//use an array to store objects
items[i]={};
//create default object for rectangle
obj = {};
obj.y = params.margins;
obj.fill = params.children[i].fill;
if(params.active==i){
obj.width = activeWidth;
}else{
obj.width = subWidth;
}
obj.x = params.margins+lastWidth;
obj.height = itemHeight;
lastWidth = lastWidth+(params.margins+obj.width);
//create group for text
items[i].text = new Kinetic.Group({
x:0,
y:itemHeight+params.margins
});
//create box for text
items[i].box = new Kinetic.Rect({
x: params.margins,
y: params.margins,
width:params.width-(params.margins*2),
height:(params.height-itemHeight)-(params.margins*2),
fill:'yellow'
});
air.Introspector.Console.log(items[i].box);
//add box to text groups
items[i].text.add(items[i].box);
//create item
items[i].item = new Kinetic.Rect(obj);
//add groups to parent
parent
.add(items[i].item)
.add(items[i].text);
}
//add parent to the bound container
bind.add(parent);
}
}
From your question, I'm assuming bind is your Kinetic.Layer.
Make sure bind has been added to the stage: stage.add(bind);
After adding your groups/rects to bind, also do bind.draw();
I have since figured it out.
The issue is that the layer must be added to the stage before anything else is added it it. It was not in my code because I did not see it as an issue as it worked elsewhere.
Basically if you add anything to a layer, then add the layer to the stage it will fail. it must go like this:
CREATE STAGE
CREATE LAYER
ADD LAYER TO STAGE
ADD GROUPS TO LAYER
DRAW IF NEEDED
The way it was done originally is like so:
CREATE STAGE
CREATE LAYER
ADD GROUPS TO LAYER
ADD LAYER TO STAGE
DRAW IF NEEDED
The question will be updated to make this apparent, this is something that is not in the documentation as a problem (as far as I have seen) and I can see it as confusing some.

Categories

Resources