Jquery - Draggable feature Containment property for a polygonal parent - svg

Referencing https://jqueryui.com/draggable/ i am able to implement a drag drop feature within a parent element (e.g. div). However my need is to have this draggable feature to work within a polygonal element (Like a SVG polygon).
I have been searching the net, however there are examples of how to make a svg polygon draggable but not 'how to contain drag drop feature within a polygonal parent (div).
Any ideas / pointers will be helpful.
Thanks.

The short story is you need a function to check if a point is within a polygon, and then check if the four corners of your draggable object are within that shape.
Here's a rough example of doing that, using the draggable sample from jQuery, along with a point in polygon function from this answer. This example is far from perfect, but I hope it points you in the right direction.
// These are the points from the polygon
var polyPoints = [
[200, 27],
[364, 146],
[301, 339],
[98, 339],
[35, 146]
];
$("#draggable").draggable({
drag: function(e, ui) {
var element = $("#draggable")[0];
var rect = element.getBoundingClientRect();
var rectPoints = rect2points(rect);
let inside = true;
rectPoints.forEach(p => {
if(!pointInside(p, polyPoints)){
inside = false;
}
});
$("#draggable")[inside ? 'addClass' : 'removeClass']('inside').text(inside ? 'Yay!' : 'Boo!');
}
});
function rect2points(rect) {
return ([
[rect.left, rect.top],
[rect.right, rect.top],
[rect.right, rect.bottom],
[rect.left, rect.bottom]
]);
};
function pointInside(point, vs) {
var x = point[0],
y = point[1];
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0],
yi = vs[i][1];
var xj = vs[j][0],
yj = vs[j][1];
var intersect = ((yi > y) != (yj > y)) &&
(x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
};
#draggable {
width: 100px;
height: 80px;
background: red;
position: absolute;
text-align: center;
padding-top: 20px;
color:#fff;
}
#draggable.inside{
background: green;
}
html, body{
margin: 0;
}
<script src="//code.jquery.com/jquery-1.12.4.js"></script>
<script src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div id="draggable">Drag me</div>
<svg width="400px" height="400px" viewBox="0 0 400 400">
<rect width="600" height="600" fill="#efefef"></rect>
<polygon points="200,27 364,146 301,339 98,339 35,146" fill="rgba(255,200,0, 1)" stroke="rgba(255,0,0,0.2" stroke-width="2"></polygon>
</svg>

Related

How to add a background image to an SVG element

I'm using this code to create a compass needle on an HTML page (instead of the range input i'm using data derived form a separate software). However, I'd like to add a compass face image behind it. Any ideas how I do that? Thanks (The code is from another thread by #Coderino Javarino) Perhaps you can help if you see this?
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body {
margin: 0;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
</style>
</head>
<body>
<input type="range" min="0" max="360" value="0" id="slider" oninput="updateAngle(this.value)">
<script>
// Feel free to change or delete any of the code you see in this editor!
var svg = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200)
.attr('viewBox', '-50 -50 100 100')
var path_d = "M 0,0 L 0,-10 L 50,0 L 0,10 Z";
function updateAngle(value) {
var angle = parseInt(value);
var data = [{
angle: angle,
color: 'black'
}, {
angle: (180 + angle) % 360,
color: 'red'
}];
paths = svg.selectAll('path')
.data(data);
paths.enter()
.append('path')
.attr('d', path_d)
.merge(paths)
.style('fill', d => d.color)
.attr('transform', d => `rotate(${d.angle})`);
paths.exit().remove();
}
updateAngle(0);
</script>
</body>

Erasing Lines on Fabric JS

