rendered Paper.text() incorrectly y-positioned on hidden papers #491

Open
rse opened this Issue Jan 6, 2012 · 35 comments

Comments

Projects
None yet
@rse

rse commented Jan 6, 2012

I've a web app which provides two calendar illustrations, each one on its own tab and rendered with Raphael. The problem is the following: text rendered with Paper.text() is incorrectly y-offset in the tab which is initially hidden (not visible because the other tab is selected). I debugged the situation and the root cause is the following:

On the last line of tuneText() Raphael 2.0.1 tries post-adjust the "dy" value of the first "tspan" element of the "text" element. For this it uses _getBBox() to find out the bounding box of the element. It even is clever enough to first make it temporarily visible (not hidden). But this doesn't make any difference if the outmost container (in my case the tab containing the illustration) is hidden. In WebKit browsers one always gets an empty bounding box with "undefined" "y" and "height" values, in Gecko based browsers one gets "y" and "height" set to "0" . As a result, _getBBox() returns a SVGRect with "y" and "height" set to 0 or undefined. Raphael then for "undefined" does nothing (and as a result has an incorrect y offset) and for "0" incorrectly calculates the new "dy" for the first "tspan" as: "dif = a.y - (bb.y + bb.height / 2);" which results in an effective "dif = ay" and hence if you draw text on say position (100,100) the "text" is at (100,100) and its "tspan" will be on (100,200) because the "tspan" becomes a "dy" = 100 as a result of the empty bounding box. In WekKit browsers one can see this incorrect placement most easily as the "dy" is too large, in Gecko browsers one still sees the problem as texts are offset also a small amount.

I've no real solution at hand, except for never drawing a Raphael illustration on a still hidden paper if the app has to function correctly ;-) Perhaps someone else finds a solution for this...

@pragmaticdave

This comment has been minimized.

Show comment
Hide comment
@pragmaticdave

pragmaticdave Feb 23, 2012

Our web app which draws circumplex graphs also suffers from the same bug. Firefox & Opera display fine whether rendered hidden or not, but IE9, Chrome, and Safari all render fine while NOT hidden, but hidden doubles the Y coordinate. Any outlook on a fix for this? It's been two months. Thanks. :o)

Our web app which draws circumplex graphs also suffers from the same bug. Firefox & Opera display fine whether rendered hidden or not, but IE9, Chrome, and Safari all render fine while NOT hidden, but hidden doubles the Y coordinate. Any outlook on a fix for this? It's been two months. Thanks. :o)

@pragmaticdave

This comment has been minimized.

Show comment
Hide comment
@pragmaticdave

pragmaticdave Feb 23, 2012

Ok so I wrote a temporary fix in our own code until Dmitry is able to attend to this issue.

Add this to your stylesheet:

.hidden {
    height: 1px;
    width: 1px;
    overflow: hidden;
    position: absolute;
    z-index: -100;
}

Then rather than applying display:none to your tab pages, use this class you just made instead.

Finally, assuming all your other hidden tabs are set to display:none, have a js check in your "toggleTab" [to selected part] function:

    if (tab == "the_raphaeled_one") {
        // Fix for bug in Raphael.js -- https://github.com/DmitryBaranovskiy/raphael/issues/491
        $('#page_' + tab).removeClass("hidden");
    } else {
        $('#page_' + tab).css('display', "");
    }

or

    if (tab == "the_raphaeled_one") {
        $('#page_' + tab).addClass("hidden");
    } else {
        $('#page_' + tab).css('display', "none");
    }

Ok so I wrote a temporary fix in our own code until Dmitry is able to attend to this issue.

Add this to your stylesheet:

.hidden {
    height: 1px;
    width: 1px;
    overflow: hidden;
    position: absolute;
    z-index: -100;
}

Then rather than applying display:none to your tab pages, use this class you just made instead.

Finally, assuming all your other hidden tabs are set to display:none, have a js check in your "toggleTab" [to selected part] function:

    if (tab == "the_raphaeled_one") {
        // Fix for bug in Raphael.js -- https://github.com/DmitryBaranovskiy/raphael/issues/491
        $('#page_' + tab).removeClass("hidden");
    } else {
        $('#page_' + tab).css('display', "");
    }

