2

I have a small application where I need to generate textPath labels for arcs. I'm drawing the arcs via Raphael and that is working great. However Raphael has no support for textPaths. We can add them to the svg element via jQuery but they don't render for some reason. When I duplicate the dynamically generated code statically it renders fine.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="600" height="600">
  <desc>Created with Raphaël</desc><defs></defs>
  <a title="This is a test entry">
    <path fill="none" stroke="#1e79a0" d="M395.2627944162883,355A110,110,0,0,1,199.5099996593139,344.74103073833805" style="stroke-width: 20px; " stroke-width="20" id="This_is_a_test_entry"></path>
  </a>
  <text>
    <textpath xlink:href="#This_is_a_test_entry">This is a test entry</textpath>
  </text>
</svg>

With the above code you never see the label for the arc. However when the following is hard coded it renders as expected:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="600" height="600">
  <a title="Bacon">
    <path id="Arc" fill="none" stroke="#1e79a0" d="M395.2627944162883,355A110,110,0,0,1,201.13265490709162,348.2208261467985" style="stroke-width: 20;" stroke-width="20">
    </path>
  </a>
  <text>
    <textPath xlink:href="#Arc">This is an Arc</textPath>
  </text>
</svg>

Any insight as to why I'm not seeing my entire svg would be much appreciated.

EDIT:

I've made changes to my javascript and I think it's almost there but the textPath still won't render. Here is the current code, mostly thanks to Femi:

function drawRange(start, end, R, range, id, title) {
                    var color = "hsb(".concat(Math.round(R) / 200, ",", end / 360, ", .75)");
                    var key_position = key[id];
                    var svg_bits = document.getElementsByTagName('svg')[0].childNodes;
                    var svgns = document.createElementNS('http://www.w3.org/2000/svg', "path");
                    var path;
                    range.attr({title: title});
                    range.animate({arc: [start, end, R]}, 900, ">");
                    //Add an id to the path element that was just drawn.  This doesn't seem to help though.
                    for(var i = 0; i < svg_bits.length; i++) {
                        if (svg_bits[i].tagName == "a" && svg_bits[i].getAttribute('title') == title){
                            path = svg_bits[i].childNodes[0];
                        }
                    }
                    path.setAttribute('id', title);
                    //Add the name to the key, set the color and unhide the element.
                    $(key_position).html(range.attr("title"));
                    $(key_position).css('color', Raphael.getRGB(color).hex);
                    $(key_position).show();
                };



function makeSVG(tag, attrs) {
                    var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
                    for (var k in attrs)
                        el.setAttribute(k, attrs[k]);
                    return el;
                };

function addLabel(label) {
                    var text = makeSVG("text", {});
                    var title = makeSVG("textPath", {"xlink:href":'#' + label.replace(/\s/g, '_')});
                    title.appendChild(document.createTextNode(label));
                    text.appendChild(title);
                    r.canvas.appendChild(text);
                };

I get a bounding box of 0x0 for the textPath but no textPath.

LeakyBucket
  • 160
  • 1
  • 11

4 Answers4

3

You may need to either remove the xlink that appears in the first sample, or add the appropriate XML namespace definition.

EDIT: when you are adding the textPath element you may need to use the correct element name (textPath, not textpath).

EDIT: moderately interesting, as it is combination of the browser and jQuery changing the path. To get the right thing, use this function (liberated from the answer to jquery's append not working with svg element?):

function makeSVG(tag, attrs){
   var el= document.createElementNS('http://www.w3.org/2000/svg', tag);
   for (var k in attrs)
     el.setAttribute(k, attrs[k]);
   return el;
 }

And then do this:

var paper = Raphael("notepad", 320, 200);

// do all your other stuff here
...

var text = makeSVG("text", {});
       var c = makeSVG("textPath", {"xlink:href":"#Arc"});
       c.appendChild(document.createTextNode("This is an Arc"));
       text.appendChild(c);
paper.canvas.appendChild(text);

That will get you the right SVG: however, it still won't draw right, which is odd.

Community
  • 1
  • 1
Femi
  • 64,273
  • 8
  • 118
  • 148
  • 1
    Ah. Somehow didn't notice that. In the example that works you have `This is an Arc `, and in the example that doesn't you have `This is a test entry` `. The element name is different in the second one (all lowercase instead of camel-case). I'm assuming SVG is case-sensitive here. – Femi Jul 13 '11 at 13:48
  • You were right initially. I fixed my post to reflect the actual code, not sure where the typo came from. I've tried it with a single word id and it still doesn't render for some reason. The only thing I can really identify as being different is that Raphael is attached to the svg that isn't rendering completely and not to the other one. – LeakyBucket Jul 13 '11 at 15:08
  • 1
    @LeakyBucket, did you miss Femi's edits? It should be `textPath` (note the capital P), as he said. – mercator Jul 14 '11 at 20:50
  • Ah, yes I did. I'll have to figure out how to get jQuery to name the element properly. However I set up another test using just jQuery and the paths don't render when they are added either so I'm not sure that will fix it. I'll definitely give it a try though. – LeakyBucket Jul 14 '11 at 20:56
  • Yeah, it isn't drawing for me either. Although I now get a bounding box of 0x0 which is an improvement. I suspect it's because you can't assign an id in Raphael and I'm not doing it right. I'll have to figure that out. – LeakyBucket Jul 15 '11 at 14:46
  • I think I'm adding the id correctly now but the path still doesn't render. Looks like it might be time to do some Ajax + Server Side magic. `var svg_bits = document.getElementsByTagName('svg')[0].childNodes; for(var i = 0; i < svg_bits.length; i++) { if (svg_bits[i].tagName == "a" && svg_bits[i].getAttribute('title') == title){ path = svg_bits[i].childNodes[0]; } } path.setAttribute('id', title);` – LeakyBucket Jul 15 '11 at 15:26
2

If your not worried about your work not working in ie8 or earlier why not use kieth wood's jquery SVG plugin that supports textpath The path syntax for the plugin is different but easy to convert from Raphael The SVG plugin delivers beautiful radial gradients but this is why I am involved with it now.

If you want text on a path without gollowing the curve with every letter then you could stay with Rap and use this...http://irunmywebsite.com/raphael/additionalhelp.php?v=1&q=anglebannersoncurves

Excuse spelling iPod newbie

Chasbeen
  • 1,448
  • 12
  • 17
  • Not sure what the browser requirements are at this point. This has been requested by our marketing department so we will probably have to "negotiate" on that detail. I wasn't sure if the jQuery SVG plugin was still alive or current. I couldn't find anything about it newer than 2008 and the google code site for it didn't have any files. – LeakyBucket Jul 13 '11 at 13:29
  • Yeah, unfortunately that won't work for this application. It looks like there is something else going on though. I can't get svg changes to render whether I use Raphael or not. – LeakyBucket Jul 15 '11 at 13:58
1

Femi's answer is pretty good, the only things missing are getting it to redraw and setting the xlink namespace.

I can't figure out the underlying reason, but it must be something in the core of Raphael that prevents the SVG canvas from updating, since all you need to do to fix drawing is force a redraw by quickly removing and then readding the svg element from the DOM:

$('#myRaphaelDiv').html($('#myRaphaelDiv').html());

It should be noted that any direct hacking of SVG attributes will break compatibility with browsers (sigh, IE) that use VML instead. I've yet to look into a way of doing text paths in VML. Code below is for completeness.

// add the new namespace attribute to the SVG canvas. 'raphael' is the Raphael instance.
// note that this only needs to be done once.
$(raphael.canvas).attr('xmlns:xlink', "http://www.w3.org/1999/xlink");

// draw the curved text
var lblId = 'some_unique_dom_id';
var path = raphael.path().attr({
    stroke : 'none' // defaults to black
    // ...other path attributes used in generating it...
});
var label = raphael.text().attr({ 'text-anchor' : 'start' });

// create and inject raw SVG textPath element
var link;
try {
    link = document.createElementNS('http://www.w3.org/2000/svg', 'textPath');
} catch (e) {
    // no createElementNS() method is pretty much synonymous with using IE, so
    // this is where you would do your VML version of a text path
    alert("Your browser does not support SVG, please use Firefox, Chrome etc");
}
link.setAttribute('xlink:href', '#' + lblId);
link.appendChild(document.createTextNode("path-oriented text"));

// finally, force a redraw of the SVG document. 
// Obviously, you would defer this until all elements had been added.
$('#myRaphaelDiv').html($('#myRaphaelDiv').html());
pospi
  • 3,540
  • 3
  • 27
  • 26
  • yeah, that patch works a charm! Note that animating the text path will require you to animate the path element, not the text element returned by Raphael.textPath(). You can access this via the '.path' property of the element returned. I would upvote but my reputation won't let me :p – pospi Aug 31 '11 at 10:54
  • Yeah, in the end I just ended up animating another path for each label and leaving the label tied to the path that is generated in the defs section but isn't rendered. – LeakyBucket Sep 01 '11 at 16:03
0

So, after finding a patch to Raphael here and making a small tweak to the patch it is working. I'm still not sure what I was missing but the textPaths now render and everyone is happy. Thanks to everyone for the help.

LeakyBucket
  • 160
  • 1
  • 11