I am using FabricJS to draw and erase lines on top of an image. I made 2 layers for the canvas, with the bottom layer as the background image and the top layer for sketching. Whenever I erase the markings, if I use freeDrawingBrush.color =white, or any basic color, it gets erased. However, if I use transparent, 'rgba(0,0,0,0)', as the color of the freeDrawingBrush, the markings are not erased.
How can I erase the marking on the top layer with a transparent as the background of the path created as I erase?
<div style="position: relative;">
<canvas id="my-canvas" width="800" height="400" style="position: absolute; left: 0; top: 0; z-index: 1;background-color:rgba(0,0,0,0)"> </canvas>
<canvas id="layer3" width="800" height="400" style="position: absolute; left: 0; top: 0; z-index: 0;background-image:url(http://gallery.nanfa.org/d/52271-3/DSC_7537.jpg);"></canvas>
</div>
<script>
canvas = window._canvas = new fabric.Canvas('layer1');
canvas.isDrawingMode= 1;
canvas.freeDrawingBrush.width = 10;
canvas.renderAll();
//eraser function
function eraser(){
canvas.freeDrawingBrush.color = "white"; // if "rgba(0,0,0,0)", not work
canvas.on('path:created', function (opt) {
opt.path.globalCompositeOperation = 'destination-out';
canvas.renderAll();
});
}
//drawing function
function draw(){
canvas.freeDrawingBrush.color = "black";
canvas.on('path:created', function (opt) {
opt.path.globalCompositeOperation = 'source-over';
canvas.renderAll();
});
}
</script>
Im using fabricjs on html https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js
Any help will be very much appreciated. Thanks in advance!

SVG text background color with border radius and padding that matches the text width

I need to wrap a background around a text element inside an SVG, it needs to have padding and a border radius. The issue is the text will be dynamic so I need the background to expand the width of the text. I found a solution to this using foreign object but this isn't support in IE 11 which is a problem. Can anyone suggest a workaround.
if you can use script, you can use this little function. It handles some of the CSS values. You could however implement whatever you need...
function makeBG(elem) {
var svgns = "http://www.w3.org/2000/svg"
var bounds = elem.getBBox()
var bg = document.createElementNS(svgns, "rect")
var style = getComputedStyle(elem)
var padding_top = parseInt(style["padding-top"])
var padding_left = parseInt(style["padding-left"])
var padding_right = parseInt(style["padding-right"])
var padding_bottom = parseInt(style["padding-bottom"])
bg.setAttribute("x", bounds.x - parseInt(style["padding-left"]))
bg.setAttribute("y", bounds.y - parseInt(style["padding-top"]))
bg.setAttribute("width", bounds.width + padding_left + padding_right)
bg.setAttribute("height", bounds.height + padding_top + padding_bottom)
bg.setAttribute("fill", style["background-color"])
bg.setAttribute("rx", style["border-radius"])
bg.setAttribute("stroke-width", style["border-top-width"])
bg.setAttribute("stroke", style["border-top-color"])
if (elem.hasAttribute("transform")) {
bg.setAttribute("transform", elem.getAttribute("transform"))
}
elem.parentNode.insertBefore(bg, elem)
}
var texts = document.querySelectorAll("text")
for (var i = 0; i < texts.length; i++) {
makeBG(texts[i])
}
text {
background: red;
border-radius: 5px;
border: 2px solid blue;
padding: 5px
}
text:nth-of-type(2) {
background: orange;
border-color: red
}
g text {
border-width: 4px
}
<svg width="400px" height="300px">
<text x="20" y="40">test text</text>
<text x="20" y="80" transform="rotate(10,20,55)">test with transform</text>
<g transform="translate(0,100) rotate(-10,20,60) ">
<text x="20" y="60">test with nested transform</text>
</g>
</svg>

Make "widget" for SVG-group in D3.js

I am using D3 to manipulate an SVG, that contains several "widgets" that have behavior and can be controlled eg via events. For instance I have a spinning fan. It should be possible to turn on and off the fan. I have been able to build that in D3, but not in an elegant way, and with all the code being global. What I want to end up with is something as below:
Creation of "widget", as known from jQuery:
d3.select('svg').append('g').attr('id', 'myFan').fan();
And then I would like to turn on and off with smth like the following, also as known from jQuery:
d3.select('#myFan').fan('start')
d3.select('#myFan').fan('stop')
Is it possible to achieve this in D3, ie to create such a "widget"?
Is there generally a different approach to such a problem, when using D3?
My current unelegant solution with global code:
Robert asked for this. Code at Codepen: link.
Javascript
var fan = d3.select('body')
.append('svg')
.attr('width', 200)
.attr('height', 200)
.append('g')
.attr('class', 'fan')
.attr('id', 'myFan')
.attr('transform', 'translate(75,75)')
// Static, background disc
fan.append('circle')
.attr('cx',0)
.attr('cy',0)
.attr('r',60)
// Dynamic, rotating path
var blade = fan.append('path')
.attr('d', 'M 0 0 L -30 15 L -60 0 Z L 30 -15 L 60 0 Z ')
.attr('transform', 'translate(50,50)')
.attr('transform', 'rotate(90)');
// Static, hub
fan.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', 10)
var rotation = 0; // degrees
var speed = 10; // degree per call
function rotate() {
rotation = (rotation + speed) % 360;
blade.attr('transform', 'rotate('+rotation+')')
}
setInterval(rotate, 100);
function stop() { speed = 0; }
function start() { speed = 10; }
var ctrlPanel = d3.select('body').append('div');
ctrlPanel.append('button').text('Start').on('click', start);
ctrlPanel.append('button').text('Stop').on('click', stop);
CSS
path {
fill: red;
strok: blue;
stroke-width: 3px;
}
.fan circle {
fill: lightgray;
stroke: black;
stroke-width: 3px;
}
.fan path {
fill: darkgray;
stroke: black;
stroke-width: 3px;
}

Cutting out a fat stroke

