Home | History | Annotate | Download | only in jquery
      1 /*
      2  * jQuery.flot.dashes
      3  *
      4  * options = {
      5  *   series: {
      6  *     dashes: {
      7  *
      8  *       // show
      9  *       // default: false
     10  *       // Whether to show dashes for the series.
     11  *       show: <boolean>,
     12  *
     13  *       // lineWidth
     14  *       // default: 2
     15  *       // The width of the dashed line in pixels.
     16  *       lineWidth: <number>,
     17  *
     18  *       // dashLength
     19  *       // default: 10
     20  *       // Controls the length of the individual dashes and the amount of
     21  *       // space between them.
     22  *       // If this is a number, the dashes and spaces will have that length.
     23  *       // If this is an array, it is read as [ dashLength, spaceLength ]
     24  *       dashLength: <number> or <array[2]>
     25  *     }
     26  *   }
     27  * }
     28  */
     29 (function($){
     30 
     31   function init(plot) {
     32 
     33     plot.hooks.processDatapoints.push(function(plot, series, datapoints) {
     34 
     35       if (!series.dashes.show) return;
     36 
     37       plot.hooks.draw.push(function(plot, ctx) {
     38 
     39         var plotOffset = plot.getPlotOffset(),
     40             axisx = series.xaxis,
     41             axisy = series.yaxis;
     42 
     43         function plotDashes(xoffset, yoffset) {
     44 
     45           var points = datapoints.points,
     46               ps = datapoints.pointsize,
     47               prevx = null,
     48               prevy = null,
     49               dashRemainder = 0,
     50               dashOn = true,
     51               dashOnLength,
     52               dashOffLength;
     53 
     54           if (series.dashes.dashLength[0]) {
     55             dashOnLength = series.dashes.dashLength[0];
     56             if (series.dashes.dashLength[1]) {
     57               dashOffLength = series.dashes.dashLength[1];
     58             } else {
     59               dashOffLength = dashOnLength;
     60             }
     61           } else {
     62             dashOffLength = dashOnLength = series.dashes.dashLength;
     63           }
     64 
     65           ctx.beginPath();
     66 
     67           for (var i = ps; i < points.length; i += ps) {
     68 
     69             var x1 = points[i - ps],
     70                 y1 = points[i - ps + 1],
     71                 x2 = points[i],
     72                 y2 = points[i + 1];
     73 
     74             if (x1 == null || x2 == null) continue;
     75 
     76             // clip with ymin
     77             if (y1 <= y2 && y1 < axisy.min) {
     78               if (y2 < axisy.min) continue;   // line segment is outside
     79               // compute new intersection point
     80               x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
     81               y1 = axisy.min;
     82             } else if (y2 <= y1 && y2 < axisy.min) {
     83               if (y1 < axisy.min) continue;
     84               x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
     85               y2 = axisy.min;
     86             }
     87 
     88             // clip with ymax
     89             if (y1 >= y2 && y1 > axisy.max) {
     90               if (y2 > axisy.max) continue;
     91               x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
     92               y1 = axisy.max;
     93             } else if (y2 >= y1 && y2 > axisy.max) {
     94               if (y1 > axisy.max) continue;
     95               x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
     96               y2 = axisy.max;
     97             }
     98 
     99             // clip with xmin
    100             if (x1 <= x2 && x1 < axisx.min) {
    101               if (x2 < axisx.min) continue;
    102               y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
    103               x1 = axisx.min;
    104             } else if (x2 <= x1 && x2 < axisx.min) {
    105               if (x1 < axisx.min) continue;
    106               y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
    107               x2 = axisx.min;
    108             }
    109 
    110             // clip with xmax
    111             if (x1 >= x2 && x1 > axisx.max) {
    112               if (x2 > axisx.max) continue;
    113               y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
    114               x1 = axisx.max;
    115             } else if (x2 >= x1 && x2 > axisx.max) {
    116               if (x1 > axisx.max) continue;
    117               y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
    118               x2 = axisx.max;
    119             }
    120 
    121             if (x1 != prevx || y1 != prevy) {
    122               ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
    123             }
    124 
    125             var ax1 = axisx.p2c(x1) + xoffset,
    126                 ay1 = axisy.p2c(y1) + yoffset,
    127                 ax2 = axisx.p2c(x2) + xoffset,
    128                 ay2 = axisy.p2c(y2) + yoffset,
    129                 dashOffset;
    130 
    131             function lineSegmentOffset(segmentLength) {
    132 
    133               var c = Math.sqrt(Math.pow(ax2 - ax1, 2) + Math.pow(ay2 - ay1, 2));
    134 
    135               if (c <= segmentLength) {
    136                 return {
    137                   deltaX: ax2 - ax1,
    138                   deltaY: ay2 - ay1,
    139                   distance: c,
    140                   remainder: segmentLength - c
    141                 }
    142               } else {
    143                 var xsign = ax2 > ax1 ? 1 : -1,
    144                     ysign = ay2 > ay1 ? 1 : -1;
    145                 return {
    146                   deltaX: xsign * Math.sqrt(Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
    147                   deltaY: ysign * Math.sqrt(Math.pow(segmentLength, 2) - Math.pow(segmentLength, 2) / (1 + Math.pow((ay2 - ay1)/(ax2 - ax1), 2))),
    148                   distance: segmentLength,
    149                   remainder: 0
    150                 };
    151               }
    152             }
    153             //-end lineSegmentOffset
    154 
    155             do {
    156 
    157               dashOffset = lineSegmentOffset(
    158                   dashRemainder > 0 ? dashRemainder :
    159                     dashOn ? dashOnLength : dashOffLength);
    160 
    161               if (dashOffset.deltaX != 0 || dashOffset.deltaY != 0) {
    162                 if (dashOn) {
    163                   ctx.lineTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
    164                 } else {
    165                   ctx.moveTo(ax1 + dashOffset.deltaX, ay1 + dashOffset.deltaY);
    166                 }
    167               }
    168 
    169               dashOn = !dashOn;
    170               dashRemainder = dashOffset.remainder;
    171               ax1 += dashOffset.deltaX;
    172               ay1 += dashOffset.deltaY;
    173 
    174             } while (dashOffset.distance > 0);
    175 
    176             prevx = x2;
    177             prevy = y2;
    178           }
    179 
    180           ctx.stroke();
    181         }
    182         //-end plotDashes
    183 
    184         ctx.save();
    185         ctx.translate(plotOffset.left, plotOffset.top);
    186         ctx.lineJoin = 'round';
    187 
    188         var lw = series.dashes.lineWidth,
    189             sw = series.shadowSize;
    190 
    191         // FIXME: consider another form of shadow when filling is turned on
    192         if (lw > 0 && sw > 0) {
    193           // draw shadow as a thick and thin line with transparency
    194           ctx.lineWidth = sw;
    195           ctx.strokeStyle = "rgba(0,0,0,0.1)";
    196           // position shadow at angle from the mid of line
    197           var angle = Math.PI/18;
    198           plotDashes(Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2));
    199           ctx.lineWidth = sw/2;
    200           plotDashes(Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4));
    201         }
    202 
    203         ctx.lineWidth = lw;
    204         ctx.strokeStyle = series.color;
    205 
    206         if (lw > 0) {
    207           plotDashes(0, 0);
    208         }
    209 
    210         ctx.restore();
    211 
    212       });
    213       //-end draw hook
    214 
    215     });
    216     //-end processDatapoints hook
    217 
    218   }
    219   //-end init
    220 
    221   $.plot.plugins.push({
    222     init: init,
    223     options: {
    224       series: {
    225         dashes: {
    226           show: false,
    227           lineWidth: 2,
    228           dashLength: 10
    229         }
    230       }
    231     },
    232     name: 'dashes',
    233     version: '0.1'
    234   });
    235 
    236 })(jQuery)
    237 
    238