or

    if (tab == "the_raphaeled_one") {
        $('#page_' + tab).addClass("hidden");
    } else {
        $('#page_' + tab).css('display', "none");
    }
@gravitypersists

This comment has been minimized.

Show comment
Hide comment
@gravitypersists

gravitypersists May 17, 2012

Contributor

I've found a hack-ish solution that might work for people until this is fixed, wrap your code in a setTimeout() function.

setTimeout(function(){
   paper.text(x, y, "text");
});

edit:

Coming back after learning how to javascript, I've learned why this works (kinda, I mean, I don't understand Raphael's code too much).

When you use setTimeout, it defers execution until the current call stack is complete. Read about timers here.

You probably don't want to litter your code with setTimeouts, as it will screw up the flow of your logic, since some code now executes much later than other code following it, which you'd naively presume is executed in reverse order if you didn't understand how setTimeout works.

I'd suggest only using this if you need a quick fix and you have no code dependent on the position of your text. I'd try and fix the problem for real but it doesn't look like the repository is getting any love. 👎

Contributor

gravitypersists commented May 17, 2012

I've found a hack-ish solution that might work for people until this is fixed, wrap your code in a setTimeout() function.

setTimeout(function(){
   paper.text(x, y, "text");
});

edit:

Coming back after learning how to javascript, I've learned why this works (kinda, I mean, I don't understand Raphael's code too much).

When you use setTimeout, it defers execution until the current call stack is complete. Read about timers here.

You probably don't want to litter your code with setTimeouts, as it will screw up the flow of your logic, since some code now executes much later than other code following it, which you'd naively presume is executed in reverse order if you didn't understand how setTimeout works.

I'd suggest only using this if you need a quick fix and you have no code dependent on the position of your text. I'd try and fix the problem for real but it doesn't look like the repository is getting any love. 👎

@kuroda

This comment has been minimized.

Show comment
Hide comment
@kuroda

kuroda May 22, 2012

@gitpullgravity

Thanks a lot. Your hack solved my problem!

kuroda commented May 22, 2012

@gitpullgravity

Thanks a lot. Your hack solved my problem!

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost May 30, 2012

I've hit this same issue. Can't really use @pragmaticdave's fix as the items I'm hiding using display:none need to be faded in using jQuery, and @gitpullgravity's hack doesn't seem to want to work with my code either.

I'm curious as to whether this is likely to be fixed in the near future or is simply a limitation that can't be avoided?

ghost commented May 30, 2012

I've hit this same issue. Can't really use @pragmaticdave's fix as the items I'm hiding using display:none need to be faded in using jQuery, and @gitpullgravity's hack doesn't seem to want to work with my code either.

I'm curious as to whether this is likely to be fixed in the near future or is simply a limitation that can't be avoided?

@oyatek

This comment has been minimized.

Show comment
Hide comment
@oyatek

oyatek Jun 15, 2012

I had the same problem and I found a solution similar to @pragmaticdave.
I'm using Tabs Plugin from Twitters's Bootsrap, so this is an extention to it, just add 2 CSS rules.
First add class "tab-raphael" to a tab DIV with Raphael.
Then add these 2 CSS rules:

<style>
.tab-raphael {
    display: block !important;
    position: absolute;
    top: -9999px;
}
.tab-raphael.active {
    position: relative;
    top: 0;
}
</style>

oyatek commented Jun 15, 2012

I had the same problem and I found a solution similar to @pragmaticdave.
I'm using Tabs Plugin from Twitters's Bootsrap, so this is an extention to it, just add 2 CSS rules.
First add class "tab-raphael" to a tab DIV with Raphael.
Then add these 2 CSS rules:

<style>
.tab-raphael {
    display: block !important;
    position: absolute;
    top: -9999px;
}
.tab-raphael.active {
    position: relative;
    top: 0;
}
</style>
@simonbarker

This comment has been minimized.

Show comment
Hide comment
@simonbarker

