d3.js foreignObject text transition - text

I am using d3.js to build a little project that involves data, shapes and text. According to the data, the text appears inside of a shape. If you mouse-over the shape, the shape should become larger and show more text.
The text is added in the following manner and it works fine:
Shapes
.append("foreignObject")
[...]
.append("xhtml:body")
.style("font", "14px 'Helvetica Neue'")
.html(function(d) {return d.text; });
I use foreignObject rather than text because I need the text to line-wrap and this is the only way that I've so far been able to achieve this effect.
What's giving me grief is the mouse-over. I can re-size the shape just fine but I am having trouble calling a transition on the text. In fact, I don't really know how to access it successfully.
Here is some of the code from the mouse-over event:
d3.select(this)
.transition()
.duration(250)
.attr("height", function(d,i) {
d.showAll()
//d.textObject.width = 4;
return d.height;
} );
d3.select(this).select("foreignObject").transition()
.duration(250)
.select("xhtml:body")
.html(function(d) {return d.text; });
The idea is that the showAll() function has already changed the text to a new value, however the code does not get that far. The message that Chrome gives me is "Uncaught TypeError: Object [object Array] has no method 'html' ".
So, how do I access the text and update it so that my transition will lead to the new text being visible and the old text disappearing?
I've been banging my head against this for a while so any help is deeply appreciated.

Replace d3.select(this).select("foreignObject") with d3.selectAll(this.getElementsByTagName("foreignObject"))
I just solved this myself and thought I would help.

Related

How could I build a resizizing or collapsing Textbox in D3.JS for mitch-tree

Hallo I am currently trying to build a tree in D3.js.
For this I donwloadet the repository of the mitch-tree:
https://github.com/deltoss/d3-mitch-tree
I wantet to ask, if maybe somebody already has expirience with this issue?
So, I build a tree with processcharts.
But I have the problem, that the text hast to fit the boxes right nor, or it will get cut out.
It would be great to be able to fit the boxes to the content, so making them resizeable or, maybe make the boxes collapsable..
Maybe somebody hast a tipp for me, how to reach that goal.
I think I found the part of the code where the cutting/ resizing might be happaning for my textBox.
But I am really not sure..
If I got it right, the textbox resizes already what is inside with this function and cuts the words, that are to much for my box are out:
// D3Plus Textbox with resizing capability
var d3PlusBodyTextBox = new d3PlusTextBox()
.select(element) // Sets the D3Plus code to append to the specified DOM element.
.data(singledOutData)
.text((data, index, arr) => {
return self.getBodyDisplayText.call(self, data);
})
.textAnchor("middle")
.verticalAlign("middle")
.fontSize(13) // in pixels
.x(nodeBodyBoxPadding.left)
.y(recalculatedPaddingTop - nodeBodyBoxHeight / 2)
.width(nodeBodyBoxWidth - nodeBodyBoxPadding.left - nodeBodyBoxPadding.right)
.height(nodeBodyBoxHeight - recalculatedPaddingTop - nodeBodyBoxPadding.bottom)
.ellipsis((text, line) => {
// If text was cut-off, add tooltip
selection.append("title")
.text(self.getBodyDisplayText(data));
return ((text.replace(/\.|,$/g, "")) + "...");
})
.render();
});
Has anyone maybe a tipp for me how to change it in a good and functional way?
Thanks in advance :)

Using rotate for images leads to blank pdf with pdfkit

I have asked the question on the repository directly, but in my experience SO is more reactive.
Hey there,
I am trying to create a pdf from photos using pdfkit.
Depending on whether an image is in landscape or portait mode, I want to turn the image around.
This basically means the following (in typescript) :
function toPostscriptPoint(mm: number) {
return mm * 2.8346456693;
}
const document = new PDFDocument({
size: [toPostscriptPoint(156), toPostscriptPoint(106)],
});
document.pipe(fs.createWriteStream('output.pdf'));
document.save();
document.rotate(90);
document.image(
'photos/sample.jpeg',
{ width: toPostscriptPoint(150), fit: [toPostscriptPoint(150), toPostscriptPoint(100)] });
document.restore();
document.end();
What happens though is that the pdf renders completely white. I do see however that something is happening, because the pdf has the size of the input image.
Is rotation for images not supported? What would be possible alternatives? I would like to avoid having to rewrite my files before putting them in the pdf.
Thanks
Alright, after investigation, I can answer my own question :).
I could see that the images were in the pdf somehow because of the size of the file so I dived deeper.
What happened was that the image was rendered out of the viewport. This was due to multiple things:
By default, the origin of a page after rotation in pdfkit is the center of the page! ( See the doc for more info)
The origin is rotated together with the transformation.
The x and y in the image method are actually inverted.
So after getting all this right, the following code shows the image as expected :
function toPostscriptPoint(mm: number) {
return mm * 2.8346456693;
}
const document = new PDFDocument({
size: [toPostscriptPoint(156), toPostscriptPoint(106)],
});
document.pipe(fs.createWriteStream('output.pdf'));
document.save();
document.rotate(90, {origin : [0, 0]});
document.image(
'photos/sample.jpeg',
toPostscriptPoint(0),
toPostscriptPoint(-150),
{ width: toPostscriptPoint(150), height: toPostscriptPoint(100) });
document.restore();
document.end();
Note the :
origin argument in the rotation
toPostscriptPoint(-150) actually takes into account the position of the origin, and corresponds to the X axis.
Hope that helps some later on :).
Its because taking picture from camera , if picture ISO is less than 100 it will be automatically rotated , take picture with iso more than or equal to 100 to avoid autorotation.

