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.

307 lines
8.4 KiB

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