simonbarker Aug 27, 2012

+1 for @pragmaticdave 's solution, I did something similar and it works well. Just remember to block out all of Bootstrap's tab CSS and Javascript or it might not work still

+1 for @pragmaticdave 's solution, I did something similar and it works well. Just remember to block out all of Bootstrap's tab CSS and Javascript or it might not work still

@marcelcastilho

This comment has been minimized.

Show comment
Hide comment
@marcelcastilho

marcelcastilho Oct 11, 2012

@oyatek 's solution works fine with Twitter Bootstrap. I wonder if this will be fixed (it's been 9 months already)...

@oyatek 's solution works fine with Twitter Bootstrap. I wonder if this will be fixed (it's been 9 months already)...

@MarcinCieslak

This comment has been minimized.

Show comment
Hide comment
@MarcinCieslak

MarcinCieslak Dec 1, 2012

For me such solution worked, using jQuery:

var label = paper.text(x, y, content);
$('tspan', label.node).attr('dy', 0);

For me such solution worked, using jQuery:

var label = paper.text(x, y, content);
$('tspan', label.node).attr('dy', 0);
@Phoscur

This comment has been minimized.

Show comment
Hide comment
@Phoscur

Phoscur Dec 3, 2012

The solution above won't work for me, neither changing CSS helps. I used setTimeout before, but it's not an option anymore, as I don't want to carry a callback for this.
Also, this occurs without the paper being hidden. I can't position any text correctly in Chrome.
Any chance for this getting fixed?

Phoscur commented Dec 3, 2012

The solution above won't work for me, neither changing CSS helps. I used setTimeout before, but it's not an option anymore, as I don't want to carry a callback for this.
Also, this occurs without the paper being hidden. I can't position any text correctly in Chrome.
Any chance for this getting fixed?

@altrome

This comment has been minimized.

Show comment
Hide comment
@altrome

altrome Dec 4, 2012

@MarcinCieslak's solution worked geat for me! Tnx a lot!

altrome commented Dec 4, 2012

@MarcinCieslak's solution worked geat for me! Tnx a lot!

@Phoscur

This comment has been minimized.

Show comment
Hide comment
@Phoscur

Phoscur Dec 5, 2012

Nevermind my problem, just make sure the container element is already in the DOM, and thereby not hidden.
@marchincieslak's solution changes text positioning on non hidden papers by some pixels (noticed this when removing this bugfix)

Phoscur commented Dec 5, 2012

Nevermind my problem, just make sure the container element is already in the DOM, and thereby not hidden.
@marchincieslak's solution changes text positioning on non hidden papers by some pixels (noticed this when removing this bugfix)

@MrSwitch

This comment has been minimized.

Show comment
Hide comment
@MrSwitch

MrSwitch Dec 14, 2012

@MarcinCieslak works great, i additionally filtered by ":first-child" otherwise multiple lines would overlap in the same space, i.e. $('tspan:first-child', txt.node).attr('dy', 0);

@MarcinCieslak works great, i additionally filtered by ":first-child" otherwise multiple lines would overlap in the same space, i.e. $('tspan:first-child', txt.node).attr('dy', 0);

@mklement0

This comment has been minimized.

Show comment
Hide comment
@mklement0

mklement0 Dec 17, 2012

+1 on getting this fixed. Thanks, everyone, for reporting and posting workarounds.

+1 on getting this fixed. Thanks, everyone, for reporting and posting workarounds.

@zgohr

This comment has been minimized.

Show comment
Hide comment

zgohr commented Feb 7, 2013

+1

@campadrenalin

This comment has been minimized.

Show comment
Hide comment
@campadrenalin

campadrenalin Mar 20, 2013

I'm working on a fix for this, and I'm struggling to understand the rationale and intentions behind the post-adjustment code.

dif = a.y - (bb.y + bb.height / 2);

For my own extremely narrow set of uses, I can just replace this line with dif = fontSize / 4; and it makes a perfectly amicable approximation of existing behavior. But for obvious reasons, this feels like it's missing some important subtleties and considerations, which aren't explained in the source. I also have a sense that this might break backwards compatibility with how multiline text renders.

