Add new text to svg - svg

I create a svg area. As things happen in the document, I want to display text strings in the svg area in xy coordinates within the svg area where I click.
How can I append a new text element to given xy coordinates in the svg area as these things happen?

var svg = document.getElementById("id-of-my-svg");
var text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("x", clickX);
text.setAttribute("y", clickY);
text.appendChild( document.createTextNode("Some text") );
svg.appendChild(text);
Demo here

Related

How to create zoomable text

I am looking to add zoomable text to the rendered image in OpenSeadragon. The desired functionality is that when you are zoomed out, the text is very small, and as you zoom in, the text increases in size.
I have tried to add text to an overlay div DOM element. This adds text, but as you zoom out, the size of the div gets smaller but the text doesn't. The text gets squeezed to multiple lines.
I use the Render2 class in Angular 7 to manipulate the DOM, but this can be done in any framework/library:
// create the DOM element need for SeaDragon's overlay
let div = this.renderer.createElement('div');
const text = this.renderer.createText(`x: ${xaxis}, y: ${yaxis}`);
// append text to div element
this.renderer.appendChild(div, text);
// add id for Seadragon overlay creation and styling of overlay
this.renderer.setAttribute(div, 'id', overlayname);
this.renderer.setAttribute(div, 'class', 'image-ruler');
// now append the div tag to ViewChild div
this.renderer.appendChild(this.singleOverlay.nativeElement, div);
I expect the text to zoom with the image. Is this possible?
I figured out how to do it. I saw this example that uses an svg plugin so d3 can be used to draw an overlay. After downloading this plugin, I noticed that when I printed text on the screen, it was incredibly large. Too large to read.
As d3 at this zoom level uses the scale of 0-1 for the size of the image, I had to divide the scale by the width of my image.
var p = viewer.viewport.pixelFromPoint(new OpenSeadragon.Point(0, 0), true);
var zoom = viewer.viewport.getZoom(true);
var rotation = viewer.viewport.getRotation();
var imageSize = viewer.world.getItemAt(0).getContentSize();
var scale = viewer.viewport._containerInnerSize.x * zoom / imageSize.x;
g.attr('transform',
'translate(' + p.x + ',' + p.y + ') scale(' + scale + ') rotate(' + rotation + ')');
I then reimplemented the plugin's resize function in my component as not to modify a node module. The result was small text on my enormous image that was readable when you zoom in.

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.

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>

Rounded corner when change LoadMoreElement background color in Monotouch Dialog

I'm trying using this code :
LoadMoreElement elm = new LoadMoreElement (normalCaption, loadingCaption, tapped);
elm.BackgroundColor = UIColor.Blue; // new UIColor (27,109,192,1);
elm.TextColor = UIColor.White;
This results in a complete blue rectangular cell with no rounded corners.
What I'm missing?
The LoadMoreElement is only intended to be used in the middle of the table, not on the corners.
If you want to use it, you will need to write custom code to render the corners, you can see some sample code in the ImageElements.

d3.js selectAll().each on svg path.. undefined?

I'm importing a svg (served as static content from the server) in this way
d3.xml("http://localhost:3000/mysvg.svg", "image/svg+xml", function(xml) {
var importedNode = document.importNode(xml.documentElement, true);
var mySvg = d3.select("#somediv").node().appendChild(importedNode);
then I'm trying to iterate through all svg paths and do something with them
d3.selectAll("#somediv svg path").each(function(d, i) {
console.log(this, d, i);
});
}
what I'm getting is this problem
i is from 1 to number of path, which is correct.
d is undefined instead of being the right svg path element.
this is the svg path element, like this one
<path id="m021" fill="#00AAFF" d="M225.438,312.609c-0.665-1.084-1.062-1.691-2.368-1.963c-0.582-0.121-1.686-0.271-2.265-0.069 c-0.507,0.174-0.637,0.649-1.431,0.368c-0.934-0.33-0.665-1.272-0.71-2.104c-0.597-0.021-1.18,0-1.733,0.262 ...etc" ></path>
I expected d to be the real svg path, why is it not?
EDIT:
A little insight on what I want to do could maybe help.
I have a svg with one path for each district of my town. I want to make some piecharts in the center of each path. I don't have the data now, it will be used for the piecharts. I want to make a mouseover function on the path, and add a little red circle (that in a future step will become the pie chart) on each path.
What is the best way to do this?
Simplifying your original request, let's suppose you want to add a circle in the center of each district. Let's assume that the districts are relatively square. Note that this would be much more simpler if you have geographical data instead of paths.
var svg = d3.select("#somediv svg");
var districts = svg.selectAll("path");
var district_centers = districts[0].map(function(d, i) {
var bbox = this.getBBox();
return [bbox.left + bbox.width/2, bbox.top + bbox.height/2];
});
svg
.selectAll("circle")
.data(district_centers)
.enter()
.append("circle")
.attr("class", "district_circle")
.attr("cx", function(d){ return d[0]})
.attr("cy", function(d){ return d[1]})
.attr("r", 10)
.attr("fill", "red");
According to the API doc for selection.each, d should be the datum, which you will not have if you have not previously called .data() to bind data to the nodes. All you have is pure SVG with no data bound to it.
I notice that your paths do have IDs, so if you have a dataset matching those ID's you can probably bind to it using the keys parameter of the .data function

Resources