    initStyles = function() {

        addCSS(defaultStyles);

    };



    $(initStyles);



    pending = [];

    $.fn.sparkline = function (userValues, userOptions) {

        return this.each(function () {

            var options = new $.fn.sparkline.options(this, userOptions),

                 $this = $(this),

                 render, i;

            render = function () {

                var values, width, height, tmp, mhandler, sp, vals;

                if (userValues === 'html' || userValues === undefined) {

                    vals = this.getAttribute(options.get('tagValuesAttribute'));

                    if (vals === undefined || vals === null) {

                        vals = $this.html();

                    }

                    values = vals.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(',');

                } else {

                    values = userValues;

                }



                width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width');

                if (options.get('height') === 'auto') {

                    if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) {

                        // must be a better way to get the line height

                        tmp = document.createElement('span');

                        tmp.innerHTML = 'a';

                        $this.html(tmp);

                        height = $(tmp).innerHeight() || $(tmp).height();

                        $(tmp).remove();

                        tmp = null;

                    }

                } else {

                    height = options.get('height');

                }



                if (!options.get('disableInteraction')) {

                    mhandler = $.data(this, '_jqs_mhandler');

                    if (!mhandler) {

                        mhandler = new MouseHandler(this, options);

                        $.data(this, '_jqs_mhandler', mhandler);

                    } else if (!options.get('composite')) {

                        mhandler.reset();

                    }

                } else {

                    mhandler = false;

                }



                if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) {

                    if (!$.data(this, '_jqs_errnotify')) {

                        alert('Attempted to attach a composite sparkline to an element with no existing sparkline');

                        $.data(this, '_jqs_errnotify', true);

                    }

                    return;

                }



                sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height);



                sp.render();



                if (mhandler) {

                    mhandler.registerSparkline(sp);

                }

            };

            if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || !$(this).parents('body').length) {

                if (!options.get('composite') && $.data(this, '_jqs_pending')) {

                    // remove any existing references to the element

                    for (i = pending.length; i; i--) {

                        if (pending[i - 1][0] == this) {

                            pending.splice(i - 1, 1);

                        }

                    }

                }

                pending.push([this, render]);

                $.data(this, '_jqs_pending', true);

            } else {

                render.call(this);

            }

        });

    };



    $.fn.sparkline.defaults = getDefaults();





    $.sparkline_display_visible = function () {

        var el, i, pl;

        var done = [];

        for (i = 0, pl = pending.length; i < pl; i++) {

            el = pending[i][0];

            if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {

                pending[i][1].call(el);

                $.data(pending[i][0], '_jqs_pending', false);

                done.push(i);

            } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) {

                // element has been inserted and removed from the DOM

                // If it was not yet inserted into the dom then the .data request

                // will return true.

                // removing from the dom causes the data to be removed.

                $.data(pending[i][0], '_jqs_pending', false);

                done.push(i);

            }

        }

        for (i = done.length; i; i--) {

            pending.splice(done[i - 1], 1);

        }

    };





    /**

     * User option handler

     */

    $.fn.sparkline.options = createClass({

        init: function (tag, userOptions) {

            var extendedOptions, defaults, base, tagOptionType;

            this.userOptions = userOptions = userOptions || {};

            this.tag = tag;

            this.tagValCache = {};

            defaults = $.fn.sparkline.defaults;

            base = defaults.common;

            this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);



            tagOptionType = this.getTagSetting('type');

            if (tagOptionType === UNSET_OPTION) {

                extendedOptions = defaults[userOptions.type || base.type];

            } else {

                extendedOptions = defaults[tagOptionType];

            }

            this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);

        },





        getTagSetting: function (key) {

            var prefix = this.tagOptionsPrefix,

                val, i, pairs, keyval;

            if (prefix === false || prefix === undefined) {

                return UNSET_OPTION;

            }

            if (this.tagValCache.hasOwnProperty(key)) {

                val = this.tagValCache.key;

            } else {

                val = this.tag.getAttribute(prefix + key);

                if (val === undefined || val === null) {

                    val = UNSET_OPTION;

                } else if (val.substr(0, 1) === '[') {

                    val = val.substr(1, val.length - 2).split(',');

                    for (i = val.length; i--;) {

                        val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));

                    }

                } else if (val.substr(0, 1) === '{') {

                    pairs = val.substr(1, val.length - 2).split(',');

                    val = {};

                    for (i = pairs.length; i--;) {

                        keyval = pairs[i].split(':', 2);

                        val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));

                    }

                } else {

                    val = normalizeValue(val);

                }

                this.tagValCache.key = val;

            }

            return val;

        },



        get: function (key, defaultval) {

            var tagOption = this.getTagSetting(key),

                result;

            if (tagOption !== UNSET_OPTION) {

                return tagOption;

            }

            return (result = this.mergedOptions[key]) === undefined ? defaultval : result;

        }

    });





    $.fn.sparkline._base = createClass({

        disabled: false,



        init: function (el, values, options, width, height) {

            this.el = el;

            this.$el = $(el);

            this.values = values;

            this.options = options;

            this.width = width;

            this.height = height;

            this.currentRegion = undefined;

        },



        /**

         * Setup the canvas

         */

        initTarget: function () {

            var interactive = !this.options.get('disableInteraction');

            if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {

                this.disabled = true;

            } else {

                this.canvasWidth = this.target.pixelWidth;

                this.canvasHeight = this.target.pixelHeight;

            }

        },



        /**

         * Actually render the chart to the canvas

         */

        render: function () {

            if (this.disabled) {

                this.el.innerHTML = '';

                return false;

            }

            return true;

        },



        /**

         * Return a region id for a given x/y co-ordinate

         */

        getRegion: function (x, y) {

        },



        /**

         * Highlight an item based on the moused-over x,y co-ordinate

         */

        setRegionHighlight: function (el, x, y) {

            var currentRegion = this.currentRegion,

                highlightEnabled = !this.options.get('disableHighlight'),

                newRegion;

            if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) {

                return null;

            }

            newRegion = this.getRegion(el, x, y);

            if (currentRegion !== newRegion) {

                if (currentRegion !== undefined && highlightEnabled) {

                    this.removeHighlight();

                }

                this.currentRegion = newRegion;

                if (newRegion !== undefined && highlightEnabled) {

                    this.renderHighlight();

                }

                return true;

            }

            return false;

        },



        /**

         * Reset any currently highlighted item

         */

        clearRegionHighlight: function () {

            if (this.currentRegion !== undefined) {

                this.removeHighlight();

                this.currentRegion = undefined;

                return true;

            }

            return false;

        },



        renderHighlight: function () {

            this.changeHighlight(true);

        },



        removeHighlight: function () {

            this.changeHighlight(false);

        },



        changeHighlight: function (highlight)  {},



        /**

         * Fetch the HTML to display as a tooltip

         */

        getCurrentRegionTooltip: function () {

            var options = this.options,

                header = '',

                entries = [],

                fields, formats, formatlen, fclass, text, i,

                showFields, showFieldsKey, newFields, fv,

                formatter, format, fieldlen, j;

            if (this.currentRegion === undefined) {

                return '';

            }

            fields = this.getCurrentRegionFields();

            formatter = options.get('tooltipFormatter');

            if (formatter) {

                return formatter(this, options, fields);

            }

            if (options.get('tooltipChartTitle')) {

                header += '<div class="jqs jqstitle">' + options.get('tooltipChartTitle') + '</div>\n';

            }

            formats = this.options.get('tooltipFormat');

            if (!formats) {

                return '';

            }

            if (!$.isArray(formats)) {

                formats = [formats];

            }

            if (!$.isArray(fields)) {

                fields = [fields];

            }

            showFields = this.options.get('tooltipFormatFieldlist');

            showFieldsKey = this.options.get('tooltipFormatFieldlistKey');

            if (showFields && showFieldsKey) {

                // user-selected ordering of fields

                newFields = [];

                for (i = fields.length; i--;) {

                    fv = fields[i][showFieldsKey];

                    if ((j = $.inArray(fv, showFields)) != -1) {

                        newFields[j] = fields[i];

                    }

                }

                fields = newFields;

            }

            formatlen = formats.length;

            fieldlen = fields.length;

            for (i = 0; i < formatlen; i++) {

                format = formats[i];

                if (typeof format === 'string') {

                    format = new SPFormat(format);

                }

                fclass = format.fclass || 'jqsfield';

                for (j = 0; j < fieldlen; j++) {

                    if (!fields[j].isNull || !options.get('tooltipSkipNull')) {

                        $.extend(fields[j], {

                            prefix: options.get('tooltipPrefix'),

                            suffix: options.get('tooltipSuffix')

                        });

                        text = format.render(fields[j], options.get('tooltipValueLookups'), options);

                        entries.push('<div class="' + fclass + '">' + text + '</div>');

                    }

                }

            }

            if (entries.length) {

                return header + entries.join('\n');

            }

            return '';

        },



        getCurrentRegionFields: function () {},



        calcHighlightColor: function (color, options) {

            var highlightColor = options.get('highlightColor'),

                lighten = options.get('highlightLighten'),

                parse, mult, rgbnew, i;

            if (highlightColor) {

                return highlightColor;

            }

            if (lighten) {

                // extract RGB values

                parse = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color) || /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);

                if (parse) {

                    rgbnew = [];

                    mult = color.length === 4 ? 16 : 1;

                    for (i = 0; i < 3; i++) {

                        rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);

                    }

                    return 'rgb(' + rgbnew.join(',') + ')';

                }



            }

            return color;

        }



    });



    barHighlightMixin = {

        changeHighlight: function (highlight) {

            var currentRegion = this.currentRegion,

                target = this.target,

                shapeids = this.regionShapes[currentRegion],

                newShapes;

            // will be null if the region value was null

            if (shapeids) {

                newShapes = this.renderRegion(currentRegion, highlight);

                if ($.isArray(newShapes) || $.isArray(shapeids)) {

                    target.replaceWithShapes(shapeids, newShapes);

                    this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) {

                        return newShape.id;

                    });

                } else {

                    target.replaceWithShape(shapeids, newShapes);

                    this.regionShapes[currentRegion] = newShapes.id;

                }

            }

        },



        render: function () {

            var values = this.values,

                target = this.target,

                regionShapes = this.regionShapes,

                shapes, ids, i, j;



            if (!this.cls._super.render.call(this)) {

                return;

            }

            for (i = values.length; i--;) {

                shapes = this.renderRegion(i);

                if (shapes) {

                    if ($.isArray(shapes)) {

                        ids = [];

                        for (j = shapes.length; j--;) {

                            shapes[j].append();

                            ids.push(shapes[j].id);

                        }

                        regionShapes[i] = ids;

                    } else {

                        shapes.append();

                        regionShapes[i] = shapes.id; // store just the shapeid

                    }

                } else {

                    // null value

                    regionShapes[i] = null;

                }

            }

            target.render();

        }

    };