I'm working on a fix for this, and I'm struggling to understand the rationale and intentions behind the post-adjustment code.

dif = a.y - (bb.y + bb.height / 2);

For my own extremely narrow set of uses, I can just replace this line with dif = fontSize / 4; and it makes a perfectly amicable approximation of existing behavior. But for obvious reasons, this feels like it's missing some important subtleties and considerations, which aren't explained in the source. I also have a sense that this might break backwards compatibility with how multiline text renders.

@Soares

This comment has been minimized.

Show comment
Hide comment
@Soares

Soares Apr 12, 2013

+1, I'm also hitting this.

Soares commented Apr 12, 2013

+1, I'm also hitting this.

@shantanubhadoria

This comment has been minimized.

Show comment
Hide comment
@shantanubhadoria

shantanubhadoria May 20, 2013

+1 I resolved this with setTimeout Function .
I was setting attributes after I created the element like this

var element = paper.text();

... few lines later

element.attr(attrObject);

In this case I had to put the timeout around the part where I set attributes so this worked for me

var element = paper.text();

setTimeout(function(){
element.attr(attrObject);
});

+1 I resolved this with setTimeout Function .
I was setting attributes after I created the element like this

var element = paper.text();

... few lines later

element.attr(attrObject);

In this case I had to put the timeout around the part where I set attributes so this worked for me

var element = paper.text();

setTimeout(function(){
element.attr(attrObject);
});

@jenstitterness

This comment has been minimized.

Show comment
Hide comment
@jenstitterness

jenstitterness Jun 3, 2013

+1. I'm also running into this issue.

+1. I'm also running into this issue.

@lukaszbachman

This comment has been minimized.

Show comment
Hide comment

+1 me too

@TossShinHwa

This comment has been minimized.

Show comment
Hide comment
@TossShinHwa

TossShinHwa Jul 5, 2013

+1 , and use @gitpullgravity 's solution worked good.

+1 , and use @gitpullgravity 's solution worked good.

@gutierfe

This comment has been minimized.

Show comment
Hide comment
@gutierfe

gutierfe Jul 20, 2013

@MarcinCieslak's solution worked geat for me! Tnx a lot!

@MarcinCieslak's solution worked geat for me! Tnx a lot!

@rlebrette

This comment has been minimized.

Show comment
Hide comment
@rlebrette

rlebrette Jun 18, 2014

Any news on this subject?

Any news on this subject?

@joshbambrick

This comment has been minimized.

Show comment
Hide comment
@joshbambrick

joshbambrick Aug 11, 2014

A run-once, vanilla javascript hack:

var oldText = Raphael.prototype.text;

Raphael.prototype.text = function () {
    var textElement = oldText.apply(this, arguments);

    setTimeout(function () {
        var tspanElement = textElement.node.getElementsByTagName('tspan')[0];
        if (tspanElement) tspanElement.setAttribute('dy', 0);
    }, 1);

    return textElement;
};

Simply run the above code and then you can just use text as normal.

A run-once, vanilla javascript hack:

var oldText = Raphael.prototype.text;

Raphael.prototype.text = function () {
    var textElement = oldText.apply(this, arguments);

    setTimeout(function () {
        var tspanElement = textElement.node.getElementsByTagName('tspan')[0];
        if (tspanElement) tspanElement.setAttribute('dy', 0);
    }, 1);

    return textElement;
};

Simply run the above code and then you can just use text as normal.

@KSoto

This comment has been minimized.

Show comment
Hide comment
@KSoto

KSoto Feb 26, 2015

+1, using the suggestions provided here

KSoto commented Feb 26, 2015

+1, using the suggestions provided here

@KSoto

This comment has been minimized.

Show comment
Hide comment
@KSoto

KSoto Feb 26, 2015

@pragmaticdave's solution didn't work for me
@gitpullgravity's solution gave me errors since it's using variables and by the time it ran, it didn't know what the variables were :(
@oyatek's solution didn't work, however I'm not using twitter bootstrap
@MarcinCieslak's solution worked!! However it doubled my rendering time :(
@joshbambrick's solution worked great! It increased by rendering time by about 15% but that's not that bad

