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.

236 lines
6.9 KiB

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