Chart/api/flow.ts

  1. /**
  2. * Copyright (c) 2017 ~ present NAVER Corp.
  3. * billboard.js project is licensed under the MIT license
  4. */
  5. import {isDefined, isTabVisible, isValue, parseDate} from "../../module/util";
  6. export default {
  7. /**
  8. * Flow data to the chart.<br><br>
  9. * By this API, you can append new data points to the chart.
  10. * @function flow
  11. * @instance
  12. * @memberof Chart
  13. * @param {object} args The object can consist with following members:<br>
  14. *
  15. * | Key | Type | Description |
  16. * | --- | --- | --- |
  17. * | json | Object | Data as JSON format (@see [data․json](Options.html#.data%25E2%2580%25A4json)) |
  18. * | rows | Array | Data in array as row format (@see [data․rows](Options.html#.data%25E2%2580%25A4json)) |
  19. * | columns | Array | Data in array as column format (@see [data․columns](Options.html#.data%25E2%2580%25A4columns)) |
  20. * | to | String | The lower x edge will move to that point. If not given, the lower x edge will move by the number of given data points |
  21. * | length | Number | The lower x edge will move by the number of this argument |
  22. * | duration | Number | The duration of the transition will be specified value. If not given, transition.duration will be used as default |
  23. * | done | Function | The specified function will be called when flow ends |
  24. *
  25. * - **NOTE:**
  26. * - If json, rows and columns given, the data will be loaded.
  27. * - If data that has the same target id is given, the chart will be appended.
  28. * - Otherwise, new target will be added. One of these is required when calling.
  29. * - If json specified, keys is required as well as data.json.
  30. * - If tab isn't visible(by evaluating `document.hidden`), will not be executed to prevent unnecessary work.
  31. * @example
  32. * // 2 data points will be apprended to the tail and popped from the head.
  33. * // After that, 4 data points will be appended and no data points will be poppoed.
  34. * chart.flow({
  35. * columns: [
  36. * ["x", "2018-01-11", "2018-01-21"],
  37. * ["data1", 500, 200],
  38. * ["data2", 100, 300],
  39. * ["data3", 200, 120]
  40. * ],
  41. * to: "2013-01-11",
  42. * done: function () {
  43. * chart.flow({
  44. * columns: [
  45. * ["x", "2018-02-11", "2018-02-12", "2018-02-13", "2018-02-14"],
  46. * ["data1", 200, 300, 100, 250],
  47. * ["data2", 100, 90, 40, 120],
  48. * ["data3", 100, 100, 300, 500]
  49. * ],
  50. * length: 2,
  51. * duration: 1500
  52. * });
  53. * }
  54. * });
  55. */
  56. flow(args): void {
  57. const $$ = this.internal;
  58. let data;
  59. if (args.json || args.rows || args.columns) {
  60. $$.convertData(args, res => {
  61. data = res;
  62. _();
  63. });
  64. }
  65. /**
  66. * Process flows
  67. * @private
  68. */
  69. function _(): void {
  70. let domain;
  71. let length: number = 0;
  72. let tail = 0;
  73. let diff;
  74. let to;
  75. if ($$.state.redrawing || !data || !isTabVisible()) {
  76. return;
  77. }
  78. const notfoundIds: string[] = [];
  79. const orgDataCount = $$.getMaxDataCount();
  80. const targets = $$.convertDataToTargets(data, true);
  81. const isTimeSeries = $$.axis.isTimeSeries();
  82. // Update/Add data
  83. $$.data.targets.forEach(t => {
  84. let found = false;
  85. for (let i = 0; i < targets.length; i++) {
  86. if (t.id === targets[i].id) {
  87. found = true;
  88. if (t.values[t.values.length - 1]) {
  89. tail = t.values[t.values.length - 1].index + 1;
  90. }
  91. length = targets[i].values.length;
  92. for (let j = 0; j < length; j++) {
  93. targets[i].values[j].index = tail + j;
  94. if (!isTimeSeries) {
  95. targets[i].values[j].x = tail + j;
  96. }
  97. }
  98. t.values = t.values.concat(targets[i].values);
  99. targets.splice(i, 1);
  100. break;
  101. }
  102. }
  103. !found && notfoundIds.push(t.id);
  104. });
  105. // Append null for not found targets
  106. $$.data.targets.forEach(t => {
  107. for (let i = 0; i < notfoundIds.length; i++) {
  108. if (t.id === notfoundIds[i]) {
  109. tail = t.values[t.values.length - 1].index + 1;
  110. for (let j = 0; j < length; j++) {
  111. t.values.push({
  112. id: t.id,
  113. index: tail + j,
  114. x: isTimeSeries ? $$.getOtherTargetX(tail + j) : tail + j,
  115. value: null
  116. });
  117. }
  118. }
  119. }
  120. });
  121. // Generate null values for new target
  122. if ($$.data.targets.length) {
  123. targets.forEach(t => {
  124. const missing: any[] = [];
  125. for (let i = $$.data.targets[0].values[0].index; i < tail; i++) {
  126. missing.push({
  127. id: t.id,
  128. index: i,
  129. x: isTimeSeries ? $$.getOtherTargetX(i) : i,
  130. value: null
  131. });
  132. }
  133. t.values.forEach(v => {
  134. v.index += tail;
  135. if (!isTimeSeries) {
  136. v.x += tail;
  137. }
  138. });
  139. t.values = missing.concat(t.values);
  140. });
  141. }
  142. $$.data.targets = $$.data.targets.concat(targets); // add remained
  143. // check data count because behavior needs to change when it"s only one
  144. // const dataCount = $$.getMaxDataCount();
  145. const baseTarget = $$.data.targets[0];
  146. const baseValue = baseTarget.values[0];
  147. // Update length to flow if needed
  148. if (isDefined(args.to)) {
  149. length = 0;
  150. to = isTimeSeries ? parseDate.call($$, args.to) : args.to;
  151. baseTarget.values.forEach(v => {
  152. v.x < to && length++;
  153. });
  154. } else if (isDefined(args.length)) {
  155. length = args.length;
  156. }
  157. // If only one data, update the domain to flow from left edge of the chart
  158. if (!orgDataCount) {
  159. if (isTimeSeries) {
  160. diff = baseTarget.values.length > 1 ?
  161. baseTarget.values[baseTarget.values.length - 1].x - baseValue.x :
  162. baseValue.x - $$.getXDomain($$.data.targets)[0];
  163. } else {
  164. diff = 1;
  165. }
  166. domain = [baseValue.x - diff, baseValue.x];
  167. } else if (orgDataCount === 1 && isTimeSeries) {
  168. diff = (baseTarget.values[baseTarget.values.length - 1].x - baseValue.x) / 2;
  169. domain = [new Date(+baseValue.x - diff), new Date(+baseValue.x + diff)];
  170. }
  171. domain && $$.updateXDomain(null, true, true, false, domain);
  172. // Set targets
  173. $$.updateTargets($$.data.targets);
  174. // Redraw with new targets
  175. $$.redraw({
  176. flow: {
  177. index: baseValue.index,
  178. length: length,
  179. duration: isValue(args.duration) ?
  180. args.duration :
  181. $$.config.transition_duration,
  182. done: args.done,
  183. orgDataCount: orgDataCount
  184. },
  185. withLegend: true,
  186. withTransition: orgDataCount > 1,
  187. withTrimXDomain: false,
  188. withUpdateXAxis: true
  189. });
  190. }
  191. }
  192. };