Dynamically adding SVG with clipping path - javascript

I'm trying to add an SVG path that's clipped, with Javascript, but some of the pieces (specifically, the clipPath) aren't working. What am I doing wrong?
Here's a comparison Codepen: working HTML version on the right, failed .js version on the right.
The relevant code:
var fieldShield = function() {
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("style", "height: 100%; width: 100%; position: absolute;");
var clipPath = document.createElement("clipPath");
clipPath.id = "fieldClip";
svg.appendChild(clipPath);
var fill = document.createElementNS("http://www.w3.org/2000/svg", "rect");
fill.id = "fieldFill";
fill.setAttribute("x", "0");
fill.setAttribute("y", "0");
fill.setAttribute("width", "450");
fill.setAttribute("height", "450");
clipPath.appendChild(fill);
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.id = "fieldShield";
path.setAttribute("d", "m395,20c0.910,57.6 0.857,115 0,173-0.833,55.5-3.60,98.8-28.5,133-29.9,40.8-79.8,70.2-144,99.2-64.2-28.9-114-58.4-144-99.2-24.9-33.9-27.6-77.2-28.5-133-0.857-57.6-0.910-115 0-173z");
path.setAttribute("style", "stroke: white; stroke-width: 3;");
svg.appendChild(path);
var use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.className = "divisions";
use.setAttribute("clip-path", "url('#fieldClip')");
use.setAttribute("xlink:href", "#fieldShield");
use.setAttribute("fill", "red");
svg.appendChild(use);
console.log(svg);
document.getElementById("svgHolder").append(svg);
}

The cause of your problem is using deprecated xlink:href attribute on <use>.
Consider using href instead:
var fieldShield = function() {
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("style", "height: 100%; width: 100%; position: absolute;");
var clipPath = document.createElement("clipPath");
clipPath.id = "fieldClip";
svg.appendChild(clipPath);
var fill = document.createElementNS("http://www.w3.org/2000/svg", "rect");
fill.id = "fieldFill";
fill.setAttribute("x", "0");
fill.setAttribute("y", "0");
fill.setAttribute("width", "450");
fill.setAttribute("height", "450");
clipPath.appendChild(fill);
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.id = "fieldShield";
path.setAttribute("d", "m395,20c0.910,57.6 0.857,115 0,173-0.833,55.5-3.60,98.8-28.5,133-29.9,40.8-79.8,70.2-144,99.2-64.2-28.9-114-58.4-144-99.2-24.9-33.9-27.6-77.2-28.5-133-0.857-57.6-0.910-115 0-173z");
path.setAttribute("style", "stroke: white; stroke-width: 3;");
svg.appendChild(path);
var use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.className = "divisions";
use.setAttribute("clip-path", "url('#fieldClip')");
use.setAttribute("href", "#fieldShield");
use.setAttribute("fill", "red");
svg.appendChild(use);
// console.log(svg);
document.getElementById("svgHolder").append(svg);
}
fieldShield();
body {
background: #aaa
}
<div id="svgHolder"></div>

You’re already using Document.createElementNS() to correctly namespace the elements, but you also need to use Element.setAttributeNS() when setting namespaced attributes.
In your example, this affects the following line:
use.setAttribute("xlink:href", "#fieldShield");
The browser treats xlink:href as a single plain attribute, not an attribute with a specified namespace. Instead, you should use the NS version of this function to specify the xlink namespace:
use.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#fieldShield");

Related

SVG Shape Lighting

