1 /* 2 * JBoss, Home of Professional Open Source 3 * Copyright ${year}, Red Hat, Inc. and individual contributors 4 * by the @authors tag. See the copyright.txt in the distribution for a 5 * full listing of individual contributors. 6 * 7 * This is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU Lesser General Public License as 9 * published by the Free Software Foundation; either version 2.1 of 10 * the License, or (at your option) any later version. 11 * 12 * This software is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this software; if not, write to the Free 19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 21 */ 22 23 (function ($, rf) { 24 var defaultOptions = { 25 26 /** 27 * Specifies the type of chart. 28 * According to this property and xtypex ytype, proper default options corresponding to chart type 29 * are applied and data are transformed to the format expected by flot. 30 * Following values are supported: `line`,`bar`,`pie` 31 * Options is not required. If it is not used the options and data format remains the same as flot uses by default. 32 * @property charttype 33 * @default null 34 */ 35 charttype: '', 36 37 /** 38 * Specify the data type of values plotted on x-axis 39 * Following options are supported: number,string,date 40 * @property xtype 41 * @default null 42 */ 43 xtype: '', 44 45 /** 46 * Specify the data type of values plotted on x-axis 47 * Following options are supported: number,string,date 48 * @property xtype 49 * @default null 50 */ 51 ytype: '', 52 53 /** 54 * Allows to zoom the chart. Supported only when line chart is used. Requires charttype to be set. 55 * @property zoom 56 * @default false 57 */ 58 zoom: false, 59 60 61 62 /** 63 * Options customizing the chart grid. 64 * @type Object 65 * @default {clickable:true, hoverable:true} 66 * @property grid 67 * @property clickable {boolean} 68 * Grid accepts click events. 69 * @property hoverable {boolean} 70 * Grid fires mouseover event. Necessary for tooltip to work. 71 * 72 */ 73 grid: { 74 clickable: true, 75 hoverable: true 76 }, 77 78 /** 79 * Turns on tooltip (text shown when mouse points to a part of a chart) 80 * @property tooltip 81 * @type boolean 82 * @default true 83 */ 84 tooltip: true, 85 86 /** 87 * Customizes the tooltip. 88 * @type Object 89 * @default { content: '%s [%x,%y]'} 90 * @property tooltipOpts 91 * @property content {String} 92 * Specify the tooltip format. Use %s for series label, %x for X values, %y for Y value 93 * @property defaultTheme 94 */ 95 tooltipOpts: { 96 content: '%s [%x,%y]', 97 shifts: { 98 x: 20, 99 y: 0 100 }, 101 defaultTheme: false 102 }, 103 104 /** 105 * Legend properties 106 * @type Object 107 * @default {postion:'ne', sorted: 'ascending'} 108 * @property legend 109 * @property position {String} 110 * Defines the placement of the legend in the grid. One of ne,nw,se,sw 111 * @property sorted {String} 112 * Defines the order of labels in the legend. One of ascending,descending,false. 113 */ 114 legend: { 115 postion:'ne', 116 sorted: 'ascending' 117 }, 118 119 /** 120 * Customizes the horizontal axis 121 * @type Object 122 * @default {min: null, max: null,autoscaleMargin: null, axisLabel: ''} 123 * @property xaxis 124 * @property min {Number} 125 * Minimal value shown on axis 126 * @property max {Number} 127 * Maximal values show on axis 128 * @property autoscaleMargin {Number} 129 * It's the fraction of margin that the scaling algorithm will add 130 * to avoid that the outermost points ends up on the grid border 131 * @property axisLabel {String} 132 * Axis description 133 */ 134 xaxis:{ 135 min: null, 136 max: null, 137 autoscaleMargin: null, 138 axisLabel: '' 139 }, 140 141 /** 142 * Customizes the vertical axis 143 * @type Object 144 * @default {min: null, max: null,autoscaleMargin: 0.2,axisLabel: ''} 145 * @property yaxis 146 * @property min {Number} 147 * Minimal value shown on axis 148 * @property max {Number} 149 * Maximal values show on axis 150 * @property autoscaleMargin {Number} 151 * It's the fraction of margin that the scaling algorithm will add to 152 * avoid that the outermost points ends up on the grid border. 153 * @property axisLabel {String} 154 * Axis description 155 */ 156 yaxis:{ 157 min: null, 158 max: null, 159 autoscaleMargin: 0.2, 160 axisLabel: '' 161 }, 162 163 164 165 166 /** 167 * Data to be plotted. The format is the same used by flot. The format may differ if the charttype 168 * option is set to pie or bar. 169 * @property data 170 * @default [] 171 */ 172 data:[] 173 174 }; 175 176 var pieDefaults = { 177 series: { 178 pie: { 179 show: true 180 } 181 }, 182 tooltipOpts: { 183 content: ' %p.0%, %s' 184 } 185 }; 186 var _plotClickServerSide = function (event, clientId) { 187 var params = {}; 188 params[clientId + 'name'] = "plotclick"; 189 params[clientId + 'seriesIndex'] = event.data.seriesIndex; 190 params[clientId + 'dataIndex'] = event.data.dataIndex; 191 params[clientId + 'x'] = event.data.x; 192 params[clientId + 'y'] = event.data.y; 193 194 rf.ajax(clientId, event, { 195 parameters: params, 196 incId: 1 197 }); 198 199 }; 200 201 202 rf.ui = rf.ui || {}; 203 204 rf.ui.Chart = rf.BaseComponent.extendClass({ 205 // class name 206 name:"Chart", 207 208 init : function (componentId, options) { 209 $super.constructor.call(this, componentId, options); 210 211 this.namespace = this.namespace || "." + RichFaces.Event.createNamespace(this.name, this.id); 212 this.attachToDom(); 213 this.options = $.extend(true,{},defaultOptions,options); 214 215 this.element = $(document.getElementById(componentId)); 216 this.chartElement = this.element.find(".chart"); 217 218 if (this.options.charttype === 'pie') { 219 this.options = $.extend(true,{},this.options,pieDefaults); 220 //transform data from 221 // [[{data:1, label:"label1"},{data:2,label:"label2"},...]] 222 //to 223 // [{data:1, label:"label1"},{data:2,label:"label2"},...] 224 this.options.data = this.options.data[0]; //pie chart data should not be in a collection 225 } 226 else if (this.options.charttype === 'bar') { 227 if (this.options.xtype === 'string') { 228 //category bar chart 229 /*transformation data from 230 [ 231 { data: {"Key1":11, "Key2":21, "Key3": 31}, 232 label: "label1", 233 bars: {show:true} 234 }, 235 236 { data: {"Key1":12,"Key2":22, "Key3": 32}, 237 label: "label2", 238 bars: {show:true} 239 }, 240 241 { data: {"Key1":13,"Key2":23,"Key3":33}, 242 label: "label3", 243 bars: {show:true} 244 }, 245 { data: {"Key1":14,"Key2":24,"Key4":44}, 246 label: "label4", 247 bars: {show:true} 248 } 249 ] 250 251 to the form: 252 253 254 [ 255 { 256 data: [ 257 [0,11],[1,21],[2, 31] 258 ], 259 label: "label1", 260 bars: { 261 show:true, 262 order=0 263 } 264 }, 265 { 266 data: [ 267 [0,12],[1,22],[2, 32] 268 ], 269 label: "label2", 270 bars: { 271 show:true, 272 order=1 273 } 274 }, 275 { 276 data: [ 277 [0,13],[1,23],[2, 33] 278 ], 279 label: "label3", 280 bars: { 281 show:true, 282 order=2 283 } 284 }, 285 { 286 data: [ 287 [0,14],[1,24],[2, 0] 288 ], 289 label: "label4", 290 bars: { 291 show:true, 292 order=3 293 } 294 }, 295 ] 296 297 and create array ticks 298 299 ticks = [[0,"Key1"],[1,"Key2"],[2,"Key3"]] and assign this array to the this.options.xaxis 300 301 302 according to the following rules: 303 data: 304 - select all keys used in the first series (set) 305 - in the following series find the keys selected in first 306 - missing consider to be 0 (ie. "Key3" in fourth series) 307 - ignore additional (ie. "Key5" in fourth series) 308 keys: 309 COUNTER=0 310 - for each key used in the first series 311 add couple [COUNTER, KEY_LABEL] to ticks 312 COUNTER++ 313 order: 314 order of the series 315 */ 316 317 318 var ticks = [], keys = [], first = true, order = 0; 319 320 /** 321 * Labels are mapped to numbers 0,1,2... 322 * If a chart consists of multiple series values(from all series) with the same label 323 * are mapped to the (one number 0,1,2..) space of width 1. 324 * barWidth takes care of bars to not overlap 325 * @type {number} 326 */ 327 var barWidth = 1 / (this.options.data.length + 1); 328 329 for (var index in this.options.data) {//loop through data series 330 var convertedData = []; 331 var cnt = 0; 332 if (first) {//the first series determine which keys (x-values are plotted) 333 for (var key in this.options.data[index].data) { 334 ticks.push([cnt, key]); 335 keys.push(key); 336 convertedData.push([cnt, this.options.data[index].data[key]]); 337 cnt++; 338 } 339 first = false; 340 } 341 else { 342 for (var k in keys) { //select values for first series keys only 343 var loopKey = keys[k]; 344 if (this.options.data[index].data[loopKey]) { 345 convertedData.push([cnt,this.options.data[index].data[loopKey]]); 346 } 347 else { 348 convertedData.push([cnt, 0]); 349 } 350 cnt++; 351 } 352 } 353 this.options.data[index].data = convertedData; 354 var bars = { 355 order: order, 356 show: true 357 }; 358 this.options.data[index].bars = bars; 359 order++; 360 361 } 362 363 //add ticks to the options 364 this.options.xaxis = $.extend({},this.options.xaxis, { 365 ticks: ticks, 366 tickLength: 0, 367 //workaround to show display proper x-value on category bars 368 tickFormatter: function (value, axis) { 369 return axis.ticks[value].label; 370 } 371 }); 372 373 374 //options for all bars 375 this.options.bars = $.extend({},this.options.bars, { 376 show: true, 377 barWidth: barWidth, 378 align: 'center' 379 }); 380 } 381 } 382 else if (options.charttype === 'line') { 383 if (options.zoom) { 384 this.options.selection = {mode: 'xy'}; 385 } 386 if (this.options.xtype === 'date') { 387 this.options = $.extend({},this.options, dateDefaults); 388 if (this.options.xaxis.format) { 389 this.options.xaxis.timeformat = this.options.xaxis.format; 390 } 391 } 392 } 393 394 this.plot = $.plot(this.chartElement,this.options.data,this.options); 395 //this.options=options; 396 this.__bindEventHandlers(this.chartElement,this.options); 397 }, 398 399 /***************************** Public Methods ****************************************************************/ 400 401 /** 402 * Returns chart object 403 * 404 * @method getPlotObject 405 */ 406 getPlotObject: function () { 407 return this.plot; 408 }, 409 410 /** 411 * Highlights the point in chart selected by seriesIndex or point index. Does not work for pie charts. 412 * @param seriesIndex {int} 413 * @param pointIndex {int} 414 * @method highlight 415 */ 416 highlight: function(seriesIndex,pointIndex){ 417 this.plot.highlight(seriesIndex,pointIndex); 418 }, 419 /** 420 * Removes highlighting of point. If method is called without parameters it unhighlights all points. 421 * @param seriesIndex {int} 422 * @param pointIndex {int} 423 * @method unghighlight 424 */ 425 unhighlight: function(seriesIndex,pointIndex){ 426 this.plot.unhighlight(seriesIndex,pointIndex); 427 }, 428 429 /***************************** Private Methods ********************************************************/ 430 __bindEventHandlers:function(element,options){ 431 432 this.chartElement.on('plotclick', this._getPlotClickHandler(this.options, this.chartElement, _plotClickServerSide)); 433 this.chartElement.on('plothover', this._getPlotHoverHandler(this.options, this.chartElement)); 434 if (this.options.handlers && this.options.handlers.onmouseout) { 435 this.chartElement.on('mouseout', this.options.handlers.onmouseout); 436 } 437 438 if (this.options.zoom) { 439 this.chartElement.on('plotselected', $.proxy(this._zoomFunction, this)); 440 } 441 442 }, 443 //function handles plotclick event. it calls server-side, client-side and particular series handlers if set. 444 _getPlotClickHandler: function (options, element, serverSideHandler) { 445 446 var clickHandler = options.handlers['onplotclick']; 447 var particularClickHandlers= options.particularSeriesHandlers['onplotclick']; 448 var clientId = this.element.attr('id'); 449 return function (event, mouse, item) { 450 if (item !== null) { 451 //point in a chart clicked 452 event.data = { 453 seriesIndex: item.seriesIndex, 454 dataIndex: item.dataIndex, 455 x: item.datapoint[0], 456 y: item.datapoint[1], 457 item: item 458 }; 459 if(options.charttype=="pie"){ 460 event.data.x = options.data[item.seriesIndex].label; 461 event.data.y = item.datapoint[1][0][1]; 462 } 463 else if(options.charttype=="bar" && options.xtype=="string"){ 464 event.data.x = options.xaxis.ticks[item.dataIndex][1]; 465 466 } 467 468 //sent request only if a server-side listener attached 469 if (options.serverSideListener) { 470 //server-side 471 if (serverSideHandler) { 472 serverSideHandler(event, clientId); 473 } 474 } 475 476 //client-side 477 if (clickHandler) { 478 clickHandler.call(element, event); 479 } 480 //client-side particular series handler 481 if (particularClickHandlers[event.data.seriesIndex]) { 482 particularClickHandlers[event.data.seriesIndex].call(element, event); 483 } 484 } 485 }; 486 }, 487 488 //function handles plothover event. it calls client-side and particular series handlers if set. 489 _getPlotHoverHandler: function (options, element) { 490 var hoverHandler = options.handlers['onplothover']; 491 var particularHoverHandlers = options.particularSeriesHandlers['onplothover']; 492 493 return function (event, mouse, item) { 494 if (item !== null) { 495 //point in a chart clicked 496 event.data = { 497 seriesIndex: item.seriesIndex, 498 dataIndex: item.dataIndex, 499 x: item.datapoint[0], 500 y: item.datapoint[1], 501 item: item 502 }; 503 504 //client-side 505 if (hoverHandler) { 506 hoverHandler.call(element, event); 507 } 508 509 //client-side particular series handler 510 if (particularHoverHandlers[event.data.seriesIndex]) { 511 particularHoverHandlers[event.data.seriesIndex].call(element, event); 512 } 513 } 514 }; 515 }, 516 517 _zoomFunction: function (event, ranges) { 518 var plot = this.getPlotObject(); 519 $.each(plot.getXAxes(), function(_, axis) { 520 var opts = axis.options; 521 opts.min = ranges.xaxis.from; 522 opts.max = ranges.xaxis.to; 523 }); 524 plot.setupGrid(); 525 plot.draw(); 526 plot.clearSelection(); 527 }, 528 529 resetZoom: function () { 530 this.plot = $.plot(this.chartElement,this.options.data,this.options); 531 }, 532 533 destroy: function () { 534 rf.Event.unbindById(this.id, "." + this.namespace); 535 $super.destroy.call(this); 536 } 537 }); 538 539 /