Related
Hi my HTML File doesn´t work right depending on the browser.
The Bottle Animation where the Bottle gets fuller depending on where you click works fine on my PC and Macbook.
But on a friends macbook it does not work. neither chrome nor safari. I discovered that the code isnt working on my Firefox but on my Chrome and Safari.
What did I do wrong?
var kh_global;
var eiweiß_global;
function draw_bottle(kh, eiweiß) {
kh_global = kh;
eiweiß_global = eiweiß;
document.getElementById('Main').addEventListener("click", printMousePos);
}
function printMousePos(event) {
//orange
const juiceMask = document.getElementById('juiceMask');
//grün
const juiceMask2 = document.getElementById('juiceMask2');
//blau
const juiceMask3 = document.getElementById('juiceMask3');
var pixeloffset = event.clientY * 2;
const percent = 1 - ((pixeloffset / 1600));
console.log((1 - percent) * 1000)
document.getElementById('anzeige').innerHTML = "Hohes C<br>"
+ "(Standardnährwert)<br>"
+ "Menge: " + Math.round(hohesc.Menge * percent * 1000) + "ml<br>"
+ "Brennwert: " + Math.round(hohesc.Brennwert * percent * 100) + "kcal<br>"
+ "Kohlenhydrate: " + Math.round(hohesc.Kohlenhydrate * percent * 100) + "g<br>"
+ "Eiweiß: " + Math.round(hohesc.Eiweiss * percent * 100) + "g<br>"
+ "Vitamin C: " + Math.round(hohesc.VitaminC * percent * 100) + "mg";
change(kh_global, eiweiß_global, pixeloffset);
}
function change(kh, eiweiß, pixeloffset) {
//orange
const juiceMask = document.getElementById('juiceMask');
//grün
const juiceMask2 = document.getElementById('juiceMask2');
//blau
const juiceMask3 = document.getElementById('juiceMask3');
const width_kh = 450 * kh / 100;
const width_eiweiß = 450 * eiweiß / 100;
const starting_point_rest = width_eiweiß + width_kh;
const width_rest = 450 - starting_point_rest;
const rest_percentage = 100-kh-eiweiß;
document.getElementById('anzeige2unten').innerHTML = "Verteilung Inhaltsstoffe<br><br>"
+ "Zucker: <br>"+kh+"%<br>"
+ "Fruchtsaft: <br>"+eiweiß+"%<br>"
+ "Rest: <br>"+rest_percentage+"%<br><br>"
+ "Vitamin C:<br> < 0,05%<br></br>";
juiceMask.style.width = width_kh;
juiceMask.style.transform = "translateY(" + pixeloffset + "px)" ;
juiceMask2.style.transform = "translateX(" + width_kh + "px) translateY(" + pixeloffset + "px)" ;
juiceMask2.style.width = width_eiweiß;
juiceMask3.style.transform = "translateX(" + starting_point_rest + "px) translateY(" + pixeloffset + "px)" ;
juiceMask3.style.width = width_rest;
}
const hohesc = {
Menge: 1,
Brennwert: 4.3,
Kohlenhydrate: 0.88,
Eiweiss: 0.07,
VitaminC: 3.2
}
draw_bottle(20, 70);
Is it the mask in the HTML?
I have no clue :(
var Belay = (function () {
var settings = {
strokeColor: '#fff',
strokeWidth: 2,
opacity: 1,
fill: 'none',
animate: true,
animationDirection: 'right',
animationDuration: .75
};
var me = {};
me.init = function (initObj) {
if (initObj) {
$.each(initObj, function (index, value) {
//TODO validation on settings
settings[index] = value;
});
}
}
me.set = function (prop, val) {
//TODO validate
settings[prop] = val;
}
me.on = function (el1, el2) {
var $el1 = $(el1);
var $el2 = $(el2);
if ($el1.length && $el2.length) {
var svgheight, p, svgleft, svgtop, svgwidth
var el1pos = $(el1).offset();
var el2pos = $(el2).offset();
var el1H = $(el1).outerHeight();
var el1W = $(el1).outerWidth();
var el2H = $(el2).outerHeight();
var el2W = $(el2).outerWidth();
var node1X = Math.round(el1pos.left + el1W);
var node2X = Math.round(el2pos.left);
var overlapping = node1X >= node2X;
var svgwidth = Math.abs(node2X - node1X);
var svgleft = Math.min(node1X, node2X);
var node1Y = Math.round(el1pos.top + el1H / 2);
var node2Y = Math.round(el2pos.top + el1H / 2);
var svgheight = Math.abs(node1Y - node2Y);
var svgtop = Math.min(node1Y, node2Y);
var pt1x = node1X - svgleft;
var pt1y = node1Y - svgtop + settings.strokeWidth;
var pt2x = node2X - svgleft;
var pt2y = node2Y - svgtop + settings.strokeWidth;
// cpt is the length of the control point vector
// variew with distance netween boxes
var cpt = Math.round(svgwidth * Math.min(svgheight / 300, 1));
if (overlapping) {
// Need to widen the svg because otherwise the bezier control
// points (and hence the curve) will extend outside it.
svgleft -= cpt;
svgwidth += 2 * cpt;
pt1x += cpt;
pt2x += cpt;
}
// Build the path decription
p = "M" + pt1x + "," + pt1y +
" L" + (pt1x + pt2x) / 8 + "," + pt1y +
" L" + (pt1x + pt2x) / 8 + "," + pt2y //+
" L" + (pt2x) / 1.03 + "," + pt2y +
" L" + (pt2x) / 1.03 + "," + (pt2y - 5) +
" L" + pt2x + "," + pt2y +
" L" + (pt2x) / 1.03 + "," + (pt2y + 5) +
" L" + (pt2x) / 1.03 + "," + pt2y;
//ugly one-liner
$ropebag = $('#ropebag').length ? $('#ropebag') : $('body').append($("<div id='ropebag' />")).find('#ropebag');
var svgnode = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
var defs = document.createElementNS('http://www.w3.org/2000/svg', "defs");
var marker = document.createElementNS('http://www.w3.org/2000/svg', "marker");
var path = document.createElementNS('http://www.w3.org/2000/svg', "path");
marker.setAttributeNS(null,"id","arrow");
marker.setAttributeNS(null,"markerWidth","13");
marker.setAttributeNS(null,"markerHeight","13");
marker.setAttributeNS(null,"refX","2");
marker.setAttributeNS(null,"refY","7");
marker.setAttributeNS(null,"markerUnits","userSpaceOnUse");
path.setAttributeNS(null,"d","M2,2 L2,13 L8,7 L2,2");
marker.appendChild(path);
defs.appendChild(marker);
var newpath = document.createElementNS('http://www.w3.org/2000/svg', "path");
newpath.setAttributeNS(null, "d", p);
newpath.setAttributeNS(null, "stroke", settings.strokeColor);
newpath.setAttributeNS(null, "stroke-width", settings.strokeWidth);
newpath.setAttributeNS(null, "opacity", settings.opacity);
newpath.setAttributeNS(null, "fill", settings.fill);
newpath.setAttributeNS(null, "marker-end", "url(#arrow)");
svgnode.appendChild(newpath);
svgnode.appendChild(defs);
$(svgnode).css({
left: svgleft,
top: svgtop - settings.strokeWidth,
position: 'absolute',
width: svgwidth,
height: svgheight + settings.strokeWidth * 2,
minHeight: '20px',
'pointer-events': 'none'
});
$ropebag.append(svgnode);
if (settings.animate) {
// THANKS to http://jakearchibald.com/2013/animated-line-drawing-svg/
var pl = newpath.getTotalLength();
// Set up the starting positions
newpath.style.strokeDasharray = pl + ' ' + pl;
if (settings.animationDirection == 'right') {
newpath.style.strokeDashoffset = pl;
} else {
newpath.style.strokeDashoffset = -pl;
}
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
// WON'T WORK IN IE. If you want that, use requestAnimationFrame to update instead of CSS animation
newpath.getBoundingClientRect();
newpath.style.transition = newpath.style.WebkitTransition = 'stroke-dashoffset ' + settings.animationDuration + 's ease-in-out';
// Go!
newpath.style.strokeDashoffset = '0';
}
}
}
me.off = function () {
$("#ropebag").empty();
}
return me;
}());
/*********************** Custom JavaScript **********************************/
$(document).ready(function () {
$(".draggable").draggable({
drag: function (event, ui) {
Belay.off();
drawConnectors();
}
});
function drawConnectors() {
$(".parent").each(function () {
var theID = this.id;
$("." + theID).each(function (i, e) {
var rand = Math.random() * .7 + .3;
Belay.set('animationDuration', rand)
Belay.on($("#" + theID), e)
});
})
}
$(window).resize(function () {
Belay.off();
drawConnectors();
});
Belay.init({
strokeWidth: 1
});
Belay.set('strokeColor', '#999');
drawConnectors();
});
.row{
margin-top:2in;
}
.box{
border:1px solid #ccc;
padding:10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<body>
<div class="row">
<div class="col-md-4">
<div class="pull-left ">
<div class="parent draggable box" id="parent1">Left drag</div>
</div>
<div class="pull-right">
<div class="child parent1 draggable box">Right drag</div>
</div>
</div>
</div>
</body>
Hi as per this example how-to-change-the-lines-between-two-points-from-curved-to-straight-lines-with-on how to draw arrow at the end of the path dynamically
can someone tell me what is wrong in adding marker end in my code
var Belay = (function ()
{
var settings = {
strokeColor: 'red',
strokeWidth: 2,
opacity: 1,
fill: 'none',
animate: true,
animationDirection: 'right',
animationDuration: .75
};
var me = {};
me.init = function (initObj) {
if (initObj) {
$.each(initObj, function (index, value) {
//TODO validation on settings
settings[index] = value;
});
}
}
me.set = function (prop, val) {
//TODO validate
settings[prop] = val;
}
me.on = function (el1, el2) {
var $el1 = $(el1);
var $el2 = $(el2);
if ($el1.length && $el2.length) {
var svgheight, p, svgleft, svgtop, svgwidth
var el1pos = $(el1).offset();
var el2pos = $(el2).offset();
var el1H = $(el1).outerHeight();
var el1W = $(el1).outerWidth();
var el2H = $(el2).outerHeight();
var el2W = $(el2).outerWidth();
var node1X = Math.round(el1pos.left + el1W);
var node2X = Math.round(el2pos.left);
var overlapping = node1X >= node2X;
var svgwidth = Math.abs(node2X - node1X);
var svgleft = Math.min(node1X, node2X);
var node1Y = Math.round(el1pos.top + el1H / 2);
var node2Y = Math.round(el2pos.top + el1H / 2);
var svgheight = Math.abs(node1Y - node2Y);
var svgtop = Math.min(node1Y, node2Y);
var pt1x = node1X - svgleft;
var pt1y = node1Y - svgtop + settings.strokeWidth;
var pt2x = node2X - svgleft;
var pt2y = node2Y - svgtop + settings.strokeWidth;
// cpt is the length of the control point vector
// variew with distance netween boxes
var cpt = Math.round(svgwidth * Math.min(svgheight / 300, 1));
if (overlapping) {
// Need to widen the svg because otherwise the bezier control
// points (and hence the curve) will extend outside it.
svgleft -= cpt;
svgwidth += 2 * cpt;
pt1x += cpt;
pt2x += cpt;
}
p = "M" + pt1x + "," + pt1y +
" L" + (pt1x + pt2x) / 8 + "," + pt1y +
" L" + (pt1x + pt2x) / 8 + "," + pt2y +
" L" + (pt2x) / 1.03 + "," + pt2y +
" L" + (pt2x) / 1.03 + "," + (pt2y - 5) +
" L" + pt2x + "," + pt2y +
" L" + (pt2x) / 1.03 + "," + (pt2y + 5) +
" L" + (pt2x) / 1.03 + "," + pt2y;
//ugly one-liner
$ropebag = $('#ropebag').length ? $('#ropebag') : $('body').append($("<div id='ropebag' />")).find('#ropebag');
var svgnode = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
//try
var defs = document.createElementNS('http://www.w3.org/2000/svg', "defs");
var marker = document.createElementNS('http://www.w3.org/2000/svg', "marker");
var path = document.createElementNS('http://www.w3.org/2000/svg', "path");
marker.setAttributeNS(null,"id","arrow");
marker.setAttributeNS(null,"markerWidth",10);
marker.setAttributeNS(null,"markerHeight",10);
marker.setAttributeNS(null,"refX",0);
marker.setAttributeNS(null,"refY",5);
marker.setAttributeNS(null,"viewbox","0 0 10 10");
marker.setAttributeNS(null,"orient","auto");
marker.setAttributeNS(null,"markerUnits","strokeWidth");
path.setAttributeNS(null,"d","M 0 0 L 10 5 L 0 10");
////try
marker.appendChild(path);
defs.appendChild(marker);
var newpath = document.createElementNS('http://www.w3.org/2000/svg', "path");
newpath.setAttributeNS(null, "d", p);
newpath.setAttributeNS(null, "stroke", settings.strokeColor);
newpath.setAttributeNS(null, "stroke-width", settings.strokeWidth);
newpath.setAttributeNS(null, "opacity", settings.opacity);
newpath.setAttributeNS(null, "fill", settings.fill);
newpath.setAttributeNS(null, "marker-end", "url(#arrow)");
newpath.appendChild(defs);
svgnode.appendChild(newpath);
//for some reason, adding a min-height to the svg div makes the lines appear more correctly.
$(svgnode).css({
left: svgleft,
top: svgtop - settings.strokeWidth,
position: 'absolute',
width: svgwidth,
height: svgheight + settings.strokeWidth * 2,
minHeight: '20px',
'pointer-events': 'none'
});
$ropebag.append(svgnode);
if (settings.animate) {
// THANKS to http://jakearchibald.com/2013/animated-line-drawing-svg/
var pl = newpath.getTotalLength();
// Set up the starting positions
newpath.style.strokeDasharray = pl + ' ' + pl;
if (settings.animationDirection == 'right') {
newpath.style.strokeDashoffset = pl;
} else {
newpath.style.strokeDashoffset = -pl;
}
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
// WON'T WORK IN IE. If you want that, use requestAnimationFrame to update instead of CSS animation
newpath.getBoundingClientRect();
newpath.style.transition = newpath.style.WebkitTransition = 'stroke-dashoffset ' + settings.animationDuration + 's ease-in-out';
// Go!
newpath.style.strokeDashoffset = '0';
}
}
}
me.off = function ()
{
$("#ropebag").empty();
}
return me;
} ());
function drawConnectors()
{
}
You don't seem to be appending newpath to anything.
Also you are appending defs to newpath. I'm not sure that works (I've never tried it). You should probably avoid doing that. Append the defs to the SVG instead.
My goal is to prevent overlapping of two or more rectangles inside my FabricJS canvas.
Imagine two rectangles having info on position and size and you can drag and drop any rectangle inside the canvas.
If rectangle A gets close enough to rectangle B, the position of rectangle A should snap to the edge of rectangle B. This should work for any edge of rectangle B. The vertices do not have to match, cause the sizes of the rectangles are variable.
I have a working example for this snapping on one dimension (x-axes).
My best jsfiddle attempt
See jsfiddle.
But I need it to work around the rectangle on both dimensions. I am quite sure, that my code is not well enough to manage this.
Code-snippets which might help:
object.oCoords.tl.x //top-left corner x position. similar goes for top-right (tr), bottom-left (bl), bottom-right (br) and .y for y-position
mouse_pos = canvas.getPointer(e.e);
mouse_pos.x //pointer x.position
mouse_pos.y //pointer y.position
object.intersectsWithObject(targ) // object = dragged rectangle, targ = targeted rectangle
The snapping should work for an unlimited amount of objects (not only for two rectangles).
I solved the problem on my own.
See jsfiddle: http://jsfiddle.net/gcollect/FD53A/
This is the code:
this.canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords(); //Sets corner position coordinates based on current angle, width and height
canvas.forEachObject(function (targ) {
var objects = this.canvas.getObjects(),
i = objects.length;
activeObject = canvas.getActiveObject();
if (targ === activeObject) return;
if (Math.abs(activeObject.oCoords.tr.x - targ.oCoords.tl.x) < edgedetection) {
activeObject.left = targ.left - activeObject.currentWidth;
}
if (Math.abs(activeObject.oCoords.tl.x - targ.oCoords.tr.x) < edgedetection) {
activeObject.left = targ.left + targ.currentWidth;
}
if (Math.abs(activeObject.oCoords.br.y - targ.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top - activeObject.currentHeight;
}
if (Math.abs(targ.oCoords.br.y - activeObject.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top + targ.currentHeight;
}
if (activeObject.intersectsWithObject(targ) && targ.intersectsWithObject(activeObject)) {
targ.strokeWidth = 10;
targ.stroke = 'red';
} else {
targ.strokeWidth = 0;
targ.stroke = false;
}
if (!activeObject.intersectsWithObject(targ)) {
activeObject.strokeWidth = 0;
activeObject.stroke = false;
}
});
Works pretty legit! Cheers!
This is based on gco's answer, updated to work with FabricJS 1.5.0, with the following improvements:
Shapes don't overlap.
Snapping is more responsive.
Shapes are contained within the canvas.
JS Fiddle: https://jsfiddle.net/aphillips8/31qbr0vn/1/
var canvas = new fabric.Canvas('canvas'),
canvasWidth = document.getElementById('canvas').width,
canvasHeight = document.getElementById('canvas').height,
counter = 0,
rectLeft = 0,
snap = 20; //Pixels to snap
canvas.selection = false;
plusrect();
plusrect();
plusrect();
function plusrect(top, left, width, height, fill) {
var rect = new fabric.Rect({
top: 300,
name: 'rectangle ' + counter,
left: 0 + rectLeft,
width: 100,
height: 100,
fill: 'rgba(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ', 0.75)',
lockRotation: true,
originX: 'left',
originY: 'top',
cornerSize: 15,
hasRotatingPoint: false,
perPixelTargetFind: true,
minScaleLimit: 1,
maxWidth: canvasWidth,
maxHeight: canvasHeight
});
rect.custom = {};
rect.custom.counter = counter;
canvas.add(rect);
counter++;
rectLeft += 200;
}
function findNewPos(distX, distY, target, obj) {
// See whether to focus on X or Y axis
if(Math.abs(distX) > Math.abs(distY)) {
if (distX > 0) {
target.setLeft(obj.getLeft() - target.getWidth());
} else {
target.setLeft(obj.getLeft() + obj.getWidth());
}
} else {
if (distY > 0) {
target.setTop(obj.getTop() - target.getHeight());
} else {
target.setTop(obj.getTop() + obj.getHeight());
}
}
}
canvas.on('object:moving', function (options) {
// Sets corner position coordinates based on current angle, width and height
options.target.setCoords();
// Don't allow objects off the canvas
if(options.target.getLeft() < snap) {
options.target.setLeft(0);
}
if(options.target.getTop() < snap) {
options.target.setTop(0);
}
if((options.target.getWidth() + options.target.getLeft()) > (canvasWidth - snap)) {
options.target.setLeft(canvasWidth - options.target.getWidth());
}
if((options.target.getHeight() + options.target.getTop()) > (canvasHeight - snap)) {
options.target.setTop(canvasHeight - options.target.getHeight());
}
// Loop through objects
canvas.forEachObject(function (obj) {
if (obj === options.target) return;
// If objects intersect
if (options.target.isContainedWithinObject(obj) || options.target.intersectsWithObject(obj) || obj.isContainedWithinObject(options.target)) {
var distX = ((obj.getLeft() + obj.getWidth()) / 2) - ((options.target.getLeft() + options.target.getWidth()) / 2);
var distY = ((obj.getTop() + obj.getHeight()) / 2) - ((options.target.getTop() + options.target.getHeight()) / 2);
// Set new position
findNewPos(distX, distY, options.target, obj);
}
// Snap objects to each other horizontally
// If bottom points are on same Y axis
if(Math.abs((options.target.getTop() + options.target.getHeight()) - (obj.getTop() + obj.getHeight())) < snap) {
// Snap target BL to object BR
if(Math.abs(options.target.getLeft() - (obj.getLeft() + obj.getWidth())) < snap) {
options.target.setLeft(obj.getLeft() + obj.getWidth());
options.target.setTop(obj.getTop() + obj.getHeight() - options.target.getHeight());
}
// Snap target BR to object BL
if(Math.abs((options.target.getLeft() + options.target.getWidth()) - obj.getLeft()) < snap) {
options.target.setLeft(obj.getLeft() - options.target.getWidth());
options.target.setTop(obj.getTop() + obj.getHeight() - options.target.getHeight());
}
}
// If top points are on same Y axis
if(Math.abs(options.target.getTop() - obj.getTop()) < snap) {
// Snap target TL to object TR
if(Math.abs(options.target.getLeft() - (obj.getLeft() + obj.getWidth())) < snap) {
options.target.setLeft(obj.getLeft() + obj.getWidth());
options.target.setTop(obj.getTop());
}
// Snap target TR to object TL
if(Math.abs((options.target.getLeft() + options.target.getWidth()) - obj.getLeft()) < snap) {
options.target.setLeft(obj.getLeft() - options.target.getWidth());
options.target.setTop(obj.getTop());
}
}
// Snap objects to each other vertically
// If right points are on same X axis
if(Math.abs((options.target.getLeft() + options.target.getWidth()) - (obj.getLeft() + obj.getWidth())) < snap) {
// Snap target TR to object BR
if(Math.abs(options.target.getTop() - (obj.getTop() + obj.getHeight())) < snap) {
options.target.setLeft(obj.getLeft() + obj.getWidth() - options.target.getWidth());
options.target.setTop(obj.getTop() + obj.getHeight());
}
// Snap target BR to object TR
if(Math.abs((options.target.getTop() + options.target.getHeight()) - obj.getTop()) < snap) {
options.target.setLeft(obj.getLeft() + obj.getWidth() - options.target.getWidth());
options.target.setTop(obj.getTop() - options.target.getHeight());
}
}
// If left points are on same X axis
if(Math.abs(options.target.getLeft() - obj.getLeft()) < snap) {
// Snap target TL to object BL
if(Math.abs(options.target.getTop() - (obj.getTop() + obj.getHeight())) < snap) {
options.target.setLeft(obj.getLeft());
options.target.setTop(obj.getTop() + obj.getHeight());
}
// Snap target BL to object TL
if(Math.abs((options.target.getTop() + options.target.getHeight()) - obj.getTop()) < snap) {
options.target.setLeft(obj.getLeft());
options.target.setTop(obj.getTop() - options.target.getHeight());
}
}
});
options.target.setCoords();
// If objects still overlap
var outerAreaLeft = null,
outerAreaTop = null,
outerAreaRight = null,
outerAreaBottom = null;
canvas.forEachObject(function (obj) {
if (obj === options.target) return;
if (options.target.isContainedWithinObject(obj) || options.target.intersectsWithObject(obj) || obj.isContainedWithinObject(options.target)) {
var intersectLeft = null,
intersectTop = null,
intersectWidth = null,
intersectHeight = null,
intersectSize = null,
targetLeft = options.target.getLeft(),
targetRight = targetLeft + options.target.getWidth(),
targetTop = options.target.getTop(),
targetBottom = targetTop + options.target.getHeight(),
objectLeft = obj.getLeft(),
objectRight = objectLeft + obj.getWidth(),
objectTop = obj.getTop(),
objectBottom = objectTop + obj.getHeight();
// Find intersect information for X axis
if(targetLeft >= objectLeft && targetLeft <= objectRight) {
intersectLeft = targetLeft;
intersectWidth = obj.getWidth() - (intersectLeft - objectLeft);
} else if(objectLeft >= targetLeft && objectLeft <= targetRight) {
intersectLeft = objectLeft;
intersectWidth = options.target.getWidth() - (intersectLeft - targetLeft);
}
// Find intersect information for Y axis
if(targetTop >= objectTop && targetTop <= objectBottom) {
intersectTop = targetTop;
intersectHeight = obj.getHeight() - (intersectTop - objectTop);
} else if(objectTop >= targetTop && objectTop <= targetBottom) {
intersectTop = objectTop;
intersectHeight = options.target.getHeight() - (intersectTop - targetTop);
}
// Find intersect size (this will be 0 if objects are touching but not overlapping)
if(intersectWidth > 0 && intersectHeight > 0) {
intersectSize = intersectWidth * intersectHeight;
}
// Set outer snapping area
if(obj.getLeft() < outerAreaLeft || outerAreaLeft == null) {
outerAreaLeft = obj.getLeft();
}
if(obj.getTop() < outerAreaTop || outerAreaTop == null) {
outerAreaTop = obj.getTop();
}
if((obj.getLeft() + obj.getWidth()) > outerAreaRight || outerAreaRight == null) {
outerAreaRight = obj.getLeft() + obj.getWidth();
}
if((obj.getTop() + obj.getHeight()) > outerAreaBottom || outerAreaBottom == null) {
outerAreaBottom = obj.getTop() + obj.getHeight();
}
// If objects are intersecting, reposition outside all shapes which touch
if(intersectSize) {
var distX = (outerAreaRight / 2) - ((options.target.getLeft() + options.target.getWidth()) / 2);
var distY = (outerAreaBottom / 2) - ((options.target.getTop() + options.target.getHeight()) / 2);
// Set new position
findNewPos(distX, distY, options.target, obj);
}
}
});
});
I based this fiddle off #Anna Phillips' and #gco's examples. It includes:
Corner snapping
Edge snapping
Objects can overlap
Objects are fully contained within the canvas
Objects cannot have a size larger than the canvas area
Here is the code:
window.canvas = new fabric.Canvas('fabriccanvas');
window.counter = 0;
var newleft = 0,
edgedetection = 20, //pixels to snap
canvasWidth = document.getElementById('fabriccanvas').width,
canvasHeight = document.getElementById('fabriccanvas').height;
canvas.selection = false;
plusrect();
plusrect();
plusrect();
function plusrect(top, left, width, height, fill) {
window.canvas.add(new fabric.Rect({
top: 300,
name: 'rectangle ' + window.counter,
left: 0 + newleft,
width: 100,
height: 100,
fill: 'rgba(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ', 0.75)',
lockRotation: true,
originX: 'left',
originY: 'top',
cornerSize: 15,
hasRotatingPoint: false,
perPixelTargetFind: true,
minScaleLimit: 1,
maxHeight: document.getElementById("fabriccanvas").height,
maxWidth: document.getElementById("fabriccanvas").width,
}));
window.counter++;
newleft += 200;
}
this.canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords(); //Sets corner position coordinates based on current angle, width and height
if(obj.getLeft() < edgedetection) {
obj.setLeft(0);
}
if(obj.getTop() < edgedetection) {
obj.setTop(0);
}
if((obj.getWidth() + obj.getLeft()) > (canvasWidth - edgedetection)) {
obj.setLeft(canvasWidth - obj.getWidth());
}
if((obj.getHeight() + obj.getTop()) > (canvasHeight - edgedetection)) {
obj.setTop(canvasHeight - obj.getHeight());
}
canvas.forEachObject(function (targ) {
activeObject = canvas.getActiveObject();
if (targ === activeObject) return;
if (Math.abs(activeObject.oCoords.tr.x - targ.oCoords.tl.x) < edgedetection) {
activeObject.left = targ.left - activeObject.currentWidth;
}
if (Math.abs(activeObject.oCoords.tl.x - targ.oCoords.tr.x) < edgedetection) {
activeObject.left = targ.left + targ.currentWidth;
}
if (Math.abs(activeObject.oCoords.br.y - targ.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top - activeObject.currentHeight;
}
if (Math.abs(targ.oCoords.br.y - activeObject.oCoords.tr.y) < edgedetection) {
activeObject.top = targ.top + targ.currentHeight;
}
if (activeObject.intersectsWithObject(targ) && targ.intersectsWithObject(activeObject)) {
targ.strokeWidth = 10;
targ.stroke = 'red';
} else {
targ.strokeWidth = 0;
targ.stroke = false;
}
if (!activeObject.intersectsWithObject(targ)) {
activeObject.strokeWidth = 0;
activeObject.stroke = false;
}
});
});
What I'd like to know is if it's possible to extend this to add the following features:
Dynamic snapping. Continuing to drag an object after the initial snap will temporarily disable snapping until the object stops moving. For example, if I drag one box next to another, they will snap together once they are within range. However if I continue moving the first box, I can "drop" it in a position where it is within the snapping range but not aligned to the other box.
Show guide lines when selected object is within range of another object. Currently we add a border around the target object, but it would be better to show guidelines that extend outwards (possibly to the edge of the canvas) to more easily visualize the bounds of the target object.
Parallel snapping. When moving an object that is already snapped to the target object, the selected object should snap to the target object in such a way that the tops, bottoms, or sides of both objects are parallel. For example, assume that the selected square is snapped to the left of the target square and that the top of the selected square is below the top of the target square. Moving the selected square up should cause its top to snap into alignment with the top of the target once in range. The same logic should apply when moving it down, or if the selected object is above/below the target and being moves horizontally.
I needed snapping of unequal sized areas. jsfiddle
var canvas = new fabric.Canvas('c');
canvas.setDimensions({width:window.innerWidth});
var edge_detection_external = 21;
var corner_detection = 5;
canvas.selection = false;
canvas.on('object:moving', function (e) {
var obj = e.target;
obj.setCoords();
function update_position(obj){
return function(targ){
if(targ === obj) return;
// Check overlap case https://www.geeksforgeeks.org/find-two-rectangles-overlap/
if(!(function(targ,obj){
if(obj.aCoords.tl.x > targ.aCoords.br.x || targ.aCoords.tl.x > obj.aCoords.br.x)
return false;
if(targ.aCoords.tl.y > obj.aCoords.br.y || obj.aCoords.tl.y > targ.aCoords.br.y)
return false;
return true;
})(targ,obj)){
// is on RIGHT or LEFT?
if((obj.top > targ.top && obj.top < targ.top + targ.height)
|| (targ.top > obj.top && targ.top < obj.top + obj.height)){
// Object is to the RIGHT and Edge detection
if(obj.aCoords.tl.x > targ.aCoords.br.x
&& obj.aCoords.tl.x - targ.aCoords.br.x < edge_detection_external){
obj.set({left:targ.aCoords.br.x});
// Corner detection
obj.setCoords();
if(Math.abs(targ.aCoords.tr.y - obj.aCoords.tl.y) < corner_detection)
obj.set({top:targ.top});
else if(Math.abs(targ.aCoords.br.y - obj.aCoords.bl.y) < corner_detection)
obj.set({top:targ.top + targ.height - obj.height});
}
// LEFT
if(targ.aCoords.tl.x > obj.aCoords.br.x
&& targ.aCoords.tl.x - obj.aCoords.br.x < edge_detection_external){
obj.set({left:targ.aCoords.tl.x - obj.width});
obj.setCoords();
if(Math.abs(targ.aCoords.tl.y - obj.aCoords.tr.y) < corner_detection)
obj.set({top:targ.top});
else if(Math.abs(targ.aCoords.bl.y - obj.aCoords.br.y) < corner_detection)
obj.set({top:targ.top + targ.height - obj.height});
}
}
// is on TOP or BOTTOM?
if((obj.left > targ.left && obj.left < targ.left + targ.width)
|| (targ.left > obj.left && targ.left < obj.left + obj.width)){
// TOP
if(targ.aCoords.tl.y > obj.aCoords.br.y
&& targ.aCoords.tl.y - obj.aCoords.br.y < edge_detection_external){
obj.set({top:targ.aCoords.tl.y - obj.height});
obj.setCoords();
if(Math.abs(targ.aCoords.tl.x - obj.aCoords.bl.x) < corner_detection)
obj.set({left:targ.left});
else if(Math.abs(targ.aCoords.tr.x - obj.aCoords.br.x) < corner_detection)
obj.set({left:targ.left + targ.width - obj.width});
}
// BOTTOM
if(obj.aCoords.tl.y > targ.aCoords.br.y
&& obj.aCoords.tl.y - targ.aCoords.br.y < edge_detection_external){
obj.set({top:targ.aCoords.br.y});
obj.setCoords();
if(Math.abs(targ.aCoords.bl.x - obj.aCoords.tl.x) < corner_detection)
obj.set({left:targ.left});
else if(Math.abs(targ.aCoords.br.x - obj.aCoords.tr.x) < corner_detection)
obj.set({left:targ.left + targ.width - obj.width});
}
}
}
}
}
canvas.getObjects('group').some(update_position(obj));
});
String.prototype.to_inches = function(){
return this.split('-').map(function(value,index){
value = Number(value);
if(index == 0)
return value * 12
else
return value
}).reduce(function(total,current){
return total + current;
});
}
Array.prototype.to_object_list = function(){
const preserved = [...this];
var header = this.splice(0,1)[0];
for(var i = 0;i < this.length; i++){
var obj = {};
for(var j = 0;j < header.length; j++){
obj[header[j].toLowerCase()] = this[i][j];
}
this[i] = obj;
}
return preserved;
}
function draw_areas(){
var offset = 0;
return function(area_params,index){
if(area_params.area.indexOf('>') === -1){
var area = new fabric.Rect({
fill: 'red',
width:area_params.width.to_inches(),
height:area_params.length.to_inches(),
});
var text = new fabric.Text(area_params.area + '\n' + area_params.width + ' x ' + area_params.length,{
fontSize:12,
fill:"white"
});
if(text.width - area.width > 0){
text.set('width',area.width);
}
if(text.height - area.height > 0){
text.set('height',area.height);
}
var group_name = 'group_' + area_params.area.split(' ').join('-');
var group = new fabric.Group([area,text],{
name: group_name,
left: 5,
top: 5*(index++) + offset,
});
canvas.add(group);
offset = area_params.length.to_inches() + offset;
canvas.setDimensions({height:5*(index++) + offset});
}
}
}
function handler_get_data(data){
data = JSON.parse(data);
data.to_object_list();
data.forEach(draw_areas());
}
var d = '[["Area","Width","Length"],["Bedroom 1","19-5.5","14"],["Kitchen","14","16-3"],["Bedroom 2","13-6","12-9"]]';
handler_get_data(d);
I wrote a code in JS for my future site and it's all fine but the only bad thing here is in menu the rectangle (div) moves with brakes. I mean it must be like in a site some features of which I want to have on my own. http://lusens.ru/ There on main menu the rectangle mouse very smoothly. I can't understand why in my case it doesn't happen.
This is a part of html
<body>
<ul >
<li id="a1" onmouseover="highlightMenu('a1')">Первый пункт</li>
<li id="a2" onmouseover="highlightMenu('a2')">Второй пункт точка</li>
<li id="a3" onmouseover="highlightMenu('a3')">Третьий пункт точка и запятая</li>
<li id="a4" onmouseover="highlightMenu('a4')">Четвёртый пункт</li>
</ul>
<div id="d1"></div>
and the JS file
function highlightMenu(id) {
time = 0;
var rect = document.getElementById(id).getBoundingClientRect();
var width = document.getElementById(id).offsetWidth;
var idTop = rect.top;
var idLeft = rect.left;
var rect1 = document.getElementById('d1').getBoundingClientRect();
var shadowWidth = document.getElementById('d1').offsetWidth;
var shadowLeft = rect1.left;
var shadowTop = rect1.top;
if (shadowLeft < idLeft) {
for (i = shadowLeft, time = 50; i < idLeft - 3; i++, time += 5) {
setTimeout("document.getElementById('d1').style.left='" + i + "px'", time);
}
} else {
for (i = shadowLeft, time = 50; i > idLeft - 3; i--, time += 5) {
setTimeout("document.getElementById('d1').style.left='" + i + "px'", time);
}
}
if (shadowWidth < width) {
for (i = shadowWidth; i < width + 10; i++, time += 0.01) {
setTimeout("document.getElementById('d1').style.width='" + i + "px'", time);
}
} else {
for (i = shadowWidth; i > width + 10; i--, time += 0.01) {
setTimeout("document.getElementById('d1').style.width='" + i + "px'", time);
}
}
if (shadowLeft < idLeft) {
for (i = idLeft + 3; i < idLeft + 20; i++, time += 25) {
setTimeout("document.getElementById('d1').style.left='" + i + "px'", time);
}
for (i = idLeft + 20; i > idLeft - 5; i--, time += 50) {
setTimeout("document.getElementById('d1').style.left='" + i + "px'", time);
}
} else {
for (i = idLeft - 3; i > idLeft - 20; i--, time += 25) {
setTimeout("document.getElementById('d1').style.left='" + i + "px'", time);
}
for (i = idLeft - 20; i < idLeft - 5; i++, time += 50) {
setTimeout("document.getElementById('d1').style.left='" + i + "px'", time);
}
}
}
http://jsfiddle.net/m2SBm/
Using the onmouseover causes to fire the event handler function multiple times, here is better to use the onmouseenter event. And also instead of scheduling 50 timeouts, rather use single timeout and global variables, so that the animation will be stoppable.
HTML:
<ul id="menu">
<li>Первый пункт</li>
<li>Второй пункт точка</li>
<li>Третьий пункт точка и запятая</li>
<li>Четвёртый пункт</li>
</ul>
<div id="d1"></div>
Javascript:
var rect, rect1; // rectangles
var shadow; // shadow div
var bpos; // begin position
var epos; // end position
var width, shadowWidth;
var step; // animation step 1..50
var timer = null;
function animateRect() {
step++;
if (step > 50) {
clearInterval(timer);
timer = null;
return;
}
var t = bpos.t + Math.round((epos.t - bpos.t) * step / 50);
var l = bpos.l + Math.round((epos.l - bpos.l) * step / 50);
var w = shadowWidth + Math.round((width - shadowWidth) * step / 50);
shadow.style.top = t + "px";
shadow.style.left = l + "px";
shadow.style.width = w + "px";
}
function highlightMenu(e) {
e = e || window.event; // for IE8,7 compatibility
var item = e.target || e.srcElement; // for IE8,7
if (timer) {
clearInterval(timer);
}
step = 0;
rect = item.getBoundingClientRect();
width = item.offsetWidth;
epos = {
t: rect.top,
l: rect.left
};
rect1 = shadow.getBoundingClientRect();
shadowWidth = shadow.offsetWidth;
bpos = {
t: rect1.top,
l: rect1.left
};
timer = setInterval(animateRect, 5);
}
function init() {
var menu = document.getElementById('menu');
var items = menu.getElementsByTagName('li');
for (var i = 0; i < items.length; i++) {
items[i].onmouseenter = highlightMenu;
}
shadow = document.getElementById('d1');
shadow.style.width = items[0].offsetWidth + 'px';
shadow.style.top = items[0].getBoundingClientRect().top + 'px';
shadow.style.left = items[0].getBoundingClientRect().left + 'px';
}
if (window.addEventListener) {
window.addEventListener('load', init, false);
} else if (window.attachEvent) {
window.attachEvent('onload', init);
}
JSFiddle: http://jsfiddle.net/m2SBm/6/ (using the getBoundingClientRect function)
Update:
To make your menu work correctly also with eventual scrollbars, compute the element offset position rather than using the getBoundingClientRect function as shown in the following answer:
finding element's position relative to the document
Here is additional CSS for transparency in menu:
ul#menu, ul#menu li {
position:relative;
background-color:transparent;
}
#d1 {
z-index:-10;
}
The complete menu here: http://jsfiddle.net/m2SBm/8/
Is there an equivalent implementation of a Bubble Tree in D3? In the link I provided, the Bubble Tree was implemented in RaphaelJS and jQuery.
The straight answer to your question is no.
Using the resources at https://github.com/okfn/bubbletree/tree/master/build, the information you already know, and the information provided on http://d3js.org/ and through D3's documentation on GitHub, you should be able to conjure up your own bubble tree for D3!
This is a piece of JavaScript I used a long time ago to visualize binary tree data:
var updateVisual;
updateVisual = function() {
var drawTree, out;
drawTree = function(out, node) {
var col, gray, i, line, lineElt, lines, sub, _results, _results1;
if (node.lines) {
out.appendChild(document.createElement("div")).innerHTML = "<b>leaf</b>: " + node.lines.length + " lines, " + Math.round(node.height) + " px";
lines = out.appendChild(document.createElement("div"));
lines.style.lineHeight = "6px";
lines.style.marginLeft = "10px";
i = 0;
_results = [];
while (i < node.lines.length) {
line = node.lines[i];
lineElt = lines.appendChild(document.createElement("div"));
lineElt.className = "lineblock";
gray = Math.min(line.text.length * 3, 230);
col = gray.toString(16);
if (col.length === 1) col = "0" + col;
lineElt.style.background = "#" + col + col + col;
console.log(line.height, line);
lineElt.style.width = Math.max(Math.round(line.height / 3), 1) + "px";
_results.push(i++);
}
return _results;
} else {
out.appendChild(document.createElement("div")).innerHTML = "<b>node</b>: " + node.size + " lines, " + Math.round(node.height) + " px";
sub = out.appendChild(document.createElement("div"));
sub.style.paddingLeft = "20px";
i = 0;
_results1 = [];
while (i < node.children.length) {
drawTree(sub, node.children[i]);
_results1.push(++i);
}
return _results1;
}
};
out = document.getElementById("btree-view");
out.innerHTML = "";
return drawTree(out, editor.getDoc());
};
Just insert some circular elements and manipulate it a bit to style in a circular manor and you should have a good program set!
Here you go. I didn't add the text or decorations, but it's the meat and potatoes:
function bubbleChart(config) {
var aspectRatio = 1,
margin = { top: 0, right: 0, bottom: 0, left: 0 },
radiusScale = d3.scale.sqrt(),
scan = function(f, data, a) {
a = a === undefined ? 0 : a;
var results = [a];
data.forEach(function(d, i) {
a = f(a, d);
results.push(a);
});
return results;
},
colorScale = d3.scale.category20(),
result = function(selection) {
selection.each(function(data) {
var outerWidth = $(this).width(),
outerHeight = outerWidth / aspectRatio,
width = outerWidth - margin.left - margin.right,
height = outerHeight - margin.top - margin.bottom,
smallestDimension = Math.min(width, height),
sum = data[1].reduce(function(a, d) {
return a + d[1];
}, 0),
radiusFractions = data[1].map(function(d) {
return Math.sqrt(d[1] / sum);
}),
radiusNormalSum = radiusFractions.reduce(function(a, d) {
return a + d;
}, 0),
scanned = scan(function(a, d) {
return a + d;
}, radiusFractions.map(function(d) {
return d / radiusNormalSum;
}), 0);
radiusScale.domain([0, sum]).range([0, smallestDimension / 6]);
var svg = d3.select(this).selectAll('svg').data([data]),
svgEnter = svg.enter().append('svg');
svg.attr('width', outerWidth).attr('height', outerHeight);
var gEnter = svgEnter.append('g'),
g = svg.select('g').attr('transform', 'translate(' + margin.left + ' ' + margin.top + ')'),
circleRing = g.selectAll('circle.ring').data(data[1]),
circleRingEnter = circleRing.enter().append('circle').attr('class', 'ring');
circleRing.attr('cx', function(d, i) {
return smallestDimension / 3 * Math.cos(2 * Math.PI * (scanned[i] + scanned[i + 1]) / 2) + width / 2;
}).attr('cy', function(d, i) {
return smallestDimension / 3 * Math.sin(2 * Math.PI * (scanned[i] + scanned[i + 1]) / 2) + height / 2;
}).attr('r', function(d) {
return radiusScale(d[1]);
}).style('fill', function(d) {
return colorScale(d[0]);
});
var circleMain = g.selectAll('circle#main').data([data[0]]),
circleMainEnter = circleMain.enter().append('circle').attr('id', 'main');
circleMain.attr('cx', width / 2).attr('cy', height / 2).attr('r', radiusScale(sum)).style('fill', function(d) {
return colorScale(d);
});
});
};
result.aspectRatio = function(value) {
if(value === undefined) return aspectRatio;
aspectRatio = value;
return result;
};
result.margin = function(value) {
if(value === undefined) return margin;
margin = value;
return result;
};
return result;
}
var myBubbleChart = bubbleChart().margin({
top: 1,
right: 1,
bottom : 1,
left: 1
});
var data = ['Random Names, Random Amounts', [['Immanuel', .4], ['Pascal', 42.9], ['Marisa', 3.3], ['Hadumod', 4.5], ['Folker', 3.2], ['Theo', 4.7], ['Barnabas', 1.0], ['Lysann', 11.1], ['Julia', .7], ['Burgis', 28.2]]];
d3.select('#here').datum(data).call(myBubbleChart);
<div class="container">
<div class="row">
<div class="col-xs-12">
<div id="here"></div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You can use the pack layout , basically you can bind any data you want to the shapes in the graph and custom parameters for them to position well respect to each other. Another alternative would be the force layout.