vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

243 lines
7.1 KiB

  1. /**
  2. *
  3. * @param {number} start
  4. * @param {number} end
  5. * @param {boolean} autoScaleStart
  6. * @param {boolean} autoScaleEnd
  7. * @param {number} containerHeight
  8. * @param {number} majorCharHeight
  9. * @param {boolean} zeroAlign
  10. * @param {function} formattingFunction
  11. * @constructor DataScale
  12. */
  13. function DataScale(start, end, autoScaleStart, autoScaleEnd, containerHeight, majorCharHeight, zeroAlign = false, formattingFunction=false) {
  14. this.majorSteps = [1, 2, 5, 10];
  15. this.minorSteps = [0.25, 0.5, 1, 2];
  16. this.customLines = null;
  17. this.containerHeight = containerHeight;
  18. this.majorCharHeight = majorCharHeight;
  19. this._start = start;
  20. this._end = end;
  21. this.scale = 1;
  22. this.minorStepIdx = -1;
  23. this.magnitudefactor = 1;
  24. this.determineScale();
  25. this.zeroAlign = zeroAlign;
  26. this.autoScaleStart = autoScaleStart;
  27. this.autoScaleEnd = autoScaleEnd;
  28. this.formattingFunction = formattingFunction;
  29. if (autoScaleStart || autoScaleEnd) {
  30. var me = this;
  31. var roundToMinor = function (value) {
  32. var rounded = value - (value % (me.magnitudefactor * me.minorSteps[me.minorStepIdx]));
  33. if (value % (me.magnitudefactor * me.minorSteps[me.minorStepIdx]) > 0.5 * (me.magnitudefactor * me.minorSteps[me.minorStepIdx])) {
  34. return rounded + (me.magnitudefactor * me.minorSteps[me.minorStepIdx]);
  35. }
  36. else {
  37. return rounded;
  38. }
  39. };
  40. if (autoScaleStart) {
  41. this._start -= this.magnitudefactor * 2 * this.minorSteps[this.minorStepIdx];
  42. this._start = roundToMinor(this._start);
  43. }
  44. if (autoScaleEnd) {
  45. this._end += this.magnitudefactor * this.minorSteps[this.minorStepIdx];
  46. this._end = roundToMinor(this._end);
  47. }
  48. this.determineScale();
  49. }
  50. }
  51. DataScale.prototype.setCharHeight = function (majorCharHeight) {
  52. this.majorCharHeight = majorCharHeight;
  53. };
  54. DataScale.prototype.setHeight = function (containerHeight) {
  55. this.containerHeight = containerHeight;
  56. };
  57. DataScale.prototype.determineScale = function () {
  58. var range = this._end - this._start;
  59. this.scale = this.containerHeight / range;
  60. var minimumStepValue = this.majorCharHeight / this.scale;
  61. var orderOfMagnitude = (range > 0)
  62. ? Math.round(Math.log(range) / Math.LN10)
  63. : 0;
  64. this.minorStepIdx = -1;
  65. this.magnitudefactor = Math.pow(10, orderOfMagnitude);
  66. var start = 0;
  67. if (orderOfMagnitude < 0) {
  68. start = orderOfMagnitude;
  69. }
  70. var solutionFound = false;
  71. for (var l = start; Math.abs(l) <= Math.abs(orderOfMagnitude); l++) {
  72. this.magnitudefactor = Math.pow(10, l);
  73. for (var j = 0; j < this.minorSteps.length; j++) {
  74. var stepSize = this.magnitudefactor * this.minorSteps[j];
  75. if (stepSize >= minimumStepValue) {
  76. solutionFound = true;
  77. this.minorStepIdx = j;
  78. break;
  79. }
  80. }
  81. if (solutionFound === true) {
  82. break;
  83. }
  84. }
  85. };
  86. DataScale.prototype.is_major = function (value) {
  87. return (value % (this.magnitudefactor * this.majorSteps[this.minorStepIdx]) === 0);
  88. };
  89. DataScale.prototype.getStep = function(){
  90. return this.magnitudefactor * this.minorSteps[this.minorStepIdx];
  91. };
  92. DataScale.prototype.getFirstMajor = function(){
  93. var majorStep = this.magnitudefactor * this.majorSteps[this.minorStepIdx];
  94. return this.convertValue(this._start + ((majorStep - (this._start % majorStep)) % majorStep));
  95. };
  96. DataScale.prototype.formatValue = function(current) {
  97. var returnValue = current.toPrecision(5);
  98. if (typeof this.formattingFunction === 'function') {
  99. returnValue = this.formattingFunction(current);
  100. }
  101. if (typeof returnValue === 'number') {
  102. return '' + returnValue;
  103. }
  104. else if (typeof returnValue === 'string') {
  105. return returnValue;
  106. }
  107. else {
  108. return current.toPrecision(5);
  109. }
  110. };
  111. DataScale.prototype.getLines = function () {
  112. var lines = [];
  113. var step = this.getStep();
  114. var bottomOffset = (step - (this._start % step)) % step;
  115. for (var i = (this._start + bottomOffset); this._end-i > 0.00001; i += step) {
  116. if (i != this._start) { //Skip the bottom line
  117. lines.push({major: this.is_major(i), y: this.convertValue(i), val: this.formatValue(i)});
  118. }
  119. }
  120. return lines;
  121. };
  122. DataScale.prototype.followScale = function (other) {
  123. var oldStepIdx = this.minorStepIdx;
  124. var oldStart = this._start;
  125. var oldEnd = this._end;
  126. var me = this;
  127. var increaseMagnitude = function () {
  128. me.magnitudefactor *= 2;
  129. };
  130. var decreaseMagnitude = function () {
  131. me.magnitudefactor /= 2;
  132. };
  133. if ((other.minorStepIdx <= 1 && this.minorStepIdx <= 1) || (other.minorStepIdx > 1 && this.minorStepIdx > 1)) {
  134. //easy, no need to change stepIdx nor multiplication factor
  135. } else if (other.minorStepIdx < this.minorStepIdx) {
  136. //I'm 5, they are 4 per major.
  137. this.minorStepIdx = 1;
  138. if (oldStepIdx == 2) {
  139. increaseMagnitude();
  140. } else {
  141. increaseMagnitude();
  142. increaseMagnitude();
  143. }
  144. } else {
  145. //I'm 4, they are 5 per major
  146. this.minorStepIdx = 2;
  147. if (oldStepIdx == 1) {
  148. decreaseMagnitude();
  149. } else {
  150. decreaseMagnitude();
  151. decreaseMagnitude();
  152. }
  153. }
  154. //Get masters stats:
  155. var otherZero = other.convertValue(0);
  156. var otherStep = other.getStep() * other.scale;
  157. var done = false;
  158. var count = 0;
  159. //Loop until magnitude is correct for given constrains.
  160. while (!done && count++ <5) {
  161. //Get my stats:
  162. this.scale = otherStep / (this.minorSteps[this.minorStepIdx] * this.magnitudefactor);
  163. var newRange = this.containerHeight / this.scale;
  164. //For the case the magnitudefactor has changed:
  165. this._start = oldStart;
  166. this._end = this._start + newRange;
  167. var myOriginalZero = this._end * this.scale;
  168. var majorStep = this.magnitudefactor * this.majorSteps[this.minorStepIdx];
  169. var majorOffset = this.getFirstMajor() - other.getFirstMajor();
  170. if (this.zeroAlign) {
  171. var zeroOffset = otherZero - myOriginalZero;
  172. this._end += (zeroOffset / this.scale);
  173. this._start = this._end - newRange;
  174. } else {
  175. if (!this.autoScaleStart) {
  176. this._start += majorStep - (majorOffset / this.scale);
  177. this._end = this._start + newRange;
  178. } else {
  179. this._start -= majorOffset / this.scale;
  180. this._end = this._start + newRange;
  181. }
  182. }
  183. if (!this.autoScaleEnd && this._end > oldEnd+0.00001) {
  184. //Need to decrease magnitude to prevent scale overshoot! (end)
  185. decreaseMagnitude();
  186. done = false;
  187. continue;
  188. }
  189. if (!this.autoScaleStart && this._start < oldStart-0.00001) {
  190. if (this.zeroAlign && oldStart >= 0) {
  191. console.warn("Can't adhere to given 'min' range, due to zeroalign");
  192. } else {
  193. //Need to decrease magnitude to prevent scale overshoot! (start)
  194. decreaseMagnitude();
  195. done = false;
  196. continue;
  197. }
  198. }
  199. if (this.autoScaleStart && this.autoScaleEnd && newRange < (oldEnd-oldStart)){
  200. increaseMagnitude();
  201. done = false;
  202. continue;
  203. }
  204. done = true;
  205. }
  206. };
  207. DataScale.prototype.convertValue = function (value) {
  208. return this.containerHeight - ((value - this._start) * this.scale);
  209. };
  210. DataScale.prototype.screenToValue = function (pixels) {
  211. return ((this.containerHeight - pixels) / this.scale) + this._start;
  212. };
  213. module.exports = DataScale;