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.

295 lines
8.2 KiB

  1. var DataSet = require('../DataSet');
  2. var DataView = require('../DataView');
  3. var Range = require('./Range');
  4. /**
  5. * Creates a container for all data of one specific 3D-graph.
  6. *
  7. * On construction, the container is totally empty; the data
  8. * needs to be initialized with method initializeData().
  9. * Failure to do so will result in the following exception begin thrown
  10. * on instantiation of Graph3D:
  11. *
  12. * Error: Array, DataSet, or DataView expected
  13. *
  14. * @constructor
  15. */
  16. function DataGroup() {
  17. this.dataTable = null; // The original data table
  18. }
  19. /**
  20. * Initializes the instance from the passed data.
  21. *
  22. * Calculates minimum and maximum values and column index values.
  23. *
  24. * The graph3d instance is used internally to access the settings for
  25. * the given instance.
  26. * TODO: Pass settings only instead.
  27. *
  28. * @param {Graph3D} graph3d Reference to the calling Graph3D instance.
  29. * @param {Array | DataSet | DataView} rawData The data containing the items for
  30. * the Graph.
  31. * @param {Number} style Style Number
  32. */
  33. DataGroup.prototype.initializeData = function(graph3d, rawData, style) {
  34. // unsubscribe from the dataTable
  35. if (this.dataSet) {
  36. this.dataSet.off('*', this._onChange);
  37. }
  38. if (rawData === undefined)
  39. return;
  40. if (Array.isArray(rawData)) {
  41. rawData = new DataSet(rawData);
  42. }
  43. var data;
  44. if (rawData instanceof DataSet || rawData instanceof DataView) {
  45. data = rawData.get();
  46. }
  47. else {
  48. throw new Error('Array, DataSet, or DataView expected');
  49. }
  50. if (data.length == 0)
  51. return;
  52. this.dataSet = rawData;
  53. this.dataTable = data;
  54. // subscribe to changes in the dataset
  55. var me = this;
  56. this._onChange = function () {
  57. graph3d.setData(me.dataSet);
  58. };
  59. this.dataSet.on('*', this._onChange);
  60. // determine the location of x,y,z,value,filter columns
  61. this.colX = 'x';
  62. this.colY = 'y';
  63. this.colZ = 'z';
  64. var withBars = graph3d.hasBars(style);
  65. // determine barWidth from data
  66. if (withBars) {
  67. if (graph3d.defaultXBarWidth !== undefined) {
  68. this.xBarWidth = graph3d.defaultXBarWidth;
  69. }
  70. else {
  71. this.xBarWidth = this.getSmallestDifference(data, this.colX) || 1;
  72. }
  73. if (graph3d.defaultYBarWidth !== undefined) {
  74. this.yBarWidth = graph3d.defaultYBarWidth;
  75. }
  76. else {
  77. this.yBarWidth = this.getSmallestDifference(data, this.colY) || 1;
  78. }
  79. }
  80. // calculate minima and maxima
  81. this._initializeRange(data, this.colX, graph3d, withBars);
  82. this._initializeRange(data, this.colY, graph3d, withBars);
  83. this._initializeRange(data, this.colZ, graph3d, false);
  84. if (data[0].hasOwnProperty('style')) {
  85. this.colValue = 'style';
  86. var valueRange = this.getColumnRange(data, this.colValue);
  87. this._setRangeDefaults(valueRange, graph3d.defaultValueMin, graph3d.defaultValueMax);
  88. this.valueRange = valueRange;
  89. }
  90. };
  91. /**
  92. * Collect the range settings for the given data column.
  93. *
  94. * This internal method is intended to make the range
  95. * initalization more generic.
  96. *
  97. * TODO: if/when combined settings per axis defined, get rid of this.
  98. *
  99. * @private
  100. *
  101. * @param {'x'|'y'|'z'} column The data column to process
  102. * @param {Graph3D} graph3d Reference to the calling Graph3D instance;
  103. * required for access to settings
  104. */
  105. DataGroup.prototype._collectRangeSettings = function(column, graph3d) {
  106. var index = ['x', 'y', 'z'].indexOf(column);
  107. if (index == -1) {
  108. throw new Error('Column \'' + column + '\' invalid');
  109. }
  110. var upper = column.toUpperCase();
  111. return {
  112. barWidth : this[column + 'BarWidth'],
  113. min : graph3d['default' + upper + 'Min'],
  114. max : graph3d['default' + upper + 'Max'],
  115. step : graph3d['default' + upper + 'Step'],
  116. range_label: column + 'Range', // Name of instance field to write to
  117. step_label : column + 'Step' // Name of instance field to write to
  118. };
  119. }
  120. /**
  121. * Initializes the settings per given column.
  122. *
  123. * TODO: if/when combined settings per axis defined, rewrite this.
  124. *
  125. * @private
  126. *
  127. * @param {DataSet | DataView} data The data containing the items for the Graph
  128. * @param {'x'|'y'|'z'} column The data column to process
  129. * @param {Graph3D} graph3d Reference to the calling Graph3D instance;
  130. * required for access to settings
  131. * @param {Boolean} withBars True if initializing for bar graph
  132. */
  133. DataGroup.prototype._initializeRange = function(data, column, graph3d, withBars) {
  134. var NUMSTEPS = 5;
  135. var settings = this._collectRangeSettings(column, graph3d);
  136. var range = this.getColumnRange(data, column);
  137. if (withBars && column != 'z') { // Safeguard for 'z'; it doesn't have a bar width
  138. range.expand(settings.barWidth / 2);
  139. }
  140. this._setRangeDefaults(range, settings.min, settings.max);
  141. this[settings.range_label] = range;
  142. this[settings.step_label ] = (settings.step !== undefined) ? settings.step : range.range()/NUMSTEPS;
  143. }
  144. /**
  145. * Creates a list with all the different values in the data for the given column.
  146. *
  147. * If no data passed, use the internal data of this instance.
  148. *
  149. * @param {'x'|'y'|'z'} column The data column to process
  150. * @param {DataSet|DataView|undefined} data The data containing the items for the Graph
  151. *
  152. * @returns {Array} All distinct values in the given column data, sorted ascending.
  153. */
  154. DataGroup.prototype.getDistinctValues = function(column, data) {
  155. if (data === undefined) {
  156. data = this.dataTable;
  157. }
  158. var values = [];
  159. for (var i = 0; i < data.length; i++) {
  160. var value = data[i][column] || 0;
  161. if (values.indexOf(value) === -1) {
  162. values.push(value);
  163. }
  164. }
  165. return values.sort(function(a,b) { return a - b; });
  166. };
  167. /**
  168. * Determine the smallest difference between the values for given
  169. * column in the passed data set.
  170. *
  171. * @param {DataSet|DataView|undefined} data The data containing the items for the Graph
  172. * @param {'x'|'y'|'z'} column The data column to process
  173. *
  174. * @returns {Number|null} Smallest difference value or
  175. * null, if it can't be determined.
  176. */
  177. DataGroup.prototype.getSmallestDifference = function(data, column) {
  178. var values = this.getDistinctValues(data, column);
  179. // Get all the distinct diffs
  180. // Array values is assumed to be sorted here
  181. var smallest_diff = null;
  182. for (var i = 1; i < values.length; i++) {
  183. var diff = values[i] - values[i - 1];
  184. if (smallest_diff == null || smallest_diff > diff ) {
  185. smallest_diff = diff;
  186. }
  187. }
  188. return smallest_diff;
  189. }
  190. /**
  191. * Get the absolute min/max values for the passed data column.
  192. *
  193. * @param {DataSet|DataView|undefined} data The data containing the items for the Graph
  194. * @param {'x'|'y'|'z'} column The data column to process
  195. *
  196. * @returns {Range} A Range instance with min/max members properly set.
  197. */
  198. DataGroup.prototype.getColumnRange = function(data, column) {
  199. var range = new Range();
  200. // Adjust the range so that it covers all values in the passed data elements.
  201. for (var i = 0; i < data.length; i++) {
  202. var item = data[i][column];
  203. range.adjust(item);
  204. }
  205. return range;
  206. };
  207. /**
  208. * Determines the number of rows in the current data.
  209. *
  210. * @returns {Number}
  211. */
  212. DataGroup.prototype.getNumberOfRows = function() {
  213. return this.dataTable.length;
  214. }
  215. /**
  216. * Set default values for range
  217. *
  218. * The default values override the range values, if defined.
  219. *
  220. * Because it's possible that only defaultMin or defaultMax is set, it's better
  221. * to pass in a range already set with the min/max set from the data. Otherwise,
  222. * it's quite hard to process the min/max properly.
  223. */
  224. DataGroup.prototype._setRangeDefaults = function (range, defaultMin, defaultMax) {
  225. if (defaultMin !== undefined) {
  226. range.min = defaultMin;
  227. }
  228. if (defaultMax !== undefined) {
  229. range.max = defaultMax;
  230. }
  231. // This is the original way that the default min/max values were adjusted.
  232. // TODO: Perhaps it's better if an error is thrown if the values do not agree.
  233. // But this will change the behaviour.
  234. if (range.max <= range.min) range.max = range.min + 1;
  235. };
  236. DataGroup.prototype.getDataTable = function() {
  237. return this.dataTable;
  238. };
  239. DataGroup.prototype.getDataSet = function() {
  240. return this.dataSet;
  241. };
  242. module.exports = DataGroup;