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.

948 lines
25 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. var util = require('./util');
  2. /**
  3. * DataSet
  4. *
  5. * Usage:
  6. * var dataSet = new DataSet({
  7. * fieldId: '_id',
  8. * type: {
  9. * // ...
  10. * }
  11. * });
  12. *
  13. * dataSet.add(item);
  14. * dataSet.add(data);
  15. * dataSet.update(item);
  16. * dataSet.update(data);
  17. * dataSet.remove(id);
  18. * dataSet.remove(ids);
  19. * var data = dataSet.get();
  20. * var data = dataSet.get(id);
  21. * var data = dataSet.get(ids);
  22. * var data = dataSet.get(ids, options, data);
  23. * dataSet.clear();
  24. *
  25. * A data set can:
  26. * - add/remove/update data
  27. * - gives triggers upon changes in the data
  28. * - can import/export data in various data formats
  29. *
  30. * @param {Array | DataTable} [data] Optional array with initial data
  31. * @param {Object} [options] Available options:
  32. * {String} fieldId Field name of the id in the
  33. * items, 'id' by default.
  34. * {Object.<String, String} type
  35. * A map with field names as key,
  36. * and the field type as value.
  37. * @constructor DataSet
  38. */
  39. // TODO: add a DataSet constructor DataSet(data, options)
  40. function DataSet (data, options) {
  41. // correctly read optional arguments
  42. if (data && !Array.isArray(data) && !util.isDataTable(data)) {
  43. options = data;
  44. data = null;
  45. }
  46. this._options = options || {};
  47. this._data = {}; // map with data indexed by id
  48. this._fieldId = this._options.fieldId || 'id'; // name of the field containing id
  49. this._type = {}; // internal field types (NOTE: this can differ from this._options.type)
  50. // all variants of a Date are internally stored as Date, so we can convert
  51. // from everything to everything (also from ISODate to Number for example)
  52. if (this._options.type) {
  53. for (var field in this._options.type) {
  54. if (this._options.type.hasOwnProperty(field)) {
  55. var value = this._options.type[field];
  56. if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
  57. this._type[field] = 'Date';
  58. }
  59. else {
  60. this._type[field] = value;
  61. }
  62. }
  63. }
  64. }
  65. // TODO: deprecated since version 1.1.1 (or 2.0.0?)
  66. if (this._options.convert) {
  67. throw new Error('Option "convert" is deprecated. Use "type" instead.');
  68. }
  69. this._subscribers = {}; // event subscribers
  70. // add initial data when provided
  71. if (data) {
  72. this.add(data);
  73. }
  74. }
  75. /**
  76. * Subscribe to an event, add an event listener
  77. * @param {String} event Event name. Available events: 'put', 'update',
  78. * 'remove'
  79. * @param {function} callback Callback method. Called with three parameters:
  80. * {String} event
  81. * {Object | null} params
  82. * {String | Number} senderId
  83. */
  84. DataSet.prototype.on = function(event, callback) {
  85. var subscribers = this._subscribers[event];
  86. if (!subscribers) {
  87. subscribers = [];
  88. this._subscribers[event] = subscribers;
  89. }
  90. subscribers.push({
  91. callback: callback
  92. });
  93. };
  94. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  95. DataSet.prototype.subscribe = DataSet.prototype.on;
  96. /**
  97. * Unsubscribe from an event, remove an event listener
  98. * @param {String} event
  99. * @param {function} callback
  100. */
  101. DataSet.prototype.off = function(event, callback) {
  102. var subscribers = this._subscribers[event];
  103. if (subscribers) {
  104. this._subscribers[event] = subscribers.filter(function (listener) {
  105. return (listener.callback != callback);
  106. });
  107. }
  108. };
  109. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  110. DataSet.prototype.unsubscribe = DataSet.prototype.off;
  111. /**
  112. * Trigger an event
  113. * @param {String} event
  114. * @param {Object | null} params
  115. * @param {String} [senderId] Optional id of the sender.
  116. * @private
  117. */
  118. DataSet.prototype._trigger = function (event, params, senderId) {
  119. if (event == '*') {
  120. throw new Error('Cannot trigger event *');
  121. }
  122. var subscribers = [];
  123. if (event in this._subscribers) {
  124. subscribers = subscribers.concat(this._subscribers[event]);
  125. }
  126. if ('*' in this._subscribers) {
  127. subscribers = subscribers.concat(this._subscribers['*']);
  128. }
  129. for (var i = 0; i < subscribers.length; i++) {
  130. var subscriber = subscribers[i];
  131. if (subscriber.callback) {
  132. subscriber.callback(event, params, senderId || null);
  133. }
  134. }
  135. };
  136. /**
  137. * Add data.
  138. * Adding an item will fail when there already is an item with the same id.
  139. * @param {Object | Array | DataTable} data
  140. * @param {String} [senderId] Optional sender id
  141. * @return {Array} addedIds Array with the ids of the added items
  142. */
  143. DataSet.prototype.add = function (data, senderId) {
  144. var addedIds = [],
  145. id,
  146. me = this;
  147. if (Array.isArray(data)) {
  148. // Array
  149. for (var i = 0, len = data.length; i < len; i++) {
  150. id = me._addItem(data[i]);
  151. addedIds.push(id);
  152. }
  153. }
  154. else if (util.isDataTable(data)) {
  155. // Google DataTable
  156. var columns = this._getColumnNames(data);
  157. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  158. var item = {};
  159. for (var col = 0, cols = columns.length; col < cols; col++) {
  160. var field = columns[col];
  161. item[field] = data.getValue(row, col);
  162. }
  163. id = me._addItem(item);
  164. addedIds.push(id);
  165. }
  166. }
  167. else if (data instanceof Object) {
  168. // Single item
  169. id = me._addItem(data);
  170. addedIds.push(id);
  171. }
  172. else {
  173. throw new Error('Unknown dataType');
  174. }
  175. if (addedIds.length) {
  176. this._trigger('add', {items: addedIds}, senderId);
  177. }
  178. return addedIds;
  179. };
  180. /**
  181. * Update existing items. When an item does not exist, it will be created
  182. * @param {Object | Array | DataTable} data
  183. * @param {String} [senderId] Optional sender id
  184. * @return {Array} updatedIds The ids of the added or updated items
  185. */
  186. DataSet.prototype.update = function (data, senderId) {
  187. var addedIds = [];
  188. var updatedIds = [];
  189. var updatedData = [];
  190. var me = this;
  191. var fieldId = me._fieldId;
  192. var addOrUpdate = function (item) {
  193. var id = item[fieldId];
  194. if (me._data[id]) {
  195. // update item
  196. id = me._updateItem(item);
  197. updatedIds.push(id);
  198. updatedData.push(item);
  199. }
  200. else {
  201. // add new item
  202. id = me._addItem(item);
  203. addedIds.push(id);
  204. }
  205. };
  206. if (Array.isArray(data)) {
  207. // Array
  208. for (var i = 0, len = data.length; i < len; i++) {
  209. addOrUpdate(data[i]);
  210. }
  211. }
  212. else if (util.isDataTable(data)) {
  213. // Google DataTable
  214. var columns = this._getColumnNames(data);
  215. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  216. var item = {};
  217. for (var col = 0, cols = columns.length; col < cols; col++) {
  218. var field = columns[col];
  219. item[field] = data.getValue(row, col);
  220. }
  221. addOrUpdate(item);
  222. }
  223. }
  224. else if (data instanceof Object) {
  225. // Single item
  226. addOrUpdate(data);
  227. }
  228. else {
  229. throw new Error('Unknown dataType');
  230. }
  231. if (addedIds.length) {
  232. this._trigger('add', {items: addedIds}, senderId);
  233. }
  234. if (updatedIds.length) {
  235. this._trigger('update', {items: updatedIds, data: updatedData}, senderId);
  236. }
  237. return addedIds.concat(updatedIds);
  238. };
  239. /**
  240. * Get a data item or multiple items.
  241. *
  242. * Usage:
  243. *
  244. * get()
  245. * get(options: Object)
  246. * get(options: Object, data: Array | DataTable)
  247. *
  248. * get(id: Number | String)
  249. * get(id: Number | String, options: Object)
  250. * get(id: Number | String, options: Object, data: Array | DataTable)
  251. *
  252. * get(ids: Number[] | String[])
  253. * get(ids: Number[] | String[], options: Object)
  254. * get(ids: Number[] | String[], options: Object, data: Array | DataTable)
  255. *
  256. * Where:
  257. *
  258. * {Number | String} id The id of an item
  259. * {Number[] | String{}} ids An array with ids of items
  260. * {Object} options An Object with options. Available options:
  261. * {String} [returnType] Type of data to be
  262. * returned. Can be 'DataTable' or 'Array' (default)
  263. * {Object.<String, String>} [type]
  264. * {String[]} [fields] field names to be returned
  265. * {function} [filter] filter items
  266. * {String | function} [order] Order the items by
  267. * a field name or custom sort function.
  268. * {Array | DataTable} [data] If provided, items will be appended to this
  269. * array or table. Required in case of Google
  270. * DataTable.
  271. *
  272. * @throws Error
  273. */
  274. DataSet.prototype.get = function (args) {
  275. var me = this;
  276. // parse the arguments
  277. var id, ids, options, data;
  278. var firstType = util.getType(arguments[0]);
  279. if (firstType == 'String' || firstType == 'Number') {
  280. // get(id [, options] [, data])
  281. id = arguments[0];
  282. options = arguments[1];
  283. data = arguments[2];
  284. }
  285. else if (firstType == 'Array') {
  286. // get(ids [, options] [, data])
  287. ids = arguments[0];
  288. options = arguments[1];
  289. data = arguments[2];
  290. }
  291. else {
  292. // get([, options] [, data])
  293. options = arguments[0];
  294. data = arguments[1];
  295. }
  296. // determine the return type
  297. var returnType;
  298. if (options && options.returnType) {
  299. var allowedValues = ["DataTable", "Array", "Object"];
  300. returnType = allowedValues.indexOf(options.returnType) == -1 ? "Array" : options.returnType;
  301. if (data && (returnType != util.getType(data))) {
  302. throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' +
  303. 'does not correspond with specified options.type (' + options.type + ')');
  304. }
  305. if (returnType == 'DataTable' && !util.isDataTable(data)) {
  306. throw new Error('Parameter "data" must be a DataTable ' +
  307. 'when options.type is "DataTable"');
  308. }
  309. }
  310. else if (data) {
  311. returnType = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array';
  312. }
  313. else {
  314. returnType = 'Array';
  315. }
  316. // build options
  317. var type = options && options.type || this._options.type;
  318. var filter = options && options.filter;
  319. var items = [], item, itemId, i, len;
  320. // convert items
  321. if (id != undefined) {
  322. // return a single item
  323. item = me._getItem(id, type);
  324. if (filter && !filter(item)) {
  325. item = null;
  326. }
  327. }
  328. else if (ids != undefined) {
  329. // return a subset of items
  330. for (i = 0, len = ids.length; i < len; i++) {
  331. item = me._getItem(ids[i], type);
  332. if (!filter || filter(item)) {
  333. items.push(item);
  334. }
  335. }
  336. }
  337. else {
  338. // return all items
  339. for (itemId in this._data) {
  340. if (this._data.hasOwnProperty(itemId)) {
  341. item = me._getItem(itemId, type);
  342. if (!filter || filter(item)) {
  343. items.push(item);
  344. }
  345. }
  346. }
  347. }
  348. // order the results
  349. if (options && options.order && id == undefined) {
  350. this._sort(items, options.order);
  351. }
  352. // filter fields of the items
  353. if (options && options.fields) {
  354. var fields = options.fields;
  355. if (id != undefined) {
  356. item = this._filterFields(item, fields);
  357. }
  358. else {
  359. for (i = 0, len = items.length; i < len; i++) {
  360. items[i] = this._filterFields(items[i], fields);
  361. }
  362. }
  363. }
  364. // return the results
  365. if (returnType == 'DataTable') {
  366. var columns = this._getColumnNames(data);
  367. if (id != undefined) {
  368. // append a single item to the data table
  369. me._appendRow(data, columns, item);
  370. }
  371. else {
  372. // copy the items to the provided data table
  373. for (i = 0; i < items.length; i++) {
  374. me._appendRow(data, columns, items[i]);
  375. }
  376. }
  377. return data;
  378. }
  379. else if (returnType == "Object") {
  380. var result = {};
  381. for (i = 0; i < items.length; i++) {
  382. result[items[i].id] = items[i];
  383. }
  384. return result;
  385. }
  386. else {
  387. // return an array
  388. if (id != undefined) {
  389. // a single item
  390. return item;
  391. }
  392. else {
  393. // multiple items
  394. if (data) {
  395. // copy the items to the provided array
  396. for (i = 0, len = items.length; i < len; i++) {
  397. data.push(items[i]);
  398. }
  399. return data;
  400. }
  401. else {
  402. // just return our array
  403. return items;
  404. }
  405. }
  406. }
  407. };
  408. /**
  409. * Get ids of all items or from a filtered set of items.
  410. * @param {Object} [options] An Object with options. Available options:
  411. * {function} [filter] filter items
  412. * {String | function} [order] Order the items by
  413. * a field name or custom sort function.
  414. * @return {Array} ids
  415. */
  416. DataSet.prototype.getIds = function (options) {
  417. var data = this._data,
  418. filter = options && options.filter,
  419. order = options && options.order,
  420. type = options && options.type || this._options.type,
  421. i,
  422. len,
  423. id,
  424. item,
  425. items,
  426. ids = [];
  427. if (filter) {
  428. // get filtered items
  429. if (order) {
  430. // create ordered list
  431. items = [];
  432. for (id in data) {
  433. if (data.hasOwnProperty(id)) {
  434. item = this._getItem(id, type);
  435. if (filter(item)) {
  436. items.push(item);
  437. }
  438. }
  439. }
  440. this._sort(items, order);
  441. for (i = 0, len = items.length; i < len; i++) {
  442. ids[i] = items[i][this._fieldId];
  443. }
  444. }
  445. else {
  446. // create unordered list
  447. for (id in data) {
  448. if (data.hasOwnProperty(id)) {
  449. item = this._getItem(id, type);
  450. if (filter(item)) {
  451. ids.push(item[this._fieldId]);
  452. }
  453. }
  454. }
  455. }
  456. }
  457. else {
  458. // get all items
  459. if (order) {
  460. // create an ordered list
  461. items = [];
  462. for (id in data) {
  463. if (data.hasOwnProperty(id)) {
  464. items.push(data[id]);
  465. }
  466. }
  467. this._sort(items, order);
  468. for (i = 0, len = items.length; i < len; i++) {
  469. ids[i] = items[i][this._fieldId];
  470. }
  471. }
  472. else {
  473. // create unordered list
  474. for (id in data) {
  475. if (data.hasOwnProperty(id)) {
  476. item = data[id];
  477. ids.push(item[this._fieldId]);
  478. }
  479. }
  480. }
  481. }
  482. return ids;
  483. };
  484. /**
  485. * Returns the DataSet itself. Is overwritten for example by the DataView,
  486. * which returns the DataSet it is connected to instead.
  487. */
  488. DataSet.prototype.getDataSet = function () {
  489. return this;
  490. };
  491. /**
  492. * Execute a callback function for every item in the dataset.
  493. * @param {function} callback
  494. * @param {Object} [options] Available options:
  495. * {Object.<String, String>} [type]
  496. * {String[]} [fields] filter fields
  497. * {function} [filter] filter items
  498. * {String | function} [order] Order the items by
  499. * a field name or custom sort function.
  500. */
  501. DataSet.prototype.forEach = function (callback, options) {
  502. var filter = options && options.filter,
  503. type = options && options.type || this._options.type,
  504. data = this._data,
  505. item,
  506. id;
  507. if (options && options.order) {
  508. // execute forEach on ordered list
  509. var items = this.get(options);
  510. for (var i = 0, len = items.length; i < len; i++) {
  511. item = items[i];
  512. id = item[this._fieldId];
  513. callback(item, id);
  514. }
  515. }
  516. else {
  517. // unordered
  518. for (id in data) {
  519. if (data.hasOwnProperty(id)) {
  520. item = this._getItem(id, type);
  521. if (!filter || filter(item)) {
  522. callback(item, id);
  523. }
  524. }
  525. }
  526. }
  527. };
  528. /**
  529. * Map every item in the dataset.
  530. * @param {function} callback
  531. * @param {Object} [options] Available options:
  532. * {Object.<String, String>} [type]
  533. * {String[]} [fields] filter fields
  534. * {function} [filter] filter items
  535. * {String | function} [order] Order the items by
  536. * a field name or custom sort function.
  537. * @return {Object[]} mappedItems
  538. */
  539. DataSet.prototype.map = function (callback, options) {
  540. var filter = options && options.filter,
  541. type = options && options.type || this._options.type,
  542. mappedItems = [],
  543. data = this._data,
  544. item;
  545. // convert and filter items
  546. for (var id in data) {
  547. if (data.hasOwnProperty(id)) {
  548. item = this._getItem(id, type);
  549. if (!filter || filter(item)) {
  550. mappedItems.push(callback(item, id));
  551. }
  552. }
  553. }
  554. // order items
  555. if (options && options.order) {
  556. this._sort(mappedItems, options.order);
  557. }
  558. return mappedItems;
  559. };
  560. /**
  561. * Filter the fields of an item
  562. * @param {Object} item
  563. * @param {String[]} fields Field names
  564. * @return {Object} filteredItem
  565. * @private
  566. */
  567. DataSet.prototype._filterFields = function (item, fields) {
  568. var filteredItem = {};
  569. for (var field in item) {
  570. if (item.hasOwnProperty(field) && (fields.indexOf(field) != -1)) {
  571. filteredItem[field] = item[field];
  572. }
  573. }
  574. return filteredItem;
  575. };
  576. /**
  577. * Sort the provided array with items
  578. * @param {Object[]} items
  579. * @param {String | function} order A field name or custom sort function.
  580. * @private
  581. */
  582. DataSet.prototype._sort = function (items, order) {
  583. if (util.isString(order)) {
  584. // order by provided field name
  585. var name = order; // field name
  586. items.sort(function (a, b) {
  587. var av = a[name];
  588. var bv = b[name];
  589. return (av > bv) ? 1 : ((av < bv) ? -1 : 0);
  590. });
  591. }
  592. else if (typeof order === 'function') {
  593. // order by sort function
  594. items.sort(order);
  595. }
  596. // TODO: extend order by an Object {field:String, direction:String}
  597. // where direction can be 'asc' or 'desc'
  598. else {
  599. throw new TypeError('Order must be a function or a string');
  600. }
  601. };
  602. /**
  603. * Remove an object by pointer or by id
  604. * @param {String | Number | Object | Array} id Object or id, or an array with
  605. * objects or ids to be removed
  606. * @param {String} [senderId] Optional sender id
  607. * @return {Array} removedIds
  608. */
  609. DataSet.prototype.remove = function (id, senderId) {
  610. var removedIds = [],
  611. i, len, removedId;
  612. if (Array.isArray(id)) {
  613. for (i = 0, len = id.length; i < len; i++) {
  614. removedId = this._remove(id[i]);
  615. if (removedId != null) {
  616. removedIds.push(removedId);
  617. }
  618. }
  619. }
  620. else {
  621. removedId = this._remove(id);
  622. if (removedId != null) {
  623. removedIds.push(removedId);
  624. }
  625. }
  626. if (removedIds.length) {
  627. this._trigger('remove', {items: removedIds}, senderId);
  628. }
  629. return removedIds;
  630. };
  631. /**
  632. * Remove an item by its id
  633. * @param {Number | String | Object} id id or item
  634. * @returns {Number | String | null} id
  635. * @private
  636. */
  637. DataSet.prototype._remove = function (id) {
  638. if (util.isNumber(id) || util.isString(id)) {
  639. if (this._data[id]) {
  640. delete this._data[id];
  641. return id;
  642. }
  643. }
  644. else if (id instanceof Object) {
  645. var itemId = id[this._fieldId];
  646. if (itemId && this._data[itemId]) {
  647. delete this._data[itemId];
  648. return itemId;
  649. }
  650. }
  651. return null;
  652. };
  653. /**
  654. * Clear the data
  655. * @param {String} [senderId] Optional sender id
  656. * @return {Array} removedIds The ids of all removed items
  657. */
  658. DataSet.prototype.clear = function (senderId) {
  659. var ids = Object.keys(this._data);
  660. this._data = {};
  661. this._trigger('remove', {items: ids}, senderId);
  662. return ids;
  663. };
  664. /**
  665. * Find the item with maximum value of a specified field
  666. * @param {String} field
  667. * @return {Object | null} item Item containing max value, or null if no items
  668. */
  669. DataSet.prototype.max = function (field) {
  670. var data = this._data,
  671. max = null,
  672. maxField = null;
  673. for (var id in data) {
  674. if (data.hasOwnProperty(id)) {
  675. var item = data[id];
  676. var itemField = item[field];
  677. if (itemField != null && (!max || itemField > maxField)) {
  678. max = item;
  679. maxField = itemField;
  680. }
  681. }
  682. }
  683. return max;
  684. };
  685. /**
  686. * Find the item with minimum value of a specified field
  687. * @param {String} field
  688. * @return {Object | null} item Item containing max value, or null if no items
  689. */
  690. DataSet.prototype.min = function (field) {
  691. var data = this._data,
  692. min = null,
  693. minField = null;
  694. for (var id in data) {
  695. if (data.hasOwnProperty(id)) {
  696. var item = data[id];
  697. var itemField = item[field];
  698. if (itemField != null && (!min || itemField < minField)) {
  699. min = item;
  700. minField = itemField;
  701. }
  702. }
  703. }
  704. return min;
  705. };
  706. /**
  707. * Find all distinct values of a specified field
  708. * @param {String} field
  709. * @return {Array} values Array containing all distinct values. If data items
  710. * do not contain the specified field are ignored.
  711. * The returned array is unordered.
  712. */
  713. DataSet.prototype.distinct = function (field) {
  714. var data = this._data;
  715. var values = [];
  716. var fieldType = this._options.type && this._options.type[field] || null;
  717. var count = 0;
  718. var i;
  719. for (var prop in data) {
  720. if (data.hasOwnProperty(prop)) {
  721. var item = data[prop];
  722. var value = item[field];
  723. var exists = false;
  724. for (i = 0; i < count; i++) {
  725. if (values[i] == value) {
  726. exists = true;
  727. break;
  728. }
  729. }
  730. if (!exists && (value !== undefined)) {
  731. values[count] = value;
  732. count++;
  733. }
  734. }
  735. }
  736. if (fieldType) {
  737. for (i = 0; i < values.length; i++) {
  738. values[i] = util.convert(values[i], fieldType);
  739. }
  740. }
  741. return values;
  742. };
  743. /**
  744. * Add a single item. Will fail when an item with the same id already exists.
  745. * @param {Object} item
  746. * @return {String} id
  747. * @private
  748. */
  749. DataSet.prototype._addItem = function (item) {
  750. var id = item[this._fieldId];
  751. if (id != undefined) {
  752. // check whether this id is already taken
  753. if (this._data[id]) {
  754. // item already exists
  755. throw new Error('Cannot add item: item with id ' + id + ' already exists');
  756. }
  757. }
  758. else {
  759. // generate an id
  760. id = util.randomUUID();
  761. item[this._fieldId] = id;
  762. }
  763. var d = {};
  764. for (var field in item) {
  765. if (item.hasOwnProperty(field)) {
  766. var fieldType = this._type[field]; // type may be undefined
  767. d[field] = util.convert(item[field], fieldType);
  768. }
  769. }
  770. this._data[id] = d;
  771. return id;
  772. };
  773. /**
  774. * Get an item. Fields can be converted to a specific type
  775. * @param {String} id
  776. * @param {Object.<String, String>} [types] field types to convert
  777. * @return {Object | null} item
  778. * @private
  779. */
  780. DataSet.prototype._getItem = function (id, types) {
  781. var field, value;
  782. // get the item from the dataset
  783. var raw = this._data[id];
  784. if (!raw) {
  785. return null;
  786. }
  787. // convert the items field types
  788. var converted = {};
  789. if (types) {
  790. for (field in raw) {
  791. if (raw.hasOwnProperty(field)) {
  792. value = raw[field];
  793. converted[field] = util.convert(value, types[field]);
  794. }
  795. }
  796. }
  797. else {
  798. // no field types specified, no converting needed
  799. for (field in raw) {
  800. if (raw.hasOwnProperty(field)) {
  801. value = raw[field];
  802. converted[field] = value;
  803. }
  804. }
  805. }
  806. return converted;
  807. };
  808. /**
  809. * Update a single item: merge with existing item.
  810. * Will fail when the item has no id, or when there does not exist an item
  811. * with the same id.
  812. * @param {Object} item
  813. * @return {String} id
  814. * @private
  815. */
  816. DataSet.prototype._updateItem = function (item) {
  817. var id = item[this._fieldId];
  818. if (id == undefined) {
  819. throw new Error('Cannot update item: item has no id (item: ' + JSON.stringify(item) + ')');
  820. }
  821. var d = this._data[id];
  822. if (!d) {
  823. // item doesn't exist
  824. throw new Error('Cannot update item: no item with id ' + id + ' found');
  825. }
  826. // merge with current item
  827. for (var field in item) {
  828. if (item.hasOwnProperty(field)) {
  829. var fieldType = this._type[field]; // type may be undefined
  830. d[field] = util.convert(item[field], fieldType);
  831. }
  832. }
  833. return id;
  834. };
  835. /**
  836. * Get an array with the column names of a Google DataTable
  837. * @param {DataTable} dataTable
  838. * @return {String[]} columnNames
  839. * @private
  840. */
  841. DataSet.prototype._getColumnNames = function (dataTable) {
  842. var columns = [];
  843. for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) {
  844. columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col);
  845. }
  846. return columns;
  847. };
  848. /**
  849. * Append an item as a row to the dataTable
  850. * @param dataTable
  851. * @param columns
  852. * @param item
  853. * @private
  854. */
  855. DataSet.prototype._appendRow = function (dataTable, columns, item) {
  856. var row = dataTable.addRow();
  857. for (var col = 0, cols = columns.length; col < cols; col++) {
  858. var field = columns[col];
  859. dataTable.setValue(row, col, item[field]);
  860. }
  861. };
  862. module.exports = DataSet;