1 vq.ScatterPlot = function() {
  2     vq.Vis.call(this);
  3     // private variables
  4 
  5     this.height(300);     // defaults
  6     this.width(700);     // defaults
  7     this.vertical_padding(20);
  8     this.horizontal_padding(30);
  9     this.selectedProbesetId('');
 10 
 11 };
 12 vq.ScatterPlot.prototype = pv.extend(vq.Vis);
 13 
 14 vq.ScatterPlot.prototype
 15         .property('selectedProbesetId');
 16 
 17 // sets default variables based on the options
 18 vq.ScatterPlot.prototype._setOptionDefaults = function(options) {
 19     // PUBLIC OPTIONS
 20     // padding
 21     if (options.selectedProbesetId) {
 22         this._selectedProbesetId = options.selectedProbesetId;
 23     }
 24 
 25     if (options.height != null) {
 26         this.height(options.height);
 27     }
 28 
 29     if (options.width != null) {
 30         this.width(options.width);
 31     }
 32 
 33     if (options.container != null) {
 34         this.container(options.container);
 35     }
 36 
 37     if (options.vertical_padding != null) {
 38         this.vertical_padding(options.vertical_padding);
 39     }
 40 
 41     if (options.horizontal_padding != null) {
 42         this.horizontal_padding(options.horizontal_padding);
 43     }
 44 };
 45 
 46 vq.ScatterPlot.prototype.onProbesetSelect = function(probesetId) {
 47     this.selectedProbesetId = probesetId;
 48 };
 49 
 50 
 51 vq.ScatterPlot.prototype.setRegression = function(obj) {
 52     this.data._regression = obj || 'none';
 53     this._render();
 54 };
 55 
 56 vq.ScatterPlot.prototype.draw = function(data) {
 57       this.data = new vq.models.ScatterPlotData(data);
 58 
 59     if (!this.data.isDataReady()) { return;}
 60 
 61         this._setOptionDefaults(this.data);
 62         this._visHeight = this.height() - ( 2 * this.vertical_padding());
 63         this._visWidth = this.width() - ( 2 * this.horizontal_padding());
 64 
 65 
 66     this._render();
 67 };
 68 
 69 vq.ScatterPlot.prototype._render = function() {
 70     var that = this;
 71     var div = this.container();
 72 
 73     function trans() {
 74         var t = this.transform().invert();
 75         var w = vis.width();
 76         var h = vis.height();
 77         var halfX = (showMaxX - showMinX) / 2, halfY = (showMaxY - showMinY) / 2,
 78                 centerX = showMaxX - halfX,  centerY = showMaxY - halfY,
 79                 scaleX = 2 * halfX / (w), scaleY = 2 * halfY / h;
 80 
 81         xScale.domain(t.x * scaleX - (halfX) + centerX, centerX + (w * t.k + t.x) * scaleX - halfX);
 82         yScale.domain(-1 * ( h * t.k + (t.y)) * scaleY + halfY + centerY, -1 * ( t.y) * scaleY + halfY + centerY);
 83 
 84         vis.render();
 85     }
 86 
 87         var dataObj = this.data;
 88 
 89         var x = dataObj.COLUMNID.x;
 90         var y = dataObj.COLUMNID.y;
 91         var value = dataObj.COLUMNID.value;
 92 
 93         var data_array = dataObj.data;
 94         var minX = data_array.reduce(function(previous, current) {
 95             return (current[x] != null) && current[x] < previous ? current[x] : previous;
 96         }, 999999);
 97         var maxX = data_array.reduce(function(previous, current) {
 98             return (current[x] != null) && current[x] > previous ? current[x] : previous;
 99         }, -999999);
100         var minY = data_array.reduce(function(previous, current) {
101             return (current[y] != null) && current[y] < previous ? current[y] : previous;
102         }, 999999);
103         var maxY = data_array.reduce(function(previous, current) {
104             return (current[y] != null) && current[y] > previous ? current[y] : previous;
105         }, -999999);
106 
107         //expand plot around highest/lowest values
108         var showMinX = minX - (Math.abs(maxX - minX) * 0.03);
109         var showMaxX = maxX + (Math.abs(maxX - minX) * 0.03);
110         var showMinY = minY - (Math.abs(maxY - minY) * 0.03);
111         var showMaxY = maxY + (Math.abs(maxY - minY) * 0.03);
112 
113         //start protovis code
114         var xScale = pv.Scale.linear(showMinX, showMaxX).range(0, this.width());
115         var yScale = pv.Scale.linear(showMinY, showMaxY).range(0, this.height());
116 
117         //regression line!
118         var regress = dataObj._regression;
119         if (regress=='linear') {
120             var valid_data = data_array.filter(function(d, e, f) {
121                 return (d[y] && d[x]);
122             }),
123                     sum_x = pv.sum(valid_data, function(d) {
124                         return d[x];
125                     }),
126                     sum_y = pv.sum(valid_data, function(d) {
127                         return d[y];
128                     }),
129                     sum_x2 = pv.sum(valid_data, function(d) {
130                         return d[x] * d[x];
131                     }),
132                 //sum_y2 = pv.sum(valid_data, function(d) { return d[y] * d[y]; }),
133                     sum_xy = pv.sum(valid_data, function(d) {
134                         return d[x] * d[y];
135                     }),
136                     slope = ((valid_data.length * sum_xy) - (sum_x * sum_y)) / ((valid_data.length * sum_x2) - (sum_x * sum_x));
137 
138             var intercept = (sum_y - slope * sum_x) / valid_data.length;
139         }
140         var line_minX = showMinX * 0.95;
141         var line_maxX = showMaxX * 1.05;
142         var line_maxY = slope * line_maxX + intercept;
143         var line_minY = slope * line_minX + intercept;
144 
145         var lineArray = pv.Scale.linear(line_minX, line_maxX).range(line_minY, line_maxY);
146 
147         //identify selected Probeset, if passed in.
148         var selectedProbesetId;
149 
150         if (this._selectedProbesetId) {
151             selectedProbesetId = this._selectedProbesetId;
152         }
153 
154         var vis = new pv.Panel()
155                 .width(this.width())
156                 .height(this.height())
157                 .bottom(this.vertical_padding())
158                 .left(this.horizontal_padding())
159                 .right(this.horizontal_padding())
160                 .top(this.vertical_padding())
161                 .strokeStyle("#aaa")
162                 .events("all")
163                 .event("mousemove", pv.Behavior.point())
164                 .canvas(div);
165 
166         //y-axis ticks
167         vis.add(pv.Rule)
168                 .data(function() {
169                     return yScale.ticks()
170                 })
171                 .bottom(yScale)
172                 .strokeStyle(function(d) {
173                     return d ? "#ccc" : "#999"
174                 })
175                 .anchor("left").add(pv.Label)
176                 .text(yScale.tickFormat);
177 
178         //y-axis label
179         vis.add(pv.Label)
180                 .text(dataObj.COLUMNLABEL.y)
181                 .font(that.font)
182                 .textAlign("center")
183                 .left(-24)
184                 .bottom(this.height() / 2)
185                 .textAngle(-1 * Math.PI / 2);
186 
187         //x-axis ticks
188         vis.add(pv.Rule)
189                 .data(function() {
190                     return xScale.ticks()
191                 })
192                 .left(xScale)
193                 .strokeStyle(function(d) {
194                     return d ? "#ccc" : "#999"
195                 })
196                 .anchor("bottom").add(pv.Label)
197                 .text(xScale.tickFormat);
198 
199         //x-axis label
200         vis.add(pv.Label)
201                 .text(dataObj.COLUMNLABEL.x)
202                 .font(that.font)
203                 .textAlign("center")
204                 .bottom(-30)
205                 .left(this.width() / 2);
206 
207         var panel = vis.add(pv.Panel)
208                 .events('all')
209                 .overflow("hidden");
210 
211         if (!isNaN(slope) && regress =='linear') {
212             panel.add(pv.Line)
213                     .data([line_minX, line_maxX])
214                     .left(function(d) {
215                         return xScale(d);
216                     })
217                     .bottom(function(d) {
218                         return yScale(lineArray(d));
219                     })
220                     .strokeStyle("#0b0");
221         }
222 
223         var strokeStyle = function(data) {
224             return pv.color(dataObj._strokeStyle(data));
225         };
226         var fillStyle = function(data) {
227             return pv.color(dataObj._fillStyle(data));
228         };
229         panel.add(pv.Dot)
230                 .def("active", -1)
231                 .data(data_array)
232                 .left(function(d) {
233                     return xScale(d[x]);
234                 })
235                 .bottom(function(d) {
236                     return yScale(d[y])
237                 })
238                 .strokeStyle(strokeStyle)
239                 .fillStyle(fillStyle)
240                 .radius(dataObj._radius)
241                 .shape(dataObj._shape)
242                 .event("point", function() {
243                     return this.active(this.index).parent;
244                 })
245                 .event("unpoint", function() {
246                     return this.active(-1).parent;
247                 })
248                 .event('click', dataObj._notifier)
249                 .anchor("right").add(pv.Label)
250                 .visible(function() {
251                     return this.anchorTarget().active() == this.index;
252                 })
253                 .text(function(d) {
254                     return dataObj.COLUMNLABEL.value + " " + d[value];
255                 });
256 
257 
258         /* Use an invisible panel to capture pan & zoom events. */
259         vis.add(pv.Panel)
260                 .left(0)
261                 .bottom(0)
262                 .events("all")
263                 .event("mousedown", pv.Behavior.pan())
264                 .event("mousewheel", pv.Behavior.zoom())
265                 .event("pan", trans)
266                 .event("zoom", trans);
267 
268         /** Update the x- and y-scale domains per the new transform. */
269 
270         vis.render();
271 
272 };
273 
274 
275 vq.models.ScatterPlotData = function(data) {
276     vq.models.VisData.call(this, data);
277     this.setDataModel();
278     if (this.getDataType() == 'vq.models.ScatterPlotData') {
279         this._build_data(this.getContents());
280     } else {
281         console.warn('Unrecognized JSON object.  Expected vq.models.ScatterPlotData object.');
282     }
283 };
284 vq.models.ScatterPlotData.prototype = pv.extend(vq.models.VisData);
285 
286 
287 vq.models.ScatterPlotData.prototype.setDataModel = function () {
288     this._dataModel = [
289         {label: 'width', id: 'PLOT.width', cast : Number, defaultValue: 400},
290         {label: 'height', id: 'PLOT.height', cast : Number, defaultValue: 300},
291         {label : 'container', id:'PLOT.container', optional : true},
292         {label:  'vertical_padding', id: 'PLOT.vertical_padding', cast : Number, defaultValue: 20},
293         {label:  'horizontal_padding', id: 'PLOT.horizontal_padding',cast : Number,  defaultValue:30},
294         {label : 'data', id: 'data_array', defaultValue : [] },
295         {label : 'COLUMNID.x', id: 'xcolumnid',cast : String, defaultValue : 'X'},
296         {label : 'COLUMNID.y', id: 'ycolumnid',cast : String, defaultValue : 'Y'},
297         {label : 'COLUMNID.value', id: 'valuecolumnid',cast : String, defaultValue : 'VALUE'},
298         {label : 'COLUMNLABEL.x', id: 'xcolumnlabel',cast : String, defaultValue : ''},
299         {label : 'COLUMNLABEL.y', id: 'ycolumnlabel',cast : String, defaultValue : ''},
300         {label : 'COLUMNLABEL.value', id: 'valuecolumnlabel',cast : String, defaultValue : ''},
301         {label : 'COLUMNLABEL.value', id: 'valuecolumnlabel',cast : String, defaultValue : ''},
302         {label : 'tooltipItems', id: 'tooltip_items', defaultValue : {
303             X : 'X', Y : 'Y', Value : 'VALUE'            }  },
304         {label : '_fillStyle', id: 'fill_style',cast :vq.utils.VisUtils.wrapProperty,
305             defaultValue : function() {
306                 return pv.color('steelblue').alpha(0.2);
307             }},
308         {label : '_strokeStyle', id: 'stroke_style',
309             cast :vq.utils.VisUtils.wrapProperty, defaultValue : function() {
310             return 'steelblue';
311         }},
312         {label : '_radius', id: 'radius',cast :vq.utils.VisUtils.wrapProperty, defaultValue : function() {
313             return 2;
314         }},
315         {label : '_shape', id: 'shape',cast : vq.utils.VisUtils.wrapProperty, defaultValue : function() {
316             return 'dot';
317         }},
318         {label : '_regression', id: 'regression',cast :String, defaultValue : 'none'},
319         {label : '_notifier', id: 'notifier', cast : Function, defaultValue : function() {
320             return null;
321         }}
322     ];
323 };
324 
325 vq.models.ScatterPlotData.prototype._build_data = function(data) {
326     this._processData(data);
327 
328     if (this.COLUMNLABEL.x == '') this.COLUMNLABEL.x = this.COLUMNID.x;
329     if (this.COLUMNLABEL.y == '') this.COLUMNLABEL.y = this.COLUMNID.y;
330     if (this.COLUMNLABEL.value == '') this.COLUMNLABEL.value = this.COLUMNID.value;
331 
332     if (this.data.length > 0) this.setDataReady(true);
333 };
334