Looking for this to get fixed by Raphael of course, though

KSoto commented Feb 26, 2015

@pragmaticdave's solution didn't work for me
@gitpullgravity's solution gave me errors since it's using variables and by the time it ran, it didn't know what the variables were :(
@oyatek's solution didn't work, however I'm not using twitter bootstrap
@MarcinCieslak's solution worked!! However it doubled my rendering time :(
@joshbambrick's solution worked great! It increased by rendering time by about 15% but that's not that bad

Looking for this to get fixed by Raphael of course, though

@MarcinCieslak

This comment has been minimized.

Show comment
Hide comment
@MarcinCieslak

MarcinCieslak Mar 2, 2015

I do not think Raphael is being developed anymore. If you have a chance, consider switching to Snap
http://snapsvg.io/about/

I do not think Raphael is being developed anymore. If you have a chance, consider switching to Snap
http://snapsvg.io/about/

@awcab

This comment has been minimized.

Show comment
Hide comment
@awcab

awcab Mar 19, 2015

Snap hasn't seen much activity in the last year. I wouldn't be so sure that it's being actively developed either.

awcab commented Mar 19, 2015

Snap hasn't seen much activity in the last year. I wouldn't be so sure that it's being actively developed either.

@tomasAlabes

This comment has been minimized.

Show comment
Hide comment
@tomasAlabes

tomasAlabes Mar 20, 2015

Collaborator

Any PR for @joshbambrick's fix? Because that code is more like an extension and setTimeout seems hacky...

Collaborator

tomasAlabes commented Mar 20, 2015

Any PR for @joshbambrick's fix? Because that code is more like an extension and setTimeout seems hacky...

@gravitypersists

This comment has been minimized.

Show comment
Hide comment
@gravitypersists

gravitypersists Apr 27, 2015

Contributor

@tomasAlabes joshbambrick's fix also uses a setTimeout. It just abstracts it by monkey-patching Raphael's text method.

Contributor

gravitypersists commented Apr 27, 2015

@tomasAlabes joshbambrick's fix also uses a setTimeout. It just abstracts it by monkey-patching Raphael's text method.

@aleph1

This comment has been minimized.

Show comment
Hide comment
@aleph1

aleph1 May 29, 2015

The dy attribute seems to be set on all tspans earlier in the function. I've had luck showing and hiding text elements with the following patch. It does not require a setTimeout, but I don't think it addresses the issue of a container object being hidden. It does however prevent the dy value being rewritten based on bounding box height of 0 — occurs when the text element is hidden.

Replace this:

dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});

With this:

if( bb.height ) dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});

aleph1 commented May 29, 2015

The dy attribute seems to be set on all tspans earlier in the function. I've had luck showing and hiding text elements with the following patch. It does not require a setTimeout, but I don't think it addresses the issue of a container object being hidden. It does however prevent the dy value being rewritten based on bounding box height of 0 — occurs when the text element is hidden.

Replace this:

dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});

With this:

if( bb.height ) dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});

@buma buma referenced this issue in conveyal/otp.js Jun 11, 2015

Open

Bike mode triangle doesn't work #44

@panxinmiao

This comment has been minimized.

Show comment
Hide comment

@aleph1 perfect!

@holgerl

This comment has been minimized.

Show comment
Hide comment
@holgerl

holgerl Oct 29, 2015

Why isn't this fixed yet? I ran into the same problem. My canvas is hidden when I draw it, and when it is shown the text has too much dy. I fixed it with by setting the dy manually, but that is a very awkward solution, and I have to figure out the correct value by trying and failing:

var label = window.paper.text(start[0], start[1], content);
$('tspan', label.node).attr('dy', 16)

holgerl commented Oct 29, 2015

Why isn't this fixed yet? I ran into the same problem. My canvas is hidden when I draw it, and when it is shown the text has too much dy. I fixed it with by setting the dy manually, but that is a very awkward solution, and I have to figure out the correct value by trying and failing:

var label = window.paper.text(start[0], start[1], content);
$('tspan', label.node).attr('dy', 16)
@zhaoruda

This comment has been minimized.

Show comment
Hide comment
@zhaoruda

zhaoruda Jan 19, 2017

Maybe we can use 'visibility' instead of 'display' to control the element hidden or show.

Maybe we can use 'visibility' instead of 'display' to control the element hidden or show.

@holgerl

This comment has been minimized.

Show comment
Hide comment
@holgerl

holgerl Jan 19, 2017

@zhaoruda Maybe you should try that and see if it works

holgerl commented Jan 19, 2017

@zhaoruda Maybe you should try that and see if it works

srkpnp added a commit to srkpnp/Entitled-Templates that referenced this issue Aug 11, 2017

gauge.jquery.js
/*
 *  

 *

 */

;(function ( $, window, document, undefined ) {
    var config = {
        radius : 80,
        paddingX : 40,
        paddingY : 40,
        gaugeWidth : 30,

        fill : '0-#1cb42f:0-#fdbe37:50-#fa4133:100',
        gaugeBackground : '#f4f4f4',
        background : '#fff',

        showNeedle : true,

        animationSpeed : 500,

        width : 0,
        height : 0,
        centerX : 0,
        centerY : 0,

        min : 0,
        max : 100,
        value : 80,

        valueLabel : {
            display : true,
            fontFamily : 'Arial',
            fontColor : '#000',
            fontSize : '20',
            fontWeight : 'normal'
        },
        title : {
            display : true,
            value : '',
            fontFamily : 'Arial',
            fontColor : '#000',
            fontSize : '20',
            fontWeight : 'normal'
        },
        label : {
            display : true,
            left : 'Low',
            right : 'High',
            fontFamily : 'Arial',
            fontColor : '#000',
            fontSize : '12',
            fontWeight : 'normal'
        }
    };

    // Create an arc with raphael.js
    Raphael.fn.arc = function(startX, startY, endX, endY, radius1, radius2, angle) {
        var arcSVG = [radius1, radius2, angle, 0, 1, endX, endY].join(' ');
        return this.path('M' + startX + ' ' + startY + ' a ' + arcSVG);
    };

    // Calculate a circular arc with raphael.js
    Raphael.fn.circularArc = function(centerX, centerY, radius, startAngle, endAngle) {
        var startX = centerX + radius * Math.cos(startAngle * Math.PI / 180);
        var startY = centerY + radius * Math.sin(startAngle * Math.PI / 180);
        var endX = centerX + radius * Math.cos(endAngle * Math.PI / 180);
        var endY = centerY + radius * Math.sin(endAngle * Math.PI / 180);
        return this.arc(startX, startY, endX-startX, endY-startY, radius, radius, 0);
    };

    // The kuma Gauge constructor
    function kumaGauge(element, options , method) {
        // This
        var _this = this;

        // The element
        _this.element = element;
        _this.$element = $(element);

        // The config
        _this.config = $.extend( {}, config, options );
        _this._config = config;

        _this.method = method;

        // The actual gauge
        _this.gauge = {};

        // Initialise
        _this.init();
    }

    // Extend the kumaGauge object
    kumaGauge.prototype = {
        init: function () {
            // this
            _this = this;

            if (!_this.method) {
                _this.draw();
            }
        },
        _setup : function() {
            // This
            _this = this;

            // Calculate some values needed do draw the gauge
            _this.config.width = (_this.config.radius * 2) + (_this.config.paddingX * 2);
            _this.config.height = _this.config.radius + (_this.config.paddingY * 2);
            _this.config.centerX = _this.config.paddingX + _this.config.radius;
            _this.config.centerY = _this.config.paddingY + _this.config.radius;

            // The div wich acts as the canvas needs an id, so we give it a unique one if it doesn't have one
            if (typeof $(this).attr('id') === 'undefined' || $(this).attr('id') === '') {
                _this.config.id = 'gauge-' + $('*[id^="gauge-"]').length;
                _this.$element.attr('id', _this.config.id);
            }
        },
        _calculateRotation : function(min, max, val) {
            var _range, _rotation;
            _range = max - min;

            if (val < max && val > min) {
                _rotation = 180 * ((val - min) / _range);
            } else if (val <= min){
                _rotation = 0;
            } else {
                _rotation = 180;
            }

            return _rotation;
        },
        draw : function() {
            //this
            var _this = this;

            // Setup all the needed config variables
            _this._setup();

            // Make the base drawing Canvas
            _this.gauge = new Raphael(_this.config.id, _this.config.width, _this.config.height);

            // Draw the gauge
            _this.gauge.gauge = _this.gauge.circularArc(_this.config.centerX, _this.config.centerY,
                           _this.config.radius, 180, 0);
            _this.gauge.gauge.attr({
                'fill' : _this.config.fill,
                'stroke' : 'none'
            });
            _this.gauge.gauge.node.setAttribute('class', 'gauge');

            // Draw the gauge background
            _this.gauge.gaugeBackground = _this.gauge.circularArc(_this.config.centerX, _this.config.centerY,
                                     _this.config.radius, 180, 0);
            _this.gauge.gaugeBackground.attr({
                'fill' : _this.config.gaugeBackground,
                'stroke' : 'none'
            });
            _this.gauge.gaugeBackground.node.setAttribute('class', 'gauge__background');

            // Draw the white center arc
            _this.gauge.centerArc = _this.gauge.circularArc(_this.config.centerX, _this.config.centerY,
                               _this.config.radius - _this.config.gaugeWidth, 180, 0);
            _this.gauge.centerArc.attr({
                'fill' : _this.config.background,
                'stroke' : 'none'
            });
            _this.gauge.centerArc.node.setAttribute('class', 'gauge__center');

            // Draw the needle
            if (_this.config.showNeedle) {
                _this.gauge.needle = _this.gauge.rect(_this.config.centerX, _this.config.paddingY, 1, 40);
                _this.gauge.needle.attr({
                    'fill' : '#000',
                    'stroke' : 'none'
                });
                _this.gauge.needle.node.setAttribute('class', 'gauge__needle');
            }

            // Draw the bottom mask to hide the rotated background arc
            _this.gauge.bottomMask = _this.gauge.rect(0, _this.config.centerY, _this.config.width, 40);
            _this.gauge.bottomMask.attr({
                'fill' : _this.config.background,
                'stroke' : 'none'
            });

            // Draw the text container for the value
            if (_this.config.valueLabel.display) {
                if (_this.config.showNeedle) {
                    _this.gauge.valueLabel = _this.gauge.text(_this.config.centerX, _this.config.centerY - 10,
                                        Math.round((_this.config.max - _this.config.min) / 2));
                } else {
                    _this.gauge.valueLabel = _this.gauge.text(_this.config.centerX, _this.config.centerY - 10,
                                        _this.config.value);
                }
                _this.gauge.valueLabel.attr({
                    'fill' : _this.config.valueLabel.fontColor,
                    'font-size' : _this.config.valueLabel.fontSize,
                    'font-family' : _this.config.valueLabel.fontColor,
                    'font-weight' : _this.config.valueLabel.fontWeight
                });
                _this.gauge.valueLabel.node.setAttribute('class', 'gauge__value');
            }

            // Draw the title
            if (_this.config.title.display) {
                _this.gauge.title = _this.gauge.text(_this.config.centerX, _this.config.paddingY - 5,
                                   _this.config.title.value);
                _this.gauge.title.attr({
                    'fill' : _this.config.title.fontColor,
                    'fill-opacity' : 0,
                    'font-size' : _this.config.title.fontSize,
                    'font-family' : _this.config.title.fontFamily,
                    'font-weight' : _this.config.title.fontWeight
                });
                _this.gauge.title.node.setAttribute('class', 'gauge__title');
            }

            if (_this.config.label.display) {
                // Draw the left label
                _this.gauge.leftLabel = _this.gauge.text((_this.config.gaugeWidth / 2) + _this.config.paddingX,
                                   _this.config.centerY + 10, _this.config.label.left);
                _this.gauge.leftLabel.attr({
                    'fill' : _this.config.title.fontColor,
                    'fill-opacity' : 0,
                    'font-size' : _this.config.label.fontSize,
                    'font-family' : _this.config.label.fontFamily,
                    'font-weight' : _this.config.label.fontWeight
                });
                _this.gauge.leftLabel.node.setAttribute('class', 'gauge__label--left');

                // Draw the right label
                _this.gauge.rightLabel = _this.gauge.text((_this.config.width - (_this.config.gaugeWidth / 2)) - _this.config.paddingX,
                                    _this.config.centerY + 10, _this.config.label.right);
                _this.gauge.rightLabel.attr({
                    'fill' : _this.config.title.fontColor,
                    'fill-opacity' : 0,
                    'font-size' : _this.config.label.fontSize,
                    'font-family' : _this.config.label.fontFamily,
                    'font-weight' : _this.config.label.fontWeight
                });
                _this.gauge.rightLabel.node.setAttribute('class', 'gauge__label--right');
            }

            // There is a bug with raphael.js and webkit which renders text element at double the Y value.
            // Resetting the Y values after a timeout fixes this.
            // See DmitryBaranovskiy/raphael#491
            setTimeout(function() {
                if (_this.config.valueLabel.display) {
                    _this.gauge.valueLabel.attr('y', _this.config.centerY - 10);
                }

                if (_this.config.title.display) {
                    _this.gauge.title.attr({
                        'y' : _this.config.paddingY - (_this.gauge.title.getBBox().height / 2),
                        'fill-opacity' : 1
                    });
                }

                if (_this.config.label.display) {
                    _this.gauge.leftLabel.attr({
                        'y' : _this.config.centerY + (_this.gauge.leftLabel.getBBox().height / 2),
                        'fill-opacity' : 1,
                    });
                    _this.gauge.rightLabel.attr({
                        'y' : _this.config.centerY + (_this.gauge.rightLabel.getBBox().height / 2),
                        'fill-opacity' : 1,
                    });
                }
            }, 1000);

            // Animate the gauge to the right value position
            _this.gauge.gaugeBackground.animate({transform:'r' +
                            _this._calculateRotation(_this.config.min, _this.config.max, _this.config.value) + ',' +
                            _this.config.centerX + ',' + _this.config.centerY}, _this.config.animationSpeed, '<>');

        },
        update: function (data) {
            //this
            var _this = this;

            var  updateGauge = function(min, max, value) {
                _this.config.min = min;
                _this.config.max = max;
                _this.config.value = value;

                // Update the rotation of the gauge
                _this.gauge.gaugeBackground.animate({transform:'r' +
                    _this._calculateRotation(min, max, value) + ',' +
                    _this.config.centerX + ',' + _this.config.centerY}, _this.config.animationSpeed, '<>');

                // Update the value label
                if (_this.config.valueLabel.display) {
                    if (_this.config.showNeedle) {
                        _this.gauge.valueLabel.attr('text', value);
                    } else {
                        _this.gauge.valueLabel.attr('text', (max - min) / 2);
                    }
                }
            };

            if (typeof data.min !== 'undefined' && typeof data.max !== 'undefined' && typeof data.value !== 'undefined') {
                updateGauge(data.min, data.max, data.value);
            } else if (typeof data.value !== 'undefined') {
                updateGauge(_this.config.min, _this.config.max, data.value);
            }
        }
    };

    $.fn.kumaGauge = function ( method, options ) {
        var _method = method,
            _arguments = arguments,
            _this = this;

        if (typeof _method !== 'string') {
            if (_arguments.length === 1 ) {
                options = _method;
                method = false;

                return this.each(function() {
                    if ( !$.data( this, 'kumaGauge' ) ) {
                        $.data( this, 'kumaGauge', new kumaGauge( this, options, method ) );
                    }
                });
            }
        } else {
            return this.each(function() {
                if (typeof $.data(this, 'kumaGauge')[method] === 'function') {
                    $.data(this, 'kumaGauge')[method](options);
                }
            });
        }


    };

})( jQuery, window, document );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment