24

Is it possible to define a custom attribute for a raphael element?

e.g.

r.circle(25,50,10).attr({fill:'#b71e16', stroke:'#71140f', 'my_custom_attribute':'a value'});

Reason I need this is I want to do some quite complex animation on a whole set of elements and I want somewhere to store the original y coordinate for each one.

boz
  • 4,891
  • 5
  • 41
  • 68

3 Answers3

41

Is the custom attribute you want:

  1. A simple store for arbitrary data, to be recorded and retrieved?
  2. An attribute where a custom action needs to be taken when it is changed (like the attributes controlled with Raphael's .attr() and .animate() )?
  3. Something you want to force into the attributes of the output SVG / VML markup on the page / DOM? (not normally recommended, but sometimes needed)

1. Custom data storage and retrieval

I'm 99% sure that the official, intended way to store arbitrary data in Raphael is to use the .data() function, e.g.

var circle = r.circle(25,50,10).attr({fill:'#b71e16', stroke:'#71140f'});
// set it
circle.data('custom-attribute', 'value');

// get it
data = circle.data('custom-attribute');
alert(data);

Note that as of Raphael 2.1 this works for elements, not sets. When I need to assign data to a set I tend to set it with a for loop and get it with someSet[0].data() - a bit of a cludge, but it works.

Annoyingly the documentation for .data doesn't say anything at all about what it is for (at time of writing)... but .data() in jQuery, data-* in HTML5, etc etc all have this purpose, using it like this works, and others on SO talk about it being intended to be used it like this, so I'm pretty confident that this is the intended method for attaching arbitrary data to Raphael objects.


2. Custom functions triggered by attr() or animate()

If you need a custom attribute that behaves like Raphael attributes - triggering a function or transformation when changed using attr or animate (kind of like a Raphael hook) - that's what paper.customAttributes is for. It defines a function that is executed any time the named custom attr is set for any element in that paper object. The return object is applied to the element's standard attributes.

The offical docs have some pretty useful examples for this one, here's an adapted one:

// A custom attribute with multiple parameters:
paper.customAttributes.hsb = function (h, s, b) {
    return {fill: "hsb(" + [h, s, b].join(",") + ")"};
};
var c = paper.circle(10, 10, 10);
// If you want to animate a custom attribute, always set it first - null isNaN
c.attr({hsb: "0.5 .8 1"});
c.animate({hsb: [1, 0, 0.5]}, 1e3);

Note that this within each customAttribute execution is the Raphael object for which the attr is being set. This means...


3. Forcing custom attribute into the SVG or VML markup in the browser

Raphael doesn't really support this, so only do this if you really, really need to. But if you really do need something in the markup that Raphael just doesn't support, you can create a rudimentary control for manipulating it using attr and animate by using paper.customAttributes and element.node (note that the documentation for element.node is pretty much just the highly unhelpful "Don't mess with it" - the reason you shouldn't mess with it is, it gives you the SVG or VML element directly, which means Raphael doesn't know about any of the changes you make to it, which may put your Raphael object out of sync with the element it controls, potentially breaking stuff. Unless you're careful, and use a technique like this...).

Here's an example (assuming jQuery is also being used, jQuery isn't essential but is more convenient) that sets the SVG property dy, which allows you to control line spacing of Raphael text (note - example code not yet tested in VML/IE. will update if it doesn't work in VML mode):

Live jsfiddle example

paper.customAttributes.lineHeight = function( value ) {
    // Sets the SVG dy attribute, which Raphael doesn't control
    var selector = Raphael.svg ? 'tspan' : 'v:textpath';
    var $node = $(this.node);
    var $tspans = $node.find(selector);
    $tspans.each(function(){
        // use $(this).attr in jquery v1.6+, fails for SVG in <= v1.5
        // probably won't work in IE
        this.setAttribute('dy', value );
    });
    // change no default Raphael attributes
    return {};
}
    // Then to use it...
    var text = paper.text(50,50,"This is \n multi-line \n text");
    // If you want to animate a custom attribute, always set it first - null isNaN
    text.attr({lineHeight: 0});
    text.animate({lineHeight: 100},500);
Community
  • 1
  • 1
user56reinstatemonica8
  • 32,576
  • 21
  • 101
  • 125
  • How can you access this data in a selector? Eg var found = document.querySelectorAll("[transaction='" + current_transaction +"']"); – jacktheripper Jun 25 '12 at 14:20
  • @jacktheripper I'll be honest, I don't know. I've never actually succeeded in figuring out where the data() data is stored. It might be stored as a property of the DOM svg/vml element for all I know. I usually avoid needing to use selectors by creating lots of tree hierarchies containing references to Raphael objects to anticipate selection needs. Your question sounds like a good question to post - someone can give a better answer than me – user56reinstatemonica8 Jun 25 '12 at 16:55
  • I did just that: http://stackoverflow.com/questions/11187856/jquery-raphael-svg-selector-based-on-custom-data/11188192#comment14691603_11188192 – jacktheripper Jun 25 '12 at 17:48
  • Also contacted the maker of the plugin - he'll probably know :p – jacktheripper Jun 25 '12 at 17:48
3

I think you can do:

var circle = r.circle(25,50,10).attr({fill:'#b71e16', stroke:'#71140f'});

then

circle["custom-attribute"] = value;

Hope this helps.

alvomenyet
  • 39
  • 1
  • 1
    This works but is probably not best practice - e.g. a future version of Raphael might theoretically add a new feature using the same namespace, or which loops through all attributes of a Raphael object. It's probably better to use the .data function in provided Raphael (although there are no clues that this is what it's for in the documentation...) – user56reinstatemonica8 Feb 23 '12 at 18:14
0

Yes, you should be able to do the following:

.attr({title: value});

Of course title is the name of the attribute you want to set or create and value should be the value. Of course the raphael element in question would be the reciever for attr.

kapa
  • 77,694
  • 21
  • 158
  • 175
LeakyBucket
  • 160
  • 1
  • 11
  • I have already tried that approach and it didn't work. I'm thinking that I am limited to the attributes defined in the SVG spec :( – boz Jul 15 '11 at 09:50
  • oh, you want to add it to the actual DOM element? Yeah, I don't believe you can do that with Raphael, even with attributes allowed by SVG like id. If your script isn't reloading the above works at the Javascript level. – LeakyBucket Jul 15 '11 at 13:11