/timeplot/scripts/geometry.js
JavaScript | 879 lines | 614 code | 105 blank | 160 comment | 167 complexity | d5f605f59fd9c9b404ee028e0ffd8a0d MD5 | raw file
1/** 2 * Geometries 3 * 4 * @fileOverview Geometries 5 * @name Geometries 6 */ 7 8/** 9 * This is the constructor for the default value geometry. 10 * A value geometry is what regulates mapping of the plot values to the screen y coordinate. 11 * If two plots share the same value geometry, they will be drawn using the same scale. 12 * If "min" and "max" parameters are not set, the geometry will stretch itself automatically 13 * so that the entire plot will be drawn without overflowing. The stretching happens also 14 * when a geometry is shared between multiple plots, the one with the biggest range will 15 * win over the others. 16 * 17 * @constructor 18 */ 19Timeplot.DefaultValueGeometry = function(params) { 20 if (!params) params = {}; 21 this._id = ("id" in params) ? params.id : "g" + Math.round(Math.random() * 1000000); 22 this._axisColor = ("axisColor" in params) ? ((typeof params.axisColor == "string") ? new Timeplot.Color(params.axisColor) : params.axisColor) : new Timeplot.Color("#606060"), 23 this._gridColor = ("gridColor" in params) ? ((typeof params.gridColor == "string") ? new Timeplot.Color(params.gridColor) : params.gridColor) : null, 24 this._gridLineWidth = ("gridLineWidth" in params) ? params.gridLineWidth : 0.5; 25 this._axisLabelsPlacement = ("axisLabelsPlacement" in params) ? params.axisLabelsPlacement : "right"; 26 this._gridSpacing = ("gridSpacing" in params) ? params.gridStep : 50; 27 this._gridType = ("gridType" in params) ? params.gridType : "short"; 28 this._gridShortSize = ("gridShortSize" in params) ? params.gridShortSize : 10; 29 this._minValue = ("min" in params) ? params.min : null; 30 this._maxValue = ("max" in params) ? params.max : null; 31 this._linMap = { 32 direct: function(v) { 33 return v; 34 }, 35 inverse: function(y) { 36 return y; 37 } 38 } 39 this._map = this._linMap; 40 this._labels = []; 41 this._grid = []; 42} 43 44Timeplot.DefaultValueGeometry.prototype = { 45 46 /** 47 * Since geometries can be reused across timeplots, we need to call this function 48 * before we can paint using this geometry. 49 */ 50 setTimeplot: function(timeplot) { 51 this._timeplot = timeplot; 52 this._canvas = timeplot.getCanvas(); 53 this.reset(); 54 }, 55 56 /** 57 * Called by all the plot layers this geometry is associated with 58 * to update the value range. Unless min/max values are specified 59 * in the parameters, the biggest value range will be used. 60 */ 61 setRange: function(range) { 62 if ((this._minValue == null) || ((this._minValue != null) && (range.min < this._minValue))) { 63 this._minValue = range.min; 64 } 65 if ((this._maxValue == null) || ((this._maxValue != null) && (range.max * 1.05 > this._maxValue))) { 66 this._maxValue = range.max * 1.05; // get a little more head room to avoid hitting the ceiling 67 } 68 69 this._updateMappedValues(); 70 71 if (!(this._minValue == 0 && this._maxValue == 0)) { 72 this._grid = this._calculateGrid(); 73 } 74 }, 75 76 /** 77 * Called after changing ranges or canvas size to reset the grid values 78 */ 79 reset: function() { 80 this._clearLabels(); 81 this._updateMappedValues(); 82 this._grid = this._calculateGrid(); 83 }, 84 85 /** 86 * Map the given value to a y screen coordinate. 87 */ 88 toScreen: function(value) { 89 if (this._canvas && this._maxValue) { 90 var v = value - this._minValue; 91 return this._canvas.height * (this._map.direct(v)) / this._mappedRange; 92 } else { 93 return -50; 94 } 95 }, 96 97 /** 98 * Map the given y screen coordinate to a value 99 */ 100 fromScreen: function(y) { 101 if (this._canvas) { 102 return this._map.inverse(this._mappedRange * y / this._canvas.height) + this._minValue; 103 } else { 104 return 0; 105 } 106 }, 107 108 /** 109 * Each geometry is also a painter and paints the value grid and grid labels. 110 */ 111 paint: function() { 112 if (this._timeplot) { 113 var ctx = this._canvas.getContext('2d'); 114 115 ctx.lineJoin = 'miter'; 116 117 // paint grid 118 if (this._gridColor) { 119 var gridGradient = ctx.createLinearGradient(0,0,0,this._canvas.height); 120 gridGradient.addColorStop(0, this._gridColor.toHexString()); 121 gridGradient.addColorStop(0.3, this._gridColor.toHexString()); 122 gridGradient.addColorStop(1, "rgba(255,255,255,0.5)"); 123 124 ctx.lineWidth = this._gridLineWidth; 125 ctx.strokeStyle = gridGradient; 126 127 for (var i = 0; i < this._grid.length; i++) { 128 var tick = this._grid[i]; 129 var y = Math.floor(tick.y) + 0.5; 130 if (typeof tick.label != "undefined") { 131 if (this._axisLabelsPlacement == "left") { 132 var div = this._timeplot.putText(this._id + "-" + i, tick.label,"timeplot-grid-label",{ 133 left: 4, 134 bottom: y + 2, 135 color: this._gridColor.toHexString(), 136 visibility: "hidden" 137 }); 138 this._labels.push(div); 139 } else if (this._axisLabelsPlacement == "right") { 140 var div = this._timeplot.putText(this._id + "-" + i, tick.label, "timeplot-grid-label",{ 141 right: 4, 142 bottom: y + 2, 143 color: this._gridColor.toHexString(), 144 visibility: "hidden" 145 }); 146 this._labels.push(div); 147 } 148 if (y + div.clientHeight < this._canvas.height + 10) { 149 div.style.visibility = "visible"; // avoid the labels that would overflow 150 } 151 } 152 153 // draw grid 154 ctx.beginPath(); 155 if (this._gridType == "long" || tick.label == 0) { 156 ctx.moveTo(0, y); 157 ctx.lineTo(this._canvas.width, y); 158 } else if (this._gridType == "short") { 159 if (this._axisLabelsPlacement == "left") { 160 ctx.moveTo(0, y); 161 ctx.lineTo(this._gridShortSize, y); 162 } else if (this._axisLabelsPlacement == "right") { 163 ctx.moveTo(this._canvas.width, y); 164 ctx.lineTo(this._canvas.width - this._gridShortSize, y); 165 } 166 } 167 ctx.stroke(); 168 } 169 } 170 171 // paint axis 172 var axisGradient = ctx.createLinearGradient(0,0,0,this._canvas.height); 173 axisGradient.addColorStop(0, this._axisColor.toString()); 174 axisGradient.addColorStop(0.5, this._axisColor.toString()); 175 axisGradient.addColorStop(1, "rgba(255,255,255,0.5)"); 176 177 ctx.lineWidth = 1; 178 ctx.strokeStyle = axisGradient; 179 180 // left axis 181 ctx.beginPath(); 182 ctx.moveTo(0,this._canvas.height); 183 ctx.lineTo(0,0); 184 ctx.stroke(); 185 186 // right axis 187 ctx.beginPath(); 188 ctx.moveTo(this._canvas.width,0); 189 ctx.lineTo(this._canvas.width,this._canvas.height); 190 ctx.stroke(); 191 } 192 }, 193 194 /** 195 * Removes all the labels that were added by this geometry 196 */ 197 _clearLabels: function() { 198 for (var i = 0; i < this._labels.length; i++) { 199 var l = this._labels[i]; 200 var parent = l.parentNode; 201 if (parent) parent.removeChild(l); 202 } 203 }, 204 205 /* 206 * This function calculates the grid spacing that it will be used 207 * by this geometry to draw the grid in order to reduce clutter. 208 */ 209 _calculateGrid: function() { 210 var grid = []; 211 212 if (!this._canvas || this._valueRange == 0) return grid; 213 214 var power = 0; 215 if (this._valueRange > 1) { 216 while (Math.pow(10,power) < this._valueRange) { 217 power++; 218 } 219 power--; 220 } else { 221 while (Math.pow(10,power) > this._valueRange) { 222 power--; 223 } 224 } 225 226 var unit = Math.pow(10,power); 227 var inc = unit; 228 while (true) { 229 var dy = this.toScreen(this._minValue + inc); 230 231 while (dy < this._gridSpacing) { 232 inc += unit; 233 dy = this.toScreen(this._minValue + inc); 234 } 235 236 if (dy > 2 * this._gridSpacing) { // grids are too spaced out 237 unit /= 10; 238 inc = unit; 239 } else { 240 break; 241 } 242 } 243 244 var v = 0; 245 var y = this.toScreen(v); 246 if (this._minValue >= 0) { 247 while (y < this._canvas.height) { 248 if (y > 0) { 249 grid.push({ y: y, label: v }); 250 } 251 v += inc; 252 y = this.toScreen(v); 253 } 254 } else if (this._maxValue <= 0) { 255 while (y > 0) { 256 if (y < this._canvas.height) { 257 grid.push({ y: y, label: v }); 258 } 259 v -= inc; 260 y = this.toScreen(v); 261 } 262 } else { 263 while (y < this._canvas.height) { 264 if (y > 0) { 265 grid.push({ y: y, label: v }); 266 } 267 v += inc; 268 y = this.toScreen(v); 269 } 270 v = -inc; 271 y = this.toScreen(v); 272 while (y > 0) { 273 if (y < this._canvas.height) { 274 grid.push({ y: y, label: v }); 275 } 276 v -= inc; 277 y = this.toScreen(v); 278 } 279 } 280 281 return grid; 282 }, 283 284 /* 285 * Update the values that are used by the paint function so that 286 * we don't have to calculate them at every repaint. 287 */ 288 _updateMappedValues: function() { 289 this._valueRange = Math.abs(this._maxValue - this._minValue); 290 this._mappedRange = this._map.direct(this._valueRange); 291 } 292 293} 294 295// -------------------------------------------------- 296 297/** 298 * This is the constructor for a Logarithmic value geometry, which 299 * is useful when plots have values in different magnitudes but 300 * exhibit similar trends and such trends want to be shown on the same 301 * plot (here a cartesian geometry would make the small magnitudes 302 * disappear). 303 * 304 * NOTE: this class extends Timeplot.DefaultValueGeometry and inherits 305 * all of the methods of that class. So refer to that class. 306 * 307 * @constructor 308 */ 309Timeplot.LogarithmicValueGeometry = function(params) { 310 Timeplot.DefaultValueGeometry.apply(this, arguments); 311 this._logMap = { 312 direct: function(v) { 313 return Math.log(v + 1) / Math.log(10); 314 }, 315 inverse: function(y) { 316 return Math.exp(Math.log(10) * y) - 1; 317 } 318 } 319 this._mode = "log"; 320 this._map = this._logMap; 321 this._calculateGrid = this._logarithmicCalculateGrid; 322}; 323 324Timeplot.LogarithmicValueGeometry.prototype._linearCalculateGrid = Timeplot.DefaultValueGeometry.prototype._calculateGrid; 325 326Object.extend(Timeplot.LogarithmicValueGeometry.prototype,Timeplot.DefaultValueGeometry.prototype); 327 328/* 329 * This function calculates the grid spacing that it will be used 330 * by this geometry to draw the grid in order to reduce clutter. 331 */ 332Timeplot.LogarithmicValueGeometry.prototype._logarithmicCalculateGrid = function() { 333 var grid = []; 334 335 if (!this._canvas || this._valueRange == 0) return grid; 336 337 var v = 1; 338 var y = this.toScreen(v); 339 while (y < this._canvas.height || isNaN(y)) { 340 if (y > 0) { 341 grid.push({ y: y, label: v }); 342 } 343 v *= 10; 344 y = this.toScreen(v); 345 } 346 347 return grid; 348}; 349 350/** 351 * Turn the logarithmic scaling off. 352 */ 353Timeplot.LogarithmicValueGeometry.prototype.actLinear = function() { 354 this._mode = "lin"; 355 this._map = this._linMap; 356 this._calculateGrid = this._linearCalculateGrid; 357 this.reset(); 358} 359 360/** 361 * Turn the logarithmic scaling on. 362 */ 363Timeplot.LogarithmicValueGeometry.prototype.actLogarithmic = function() { 364 this._mode = "log"; 365 this._map = this._logMap; 366 this._calculateGrid = this._logarithmicCalculateGrid; 367 this.reset(); 368} 369 370/** 371 * Toggle logarithmic scaling seeting it to on if off and viceversa. 372 */ 373Timeplot.LogarithmicValueGeometry.prototype.toggle = function() { 374 if (this._mode == "log") { 375 this.actLinear(); 376 } else { 377 this.actLogarithmic(); 378 } 379} 380 381// ----------------------------------------------------- 382 383/** 384 * This is the constructor for the default time geometry. 385 * 386 * @constructor 387 */ 388Timeplot.DefaultTimeGeometry = function(params) { 389 if (!params) params = {}; 390 this._id = ("id" in params) ? params.id : "g" + Math.round(Math.random() * 1000000); 391 this._locale = ("locale" in params) ? params.locale : "en"; 392 this._timeZone = ("timeZone" in params) ? params.timeZone : SimileAjax.DateTime.getTimezone(); 393 this._labeler = ("labeller" in params) ? params.labeller : null; 394 this._axisColor = ("axisColor" in params) ? ((params.axisColor == "string") ? new Timeplot.Color(params.axisColor) : params.axisColor) : new Timeplot.Color("#606060"), 395 this._gridColor = ("gridColor" in params) ? ((params.gridColor == "string") ? new Timeplot.Color(params.gridColor) : params.gridColor) : null, 396 this._gridLineWidth = ("gridLineWidth" in params) ? params.gridLineWidth : 0.5; 397 this._axisLabelsPlacement = ("axisLabelsPlacement" in params) ? params.axisLabelsPlacement : "bottom"; 398 this._gridStep = ("gridStep" in params) ? params.gridStep : 100; 399 this._gridStepRange = ("gridStepRange" in params) ? params.gridStepRange : 20; 400 this._min = ("min" in params) ? params.min : null; 401 this._max = ("max" in params) ? params.max : null; 402 this._timeValuePosition =("timeValuePosition" in params) ? params.timeValuePosition : "bottom"; 403 this._unit = ("unit" in params) ? params.unit : SimileAjax.NativeDateUnit; 404 this._linMap = { 405 direct: function(t) { 406 return t; 407 }, 408 inverse: function(x) { 409 return x; 410 } 411 } 412 this._map = this._linMap; 413 if (!this._labeler) 414 this._labeler = (this._unit && ("createLabeller" in this._unit)) ? this._unit.createLabeller(this._locale, this._timeZone) : new Timeline.GregorianDateLabeller(this._locale, this._timeZone); 415 var dateParser = this._unit.getParser("iso8601"); 416 if (this._min && !this._min.getTime) { 417 this._min = dateParser(this._min); 418 } 419 if (this._max && !this._max.getTime) { 420 this._max = dateParser(this._max); 421 } 422 this._labels = []; 423 this._grid = []; 424} 425 426Timeplot.DefaultTimeGeometry.prototype = { 427 428 /** 429 * Since geometries can be reused across timeplots, we need to call this function 430 * before we can paint using this geometry. 431 */ 432 setTimeplot: function(timeplot) { 433 this._timeplot = timeplot; 434 this._canvas = timeplot.getCanvas(); 435 this.reset(); 436 }, 437 438 /** 439 * Called by all the plot layers this geometry is associated with 440 * to update the time range. Unless min/max values are specified 441 * in the parameters, the biggest range will be used. 442 */ 443 setRange: function(range) { 444 if (this._min) { 445 this._earliestDate = this._min; 446 } else if (range.earliestDate && ((this._earliestDate == null) || ((this._earliestDate != null) && (range.earliestDate.getTime() < this._earliestDate.getTime())))) { 447 this._earliestDate = range.earliestDate; 448 } 449 450 if (this._max) { 451 this._latestDate = this._max; 452 } else if (range.latestDate && ((this._latestDate == null) || ((this._latestDate != null) && (range.latestDate.getTime() > this._latestDate.getTime())))) { 453 this._latestDate = range.latestDate; 454 } 455 456 if (!this._earliestDate && !this._latestDate) { 457 this._grid = []; 458 } else { 459 this.reset(); 460 } 461 }, 462 463 /** 464 * Called after changing ranges or canvas size to reset the grid values 465 */ 466 reset: function() { 467 this._updateMappedValues(); 468 if (this._canvas) this._grid = this._calculateGrid(); 469 }, 470 471 /** 472 * Map the given date to a x screen coordinate. 473 */ 474 toScreen: function(time) { 475 if (this._canvas && this._latestDate) { 476 var t = time - this._earliestDate.getTime(); 477 var fraction = (this._mappedPeriod > 0) ? this._map.direct(t) / this._mappedPeriod : 0; 478 return this._canvas.width * fraction; 479 } else { 480 return -50; 481 } 482 }, 483 484 /** 485 * Map the given x screen coordinate to a date. 486 */ 487 fromScreen: function(x) { 488 if (this._canvas) { 489 return this._map.inverse(this._mappedPeriod * x / this._canvas.width) + this._earliestDate.getTime(); 490 } else { 491 return 0; 492 } 493 }, 494 495 /** 496 * Get a period (in milliseconds) this time geometry spans. 497 */ 498 getPeriod: function() { 499 return this._period; 500 }, 501 502 /** 503 * Return the labeler that has been associated with this time geometry 504 */ 505 getLabeler: function() { 506 return this._labeler; 507 }, 508 509 /** 510 * Return the time unit associated with this time geometry 511 */ 512 getUnit: function() { 513 return this._unit; 514 }, 515 516 /** 517 * Each geometry is also a painter and paints the value grid and grid labels. 518 */ 519 paint: function() { 520 if (this._canvas) { 521 var unit = this._unit; 522 var ctx = this._canvas.getContext('2d'); 523 524 var gradient = ctx.createLinearGradient(0,0,0,this._canvas.height); 525 526 ctx.strokeStyle = gradient; 527 ctx.lineWidth = this._gridLineWidth; 528 ctx.lineJoin = 'miter'; 529 530 // paint grid 531 if (this._gridColor) { 532 gradient.addColorStop(0, this._gridColor.toString()); 533 gradient.addColorStop(1, "rgba(255,255,255,0.9)"); 534 535 for (var i = 0; i < this._grid.length; i++) { 536 var tick = this._grid[i]; 537 var x = Math.floor(tick.x) + 0.5; 538 if (this._axisLabelsPlacement == "top") { 539 var div = this._timeplot.putText(this._id + "-" + i, tick.label,"timeplot-grid-label",{ 540 left: x + 4, 541 top: 2, 542 visibility: "hidden" 543 }); 544 this._labels.push(div); 545 } else if (this._axisLabelsPlacement == "bottom") { 546 var div = this._timeplot.putText(this._id + "-" + i, tick.label, "timeplot-grid-label",{ 547 left: x + 4, 548 bottom: 2, 549 visibility: "hidden" 550 }); 551 this._labels.push(div); 552 } 553 if (x + div.clientWidth < this._canvas.width + 10) { 554 div.style.visibility = "visible"; // avoid the labels that would overflow 555 } 556 557 // draw separator 558 ctx.beginPath(); 559 ctx.moveTo(x,0); 560 ctx.lineTo(x,this._canvas.height); 561 ctx.stroke(); 562 } 563 } 564 565 // paint axis 566 gradient.addColorStop(0, this._axisColor.toString()); 567 gradient.addColorStop(1, "rgba(255,255,255,0.5)"); 568 569 ctx.lineWidth = 1; 570 gradient.addColorStop(0, this._axisColor.toString()); 571 572 ctx.beginPath(); 573 ctx.moveTo(0,0); 574 ctx.lineTo(this._canvas.width,0); 575 ctx.stroke(); 576 } 577 }, 578 579 /* 580 * This function calculates the grid spacing that it will be used 581 * by this geometry to draw the grid in order to reduce clutter. 582 */ 583 _calculateGrid: function() { 584 var grid = []; 585 586 var time = SimileAjax.DateTime; 587 var u = this._unit; 588 var p = this._period; 589 590 if (p == 0) return grid; 591 592 // find the time units nearest to the time period 593 if (p > time.gregorianUnitLengths[time.MILLENNIUM]) { 594 unit = time.MILLENNIUM; 595 } else { 596 for (var unit = time.MILLENNIUM; unit > 0; unit--) { 597 if (time.gregorianUnitLengths[unit-1] <= p && p < time.gregorianUnitLengths[unit]) { 598 unit--; 599 break; 600 } 601 } 602 } 603 604 var t = u.cloneValue(this._earliestDate); 605 606 do { 607 time.roundDownToInterval(t, unit, this._timeZone, 1, 0); 608 var x = this.toScreen(u.toNumber(t)); 609 switch (unit) { 610 case time.SECOND: 611 var l = t.toLocaleTimeString(); 612 break; 613 case time.MINUTE: 614 var m = t.getMinutes(); 615 var l = t.getHours() + ":" + ((m < 10) ? "0" : "") + m; 616 break; 617 case time.HOUR: 618 var l = t.getHours() + ":00"; 619 break; 620 case time.DAY: 621 case time.WEEK: 622 case time.MONTH: 623 var l = t.toLocaleDateString(); 624 break; 625 case time.YEAR: 626 case time.DECADE: 627 case time.CENTURY: 628 case time.MILLENNIUM: 629 var l = t.getUTCFullYear(); 630 break; 631 } 632 if (x > 0) { 633 grid.push({ x: x, label: l }); 634 } 635 time.incrementByInterval(t, unit, this._timeZone); 636 } while (t.getTime() < this._latestDate.getTime()); 637 638 return grid; 639 }, 640 641 /* 642 * Clear labels generated by this time geometry. 643 */ 644 _clearLabels: function() { 645 for (var i = 0; i < this._labels.length; i++) { 646 var l = this._labels[i]; 647 var parent = l.parentNode; 648 if (parent) parent.removeChild(l); 649 } 650 }, 651 652 /* 653 * Update the values that are used by the paint function so that 654 * we don't have to calculate them at every repaint. 655 */ 656 _updateMappedValues: function() { 657 if (this._latestDate && this._earliestDate) { 658 this._period = this._latestDate.getTime() - this._earliestDate.getTime(); 659 this._mappedPeriod = this._map.direct(this._period); 660 } else { 661 this._period = 0; 662 this._mappedPeriod = 0; 663 } 664 } 665 666} 667 668// -------------------------------------------------------------- 669 670/** 671 * This is the constructor for the magnifying time geometry. 672 * Users can interact with this geometry and 'magnify' certain areas of the 673 * plot to see the plot enlarged and resolve details that would otherwise 674 * get lost or cluttered with a linear time geometry. 675 * 676 * @constructor 677 */ 678Timeplot.MagnifyingTimeGeometry = function(params) { 679 Timeplot.DefaultTimeGeometry.apply(this, arguments); 680 681 var g = this; 682 this._MagnifyingMap = { 683 direct: function(t) { 684 if (t < g._leftTimeMargin) { 685 var x = t * g._leftRate; 686 } else if ( g._leftTimeMargin < t && t < g._rightTimeMargin ) { 687 var x = t * g._expandedRate + g._expandedTimeTranslation; 688 } else { 689 var x = t * g._rightRate + g._rightTimeTranslation; 690 } 691 return x; 692 }, 693 inverse: function(x) { 694 if (x < g._leftScreenMargin) { 695 var t = x / g._leftRate; 696 } else if ( g._leftScreenMargin < x && x < g._rightScreenMargin ) { 697 var t = x / g._expandedRate + g._expandedScreenTranslation; 698 } else { 699 var t = x / g._rightRate + g._rightScreenTranslation; 700 } 701 return t; 702 } 703 } 704 705 this._mode = "lin"; 706 this._map = this._linMap; 707}; 708 709Object.extend(Timeplot.MagnifyingTimeGeometry.prototype,Timeplot.DefaultTimeGeometry.prototype); 710 711/** 712 * Initialize this geometry associating it with the given timeplot and 713 * register the geometry event handlers to the timeplot so that it can 714 * interact with the user. 715 */ 716Timeplot.MagnifyingTimeGeometry.prototype.initialize = function(timeplot) { 717 Timeplot.DefaultTimeGeometry.prototype.initialize.apply(this, arguments); 718 719 if (!this._lens) { 720 this._lens = this._timeplot.putDiv("lens","timeplot-lens"); 721 } 722 723 var period = 1000 * 60 * 60 * 24 * 30; // a month in the magnifying lens 724 725 var geometry = this; 726 727 var magnifyWith = function(lens) { 728 var aperture = lens.clientWidth; 729 var loc = geometry._timeplot.locate(lens); 730 geometry.setMagnifyingParams(loc.x + aperture / 2, aperture, period); 731 geometry.actMagnifying(); 732 geometry._timeplot.paint(); 733 } 734 735 var canvasMouseDown = function(elmt, evt, target) { 736 geometry._canvas.startCoords = SimileAjax.DOM.getEventRelativeCoordinates(evt,elmt); 737 geometry._canvas.pressed = true; 738 } 739 740 var canvasMouseUp = function(elmt, evt, target) { 741 geometry._canvas.pressed = false; 742 var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt,elmt); 743 if (Timeplot.Math.isClose(coords,geometry._canvas.startCoords,5)) { 744 geometry._lens.style.display = "none"; 745 geometry.actLinear(); 746 geometry._timeplot.paint(); 747 } else { 748 geometry._lens.style.cursor = "move"; 749 magnifyWith(geometry._lens); 750 } 751 } 752 753 var canvasMouseMove = function(elmt, evt, target) { 754 if (geometry._canvas.pressed) { 755 var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt,elmt); 756 if (coords.x < 0) coords.x = 0; 757 if (coords.x > geometry._canvas.width) coords.x = geometry._canvas.width; 758 geometry._timeplot.placeDiv(geometry._lens, { 759 left: geometry._canvas.startCoords.x, 760 width: coords.x - geometry._canvas.startCoords.x, 761 bottom: 0, 762 height: geometry._canvas.height, 763 display: "block" 764 }); 765 } 766 } 767 768 var lensMouseDown = function(elmt, evt, target) { 769 geometry._lens.startCoords = SimileAjax.DOM.getEventRelativeCoordinates(evt,elmt);; 770 geometry._lens.pressed = true; 771 } 772 773 var lensMouseUp = function(elmt, evt, target) { 774 geometry._lens.pressed = false; 775 } 776 777 var lensMouseMove = function(elmt, evt, target) { 778 if (geometry._lens.pressed) { 779 var coords = SimileAjax.DOM.getEventRelativeCoordinates(evt,elmt); 780 var lens = geometry._lens; 781 var left = lens.offsetLeft + coords.x - lens.startCoords.x; 782 if (left < geometry._timeplot._paddingX) left = geometry._timeplot._paddingX; 783 if (left + lens.clientWidth > geometry._canvas.width - geometry._timeplot._paddingX) left = geometry._canvas.width - lens.clientWidth + geometry._timeplot._paddingX; 784 lens.style.left = left; 785 magnifyWith(lens); 786 } 787 } 788 789 if (!this._canvas.instrumented) { 790 SimileAjax.DOM.registerEvent(this._canvas, "mousedown", canvasMouseDown); 791 SimileAjax.DOM.registerEvent(this._canvas, "mousemove", canvasMouseMove); 792 SimileAjax.DOM.registerEvent(this._canvas, "mouseup" , canvasMouseUp); 793 SimileAjax.DOM.registerEvent(this._canvas, "mouseup" , lensMouseUp); 794 this._canvas.instrumented = true; 795 } 796 797 if (!this._lens.instrumented) { 798 SimileAjax.DOM.registerEvent(this._lens, "mousedown", lensMouseDown); 799 SimileAjax.DOM.registerEvent(this._lens, "mousemove", lensMouseMove); 800 SimileAjax.DOM.registerEvent(this._lens, "mouseup" , lensMouseUp); 801 SimileAjax.DOM.registerEvent(this._lens, "mouseup" , canvasMouseUp); 802 this._lens.instrumented = true; 803 } 804} 805 806/** 807 * Set the Magnifying parameters. c is the location in pixels where the Magnifying 808 * center should be located in the timeplot, a is the aperture in pixel of 809 * the Magnifying and b is the time period in milliseconds that the Magnifying 810 * should span. 811 */ 812Timeplot.MagnifyingTimeGeometry.prototype.setMagnifyingParams = function(c,a,b) { 813 a = a / 2; 814 b = b / 2; 815 816 var w = this._canvas.width; 817 var d = this._period; 818 819 if (c < 0) c = 0; 820 if (c > w) c = w; 821 822 if (c - a < 0) a = c; 823 if (c + a > w) a = w - c; 824 825 var ct = this.fromScreen(c) - this._earliestDate.getTime(); 826 if (ct - b < 0) b = ct; 827 if (ct + b > d) b = d - ct; 828 829 this._centerX = c; 830 this._centerTime = ct; 831 this._aperture = a; 832 this._aperturePeriod = b; 833 834 this._leftScreenMargin = this._centerX - this._aperture; 835 this._rightScreenMargin = this._centerX + this._aperture; 836 this._leftTimeMargin = this._centerTime - this._aperturePeriod; 837 this._rightTimeMargin = this._centerTime + this._aperturePeriod; 838 839 this._leftRate = (c - a) / (ct - b); 840 this._expandedRate = a / b; 841 this._rightRate = (w - c - a) / (d - ct - b); 842 843 this._expandedTimeTranslation = this._centerX - this._centerTime * this._expandedRate; 844 this._expandedScreenTranslation = this._centerTime - this._centerX / this._expandedRate; 845 this._rightTimeTranslation = (c + a) - (ct + b) * this._rightRate; 846 this._rightScreenTranslation = (ct + b) - (c + a) / this._rightRate; 847 848 this._updateMappedValues(); 849} 850 851/* 852 * Turn magnification off. 853 */ 854Timeplot.MagnifyingTimeGeometry.prototype.actLinear = function() { 855 this._mode = "lin"; 856 this._map = this._linMap; 857 this.reset(); 858} 859 860/* 861 * Turn magnification on. 862 */ 863Timeplot.MagnifyingTimeGeometry.prototype.actMagnifying = function() { 864 this._mode = "Magnifying"; 865 this._map = this._MagnifyingMap; 866 this.reset(); 867} 868 869/* 870 * Toggle magnification. 871 */ 872Timeplot.MagnifyingTimeGeometry.prototype.toggle = function() { 873 if (this._mode == "Magnifying") { 874 this.actLinear(); 875 } else { 876 this.actMagnifying(); 877 } 878} 879