render.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { Canvas as GCanvas, Group } from '@antv/g';
  2. import { Renderer as CanvasRenderer } from '@antv/g-canvas';
  3. import { Plugin as DragAndDropPlugin } from '@antv/g-plugin-dragndrop';
  4. import { deepMix } from '@antv/util';
  5. import EventEmitter from '@antv/event-emitter';
  6. import { createLibrary } from '../stdlib';
  7. import { select } from '../utils/selection';
  8. import { ChartEvent } from '../utils/event';
  9. import { error } from '../utils/helper';
  10. import { plot } from './plot';
  11. import { VIEW_CLASS_NAME } from './constant';
  12. /**
  13. * Infer key for each node of view tree.
  14. * Each key should be unique in the entire view tree.
  15. * The key is for incremental render when view tree is changed.
  16. * @todo Fix custom key equals to inferred key.
  17. */
  18. function inferKeys(options) {
  19. const root = deepMix({}, options);
  20. const nodeParent = new Map([[root, null]]);
  21. const nodeIndex = new Map([[null, -1]]);
  22. const discovered = [root];
  23. while (discovered.length) {
  24. const node = discovered.shift();
  25. // If key of node is not specified, using parentKey and the index for it
  26. // in parent.children as its key.
  27. // e.g. The key of node named 'a' will be 'a', and the key of node named
  28. // 'b' will be 'parent-1' in the following view tree specification.
  29. // { key: 'parent', children: [{ name: 'a', key: 'a' }, { name: 'b' }] }
  30. if (node.key === undefined) {
  31. const parent = nodeParent.get(node);
  32. const index = nodeIndex.get(node);
  33. const key = parent === null ? `${0}` : `${parent.key}-${index}`;
  34. node.key = key;
  35. }
  36. const { children = [] } = node;
  37. if (Array.isArray(children)) {
  38. for (let i = 0; i < children.length; i++) {
  39. // Clone node as well.
  40. const child = deepMix({}, children[i]);
  41. children[i] = child;
  42. nodeParent.set(child, node);
  43. nodeIndex.set(child, i);
  44. discovered.push(child);
  45. }
  46. }
  47. }
  48. return root;
  49. }
  50. function Canvas(width, height) {
  51. const renderer = new CanvasRenderer();
  52. // DragAndDropPlugin is for interaction.
  53. renderer.registerPlugin(new DragAndDropPlugin());
  54. return new GCanvas({
  55. width,
  56. height,
  57. container: document.createElement('div'),
  58. renderer: renderer,
  59. });
  60. }
  61. export function render(options, context = {}, resolve = () => { }, reject = (e) => {
  62. throw e;
  63. }) {
  64. // Initialize the context if it is not provided.
  65. const { width = 640, height = 480, theme } = options;
  66. if (!theme) {
  67. error('ChartOptions.theme is required, such as `const chart = new Chart({ theme: "classic"})`.');
  68. }
  69. const keyed = inferKeys(options);
  70. const { canvas = Canvas(width, height), library = createLibrary(), emitter = new EventEmitter(), } = context;
  71. context.canvas = canvas;
  72. context.library = library;
  73. context.emitter = emitter;
  74. canvas.resize(width, height);
  75. emitter.emit(ChartEvent.BEFORE_RENDER);
  76. // Plot the chart and mutate context.
  77. // Make sure that plot chart after container is ready for every time.
  78. const selection = select(canvas.document.documentElement);
  79. canvas.ready
  80. .then(() => plot(Object.assign(Object.assign({}, keyed), { width, height }), selection, library, context))
  81. .then(() => {
  82. // Wait for the next tick.
  83. canvas.requestAnimationFrame(() => {
  84. emitter.emit(ChartEvent.AFTER_RENDER);
  85. resolve === null || resolve === void 0 ? void 0 : resolve();
  86. });
  87. })
  88. .catch((e) => {
  89. reject === null || reject === void 0 ? void 0 : reject(e);
  90. });
  91. // Return the container HTML element wraps the canvas or svg element.
  92. return normalizeContainer(canvas.getConfig().container);
  93. }
  94. export function renderToMountedElement(options, context = {}, resolve = () => { }, reject = (e) => {
  95. throw e;
  96. }) {
  97. // Initialize the context if it is not provided.
  98. const { width = 640, height = 480, on } = options;
  99. const keyed = inferKeys(options);
  100. const { library = createLibrary(), group = new Group(), emitter = new EventEmitter(), } = context;
  101. if (!(group === null || group === void 0 ? void 0 : group.parentElement)) {
  102. error(`renderToMountedElement can't render chart to unmounted group.`);
  103. }
  104. const selection = select(group);
  105. context.group = group;
  106. context.library = library;
  107. context.emitter = emitter;
  108. emitter.emit(ChartEvent.BEFORE_RENDER);
  109. // Plot the chart and mutate context.
  110. // Make sure that plot chart after container is ready for every time.
  111. plot(Object.assign(Object.assign({}, keyed), { width, height }), selection, library, context)
  112. .then(() => {
  113. const canvas = group.ownerDocument.defaultView;
  114. canvas.requestAnimationFrame(() => {
  115. emitter.emit(ChartEvent.AFTER_RENDER);
  116. resolve === null || resolve === void 0 ? void 0 : resolve();
  117. });
  118. })
  119. .catch((e) => {
  120. reject === null || reject === void 0 ? void 0 : reject(e);
  121. });
  122. // Return the Group wraps the canvas or svg element.
  123. return group;
  124. }
  125. export function destroy(options, context = {}, isDestroyCanvas = false) {
  126. const { canvas, emitter } = context;
  127. if (canvas) {
  128. destroyAllInteractions(canvas);
  129. isDestroyCanvas ? canvas.destroy() : canvas.destroyChildren();
  130. }
  131. emitter.off();
  132. }
  133. /**
  134. * Destroy all interactions mounted on the canvas.
  135. */
  136. function destroyAllInteractions(canvas) {
  137. const viewGroups = canvas.getRoot().querySelectorAll(`.${VIEW_CLASS_NAME}`);
  138. viewGroups === null || viewGroups === void 0 ? void 0 : viewGroups.forEach((group) => {
  139. const { nameInteraction = new Map() } = group;
  140. if ((nameInteraction === null || nameInteraction === void 0 ? void 0 : nameInteraction.size) > 0) {
  141. Array.from(nameInteraction === null || nameInteraction === void 0 ? void 0 : nameInteraction.values()).forEach((value) => {
  142. value === null || value === void 0 ? void 0 : value.destroy();
  143. });
  144. }
  145. });
  146. }
  147. function normalizeContainer(container) {
  148. return typeof container === 'string'
  149. ? document.getElementById(container)
  150. : container;
  151. }
  152. //# sourceMappingURL=render.js.map