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.

468 lines
13 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. /**
  2. * A horizontal time axis
  3. * @param {Object} [options] See DataAxis.setOptions for the available
  4. * options.
  5. * @constructor DataAxis
  6. * @extends Component
  7. * @param body
  8. */
  9. function DataAxis (body, options, svg) {
  10. this.id = util.randomUUID();
  11. this.body = body;
  12. this.defaultOptions = {
  13. orientation: 'left', // supported: 'left', 'right'
  14. showMinorLabels: true,
  15. showMajorLabels: true,
  16. icons: true,
  17. majorLinesOffset: 7,
  18. minorLinesOffset: 4,
  19. labelOffsetX: 10,
  20. labelOffsetY: 2,
  21. iconWidth: 20,
  22. width: '40px',
  23. visible: true
  24. };
  25. this.linegraphSVG = svg;
  26. this.props = {};
  27. this.DOMelements = { // dynamic elements
  28. lines: {},
  29. labels: {}
  30. };
  31. this.dom = {};
  32. this.range = {start:0, end:0};
  33. this.options = util.extend({}, this.defaultOptions);
  34. this.conversionFactor = 1;
  35. this.setOptions(options);
  36. this.width = Number(('' + this.options.width).replace("px",""));
  37. this.minWidth = this.width;
  38. this.height = this.linegraphSVG.offsetHeight;
  39. this.stepPixels = 25;
  40. this.stepPixelsForced = 25;
  41. this.lineOffset = 0;
  42. this.master = true;
  43. this.svgElements = {};
  44. this.groups = {};
  45. this.amountOfGroups = 0;
  46. // create the HTML DOM
  47. this._create();
  48. }
  49. DataAxis.prototype = new Component();
  50. DataAxis.prototype.addGroup = function(label, graphOptions) {
  51. if (!this.groups.hasOwnProperty(label)) {
  52. this.groups[label] = graphOptions;
  53. }
  54. this.amountOfGroups += 1;
  55. };
  56. DataAxis.prototype.updateGroup = function(label, graphOptions) {
  57. this.groups[label] = graphOptions;
  58. };
  59. DataAxis.prototype.removeGroup = function(label) {
  60. if (this.groups.hasOwnProperty(label)) {
  61. delete this.groups[label];
  62. this.amountOfGroups -= 1;
  63. }
  64. };
  65. DataAxis.prototype.setOptions = function (options) {
  66. if (options) {
  67. var redraw = false;
  68. if (this.options.orientation != options.orientation && options.orientation !== undefined) {
  69. redraw = true;
  70. }
  71. var fields = [
  72. 'orientation',
  73. 'showMinorLabels',
  74. 'showMajorLabels',
  75. 'icons',
  76. 'majorLinesOffset',
  77. 'minorLinesOffset',
  78. 'labelOffsetX',
  79. 'labelOffsetY',
  80. 'iconWidth',
  81. 'width',
  82. 'visible'];
  83. util.selectiveExtend(fields, this.options, options);
  84. this.minWidth = Number(('' + this.options.width).replace("px",""));
  85. if (redraw == true && this.dom.frame) {
  86. this.hide();
  87. this.show();
  88. }
  89. }
  90. };
  91. /**
  92. * Create the HTML DOM for the DataAxis
  93. */
  94. DataAxis.prototype._create = function() {
  95. this.dom.frame = document.createElement('div');
  96. this.dom.frame.style.width = this.options.width;
  97. this.dom.frame.style.height = this.height;
  98. this.dom.lineContainer = document.createElement('div');
  99. this.dom.lineContainer.style.width = '100%';
  100. this.dom.lineContainer.style.height = this.height;
  101. // create svg element for graph drawing.
  102. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  103. this.svg.style.position = "absolute";
  104. this.svg.style.top = '0px';
  105. this.svg.style.height = '100%';
  106. this.svg.style.width = '100%';
  107. this.svg.style.display = "block";
  108. this.dom.frame.appendChild(this.svg);
  109. };
  110. DataAxis.prototype._redrawGroupIcons = function () {
  111. DOMutil.prepareElements(this.svgElements);
  112. var x;
  113. var iconWidth = this.options.iconWidth;
  114. var iconHeight = 15;
  115. var iconOffset = 4;
  116. var y = iconOffset + 0.5 * iconHeight;
  117. if (this.options.orientation == 'left') {
  118. x = iconOffset;
  119. }
  120. else {
  121. x = this.width - iconWidth - iconOffset;
  122. }
  123. for (var groupId in this.groups) {
  124. if (this.groups.hasOwnProperty(groupId)) {
  125. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  126. y += iconHeight + iconOffset;
  127. }
  128. }
  129. DOMutil.cleanupElements(this.svgElements);
  130. };
  131. /**
  132. * Create the HTML DOM for the DataAxis
  133. */
  134. DataAxis.prototype.show = function() {
  135. if (!this.dom.frame.parentNode) {
  136. if (this.options.orientation == 'left') {
  137. this.body.dom.left.appendChild(this.dom.frame);
  138. }
  139. else {
  140. this.body.dom.right.appendChild(this.dom.frame);
  141. }
  142. }
  143. if (!this.dom.lineContainer.parentNode) {
  144. this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
  145. }
  146. };
  147. /**
  148. * Create the HTML DOM for the DataAxis
  149. */
  150. DataAxis.prototype.hide = function() {
  151. if (this.dom.frame.parentNode) {
  152. this.dom.frame.parentNode.removeChild(this.dom.frame);
  153. }
  154. if (this.dom.lineContainer.parentNode) {
  155. this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
  156. }
  157. };
  158. /**
  159. * Set a range (start and end)
  160. * @param end
  161. * @param start
  162. * @param end
  163. */
  164. DataAxis.prototype.setRange = function (start, end) {
  165. this.range.start = start;
  166. this.range.end = end;
  167. };
  168. /**
  169. * Repaint the component
  170. * @return {boolean} Returns true if the component is resized
  171. */
  172. DataAxis.prototype.redraw = function () {
  173. var changeCalled = false;
  174. if (this.amountOfGroups == 0) {
  175. this.hide();
  176. }
  177. else {
  178. this.show();
  179. this.height = Number(this.linegraphSVG.style.height.replace("px",""));
  180. // svg offsetheight did not work in firefox and explorer...
  181. this.dom.lineContainer.style.height = this.height + 'px';
  182. this.width = this.options.visible == true ? Number(('' + this.options.width).replace("px","")) : 0;
  183. var props = this.props;
  184. var frame = this.dom.frame;
  185. // update classname
  186. frame.className = 'dataaxis';
  187. // calculate character width and height
  188. this._calculateCharSize();
  189. var orientation = this.options.orientation;
  190. var showMinorLabels = this.options.showMinorLabels;
  191. var showMajorLabels = this.options.showMajorLabels;
  192. // determine the width and height of the elemens for the axis
  193. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  194. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  195. props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
  196. props.minorLineHeight = 1;
  197. props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
  198. props.majorLineHeight = 1;
  199. // take frame offline while updating (is almost twice as fast)
  200. if (orientation == 'left') {
  201. frame.style.top = '0';
  202. frame.style.left = '0';
  203. frame.style.bottom = '';
  204. frame.style.width = this.width + 'px';
  205. frame.style.height = this.height + "px";
  206. }
  207. else { // right
  208. frame.style.top = '';
  209. frame.style.bottom = '0';
  210. frame.style.left = '0';
  211. frame.style.width = this.width + 'px';
  212. frame.style.height = this.height + "px";
  213. }
  214. changeCalled = this._redrawLabels();
  215. if (this.options.icons == true) {
  216. this._redrawGroupIcons();
  217. }
  218. }
  219. return changeCalled;
  220. };
  221. /**
  222. * Repaint major and minor text labels and vertical grid lines
  223. * @private
  224. */
  225. DataAxis.prototype._redrawLabels = function () {
  226. DOMutil.prepareElements(this.DOMelements);
  227. var orientation = this.options['orientation'];
  228. // calculate range and step (step such that we have space for 7 characters per label)
  229. var minimumStep = this.master ? this.props.majorCharHeight || 10 : this.stepPixelsForced;
  230. var step = new DataStep(this.range.start, this.range.end, minimumStep, this.dom.frame.offsetHeight);
  231. this.step = step;
  232. step.first();
  233. // get the distance in pixels for a step
  234. var stepPixels = this.dom.frame.offsetHeight / ((step.marginRange / step.step) + 1);
  235. this.stepPixels = stepPixels;
  236. var amountOfSteps = this.height / stepPixels;
  237. var stepDifference = 0;
  238. if (this.master == false) {
  239. stepPixels = this.stepPixelsForced;
  240. stepDifference = Math.round((this.height / stepPixels) - amountOfSteps);
  241. for (var i = 0; i < 0.5 * stepDifference; i++) {
  242. step.previous();
  243. }
  244. amountOfSteps = this.height / stepPixels;
  245. }
  246. this.valueAtZero = step.marginEnd;
  247. var marginStartPos = 0;
  248. // do not draw the first label
  249. var max = 1;
  250. step.next();
  251. this.maxLabelSize = 0;
  252. var y = 0;
  253. while (max < Math.round(amountOfSteps)) {
  254. y = Math.round(max * stepPixels);
  255. marginStartPos = max * stepPixels;
  256. var isMajor = step.isMajor();
  257. if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) {
  258. this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis minor', this.props.minorCharHeight);
  259. }
  260. if (isMajor && this.options['showMajorLabels'] && this.master == true ||
  261. this.options['showMinorLabels'] == false && this.master == false && isMajor == true) {
  262. if (y >= 0) {
  263. this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis major', this.props.majorCharHeight);
  264. }
  265. this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth);
  266. }
  267. else {
  268. this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth);
  269. }
  270. step.next();
  271. max++;
  272. }
  273. this.conversionFactor = marginStartPos/((amountOfSteps-1) * step.step);
  274. var offset = this.options.icons == true ? this.options.iconWidth + this.options.labelOffsetX + 15 : this.options.labelOffsetX + 15;
  275. // this will resize the yAxis to accomodate the labels.
  276. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) {
  277. this.width = this.maxLabelSize + offset;
  278. this.options.width = this.width + "px";
  279. DOMutil.cleanupElements(this.DOMelements);
  280. this.redraw();
  281. return true;
  282. }
  283. // this will resize the yAxis if it is too big for the labels.
  284. else if (this.maxLabelSize < (this.width - offset) && this.options.visible == true && this.width > this.minWidth) {
  285. this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
  286. this.options.width = this.width + "px";
  287. DOMutil.cleanupElements(this.DOMelements);
  288. this.redraw();
  289. return true;
  290. }
  291. else {
  292. DOMutil.cleanupElements(this.DOMelements);
  293. return false;
  294. }
  295. };
  296. /**
  297. * Create a label for the axis at position x
  298. * @private
  299. * @param y
  300. * @param text
  301. * @param orientation
  302. * @param className
  303. * @param characterHeight
  304. */
  305. DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
  306. // reuse redundant label
  307. var label = DOMutil.getDOMElement('div',this.DOMelements, this.dom.frame); //this.dom.redundant.labels.shift();
  308. label.className = className;
  309. label.innerHTML = text;
  310. if (orientation == 'left') {
  311. label.style.left = '-' + this.options.labelOffsetX + 'px';
  312. label.style.textAlign = "right";
  313. }
  314. else {
  315. label.style.right = '-' + this.options.labelOffsetX + 'px';
  316. label.style.textAlign = "left";
  317. }
  318. label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';
  319. text += '';
  320. var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
  321. if (this.maxLabelSize < text.length * largestWidth) {
  322. this.maxLabelSize = text.length * largestWidth;
  323. }
  324. };
  325. /**
  326. * Create a minor line for the axis at position y
  327. * @param y
  328. * @param orientation
  329. * @param className
  330. * @param offset
  331. * @param width
  332. */
  333. DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
  334. if (this.master == true) {
  335. var line = DOMutil.getDOMElement('div',this.DOMelements, this.dom.lineContainer);//this.dom.redundant.lines.shift();
  336. line.className = className;
  337. line.innerHTML = '';
  338. if (orientation == 'left') {
  339. line.style.left = (this.width - offset) + 'px';
  340. }
  341. else {
  342. line.style.right = (this.width - offset) + 'px';
  343. }
  344. line.style.width = width + 'px';
  345. line.style.top = y + 'px';
  346. }
  347. };
  348. DataAxis.prototype.convertValue = function (value) {
  349. var invertedValue = this.valueAtZero - value;
  350. var convertedValue = invertedValue * this.conversionFactor;
  351. return convertedValue; // the -2 is to compensate for the borders
  352. };
  353. /**
  354. * Determine the size of text on the axis (both major and minor axis).
  355. * The size is calculated only once and then cached in this.props.
  356. * @private
  357. */
  358. DataAxis.prototype._calculateCharSize = function () {
  359. // determine the char width and height on the minor axis
  360. if (!('minorCharHeight' in this.props)) {
  361. var textMinor = document.createTextNode('0');
  362. var measureCharMinor = document.createElement('DIV');
  363. measureCharMinor.className = 'yAxis minor measure';
  364. measureCharMinor.appendChild(textMinor);
  365. this.dom.frame.appendChild(measureCharMinor);
  366. this.props.minorCharHeight = measureCharMinor.clientHeight;
  367. this.props.minorCharWidth = measureCharMinor.clientWidth;
  368. this.dom.frame.removeChild(measureCharMinor);
  369. }
  370. if (!('majorCharHeight' in this.props)) {
  371. var textMajor = document.createTextNode('0');
  372. var measureCharMajor = document.createElement('DIV');
  373. measureCharMajor.className = 'yAxis major measure';
  374. measureCharMajor.appendChild(textMajor);
  375. this.dom.frame.appendChild(measureCharMajor);
  376. this.props.majorCharHeight = measureCharMajor.clientHeight;
  377. this.props.majorCharWidth = measureCharMajor.clientWidth;
  378. this.dom.frame.removeChild(measureCharMajor);
  379. }
  380. };
  381. /**
  382. * Snap a date to a rounded value.
  383. * The snap intervals are dependent on the current scale and step.
  384. * @param {Date} date the date to be snapped.
  385. * @return {Date} snappedDate
  386. */
  387. DataAxis.prototype.snap = function(date) {
  388. return this.step.snap(date);
  389. };