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