I want to draw this:
Two lines following a along a beizer path with equal distance. The fat lines are what I want, the small dotted one is the guiding path.
The image above is done by first stroking the path with width 30, and then with width 25 in the same color as the background. This works OK sometimes, but not if the background isn't plain colored.
I want something like 'clip' with a shape. And I would prefer to do it using standard graphic libraries. Even better the tools that come in most 2d graphics canvases.
Notes:
I'm using HTML canvas, but I don't think that's important to the problem.
I can translate the path up and down, but that won't give me the same distance to the center everywhere.
Maybe it could be done with a gradient stroke, but I'm not sure how those work.
I can think of a way to do this in HTML5 Canvas.
What you want to do is draw a curve - on an in-memory, temporary canvas - and then draw the same curve with less thickness and with the globalCompositeOperation set to destination-out on that same canvas.
That will get you the shape you want, essentially 2 lines with transparency between them.
Then you draw that canvas onto the real canvas that has whatever on it (complex background, etc).
Here's an example:
http://jsfiddle.net/at3zR/
The following should better illustrate what I meant. There is a simple algorithm that needs to be added to adjust the offset depending on where the control points are located in relation to each other. If I get more time, and remember, I'll add it.
bezier.js
/*
*
* This code was Shamlessly stolen from:
* Canvas curves example
*
* By Craig Buckler, http://twitter.com/craigbuckler
* of OptimalWorks.net http://optimalworks.net/
* for SitePoint.com http://sitepoint.com/
*
* Refer to:
* http://blogs.sitepoint.com/html5-canvas-draw-quadratic-curves/
* http://blogs.sitepoint.com/html5-canvas-draw-bezier-curves/
*
* This code can be used without restriction.
*/
(function() {
var canvas, ctx, code, point, style, drag = null, dPoint;
// define initial points
function Init(quadratic) {
point = {
p1: { x:100, y:250 },
p2: { x:400, y:250 }
};
if (quadratic) {
point.cp1 = { x: 250, y: 100 };
}
else {
point.cp1 = { x: 150, y: 100 };
point.cp2 = { x: 350, y: 100 };
}
// default styles
style = {
//#333
curve: { width: 2, color: "#C11" },
cpline: { width: 1, color: "#C11" },
point: { radius: 10, width: 2, color: "#900", fill: "rgba(200,200,200,0.5)", arc1: 0, arc2: 2 * Math.PI }
}
// line style defaults
ctx.lineCap = "round";
ctx.lineJoin = "round";
// event handlers
canvas.onmousedown = DragStart;
canvas.onmousemove = Dragging;
canvas.onmouseup = canvas.onmouseout = DragEnd;
DrawCanvas();
}
function controlLine(offset) {
// curve
ctx.lineWidth = style.curve.width;
ctx.strokeStyle = style.curve.color;
ctx.beginPath();
ctx.moveTo(point.p1.x+offset, point.p1.y+offset);
ctx.bezierCurveTo(point.cp1.x+offset, point.cp1.y+offset, point.cp2.x+offset, point.cp2.y+offset, point.p2.x+offset, point.p2.y+offset);
ctx.stroke();
}
function controlPoints(/*hidden*/) {
// control point tethers
ctx.lineWidth = style.cpline.width;
ctx.strokeStyle = style.cpline.color;
ctx.beginPath();
ctx.moveTo(point.p1.x, point.p1.y);
ctx.lineTo(point.cp1.x, point.cp1.y);
ctx.moveTo(point.p2.x, point.p2.y);
ctx.lineTo(point.cp2.x, point.cp2.y);
ctx.stroke();
// control points
for (var p in point) {
ctx.lineWidth = style.point.width;
ctx.strokeStyle = style.point.color;
ctx.fillStyle = style.point.fill;
ctx.beginPath();
ctx.arc(point[p].x, point[p].y, style.point.radius, style.point.arc1, style.point.arc2, true);
ctx.fill();
ctx.stroke();
}
}
// draw canvas
function DrawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
controlLine(-10);
controlLine(+10);
controlPoints();
}
// start dragging
function DragStart(e) {
e = MousePos(e);
var dx, dy;
for (var p in point) {
dx = point[p].x - e.x;
dy = point[p].y - e.y;
if ((dx * dx) + (dy * dy) < style.point.radius * style.point.radius) {
drag = p;
dPoint = e;
canvas.style.cursor = "move";
return;
}
}
}
// dragging
function Dragging(e) {
if (drag) {
e = MousePos(e);
point[drag].x += e.x - dPoint.x;
point[drag].y += e.y - dPoint.y;
dPoint = e;
DrawCanvas();
}
}
// end dragging
function DragEnd(e) {
drag = null;
canvas.style.cursor = "default";
DrawCanvas();
}
// event parser
function MousePos(event) {
event = (event ? event : window.event);
return {
x: event.pageX - canvas.offsetLeft,
y: event.pageY - canvas.offsetTop
}
}
// start
canvas = document.getElementById("canvas");
code = document.getElementById("code");
if (canvas.getContext) {
ctx = canvas.getContext("2d");
Init(canvas.className == "quadratic");
}
})();
bezier.html
<!--
bezier.html
Copyright 2012 DT <dtyree#inkcogito>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>untitled</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<meta name="generator" content="Geany 0.21" />
<meta charset="UTF-8" />
<title>Bézier Example</title>
</head>
<link rel="stylesheet" type="text/css" media="all" href="styles.css" />
<body>
<h1>Canvas Bézier Curve Example</h1>
<canvas id="canvas" height="500" width="500" class="bezier"></canvas>
<pre id="code">code</pre>
<p>This demonstration shows how parallel bézier curves can be drawn on a canvas element. Drag the line ends or the control points to change the curve.</p>
<script type="text/javascript" src="bezier.js"></script>
</body>
</html>
styles.css
/* CSS */
body
{
font-family: arial, helvetica, sans-serif;
font-size: 85%;
margin: 10px 15px;
color: #333;
background-color: #ddd;
}
h1
{
font-size: 1.6em;
font-weight: normal;
margin: 0 0 0.3em 0;
}
h2
{
font-size: 1.4em;
font-weight: normal;
margin: 1.5em 0 0 0;
}
p
{
margin: 1em 0;
}
#canvas
{
display: inline;
float: left;
margin: 0 10px 10px 0;
background-color: #fff;
}

Resources