Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to change the color of plots in jQuery flot without redrawing the whole chart?

Tags:

jquery

flot

I have a chart that I'm drawing using flot. I want to change the colors of the data plots when someone hovers over text. Currently, I am doing this by redrawing the entire chart each time I need to highlight something. This is pretty slow (about 30 milliseconds for a simple chart, as much as 100 for a more complicated chart). Since all I want to do is change the color, is there a faster solution?

like image 538
Sam Lee Avatar asked May 06 '10 22:05

Sam Lee


1 Answers

There are immediate steps you can take to optimize Flot's drawing process. If you don't need points on your graph turn them off, this can reduce the drawing time significantly. You should also turn shadows off, by setting the shadowSize option to 0, to improve speed.

Besides those two tweaks, you'd have to change the flot source code to be able to highlight a series individually. I've created a patch that does exactly that, along with a small plugin that allows you to highlight series automatically or manually. The patch and plugin can be up to three times faster than redrawing the entire graph, it also practically eliminates the time it takes to un-highlight a series, since it uses flot's "overlay" feature.

There's a patch for jquery.flot.js and source code for the highlightSeries plugin below and I've created a demonstration page that lets you see the difference between various settings as well as the patch/plugin combination.

Flot patch

--- .\jquery.flot.js
+++ .\jquery.flot.js
@@ -147,6 +147,7 @@
         plot.setData = setData;
         plot.setupGrid = setupGrid;
         plot.draw = draw;
+        plot.drawSeries = drawSeries;
         plot.getPlaceholder = function() { return placeholder; };
         plot.getCanvas = function() { return canvas; };
         plot.getPlotOffset = function() { return plotOffset; };
@@ -164,6 +165,7 @@
         plot.highlight = highlight;
         plot.unhighlight = unhighlight;
         plot.triggerRedrawOverlay = triggerRedrawOverlay;
+        plot.drawOverlay = drawOverlay;
         plot.pointOffset = function(point) {
             return { left: parseInt(axisSpecToRealAxis(point, "xaxis").p2c(+point.x) + plotOffset.left),
                      top: parseInt(axisSpecToRealAxis(point, "yaxis").p2c(+point.y) + plotOffset.top) };
@@ -1059,7 +1061,7 @@
                 drawGrid();

             for (var i = 0; i < series.length; ++i)
-                drawSeries(series[i]);
+                drawSeries(series[i], ctx);

             executeHooks(hooks.draw, [ctx]);

@@ -1265,16 +1267,16 @@
             placeholder.append(html.join(""));
         }

-        function drawSeries(series) {
+        function drawSeries(series, ctx) {
             if (series.lines.show)
-                drawSeriesLines(series);
+                drawSeriesLines(series, ctx);
             if (series.bars.show)
-                drawSeriesBars(series);
+                drawSeriesBars(series, ctx);
             if (series.points.show)
-                drawSeriesPoints(series);
+                drawSeriesPoints(series, ctx);
         }

-        function drawSeriesLines(series) {
+        function drawSeriesLines(series, ctx) {
             function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
                 var points = datapoints.points,
                     ps = datapoints.pointsize,
@@ -1522,7 +1524,7 @@
             ctx.restore();
         }

-        function drawSeriesPoints(series) {
+        function drawSeriesPoints(series, ctx) {
             function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) {
                 var points = datapoints.points, ps = datapoints.pointsize;

@@ -1675,7 +1677,7 @@
             }
         }

-        function drawSeriesBars(series) {
+        function drawSeriesBars(series, ctx) {
             function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
                 var points = datapoints.points, ps = datapoints.pointsize;

@@ -1942,7 +1944,7 @@

                 if (hi.series.bars.show)
                     drawBarHighlight(hi.series, hi.point);
-                else
+                else if (hi.series.points.show)
                     drawPointHighlight(hi.series, hi.point);
             }
             octx.restore();

highlightSeries plugin

/*
Flot plugin for highlighting series.

    highlightSeries: {
        autoHighlight: true (default) or false
        , color: color
    }

If "autoHighlight" is true (the default) and the plot's "hoverable" setting is true
series are highlighted when the mouse hovers near an item.
"color" is the color of the highlighted series (default is "red").

The plugin also adds two public methods that allow you to highlight and
unhighlight a series manually by specifying a series by label, index or object.

    - highlightSeries(series, [color])

    - unHighlightSeries(series)
*/

(function ($) {
    var log = (function () {
        var out = $("#out");
        return function () {
            if (!arguments) { return; }
            var msg = Array.prototype.slice.call(arguments).join(" ");
            if (!out.length) {
                out = $("#out");
            }
            if (out.length) {
                out.text(msg);
            }
        };
    })();

    var options = {
        highlightSeries: {
            autoHighlight: true
            , color: "black"
            , _optimized: true
            , _debug: false
        }
    };

    function init(plot) {
        var highlightedSeries = {};
        var originalColors = {};

        function highlightSeries(series, color) {
            var
                seriesAndIndex = getSeriesAndIndex(series)
                , options = plot.getOptions().highlightSeries;

            series = seriesAndIndex[1];

            highlightedSeries[seriesAndIndex[0]] = series;
            originalColors[seriesAndIndex[0]] = series.color;

            series.color = color || options.color;

            if (options._debug) { var start = new Date(); }
            if (options._optimized) {
                if (plot.drawOverlay && options._debug) {
                    plot.drawOverlay();
                }
                else {
                    plot.triggerRedrawOverlay();
                }
            }
            else {
                plot.draw();
            }
            if (options._debug) { 
                log("Time taken to highlight:", (new Date()).getTime() - start.getTime(), "ms");
            }
        };
        plot.highlightSeries = highlightSeries;

        function unHighlightSeries(series) {
            var
                seriesAndIndex = getSeriesAndIndex(series)
                , options = plot.getOptions().highlightSeries;

            seriesAndIndex[1].color = originalColors[seriesAndIndex[0]];

            if (options._debug) { var start = new Date(); }
            if (options._optimized) {
                delete highlightedSeries[seriesAndIndex[0]];
                if (plot.drawOverlay && options._debug) {
                    plot.drawOverlay();
                }
                else {
                    plot.triggerRedrawOverlay();
                }
            }
            else {
                plot.draw();
            }
            if (options._debug) { 
                log("Time taken to un-highlight:", (new Date()).getTime() - start.getTime(), "ms");
            }
        };
        plot.unHighlightSeries = unHighlightSeries;

        plot.hooks.bindEvents.push(function (plot, eventHolder) {
            if (!plot.getOptions().highlightSeries.autoHighlight) {
                return;
            }

            var lastHighlighted = null;
            plot.getPlaceholder().bind("plothover", function (evt, pos, item) {
                if (item && lastHighlighted !== item.series) {
                    for(var seriesIndex in highlightedSeries) {
                        delete highlightedSeries[seriesIndex];
                    }
                    if (lastHighlighted) {
                        unHighlightSeries(lastHighlighted);
                    }
                    lastHighlighted = item.series;
                    highlightSeries(item.series);
                }
                else if (!item && lastHighlighted) {
                    unHighlightSeries(lastHighlighted);
                    lastHighlighted = null;
                }
            });
        });

        function getSeriesAndIndex(series) {
            var allPlotSeries = plot.getData();
            if (typeof series == "number") {
                return [series, allPlotSeries[series]];
            }
            else {
                for (var ii = 0; ii < allPlotSeries.length; ii++) {
                    var plotSeries = allPlotSeries[ii];
                    if (
                        plotSeries === series
                        || plotSeries.label === series
                        || plotSeries.label === series.label
                    ) {
                        return [ii, plotSeries];
                    }
                }
            }
        }

        plot.hooks.drawOverlay.push(function (plot, ctx) {
            for(var seriesIndex in highlightedSeries) {
                plot.drawSeries(highlightedSeries[seriesIndex], ctx);
            }
        });
    }

    $.plot.plugins.push({
        init: init,
        options: options,
        name: "highlightSeries",
        version: "1.0"
    });
})(jQuery);
like image 178
brianpeiris Avatar answered Oct 07 '22 13:10

brianpeiris