How to right/end align text along an textPath inside an arc using d3.js?

Here's the fiddle: http://jsfiddle.net/DevChefOwen/CZ6Dp/
var text = g.append("text")
.style("font-size",30)
.style("fill","#000")
.attr("dy",0)
.append("textPath")
.attr("xlink:href","#yyy")
.style("text-anchor","left") // using "end", the entire text disappears
.text("some text");
I've tried a number of different things to no avail. The left align is the easy part. If you did a middle, though, you see only "text" instead of "some text", implying that "some" is just hidden because it went "out of span" for the given arc.
If, however, I added:
.attr("startOffset","39%")
(as in here: http://jsfiddle.net/DevChefOwen/2H99c/)
It would look right aligned, but outside of programmatically trying to get the width/height of the text element and look for sharp changes in width/height (which seems wrong and likely error-prone), I can't seem to find a way to right align the text.
I've also tried using an SVG path (essentially a curved arc line) and the same disappearing act happens with the text when "text-anchor" is set to "left".
Thanks ahead for your time!
The question is somewhat confusing matters. The issue isn't aligning text at the end of the path -- that's easy to do with "text-anchor"="end" and "startOffset"="100%".
However, using those settings with the path created by the d3 arc function, you end up with the text cornering around the end of the inside curve and the left straight edge, to the end of the path as defined by the arc function:
http://jsfiddle.net/CZ6Dp/8/
The real issue is that the path that you want the text to be aligned along (the outside arc of the shape) is only one segment of the path that defines the shape.
(By the way, "left" and "right" are not valid values for the "text-anchor" property, and will just be ignored).
The answer by #defghi1977 gives one way to approach the problem, by figuring out the length of the path segment that you do want to use and adjusting the start offset accordingly.
Another way to approach the problem is to create a separate path (not drawn on screen) that represents only the part of the path that you want to be used for positioning text.
There are a number of possible ways to create a path that only represents the outside arc (some example code here). #defghi1977's approach of grabbing it from the existing path with regular expressions is probably the most efficent for your situation. But instead of just creating a temporary element to calculate a length, I actually have to add the new path to the DOM so it can be used as the reference path for the <textPath> element. (Which I suppose is the downside to this approach -- twice as many DOM elements!)
var path = g.append("svg:path")
.attr("d", arct)
.style("fill","#ccc")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/2+")")
.each(function(d,i) {
var justArc = /(^.+?)L/;
//grab everything up to the first Line statement
var thisSelected = d3.select(this);
var arcD = justArc.exec( thisSelected.attr("d") )[1];
defs.append("path")
.attr("id", "yyy") //normally the id would be based on the data or index
.attr("d", arcD)
.attr("transform", thisSelected.attr("transform") );
//if you can avoid using transforms directly on the path element,
//you'll save yourself having to repeat them for the text paths...
});
var text = g.append("text")
.style("font-size",30)
.style("fill","#000")
.attr("dy",0)
.append("textPath")
.attr("xlink:href","#yyy")
.style("text-anchor","end")
.attr("startOffset","100%")
.text("some text");
http://jsfiddle.net/CZ6Dp/9/
Again, factoring in the extra DOM load #defghi1977's method is probably slightly preferrable, although this version has the benefit of not being dependent on browser support for getTotalLength. But as far as I know that method is fairly well implemented.
So just consider this an alternate approach for completeness' sake.
This path is constructed by 4(or 5) path segments.
So, this probrem will be solved to get first arc path length.
But I don't know how to get sub path length by using d3.js, thus I use svgdom directly.
I tried to fix your code. If this code is not what you hope, I'm sorry.
path-anchor attribute to end.
define function to get startOffset value.
var path = g.append("svg:path")
.attr("id","yyy")
.attr("d", arct)
.style("fill","#ccc")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/2+")");
var text = g.append("text")
.style("font-size",30)
.style("fill","#000")
.attr("dy",0)
.append("textPath")
.attr("xlink:href","#yyy")
//.style("text-anchor","left") // using "end", the entire text disappears
.attr("text-anchor", "end")
.text("some text")
.attr("startOffset",function(){
var d = document.getElementById("yyy").getAttribute("d");
var tmp = document.createElementNS("http://www.w3.org/2000/svg" ,"path");
//get the arc segment of path
var arc = d.match(/(^.+?)L/)[1];
tmp.setAttribute("d", arc);
//return offset position
return tmp.getTotalLength();
});
I think the confusion comes from the meaning of text-anchor - it's not "relative to where on the parent will I justify" but rather "what part of me should I align to the start".
You're right to try to use startOffset to move the origin. Since the outer radius of your path is longer than the inner radius, the correct start offset is a little more than half of the path (around 53%).
Just a little more twiddling with your settings and you should have it. Here's a fiddle with my interpretation of what you're looking for.

