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