/timeplot/scripts/geometry.js

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