1 2 3 //intialize with var data ={DATATYPE : "vq.models.FlexScrollData", CONTENTS : "test"}; 4 // notifier = function(x,dx) where x = position within scroll bar range, dx = total length of window in scale of scroll bar 5 6 //draw with draw (data ={DATATYPE : "vq.models.FlexScrollData", CONTENTS : "test"} 7 // and options = {plotHeight: xx, plotWidth : xx, vertical_padding: xx, 8 // horizontal_padding : xx , max_position: xx, min_position: xx, 9 // maxRange: xx, minRange: xx, dblclick_notifier : function(x,dx), 10 // fixed_window_width: xx, scale_multiplier : xx, interval : xx, font : "fontname"} 11 //note dblclick_notifer is the last listed option. This can be used to create a "zoom" effect by re-instanstiating the scroll bar with new parameters. 12 13 14 vq.FlexScroll = function(){ 15 vq.Vis.call(this); 16 17 //set option variables to useful values before options are set. 18 this.height(40); // defaults 19 this.width(600); // defaults 20 this.vertical_padding(10); 21 this.horizontal_padding(10); 22 this.max_position(100); 23 this.min_position(0); 24 this.interval(5); 25 this.scale_multiplier(1); 26 this.fixed_window_width(-1); 27 this.callback_always(false); 28 29 }; 30 31 vq.FlexScroll.prototype = pv.extend(vq.Vis); 32 vq.FlexScroll.prototype 33 .property('max_position',Number) 34 .property('min_position',Number) 35 .property('interval', Number) 36 .property('scale_multiplier',Number) 37 .property('callback_always', Boolean) 38 .property('fixed_window_width',Number); 39 40 41 vq.FlexScroll.prototype._setOptionDefaults = function(options) { 42 43 if (options.max_position != null) { this.max_position(options.max_position); } 44 45 if (options.min_position != null) { this.min_position(options.min_position); } 46 47 if (options.plotHeight != null) { this.height(options.plotHeight); } 48 49 if (options.plotWidth != null) { this.width(options.plotWidth); } 50 51 if (options.vertical_padding != null) { this.vertical_padding(options.vertical_padding); } 52 53 if (options.horizontal_padding != null) { this.horizontal_padding(options.horizontal_padding); } 54 55 if (options.interval != null) { this.interval(options.interval); } 56 57 if (options.callback_always != null) this.callback_always = options.callbackAlways; 58 59 if (options.scale_multiplier != null) { this.scale_multiplier(options.scale_multiplier); } 60 61 if (options.fixed_window_width != null) {this.fixed_window_width(options.fixed_window_width); } 62 63 if (options.container) { this.container(options.container); } 64 if (options.notifier) { this.notifier= options.notifier; } 65 if (options.dblclick_notifier) { this.dblclick_notifier = options.dblclick_notifier; } 66 67 }; 68 69 vq.FlexScroll.prototype.set_position = function(x_var,dx_var) { 70 this.window = {x: this.xScale(x_var), dx : this.xScale(dx_var)}; 71 this.render(); 72 }; 73 74 75 vq.FlexScroll.prototype.draw = function(data) { 76 var data_obj = new vq.models.FlexScrollData(data); 77 if(data_obj.isDataReady()) { 78 this._setOptionDefaults(data_obj); 79 this.data = data_obj; 80 this.render(); 81 } 82 }; 83 84 vq.FlexScroll.prototype.render = function() { 85 var that = this; 86 87 var w = this.width(), 88 h2 = this.height(), 89 scrollWidth = this.width() - (2 * this.horizontal_padding()) , 90 focusMap = pv.Scale.linear(0, w).range(this.min_position(),this.max_position()), 91 x=pv.Scale.linear(this.min_position(), this.max_position()).range(0, scrollWidth), 92 isWindowWidthFixed = (this.fixed_window_width() > 0), 93 windowWidth = (isWindowWidthFixed) ? this.fixed_window_width() : this.interval(), 94 maxX = this.max_position(), 95 minX = this.min_position(), 96 halfX = (maxX - minX) / 2, 97 formatter = pv.Format.number(), 98 scale_multiplier = this.scale_multiplier(), 99 scaleX = 2 * halfX / scrollWidth; 100 101 if (this.window == undefined){ 102 this.window = {x:x(this.min_position()), dx:x(this.min_position() + windowWidth) - x(this.min_position()) }; 103 104 } 105 this.xScale = x; 106 107 var notify_dblclick = function(d) { 108 var window = translateAndScale(d); 109 that.dblclick_notifier(window.x, window.dx); 110 }; 111 112 var zoom = function() { 113 var t = this.transform().invert(); 114 var halfX = (maxX - minX) / 2, 115 centerX = maxX - halfX, 116 scaleX = 2 * halfX / (scrollWidth); 117 var start = t.x * scaleX - (halfX) + centerX, 118 end = centerX + (scrollWidth * t.k + t.x) * scaleX - halfX; 119 x.domain(start, end).nice(); 120 focusMap.range(start, end); 121 that.notifier(i.x * scaleX + centerX - halfX, (i.dx) * scaleX); 122 vis.render(); 123 }; 124 125 var translateFocus = function(d) { 126 var endX = pv.min( [ pv.max(x.domain()), maxX ] ); 127 var startX = pv.max( [ pv.min(x.domain()), minX] ); 128 var halfX = (endX - startX) / 2; 129 var scaleX = 2 * halfX / scrollWidth; 130 return { x: Math.round((d.x *scaleX + startX)) , dx : (isWindowWidthFixed) ? Math.round(windowWidth * scaleX) : Math.round((d.dx * scaleX)) }; 131 }; 132 133 var translateAndScale = function(d) { 134 var translated = translateFocus(d); 135 return {x : translated.x * scale_multiplier, dx : translated.dx * scale_multiplier }; 136 }; 137 138 var dragStart = function(d) { 139 this.parent.active(true) ; 140 this.fillStyle("steelblue"); 141 if (that.callbackAlways) { dispatchEvent(d);} 142 vis.render(); 143 }; 144 145 var dragEnd = function(d) { 146 // this.parent.active(false) ; 147 this.fillStyle(undefined); 148 dispatchEvent(d); 149 vis.render(); 150 }; 151 152 var selectStart = function(d) { 153 this.parent.active(true) ; 154 if (that.callbackAlways) { dispatchEvent(d);} 155 vis.render(); 156 }; 157 158 var selectEnd = function(d) { 159 // this.parent.active(false) ; 160 dispatchEvent(d); 161 vis.render(); 162 }; 163 164 var isActive = function(active) { 165 return active; 166 }; 167 168 var dispatchEvent = function(d) { 169 //if we're at the same place as last time, do nothing 170 var window = translateAndScale(d); 171 that.notifier(window.x ,window.dx ); 172 }; 173 174 var vis = new pv.Panel() 175 .width(w) 176 .height(h2) 177 .strokeStyle("#aaa") 178 .canvas(that.container()); 179 180 var scroll = vis.add(pv.Panel) 181 .left(this.horizontal_padding()) 182 .width(scrollWidth ) 183 .top(this.vertical_padding()) 184 .bottom(12) 185 .events("all") 186 .event("mousemove",pv.Behavior.point(Infinity).collapse("y")) 187 .def ("active", false); 188 189 var panel = scroll.add(pv.Panel) 190 .data([that.window]) 191 .fillStyle("white") 192 .events("all") 193 .cursor("crosshair"); 194 var bar = scroll.add(pv.Bar) 195 .data([that.window]) 196 .left(function(d) {return d.x;}) 197 .width(function(d) {return isWindowWidthFixed == true ? x(windowWidth) : d.dx;}) 198 .def ("fillStyle", 'rgba(255, 128, 128, .4)') 199 .cursor("move") 200 .events("painted") 201 .event("mousedown", pv.Behavior.drag()) 202 .event("drag", function(d) { if(that.callbackAlways) {dispatchEvent(d);}vis.render();}) 203 .event("dragstart",dragStart ) 204 .event("dragend", dragEnd ) 205 .event("point", function() { this.parent.active(true); return vis.render(); } ) 206 .event("unpoint", function() { this.parent.active(false); return vis.render(); } ) 207 .event("dblclick", function(d) { notify_dblclick(d) } ); 208 panel.add(pv.Label) 209 .top(2) 210 .left(function(d) { return d.x + (d.dx/2) - 10;} ) 211 .text(function(d) { return translateFocus(d).dx}); 212 213 scroll.add(pv.Rule) 214 .data(function() { return x.ticks(); }) 215 .left(x) 216 .strokeStyle("#eee") 217 .fillStyle("white") 218 .anchor("bottom").add(pv.Label) 219 .text( x.tickFormat ); 220 221 if(!isWindowWidthFixed) { 222 scroll.add(pv.Bar) 223 .data([this.window]) 224 .fillStyle("steelblue") 225 .left(function(d) {return d.x+ d.dx;}) 226 .width(function(d) {return 4;}) 227 .events("painted") 228 .event("mousedown",pv.Behavior.resize("right")) 229 .event("resize",function(d) { if(that.callbackAlways) {dispatchEvent(d);}vis.render();}) 230 .event("resizestart", selectStart) 231 .event("resizeend",selectEnd); 232 var labels = vis.add(pv.Panel) 233 .data([this.window]); 234 labels.anchor("left").add(pv.Label) 235 .visible(function() { return isActive(scroll.active()); }) 236 .text(function(d) { return translateFocus({x: d.x , dx: 0}).x; } ); 237 labels.anchor("right").add(pv.Label) 238 .visible(function() { return isActive(scroll.active()); }) 239 .text(function(d) { return translateFocus({x: d.x+d.dx , dx: 0}).x; } ); 240 241 scroll.add(pv.Bar) 242 .data([this.window]) 243 .fillStyle("steelblue") 244 .left(function(d) {return d.x-4;}) 245 .width(function(d) {return 4;}) 246 .events("painted") 247 .event("mousedown",pv.Behavior.resize("left")) 248 .event("resize",function(d) { if(that.callbackAlways) {dispatchEvent(d);}vis.render(); }) 249 .event("resizeend", selectEnd) 250 .event("resizestart", selectStart); 251 252 } 253 254 vis.render(); 255 256 dispatchEvent(this.window); 257 }; 258 259 260 vq.models.FlexScrollData = function(data) { 261 vq.models.VisData.call(this,data); 262 263 this.setDataModel(); 264 265 if (this.getDataType() == 'vq.models.FlexScrollData') { 266 this._build_data(this.getContents()); 267 } else { 268 console.warn('Unrecognized JSON object. Expected vq.models.FlexPlotData object.'); 269 } 270 }; 271 vq.models.FlexScrollData.prototype = pv.extend(vq.models.VisData); 272 273 vq.models.FlexScrollData.prototype._build_data = function(data) { 274 this._processData(data); 275 276 if (this.interval === undefined) {this.interval = (this.max_position - this.min_position) / 20; } 277 278 this.setDataReady(true); 279 }; 280 281 282 vq.models.FlexScrollData.prototype.setDataModel = function () { 283 this._dataModel = [ 284 {label: 'width', id: 'PLOT.width', cast : Number, defaultValue: 600}, 285 {label: 'height', id: 'PLOT.height', cast : Number, defaultValue: 40}, 286 {label :'container', id:'PLOT.container', optional : true}, 287 {label: 'vertical_padding', id: 'PLOT.vertical_padding', cast : Number, defaultValue: 10}, 288 {label: 'horizontal_padding', id: 'PLOT.horizontal_padding',cast : Number, defaultValue: 10}, 289 {label: 'min_position', id: 'PLOT.min_position', cast : Number, defaultValue: 0}, 290 {label: 'max_position', id: 'PLOT.max_position',cast : Number, defaultValue: 100}, 291 {label: 'scale_multiplier', id: 'PLOT.scale_multiplier',cast : Number, defaultValue : 1}, 292 {label: 'fixed_window_width', id: 'PLOT.fixed_window_width',cast : Number, defaultValue : -1}, 293 {label: 'interval', id: 'PLOT.interval', cast : Number, optional : true}, 294 {label: 'notifier', id: 'notifier', defaultValue : function(a){return null;}}, 295 {label :'callback_always', id:'callback_always', cast : Boolean, defaultValue : true}, 296 {label: 'dblclick_notifier', id: 'dblclick_notifier', defaultValue : function(a){return null;}} 297 298 ]; 299 }; 300 301