Summary: I'm trying to create a canvas of randomized rock climbing holds using vector graphics generated with properties such as color, rotation, size and path values.
To add depth to these I'm trying to add a sort of randomized shadow to them to show that one or more sides of the shape are raised from the background.
Question:
I've been able to apply this filter onto the svg however as you can see on the below image "Light Filter" I get this white effect bleeding out to the edge of the svg element.
I'd like to find a way to keep that raised effect and have the color show or find a new way to show shadow randomized to each edge of the svg path?
You can find the filter code in the function: addFilter
You can disable the filter effect by commenting out the function addFilter(); and applyFilter();
No Filter:
Light Filter:
//create a filter for the svg copying the rough paper filter and apply it to the svg
var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
filter.setAttribute("id", "roughpaper");
filter.setAttribute("x", "0%");
filter.setAttribute("y", "0%");
filter.setAttribute("width", "100%");
filter.setAttribute("height", "100%");
var feDiffuseLighting = document.createElementNS("http://www.w3.org/2000/svg", "feDiffuseLighting");
feDiffuseLighting.setAttribute("in", "noise");
feDiffuseLighting.setAttribute("lighting-color", "#ffffff");
feDiffuseLighting.setAttribute("surfaceScale", "2");
var feDistantLight = document.createElementNS("http://www.w3.org/2000/svg", "feDistantLight");
feDistantLight.setAttribute("azimuth", "45");
feDistantLight.setAttribute("elevation", "60");
feDiffuseLighting.appendChild(feDistantLight);
filter.appendChild(feDiffuseLighting);
document.getElementById("svg-0").appendChild(filter);
body {
background-color: #333;
overflow: hidden;
}
#svg-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.svg-element {
position: absolute;
width: 150px;
height: 150px;
}
<div id="svg-container">
<svg
viewBox="0 0 200 200"
preserveAspectRatio="none"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
class="svg-element"
id="svg-0"
filter="url(#roughpaper)"
>
<path
d="M27.8,-30.1C31.8,-29.8,27.8,-17,30.3,-5C32.9,7,42,18.2,38.3,19.7C34.5,21.1,18,12.7,4.6,21.4C-8.9,30.1,-19.3,55.9,-25.4,58.5C-31.5,61.2,-33.3,40.7,-44.1,24.4C-54.8,8,-74.4,-4.3,-75.5,-15.9C-76.6,-27.6,-59.1,-38.6,-43.4,-36.8C-27.7,-34.9,-13.9,-20.3,-1,-19.1C11.9,-17.9,23.9,-30.3,27.8,-30.1Z"
transform="translate(100, 100)"
class="path"
id="path-0"
style="fill: rgb(106, 76, 147)"
></path>
</svg>
</div>
Diffuse light effects should be multiplied with the original, otherwise you'll see the the lighting color rather than the combination of original color + lighting. So just add a feBlend with a multiply - like so. Update: and then add a feComposite/in to "clip to self" - so you don't see the background lit with the light as well.
//create an array with multiple svg paths
var svgPaths = [
{
path: "M27.8,-30.1C31.8,-29.8,27.8,-17,30.3,-5C32.9,7,42,18.2,38.3,19.7C34.5,21.1,18,12.7,4.6,21.4C-8.9,30.1,-19.3,55.9,-25.4,58.5C-31.5,61.2,-33.3,40.7,-44.1,24.4C-54.8,8,-74.4,-4.3,-75.5,-15.9C-76.6,-27.6,-59.1,-38.6,-43.4,-36.8C-27.7,-34.9,-13.9,-20.3,-1,-19.1C11.9,-17.9,23.9,-30.3,27.8,-30.1Z",
},
{
path: "M36.5,-45.3C49.8,-32.4,64.8,-23.2,69,-10.5C73.3,2.2,66.7,18.5,58.3,34.1C49.8,49.8,39.4,64.8,23.8,74.4C8.1,84,-12.7,88.2,-22.9,77.9C-33,67.7,-32.3,43,-35.4,26.1C-38.5,9.1,-45.3,0,-46.2,-10.5C-47.2,-21,-42.3,-32.7,-33.6,-46.4C-24.9,-60.1,-12.5,-75.7,-0.4,-75.2C11.6,-74.7,23.2,-58.1,36.5,-45.3Z",
}
];
var colors = ["#FF595E", "#FFCA3A", "#8AC926", "#1982C4", "#6A4C93"];
//create a function to apply properties to each svg element
function holdProps() {
//based on the id of the svg element, re-position the svg element on the screen
var svgElements = document.getElementsByClassName("svg-element");
//set the position of the svg element to the top left
for (var i = 0; i < svgElements.length; i++) {
svgElements[i].style.position = "absolute";
svgElements[i].style.top = "0";
svgElements[i].style.left = "0";
}
//console log the bounding box of each svg element
for (var i = 0; i < svgElements.length; i++) {
var svg = svgElements[i];
console.log(svg.getBoundingClientRect());
}
}
//create a function to apply properties to each svg elements path value
function holdPaths() {
//create a path and append it to each svg element
$(".svg-element").each(function () {
var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
//set attributes to the above created path
path.setAttribute(
"d",
svgPaths[Math.floor(Math.random() * svgPaths.length)].path
);
path.setAttribute("transform", "translate(100, 100)");
path.setAttribute("class", "path");
path.setAttribute("id", "path-" + $(this).attr("id").split("svg-")[1]);
this.appendChild(path);
});
//If a path id is clicked change the path to a random path with a random color
$(".svg-element").click(function () {
var path = svgPaths[Math.floor(Math.random() * svgPaths.length)].path;
var color = colors[Math.floor(Math.random() * colors.length)];
$(this).find("path").attr("d", path);
$(this).find("path").css("fill", color);
});
}
//create a function to apply random colors to each svg element
function colorHold() {
$(".svg-element path").each(function () {
var color = colors[Math.floor(Math.random() * colors.length)];
$(this).css("fill", color);
//stroke white width 5 if the svg is hoverd over
$(this).hover(function () {
$(this).css("stroke", "white");
$(this).css("stroke-width", "5");
}
//reset stroke to black and stroke width to 1 if the svg is not hovered over
, function () {
$(this).css("stroke", "black");
$(this).css("stroke-width", "0");
}
);
});
}
//create feDistantLight and a fePointLight to each svg element
function addFilter() {
var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
filter.setAttribute("id", "roughpaper");
filter.setAttribute("x", "0%");
filter.setAttribute("y", "0%");
filter.setAttribute("width", "100%");
filter.setAttribute("height", "100%");
var feGauss= document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
feGauss.setAttribute("stdDeviation", "4");
feGauss.setAttribute("result", "blur");
filter.appendChild(feGauss);
var feDiffuseLighting = document.createElementNS("http://www.w3.org/2000/svg", "feDiffuseLighting");
feDiffuseLighting.setAttribute("in", "blur");
feDiffuseLighting.setAttribute("lighting-color", "#ffffff");
feDiffuseLighting.setAttribute("surfaceScale", "6");
var feDistantLight = document.createElementNS("http://www.w3.org/2000/svg", "feDistantLight");
feDistantLight.setAttribute("azimuth", "235");
feDistantLight.setAttribute("elevation", "50");
feDiffuseLighting.appendChild(feDistantLight);
filter.appendChild(feDiffuseLighting);
var feBlend= document.createElementNS("http://www.w3.org/2000/svg", "feBlend");
feBlend.setAttribute("mode", "multiply");
feBlend.setAttribute("in2", "SourceGraphic");
filter.appendChild(feBlend);
var feComp= document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
feComp.setAttribute("operator", "in");
feComp.setAttribute("in2", "SourceGraphic");
filter.appendChild(feComp);
document.getElementsByTagName("svg")[0].appendChild(filter);
}
//apply the filter to each svg element
function applyFilter() {
var svgElements = document.getElementsByClassName("svg-element");
for (var i = 0; i < svgElements.length; i++) {
svgElements[i].setAttribute("filter", "url(#roughpaper)");
}
}
//on load run the functions
$(document).ready(function () {
//create a path for each svg element
holdPaths();
//apply properties to each svg element
holdProps();
//apply random colors to each svg element
colorHold();
//Disable these two to remove the filter
//create a filter to each svg element
addFilter();
//apply the filter to each svg element
applyFilter();
});
body {
background-color: #333;
overflow: hidden;
}
#svg-container {
/* full size of page*/
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.svg-element {
position: absolute;
}
svg path {
transition: 0.2s;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<div id="svg-container">
<!-- Create SVG -->
<svg id="svg-01" class="svg-element" width="100%" height="100%" viewBox="0 0 200 200" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
</svg>
</div>

How to pan in PaperJS without using PaperScript

Referring to
How to pan using paperjs
I was trying to implement the pan.
This however only works for me when I use PaperScript and stops to work when I do it in regular JavaScript.
var canvas = document.querySelector('#myCanvas');
paper.install(window);
window.onload = function() {
paper.setup(canvas);
var myCircle = new Path.Circle(new Point(100, 70), 50);
myCircle.fillColor = 'black';
var toolPan = new paper.Tool();
toolPan.onMouseDrag = function (event) {
var offset = event.downPoint - event.point;
// console.log(offset);
paper.view.center = paper.view.center + offset;
};
view.draw();
}
html,
body {
margin: 0;
overflow: hidden;
height: 100%;
}
canvas[resize] {
width: 100%;
height: 100%;
}
<canvas id="myCanvas" resize></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.11/paper-full.min.js"></script>
Logging out offset to the console tells me it can’t substract the points. In PaperScript the exact same works well, however I have to make it work in regular JavaScript for my Project.
In PaperScript, you can use mathematical operators on Point instances but you can't do this in JavaScript so you have to use the corresponding methods instead.
point1 + point2 => point1.add(point2), etc...
Here is the corrected code:
var canvas = document.querySelector('#myCanvas');
paper.install(window);
window.onload = function() {
paper.setup(canvas);
var myCircle = new Path.Circle(new Point(100, 70), 50);
myCircle.fillColor = 'black';
var toolPan = new paper.Tool();
toolPan.onMouseDrag = function (event) {
var offset = event.downPoint.subtract(event.point);
// console.log(offset);
paper.view.center = paper.view.center.add(offset);
};
view.draw();
}
html,
body {
margin: 0;
overflow: hidden;
height: 100%;
}
canvas[resize] {
width: 100%;
height: 100%;
}
<canvas id="myCanvas" resize></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.11/paper-full.min.js"></script>

Trying to add child to div class

Im trying to add a canvas child to my div with the bb-item class.
this is my current code.
var createImage = function(w, h) {
var i = document.createElement("canvas");
i.width = w;
i.height = h;
i.ctx = i.getContext("2d");
return i;
}
var canvas = createImage(1024, 512);
var ctx = canvas.ctx;
document.getElementsByClassName('bb-item').appendChild(canvas);
<div class="bb-item cover">
<canvas></canvas>
</div>
That is because when you are using document.getElementsByClassName, it returns a collection of DOM nodes (even if only a single element matches the selector):
Returns an array-like object of all child elements which have all of the given class names.
That means that you either have to
select the first element in the collection, if you are sure you only want to work with the very first element in the collection, i.e.:
document.getElementsByClassName('bb-item')[0].appendChild(canvas);
Or, if you want to apply the same method to all elements in the collection, then iterate through the collection before applying the .appendChild() method:
var bbItems = document.getElementsByClassName('bb-item');
for (var i = 0; i < bbItems.length; i++) {
var bbItem = bbItems[i];
bbItem.appendChild(canvas);
}
Replace this statement:
document.getElementsByClassName('bb-item').appendChild(canvas);
with this:
document.getElementsByClassName('bb-item')[0].appendChild(canvas);
var createImage = function(w, h) {
var i = document.createElement("canvas");
i.width = w;
i.height = h;
i.ctx = i.getContext("2d");
return i;
}
var canvas = createImage(1024, 512);
var ctx = canvas.ctx;
document.getElementsByClassName('bb-item')[0].appendChild(canvas);
.bb-item{
min-height: 400px;
background-color: yellow;
}
canvas{
background-color: white;
}
<div class="bb-item cover">
<canvas></canvas>
</div>

Add Element to DOM, change styles and apply JS library

I have a page with any number of images with the class pinch-zoom-element I want to replace all of that images with canvas and apply them the PinchZoomCanvas library. Canvas is added to the code, even the library is applied, but the canvas remains at 0px width and height.
var pictures = document.getElementsByClassName('pinch-zoom-element');
for(var i = 0; i < pictures.length; i++){
var imageItem = pictures.item(i);
var canv = document.createElement('canvas');
var foto = imageItem.src;
canv.id = 'canvasIdItem';
canv.style.width = "300px";
canv.style.height = "300px";
imageItem.parentNode.insertBefore(canv, imageItem.nextSibling);
imageItem.parentNode.removeChild(imageItem);
console.log(canv);
var pinchZoom = new PinchZoomCanvas({
canvas: canv,
path: foto,
zoomMax: 2,
doubletap: true,
onZoomEnd: function (zoom, zoomed) {
console.log("---> is zoomed: %s", zoomed);
console.log("---> zoom end at %s", zoom);
},
onZoom: function (zoom) {
console.log("---> zoom is %s", zoom);
}
});
}
How can I get this working? Thanks in advance!
Set your canvas height and width like:
canv.style.width = "300";
canv.style.height = "300";
You can also set it in your css using specific selector:
canvas {
width: 500px;
height: 400px;
}

Long text in tipsy tooltips

I'm happily using tipsy for my d3.js graphs, but now I noticed that tipsy is cutting off text if it's too long.
For instance the line containing the word CALCULATOR is cut off:
Is there a way to extend the width of the tooltip or autosize it according to the longest line of the tooltip text (I didn't find anything in the docs)?
I ended up writing my own tooltips module for D3 (see below). It uses require.js.
I use it like this:
define('myD3module',
// This module depends on the following modules
['tooltips', 'd3'],
function (tooltips, d3){
// Code omitted...
var node = svg.selectAll('g.node');
var nodeEnter = node.enter().append('g')
.on('mouseover', onMouseOver)
.on('mouseout', onMouseOut);
// Code omitted...
var onMouseOver = function(d){
tooltips.showTooltip(d, getTooltipText);
};
var onMouseOut = function(d){
tooltips.hideTooltip(d);
};
// Code omitted...
var getTooltipText = function(d){
var tipText = '';
var name = d.name;
if(typeof name != 'undefined'){
tipText += 'Name = ' + name + '<br>';
}
return tipText;
};
This is the module.
// This module shows tooltips near the user's cursor. The text of the tooltip must be provided by the consumer of this module. This module can be used with require.js.
define('tooltips',
// Required modules:
['d3','jquery'],
function (d3, $){
// Contains the tooltip. This div will be repositioned as needed.
var tooltip = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
// This CSS styles the tooltip
var css = './config/tooltip.css';
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = css;
document.getElementsByTagName('head')[0].appendChild(link);
var hideTooltip = function (d) {
tooltip.style('opacity', 0);
},
/** Show the tooltip. The text is defined by a function and the element the mouse cursor is placed on. */
showTooltip = function (elem, getTooltipText){
tooltip.style('opacity', 0.9);
var text = getTooltipText(elem);
var tipWidth = getTooltipWidth(text);
// Draw the tips below the mouse cursor
var tipYpos = d3.event.pageY + 20;
tooltip.html(text)
// The tooltip is centered below the cursor
.style('left', (d3.event.pageX - tipWidth/2) + 'px')
.style('top', tipYpos + 'px')
.style('width', tipWidth + 'px')
.style('height', getTooltipHeight(text) + 'px');
},
/**
* The tooltip spans all the text's lines vertically.
*/
getTooltipHeight = function (text) {
var totalHeight = 0;
var lines = text.split('<br>');
for (var i = 0; i < lines.length; i++){
var line = $.trim(lines[i]);
// Ignore empty strings
if(line.length > 0){
var span = document.createElement('span');
span.innerHTML = line;
$('body').append(span);
totalHeight += $(span).height();
$(span).remove();
}
}
return totalHeight;
},
/*
* The width of the tooltip is the width of the longest line in the tooltip text
*/
getTooltipWidth = function (text){
var lines = text.split('<br>');
// Append a dummy span containing the current line to the DOM. The browser calculates the width of that span
var maxWidth = d3.max(lines, function(line) {
var span = document.createElement('span');
span.innerHTML = line;
$('body').append(span);
var spanWidth = $(span).width();
$(span).remove();
return spanWidth;
});
return maxWidth;
};
// These functions are public
return {
showTooltip: showTooltip,
hideTooltip: hideTooltip
};
}
);
This is the tooltip.css:
.tooltip {
position: absolute;
text-align: center;
font: 12px sans-serif;
background-color: rgb(95,230,245);
border-radius: 2px;
pointer-events: none;
}

Categories