Snap SVG. Turn rect into an image mask and insert image - svg

Please could someone suggest how I can transform a loaded rectangle into a mask for an image? I want to be able to create rectangles my svg in illustrator - so the size and position is setup prior to loading the svg
I think this could work in the following steps.
Step1: Add jpeg image to svg
Step2: Group rect and jpeg
Step3: Turn rect into mask/clipping mask
Thanks
David

The code that you look for is element.attr({mask:maskelement});.
In this case, element is the image element, referable e.g. by its id in the page. As is maskelement the element that would be your rectangle. Keep in mind that the color of the rectangle has influence on the transparency of the masked part, white making it 0% transparent, black making it 100% transparent.
As a simple example:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Snap test</title>
<script src="snap/snap.svg.js"></script>
<script>
window.onload = function () {
var s = Snap(800, 600);
Snap.load("myexample.svg", function (f) {
var element = f.select("#myimage");
var maskelement = f.select("#mymask");
element.attr({mask:maskelement});
g = f.select("svg");
s.append(g);
});
};
</script>
</head>
</html>
Keep in mind that the SVG in this case contains the PNG and the masking rectangle. The PNG must have the id that you use in your code, as does the masking rectangle.
Edit: here you see the previous PNG:
And the SVG sourcecode can be found here.

Related

d3 create SVG path from array

