Personal blog written from scratch using Node.js, Bootstrap, and MySQL. https://jrtechs.net
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.

479 lines
13 KiB

  1. ;(function(undefined) {
  2. 'use strict';
  3. if (typeof sigma === 'undefined')
  4. throw 'sigma is not declared';
  5. if (typeof conrad === 'undefined')
  6. throw 'conrad is not declared';
  7. // Initialize packages:
  8. sigma.utils.pkg('sigma.renderers');
  9. /**
  10. * This function is the constructor of the svg sigma's renderer.
  11. *
  12. * @param {sigma.classes.graph} graph The graph to render.
  13. * @param {sigma.classes.camera} camera The camera.
  14. * @param {configurable} settings The sigma instance settings
  15. * function.
  16. * @param {object} object The options object.
  17. * @return {sigma.renderers.svg} The renderer instance.
  18. */
  19. sigma.renderers.svg = function(graph, camera, settings, options) {
  20. if (typeof options !== 'object')
  21. throw 'sigma.renderers.svg: Wrong arguments.';
  22. if (!(options.container instanceof HTMLElement))
  23. throw 'Container not found.';
  24. var i,
  25. l,
  26. a,
  27. fn,
  28. self = this;
  29. sigma.classes.dispatcher.extend(this);
  30. // Initialize main attributes:
  31. this.graph = graph;
  32. this.camera = camera;
  33. this.domElements = {
  34. graph: null,
  35. groups: {},
  36. nodes: {},
  37. edges: {},
  38. labels: {},
  39. hovers: {}
  40. };
  41. this.measurementCanvas = null;
  42. this.options = options;
  43. this.container = this.options.container;
  44. this.settings = (
  45. typeof options.settings === 'object' &&
  46. options.settings
  47. ) ?
  48. settings.embedObjects(options.settings) :
  49. settings;
  50. // Is the renderer meant to be freestyle?
  51. this.settings('freeStyle', !!this.options.freeStyle);
  52. // SVG xmlns
  53. this.settings('xmlns', 'http://www.w3.org/2000/svg');
  54. // Indexes:
  55. this.nodesOnScreen = [];
  56. this.edgesOnScreen = [];
  57. // Find the prefix:
  58. this.options.prefix = 'renderer' + sigma.utils.id() + ':';
  59. // Initialize the DOM elements
  60. this.initDOM('svg');
  61. // Initialize captors:
  62. this.captors = [];
  63. a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch];
  64. for (i = 0, l = a.length; i < l; i++) {
  65. fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]];
  66. this.captors.push(
  67. new fn(
  68. this.domElements.graph,
  69. this.camera,
  70. this.settings
  71. )
  72. );
  73. }
  74. // Bind resize:
  75. window.addEventListener('resize', function() {
  76. self.resize();
  77. });
  78. // Deal with sigma events:
  79. // TODO: keep an option to override the DOM events?
  80. sigma.misc.bindDOMEvents.call(this, this.domElements.graph);
  81. this.bindHovers(this.options.prefix);
  82. // Resize
  83. this.resize(false);
  84. };
  85. /**
  86. * This method renders the graph on the svg scene.
  87. *
  88. * @param {?object} options Eventually an object of options.
  89. * @return {sigma.renderers.svg} Returns the instance itself.
  90. */
  91. sigma.renderers.svg.prototype.render = function(options) {
  92. options = options || {};
  93. var a,
  94. i,
  95. k,
  96. e,
  97. l,
  98. o,
  99. source,
  100. target,
  101. start,
  102. edges,
  103. renderers,
  104. subrenderers,
  105. index = {},
  106. graph = this.graph,
  107. nodes = this.graph.nodes,
  108. prefix = this.options.prefix || '',
  109. drawEdges = this.settings(options, 'drawEdges'),
  110. drawNodes = this.settings(options, 'drawNodes'),
  111. drawLabels = this.settings(options, 'drawLabels'),
  112. embedSettings = this.settings.embedObjects(options, {
  113. prefix: this.options.prefix,
  114. forceLabels: this.options.forceLabels
  115. });
  116. // Check the 'hideEdgesOnMove' setting:
  117. if (this.settings(options, 'hideEdgesOnMove'))
  118. if (this.camera.isAnimated || this.camera.isMoving)
  119. drawEdges = false;
  120. // Apply the camera's view:
  121. this.camera.applyView(
  122. undefined,
  123. this.options.prefix,
  124. {
  125. width: this.width,
  126. height: this.height
  127. }
  128. );
  129. // Hiding everything
  130. // TODO: find a more sensible way to perform this operation
  131. this.hideDOMElements(this.domElements.nodes);
  132. this.hideDOMElements(this.domElements.edges);
  133. this.hideDOMElements(this.domElements.labels);
  134. // Find which nodes are on screen
  135. this.edgesOnScreen = [];
  136. this.nodesOnScreen = this.camera.quadtree.area(
  137. this.camera.getRectangle(this.width, this.height)
  138. );
  139. // Node index
  140. for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++)
  141. index[a[i].id] = a[i];
  142. // Find which edges are on screen
  143. for (a = graph.edges(), i = 0, l = a.length; i < l; i++) {
  144. o = a[i];
  145. if (
  146. (index[o.source] || index[o.target]) &&
  147. (!o.hidden && !nodes(o.source).hidden && !nodes(o.target).hidden)
  148. )
  149. this.edgesOnScreen.push(o);
  150. }
  151. // Display nodes
  152. //---------------
  153. renderers = sigma.svg.nodes;
  154. subrenderers = sigma.svg.labels;
  155. //-- First we create the nodes which are not already created
  156. if (drawNodes)
  157. for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) {
  158. if (!a[i].hidden && !this.domElements.nodes[a[i].id]) {
  159. // Node
  160. e = (renderers[a[i].type] || renderers.def).create(
  161. a[i],
  162. embedSettings
  163. );
  164. this.domElements.nodes[a[i].id] = e;
  165. this.domElements.groups.nodes.appendChild(e);
  166. // Label
  167. e = (subrenderers[a[i].type] || subrenderers.def).create(
  168. a[i],
  169. embedSettings
  170. );
  171. this.domElements.labels[a[i].id] = e;
  172. this.domElements.groups.labels.appendChild(e);
  173. }
  174. }
  175. //-- Second we update the nodes
  176. if (drawNodes)
  177. for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) {
  178. if (a[i].hidden)
  179. continue;
  180. // Node
  181. (renderers[a[i].type] || renderers.def).update(
  182. a[i],
  183. this.domElements.nodes[a[i].id],
  184. embedSettings
  185. );
  186. // Label
  187. (subrenderers[a[i].type] || subrenderers.def).update(
  188. a[i],
  189. this.domElements.labels[a[i].id],
  190. embedSettings
  191. );
  192. }
  193. // Display edges
  194. //---------------
  195. renderers = sigma.svg.edges;
  196. //-- First we create the edges which are not already created
  197. if (drawEdges)
  198. for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) {
  199. if (!this.domElements.edges[a[i].id]) {
  200. source = nodes(a[i].source);
  201. target = nodes(a[i].target);
  202. e = (renderers[a[i].type] || renderers.def).create(
  203. a[i],
  204. source,
  205. target,
  206. embedSettings
  207. );
  208. this.domElements.edges[a[i].id] = e;
  209. this.domElements.groups.edges.appendChild(e);
  210. }
  211. }
  212. //-- Second we update the edges
  213. if (drawEdges)
  214. for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) {
  215. source = nodes(a[i].source);
  216. target = nodes(a[i].target);
  217. (renderers[a[i].type] || renderers.def).update(
  218. a[i],
  219. this.domElements.edges[a[i].id],
  220. source,
  221. target,
  222. embedSettings
  223. );
  224. }
  225. this.dispatchEvent('render');
  226. return this;
  227. };
  228. /**
  229. * This method creates a DOM element of the specified type, switches its
  230. * position to "absolute", references it to the domElements attribute, and
  231. * finally appends it to the container.
  232. *
  233. * @param {string} tag The label tag.
  234. * @param {string} id The id of the element (to store it in "domElements").
  235. */
  236. sigma.renderers.svg.prototype.initDOM = function(tag) {
  237. var dom = document.createElementNS(this.settings('xmlns'), tag),
  238. c = this.settings('classPrefix'),
  239. g,
  240. l,
  241. i;
  242. dom.style.position = 'absolute';
  243. dom.setAttribute('class', c + '-svg');
  244. // Setting SVG namespace
  245. dom.setAttribute('xmlns', this.settings('xmlns'));
  246. dom.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
  247. dom.setAttribute('version', '1.1');
  248. // Creating the measurement canvas
  249. var canvas = document.createElement('canvas');
  250. canvas.setAttribute('class', c + '-measurement-canvas');
  251. // Appending elements
  252. this.domElements.graph = this.container.appendChild(dom);
  253. // Creating groups
  254. var groups = ['edges', 'nodes', 'labels', 'hovers'];
  255. for (i = 0, l = groups.length; i < l; i++) {
  256. g = document.createElementNS(this.settings('xmlns'), 'g');
  257. g.setAttributeNS(null, 'id', c + '-group-' + groups[i]);
  258. g.setAttributeNS(null, 'class', c + '-group');
  259. this.domElements.groups[groups[i]] =
  260. this.domElements.graph.appendChild(g);
  261. }
  262. // Appending measurement canvas
  263. this.container.appendChild(canvas);
  264. this.measurementCanvas = canvas.getContext('2d');
  265. };
  266. /**
  267. * This method hides a batch of SVG DOM elements.
  268. *
  269. * @param {array} elements An array of elements to hide.
  270. * @param {object} renderer The renderer to use.
  271. * @return {sigma.renderers.svg} Returns the instance itself.
  272. */
  273. sigma.renderers.svg.prototype.hideDOMElements = function(elements) {
  274. var o,
  275. i;
  276. for (i in elements) {
  277. o = elements[i];
  278. sigma.svg.utils.hide(o);
  279. }
  280. return this;
  281. };
  282. /**
  283. * This method binds the hover events to the renderer.
  284. *
  285. * @param {string} prefix The renderer prefix.
  286. */
  287. // TODO: add option about whether to display hovers or not
  288. sigma.renderers.svg.prototype.bindHovers = function(prefix) {
  289. var renderers = sigma.svg.hovers,
  290. self = this,
  291. hoveredNode;
  292. function overNode(e) {
  293. var node = e.data.node,
  294. embedSettings = self.settings.embedObjects({
  295. prefix: prefix
  296. });
  297. if (!embedSettings('enableHovering'))
  298. return;
  299. var hover = (renderers[node.type] || renderers.def).create(
  300. node,
  301. self.domElements.nodes[node.id],
  302. self.measurementCanvas,
  303. embedSettings
  304. );
  305. self.domElements.hovers[node.id] = hover;
  306. // Inserting the hover in the dom
  307. self.domElements.groups.hovers.appendChild(hover);
  308. hoveredNode = node;
  309. }
  310. function outNode(e) {
  311. var node = e.data.node,
  312. embedSettings = self.settings.embedObjects({
  313. prefix: prefix
  314. });
  315. if (!embedSettings('enableHovering'))
  316. return;
  317. // Deleting element
  318. self.domElements.groups.hovers.removeChild(
  319. self.domElements.hovers[node.id]
  320. );
  321. hoveredNode = null;
  322. delete self.domElements.hovers[node.id];
  323. // Reinstate
  324. self.domElements.groups.nodes.appendChild(
  325. self.domElements.nodes[node.id]
  326. );
  327. }
  328. // OPTIMIZE: perform a real update rather than a deletion
  329. function update() {
  330. if (!hoveredNode)
  331. return;
  332. var embedSettings = self.settings.embedObjects({
  333. prefix: prefix
  334. });
  335. // Deleting element before update
  336. self.domElements.groups.hovers.removeChild(
  337. self.domElements.hovers[hoveredNode.id]
  338. );
  339. delete self.domElements.hovers[hoveredNode.id];
  340. var hover = (renderers[hoveredNode.type] || renderers.def).create(
  341. hoveredNode,
  342. self.domElements.nodes[hoveredNode.id],
  343. self.measurementCanvas,
  344. embedSettings
  345. );
  346. self.domElements.hovers[hoveredNode.id] = hover;
  347. // Inserting the hover in the dom
  348. self.domElements.groups.hovers.appendChild(hover);
  349. }
  350. // Binding events
  351. this.bind('overNode', overNode);
  352. this.bind('outNode', outNode);
  353. // Update on render
  354. this.bind('render', update);
  355. };
  356. /**
  357. * This method resizes each DOM elements in the container and stores the new
  358. * dimensions. Then, it renders the graph.
  359. *
  360. * @param {?number} width The new width of the container.
  361. * @param {?number} height The new height of the container.
  362. * @return {sigma.renderers.svg} Returns the instance itself.
  363. */
  364. sigma.renderers.svg.prototype.resize = function(w, h) {
  365. var oldWidth = this.width,
  366. oldHeight = this.height,
  367. pixelRatio = 1;
  368. if (w !== undefined && h !== undefined) {
  369. this.width = w;
  370. this.height = h;
  371. } else {
  372. this.width = this.container.offsetWidth;
  373. this.height = this.container.offsetHeight;
  374. w = this.width;
  375. h = this.height;
  376. }
  377. if (oldWidth !== this.width || oldHeight !== this.height) {
  378. this.domElements.graph.style.width = w + 'px';
  379. this.domElements.graph.style.height = h + 'px';
  380. if (this.domElements.graph.tagName.toLowerCase() === 'svg') {
  381. this.domElements.graph.setAttribute('width', (w * pixelRatio));
  382. this.domElements.graph.setAttribute('height', (h * pixelRatio));
  383. }
  384. }
  385. return this;
  386. };
  387. /**
  388. * The labels, nodes and edges renderers are stored in the three following
  389. * objects. When an element is drawn, its type will be checked and if a
  390. * renderer with the same name exists, it will be used. If not found, the
  391. * default renderer will be used instead.
  392. *
  393. * They are stored in different files, in the "./svg" folder.
  394. */
  395. sigma.utils.pkg('sigma.svg.nodes');
  396. sigma.utils.pkg('sigma.svg.edges');
  397. sigma.utils.pkg('sigma.svg.labels');
  398. }).call(this);