D3 - Positioning tooltip on SVG element not working

I have a webpage with an SVG. On some of its shapes I need to display a tooltip. However, I can't get the tooltip to appear where it should, just some pixels away from the shape itself.
It appears way on the right hand side of the screen, maybe some 300px away.
The code I am using to get the coordinates is as follows:
d3.select("body")
.select("svg")
.select("g")
.selectAll("circle")
.on("mouseover", function(){return tooltip.style("visibility", "visible");})
.on("mousemove", function(){
var svgPos = $('svg').offset(),
/*** Tooltip ***/
//This should be the correct one, but is displaying at all working at all.
/*x = svgPos.left + d3.event.target.cx.animVal.value,
y = svgPos.top + d3.event.target.cy.animVal.value;*/
//This displays a tool tip but way much to the left of the screen.
x = svgPos.left + d3.event.target.cx.animVal.value,
y = svgPos.top + d3.event.target.cy.animVal.value;
Tooltip
window.alert("svgPos: "+svgPos+" top: "+y+"px left: "+x+"px "+d3.event.target.cx.animVal.value);
return tooltip.style("top", x+"px").style("left",y+"px");
})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
I got to this code following this SO post.
I have changed $(ev.target).attr(cx) as it is not returning a value on my machine; d3.event.target.cx is, even though it seems it is not affecting the end result anyway.
What am I doing wrong? Could somebody help me please? Thank you very much in advance for your time.
If your tooltip is an HTML element, then you want to position it relative to the page as a whole, not the internal SVG coordinates, so accessing the cx/cy value is just complicating things. I can't say for sure without looking at your code, but if you have any transforms on your <svg> or <g> elements, then that could be what's throwing you off.
However, there is a much easier solution. Just access the mouse event's default .pageX and .pageY properties, which give the position of the mouse relative to the HTML body, and use these coordinates to position your tooltip div.
Example here: http://fiddle.jshell.net/tPv46/1/
Key code:
.on("mousemove", function () {
//console.log(d3.event);
return tooltip
.style("top", (d3.event.pageY + 16) + "px")
.style("left", (d3.event.pageX + 16) + "px");
})
Even with rotational transforms on the SVG circles, the mouse knows where it is on the page and the tooltip is positioned accordingly.
There are other ways to do this, including getting a tooltip to show up in a fixed location relative to the circle instead of following the mouse around, but I just checked the examples I was working on and realized they aren't cross-browser compatible, so I'll have to standardize them and get back to you. In the meantime, I hope this gets you back on track with your project.
Edit 1
For comparison, here is the same example implemented with both an HTML tooltip (a <div> element) and an SVG tooltip (a <g> element).
http://fiddle.jshell.net/tPv46/4/
The default mouse event coordinates may be great for positioning HTML elements that are direct children of <body>, but they are less useful for positioning SVG elements. The d3.mouse() function calculates the mouse coordinates of the current event relative to a specified SVG element's coordinate system, after all transformations have been applied. It can therefore be used to get the mouse coordinates in the form we need to position an SVG tooltip.
Key code:
.on("mousemove", function () {
var mouseCoords = d3.mouse(
SVGtooltip[0][0].parentNode);
//the d3.mouse() function calculates the mouse
//position relative to an SVG Element, in that
//element's coordinate system
//(after transform or viewBox attributes).
//Because we're using the coordinates to position
//the SVG tooltip, we want the coordinates to be
//with respect to that element's parent.
//SVGtooltip[0][0] accesses the (first and only)
//selected element from the saved d3 selection object.
SVGtooltip
.attr("transform", "translate(" + (mouseCoords[0]-30)
+ "," + (mouseCoords[1]-30) + ")");
HTMLtooltip
.style("top", (d3.event.pageY + 16) + "px")
.style("left", (d3.event.pageX + 16) + "px");
})
Note that it works even though I've scaled the SVG with a viewBox attribute and put the tooltip inside a <g> with a transform attribute.
Tested and works in Chrome, Firefox, and Opera (reasonably recent versions) -- although the text in the SVG tooltip might extend past its rectangle depending on your font settings. One reason to use an HTML tooltip! Another reason is that it doesn't get cut off by the edge of the SVG.
Leave a comment if you have any bugs in Safari or IE9/10/11. (IE8 and under are out of luck, since they don't do SVG).
Edit 2
So what about your original idea, to position the tooltip on the circle itself? There are definite benefits to being able to position the tip exactly: better layout control, and the text doesn't wiggle around with the mouse. And most importantly, you can just position it once, on the mouseover event, instead of reacting to every mousemove event.
But to do this, you can no longer just use the mouse position to figure out where to put the tooltip -- you need to figure out the position of the element, which means you have to deal with transformations. The SVG spec introduces a set of interfaces for locating SVG elements relative to other parts of the DOM.
For converting between two SVG transformation systems you use SVGElement.getTransformToElement(SVGElement); for converting between an SVG coordinate system and the screen, you use SVGElement.getScreenCTM(). The result are transformation matrices from which you can
extract the net horizontal and vertical translation.
The key code for the SVG tooltip is
var tooltipParent = SVGtooltip[0][0].parentNode;
var matrix =
this.getTransformToElement(tooltipParent)
.translate(+this.getAttribute("cx"),
+this.getAttribute("cy"));
SVGtooltip
.attr("transform", "translate(" + (matrix.e)
+ "," + (matrix.f - 30) + ")");
The key code for the HTML tooltip is
var matrix = this.getScreenCTM()
.translate(+this.getAttribute("cx"),
+this.getAttribute("cy"));
absoluteHTMLtooltip
.style("left",
(window.pageXOffset + matrix.e) + "px")
.style("top",
(window.pageYOffset + matrix.f + 30) + "px");
Live example: http://fiddle.jshell.net/tPv46/89/
Again, I'd appreciate a confirmation comment from anyone who can test this in Safari or IE -- or any mobile browser. I'm pretty sure I've used standard API for everything, but just because the API is standard doesn't mean it's universally implemented!

SVG Word Wrap - Show stopper?

For fun I am trying to see how far I can get at implementing an SVG browser client for a RIA I'm messing around with in my spare time.
But have hit what appears to be a HUGE stumbling block. There is no word wrap!!
Does anyone know of any work around (I'm thinking some kind of JavaScript or special tag I don't know)?
If not I'm either going to have to go the xhtml route and start sticking HTML elements in my SVG (ouch), or just come back again in ten years when SVG 1.2 is ready.
This SVG stuff is baffling, isn't it ?
Thankfully, you can achieve some good results, but it takes more work than using the HTML 5 .
Here's a screenshot of my ASP.Net / SVG app, featuring a bit of "faked" word wrapping.
The following function will create an SVG text element for you, broken into tspan pieces, where each line is no longer than 20 characters in length.
<text x="600" y="400" font-size="12" fill="#FFFFFF" text-anchor="middle">
<tspan x="600" y="400">Here a realy long </tspan>
<tspan x="600" y="416">title which needs </tspan>
<tspan x="600" y="432">wrapping </tspan>
</text>
It's not perfect, but it's simple, fast, and the users will never know the difference.
My createSVGtext() JavaScript function takes three parameters: an x-position, y-position and the text to be displayed. The font, maximum-chars-per-line and text color are all hardcoded in my function, but this can be easily changed.
To display the right-hand label shown in the screenshot above, you would call the function using:
var svgText = createSVGtext("Here a realy long title which needs wrapping", 600, 400);
$('svg').append(svgText);
And here's the JavaScript function:
function createSVGtext(caption, x, y) {
// This function attempts to create a new svg "text" element, chopping
// it up into "tspan" pieces, if the caption is too long
//
var svgText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svgText.setAttributeNS(null, 'x', x);
svgText.setAttributeNS(null, 'y', y);
svgText.setAttributeNS(null, 'font-size', 12);
svgText.setAttributeNS(null, 'fill', '#FFFFFF'); // White text
svgText.setAttributeNS(null, 'text-anchor', 'middle'); // Center the text
// The following two variables should really be passed as parameters
var MAXIMUM_CHARS_PER_LINE = 20;
var LINE_HEIGHT = 16;
var words = caption.split(" ");
var line = "";
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + " ";
if (testLine.length > MAXIMUM_CHARS_PER_LINE)
{
// Add a new <tspan> element
var svgTSpan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
svgTSpan.setAttributeNS(null, 'x', x);
svgTSpan.setAttributeNS(null, 'y', y);
var tSpanTextNode = document.createTextNode(line);
svgTSpan.appendChild(tSpanTextNode);
svgText.appendChild(svgTSpan);
line = words[n] + " ";
y += LINE_HEIGHT;
}
else {
line = testLine;
}
}
var svgTSpan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
svgTSpan.setAttributeNS(null, 'x', x);
svgTSpan.setAttributeNS(null, 'y', y);
var tSpanTextNode = document.createTextNode(line);
svgTSpan.appendChild(tSpanTextNode);
svgText.appendChild(svgTSpan);
return svgText;
}
The logic for word-wrapping is based on this HTML5 Canvas tutorial
I hope you find this useful !
Mike
http://www.MikesKnowledgeBase.com
UPDATE
One thing I forgot to mention.
That "Workflow diagram" screen that I've shown above was originally just written using an HTML 5 canvas. It worked beautifully, the icons could be dragged, popup menus could appear when you clicked on them, and even IE8 seemed happy with it.
But I found that if the diagram became "too big" (eg 4000 x 4000 pixels), then the would fail to initialise in all browsers, nothing would appear - but - as far as the JavaScript code was concerned, everything was working fine.
So, even with error-checking, my diagram was appearing blank, and I was unable to detect when this showstopper problem was occurring.
var canvasSupported = !!c.getContext;
if (!canvasSupported) {
// The user's browser doesn't support HTML 5 <Canvas> controls.
prompt("Workflow", "Your browser doesn't support drawing on HTML 5 canvases.");
return;
}
var context = c.getContext("2d");
if (context == null) {
// The user's browser doesn't support HTML 5 <Canvas> controls.
prompt("Workflow", "The canvas isn't drawable.");
return;
}
// With larger diagrams, the error-checking above failed to notice that
// the canvas wasn't being drawn.
So, this is why I've had to rewrite the JavaScript code to use SVG instead. It just seems to cope better with larger diagrams.
There is also foreignObject tag. Then you can embed HTML in SVG which gives the greatest flexibility. HTML is great for document layout and has been hacked to no end to support application layout, drawing, and everything us developers want. But it's strength is word wrapping and document layout. Let HTML do what it does best, and let SVG do what it does best.
http://www.w3.org/TR/SVG/extend.html
This works for most browsers FireFox, Opera, Webkit, except IE (as of IE11). :-( Story of the web ain't it?
SVGT 1.2 introduces the textArea element http://www.w3.org/TR/SVGTiny12/text.html#TextInAnArea , but it is only experimentally supported by Opera 10 at the moment. I don't know if other browsers will ever plan on implementing it, though I hope they will.
Per this document, it appears that tspan can give the illusion of word wrap:
The tspan tag is identical to the text tag but can be nested inside text tags and inside itself. Coupled with the 'dy' attribute this allows the illusion of word wrap in SVG 1.1. Note that 'dy' is relative to the last glyph (character) drawn.
The svg.js library has a svg.textflow.js plugin. It's not ultra fast but it does the trick. It even stores overflowing text in a data attribute so you can use it to create continuously flowing columns. Here the text flow example page.
An alternative method is to use Andreas Neuman's text box object.
These days, flowPara can do word wrapping, but I have yet to find a browser that supports it properly.
I've been looking for a solution about word wrapping in svg so many hours (or many days).
If you can in your app, edit your code to put some tspan, or any other method, go in it.
Text wrapping will be implement in the 1.2 version but except opera, no browser fully implement it yet (4 years, the specification are on the W3 ...).
Because I had to use some alignment settings, i couldn't use any of the code that many forum can provide (no foreign object, no carto script or anything).
If I post this message, it's just in order to be usefull to some other people when googling word wrapping svg because this post on the top result and in many case, this post doesn't help.
Here is a cool, easy and light solution :
http://dev.w3.org/SVG/profiles/1.1F2/test/svg/text-dom-01-f.svg

Resources