Following this example
http://jsfiddle.net/4xXQT/
I was able to render the points coordinates stored in one array using D3 as follow
https://jsfiddle.net/il_pres/qq9o1ovt/.
var vis = d3.select("body").append("svg")
.attr("width", 30)
.attr("height", 30);
var regioni = [{regione:'Abruzzo',polygon:{points:'25.171,18.844 25.094,18.582 24.567,17.714 24.015,17.714 23.226,16.899 22.805,16.268 21.911,14.558 21.043,14.427 20.753,15.163 20.043,15.084 19.938,15.741 19.386,16.268 19.386,17.056 18.439,16.899 18.202,17.345 18.334,17.924 18.281,18.582 19.57,19.423 18.939,20.055 18.176,19.581 17.756,20.16 17.808,20.844 18.703,21.08 19.517,21.396 19.491,22.079 20.517,22.185 20.596,22.605 21.122,22.133 21.832,22.658 22.568,22.737 23.094,23.473 23.646,22.553 23.751,21.738 24.646,21.212 25.094,21.738 25.409,22.29 26.25,21.238 26.409,20.475 26.776,19.923 26.303,19.634 26.145,19.292'}},{regione:'Basilicata',polygon:{points:'24.607,15.268 23.476,14.716 23.818,14.4 23.161,13.848 22.556,14.111 22.135,14.005 21.529,14.452 20.53,14.716 19.294,14.111 18.952,13.742 18.426,14.242 18.479,14.926 17.637,14.321 17.295,14.531 17.479,15.426 16.611,16.083 17.216,16.451 17.637,17.556 18.321,18.214 18.453,18.792 19.189,18.871 19.61,18.424 20.662,19.186 20.662,19.581 20.109,19.844 19.978,20.344 20.662,20.081 21.188,20.318 21.372,20.002 21.951,19.713 22.424,19.713 22.949,20.265 23.423,20.896 23.555,21.448 23.187,22.053 23.213,22.658 23.844,23.027 24.475,23.105 25.159,23.605 25.08,23.894 25.58,24.157 26.158,24.157 26.5,23.552 27.105,23.552 27.631,22.816 27.92,22.238 27.579,21.291 26.658,20.475 26.132,19.844 26.053,19.239 25.238,18.45 25.238,17.766 26.001,17.74 26.264,17.398 26.21,16.609 25.764,16.32 24.712,16.136'}}];
vis.selectAll("polygon")
.data(regioni) .enter().append("polygon")
.attr("points",function(d) {return d.polygon.points})
.attr("stroke","red")
.attr("stroke-width",0.1);
Now I was trying to do the same with the same svg shape, this time stored as d coordinates
var regionico =[{Regione:'Abruzzo',polygon:{points:'m 127.945,84.9805 -0.781,2.6172 -5.273,8.6835 -5.508,0 -7.891,8.1528 -4.219,6.308 -8.9292,17.09 -8.6836,1.32 -2.8985,-7.363 -7.1015,0.789 -1.0547,-6.57 -5.5157,-5.266 0,-7.89 -9.4648,1.582 -2.3711,-4.4731 1.3164,-5.7812 -0.5273,-6.582 12.8906,-8.4063 -6.3086,-6.3203 -7.6367,4.7383 -4.1992,-5.793 0.5273,-6.8359 8.9453,-2.3633 8.1445,-3.1641 -0.2617,-6.8242 10.2617,-1.0664 0.7813,-4.1992 5.2656,4.7265 7.0977,-5.2539 7.3632,-0.7812 5.25,-7.3633 5.527,9.1992 1.055,8.1446 8.945,5.2656 4.473,-5.2656 3.164,-5.5157 8.399,10.5157 1.601,7.6367 3.652,5.5195 -4.726,2.8906 -1.582,3.418 -9.727,4.4805'}},{Regione:'Basilicata',polygon:{points:'m 123.746,104.57 -7.637,-4.7458 -9.453,3.4178 -3.961,8.692 -12.3512,2.089 2.1093,9.739 -7.6289,4.734 -5,-5.516 -6.0547,3.418 -7.3633,-7.109 0.5274,-7.891 -2.6172,-3.41 -7.6367,-0.527 0,-6.57 8.1445,-7.9027 0.8008,-6.0352 5.2539,-6.3281 9.4727,-8.1445 3.4101,-9.4727 -3.1562,-6.0547 -5,-7.0898 5.2617,-5.2617 2.1094,-5.2539 2.8906,6.8242 8.9375,0.8008 3.4375,-7.625 16.8128,4.207 3.945,14.9805 12.109,-1.0547 9.981,23.4101 -9.727,5.7891 0,14.4723 -7.617,3.418'}}
but if I use the same code with d as attr.
vis.selectAll("path")
.data(regionico).enter().append("path")
.attr("d",function(d) { return d.polygon.points})
.attr("stroke","red")
.attr("stroke-width",0.1);
it doesn't work.
Any suggestion?
The problem is simply that your SVG isn't big enough to make the path visible -- note in particular how you're first moving more than 100 pixels to the right before starting the path. It works fine if you make the SVG bigger, e.g. 300x300 here.

Rendering MathML on svg using d3.js

I am trying to render MathML equations on svg using d3.js. Can anyone help me getting a quadratic equation on svg. I tried doing it using foreign object with no success.
I spent quite some time trying to make it work in a JSFiddle with no success, but it works great on my PC. JSFiddle here. Do you mind trying the following and let me know if it works with you too?
Step 1. Load MathJax
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
Step 2. Use this code to append a foreignObject
var svg = d3.select("body").append("svg").attr("width",400).attr("height",400)
var text = svg.append("foreignObject").attr("width",100).attr("height",100)
text.text("$$ x = \\sum_{i \\in A} i^{2} $$")
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
However, if you still prefer MathML, then you can use the following:
text.html("<math display=\"block\"><mrow><mi>x</mi><mo>=</mo><mfrac><mrow><mo>−</mo><mi>b</mi><mo>±</mo><msqrt><mrow><msup><mi>b</mi><mn>2</mn></msup><mo>−</mo><mn>4</mn><mi>a</mi><mi>c</mi></mrow></msqrt></mrow><mrow><mn>2</mn><mi>a</mi></mrow></mfrac></mrow></math>")
I know I am adding more scripts for you to load, but my understanding is that MathML is not really much used any more.
I hope it helps.
EDIT
Finally a JSFiddle here: link
Thanks
You've two bugs
foreignObject must have width/height attributes
mathml elements must be created in the mathml namespace
Fixing these results in this...
d3.ns.prefix.mathml = "http://www.w3.org/1998/Math/MathML";
var foreignObject = d3.select("body")
.append("svg")
var x = foreignObject.append("foreignObject")
.attr("requiredExtensions", "http://www.w3.org/1999/xhtml")
.attr("width", "100")
.attr("height", "100")
var text = x.append("mathml:mo")
var row = x.append("mathml:mrow")
row.append("mathml:mi").text("a")
row.append("mathml:mo").text('\u2062')
var msup = row.append("msup")
msup.append("mathml:mi").text("x")
msup.append("mathml:mi").text("2")
row.append("mathml:mo").text("+")
row.append("mathml:mi").text("b")
row.append("mathml:mo").text('\u2062')
row.append("mathml:mi").text('x')
row.append("mathml:mo").text('+')
row.append("mathml:mi").text('c')
or as a fiddle

Wrap text to a circle shape in svg or canvas

What would be a good solution for fitting text to a circle on a website, so it flows with the curves of the circle, instead of a rectangular bounding box?
Here's what I'm trying to achieve:
There are a number of black circles (of a fixed size) on a page, with a textarea next to each of them.
When text is entered into the textarea, it appears in the black circle, where it is centered on both axes.
If so much text is entered that line becomes longer than the radius of the circle, minus a specified value for margin, the line will break like you would expect from regular wrapping, with the block of text still being centered.
Lines nearer the top or bottom will, of course, be shorter than the ones near the middle.
The text will have a fixed size and when the circle is filled with text, the extra content should not be shown (like overflow hidden).
The black circles with the text are really speech bubbles, which are meant to be printed and glued onto a poster.
Do any of the fantastic SVG/Canvas libraries support this or will I have to figure our a method from scratch?
There is a proposed CSS feature call "exclusions" that would make it possible to flow text inside defined areas: http://www.w3.org/TR/css3-exclusions/
This means that SVG and Canvas paths would likely be defined as containers and text would flow/wrap inside the containers.
I did say "proposed" -- it's a ways off from being a reality in browsers.
However...
You can fairly easily wrap text inside a circle using html canvas
The width available to display text on any line changes as you move down the circle.
Here’s how to determine the maximum available width of any horizontal line on a circle
// var r is the radius of the circle
// var h is the distance from the top of the circle to the horizontal line you’ll put text on
var maxWidth=2*Math.sqrt(h*(2*r-h));
You fit text to the line by measuring the width of text—adding one word at a time, until you’ve used up all the available width of that line.
Here’s how to use canvas to measure any text using the current context.font:
var width=ctx.measureText(“This is some test text.”).width;
The rest is just adding text to each line up to the maximum line width and then starting a new line.
If you prefer SVG, you can do similar in SVG using the element.getComputedTextLength method for text metrics.
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/upq6L/
<!doctype html>
<html lang="en">
<head>
<style>
body{ background-color: ivory; padding:20px; }
canvas{ border:1px solid red;}
</style>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script>
$(function() {
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var text = "'Twas the night before Christmas, when all through the house, Not a creature was stirring, not even a mouse. And so begins the story of the day of Christmas";
var font="12pt verdana";
var textHeight=15;
var lineHeight=textHeight+5;
var lines=[];
var cx=150;
var cy=150;
var r=100;
initLines();
wrapText();
ctx.beginPath();
ctx.arc(cx,cy,r,0,Math.PI*2,false);
ctx.closePath();
ctx.strokeStyle="skyblue";
ctx.lineWidth=2;
ctx.stroke();
// pre-calculate width of each horizontal chord of the circle
// This is the max width allowed for text
function initLines(){
for(var y=r*.90; y>-r; y-=lineHeight){
var h=Math.abs(r-y);
if(y-lineHeight<0){ h+=20; }
var length=2*Math.sqrt(h*(2*r-h));
if(length && length>10){
lines.push({ y:y, maxLength:length });
}
}
}
// draw text on each line of the circle
function wrapText(){
var i=0;
var words=text.split(" ");
while(i<lines.length && words.length>0){
line=lines[i++];
var lineData=calcAllowableWords(line.maxLength,words);
ctx.fillText(lineData.text, cx-lineData.width/2, cy-line.y+textHeight);
words.splice(0,lineData.count);
};
}
// calculate how many words will fit on a line
function calcAllowableWords(maxWidth,words){
var wordCount=0;
var testLine="";
var spacer="";
var fittedWidth=0;
var fittedText="";
ctx.font=font;
for(var i=0;i<words.length; i++){
testLine+=spacer+words[i];
spacer=" ";
var width=ctx.measureText(testLine).width;
if(width>maxWidth){
return({
count:i,
width:fittedWidth,
text:fittedText
});
}
fittedWidth=width;
fittedText=testLine;
}
}
}); // end $(function(){});
</script>
</head>
<body>
<p>Text wrapped and clipped inside a circle</p>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

SVG Text bounding box is different from browser to browser when using #font-face?

I am trying to place an SVG Text-element according to the width and height of the text by getting the bounding box using the getBBox() method.
If the text is using a websafe font, this works reasonably well across different browsers, but if the text is styled using #font-face and a custom webfont, then the width of the text is returned incorrectly in Firefox (Mac) and Safari (iOS).
It works perfectly in both Safari (Mac) and Chrome (Mac).
If the gray box has the same width as the text, then it works in that browser.
Does anybody have an idea on how to get the correct width of the text bounding box in all browsers?
The browser is calculating the bounding box before it has finished loading/applying #font-face, assuming you don't need IE, you can wrap your BBox calculation function inside a document.fonts.ready promise...
document.fonts.ready.then(() => const bbox = textEl.getBBox());
Here is an example at work that exhibits the problem and the fix:
const xmlns = "http://www.w3.org/2000/svg";
const correct = document.getElementById("correct");
const incorrect = document.getElementById("incorrect");
visualizeBBox(incorrect);
document.fonts.ready.then(()=> visualizeBBox(correct));
function visualizeBBox(el){
const bbox = el.getBBox();
const rect = document.createElementNS(xmlns, "rect");
for (prop in bbox) rect.setAttribute(prop, bbox[prop]);
document.querySelector("svg").appendChild(rect);
}
svg text {
font-family: 'Diplomata SC', serif;
}
svg rect {
stroke: red;
fill: none;
}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Diplomata+SC&display=swap" rel="stylesheet">
<svg xmlns="https://www.w3.org/2000/svg" width="600" height="400">
<text x="0" y="40" font-size="24" id="correct">Correct dimensions</text>
<text y="100" font-size="24" id="incorrect">Incorrect dimensions</text>
<svg>
Today I ran into similar issue. Duopixel is right that getBBox() might return momental metric which may be unexpected because external font hasn't been loaded yet and some standard font is used instead.
The problem in WebKit (tested in Chrome 24.0.1312.52 and 26.0.1389.0 canary) is that the browser defers external font loading until it is first effectively used anywhere on the page. So even if you wait for onreadystatechange to become "complete" you are not guaranteed to have font metrics ready when calling getBBox() - you may still be the first one rendering a text styled with external font, inserting it into the document and immediately calling getBBox() on it (my case).
My workaround instead of calling mySVGInitCode() directly I do:
$("body").append(
$("<div/>")
.attr("class", "force-external-font-loading")
.attr("style", "font-family: \"xkcd\";visibility:hidden;position:absolute")
.text("x")
);
setTimeout(function(){ mySVGInitCode() }, 100); // 100ms is just arbitrary waiting time which should be sufficient for fetching the external font on a fast network, anyone has a better solution?
As you can see I dynamically insert absolutely positioned styled piece of text to force external font loading (visibility:hidden is important here instead of display:none). Then I wait some time before I execute my SVG code which could potentially render something and then immediately ask for metrics.

SVG viewbox and scrolling on the y-axis

I have a huge svg 3200*1800. I only want to show a part of that image something like 400*1000, ensuring that the width is the dominant attribute and having a scroll bar for the height but when I set viewbox it increase the width to display the added height.
viewBox="900 550 400 1000"
Is their a way to stop this happening?
I worked it out you need to increase the height relative to the viewbox for example I ended up with something like this:
width="1400"
height="4000"
viewBox="966 555 350 1000"
Compared to what I used to have:
width="350"
height="1000"
viewBox="966 555 350 1000"
You just set 'preserveAspectRatio' to "none" along with your 'viewBox' attribute, then your problem is solved.
This answer builds on Shane's answer (which does not cater to variable window sizes)...
To have width-dominant overflows:
Let the 'viewbox' define the portion of the graphic to display (any known aspect ratio)
Let the svg element have default width and height (100%)
With javascript, dynamically set the height of the svg element every time the window resizes
The code below works for my learning project and is NOT production code.
In the head element:
<script type="application/javascript">
var svgRatio = ${viewboxRatio}; // ratio must be known
// From http://stackoverflow.com/a/13651455
if(window.attachEvent) {
window.attachEvent('onresize', resizeSvg);
}
else if(window.addEventListener) {
window.addEventListener('resize', resizeSvg, true);
}
else {
//The browser does not support Javascript event binding
}
function resizeSvg() {
var height = window.innerWidth * svgRatio;
var svg = document.getElementsByTagName('svg')[0];
svg.setAttribute("height", height.toString());
}
</script>
At the end of the body:
<script type="application/javascript">
resizeSvg();
</script>

Resources