visualize.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /**
  2. * @fileoverview This file is the main entrypoint for DRAGNN trace
  3. * visualization. The main function that it exposes is visualizeToDiv(), which
  4. * it will save to `window` (supposing `window` is defined).
  5. */
  6. const cytoscape = require('cytoscape');
  7. const _ = require('lodash');
  8. // Legacy JS, downloaded in the Dockerfile from github. This generates
  9. // colorblind-friendly color palettes, for a variable number of components.
  10. //
  11. // This library consumes 27kb minified-but-uncompressed. Currently we mostly
  12. // serve JS to high-bandwidth clients like IPython, but if minified size is ever
  13. // a concern, it should be fairly easy to pre-compute our color palettes.
  14. const palette = require('exports-loader?palette!../palette.js');
  15. // Register our custom DRAGNN layout class.
  16. const DragnnLayout = require('./dragnn_layout.js');
  17. cytoscape('layout', 'dragnn', DragnnLayout);
  18. import preact from 'preact';
  19. import InteractiveGraph from './interactive_graph.jsx';
  20. import setupTraceInteractionHandlers from './trace_interaction_handlers';
  21. // Helper class to build a Cytoscape graph from a DRAGNN master spec.
  22. class DragnnCytoscapeGraphBuilder {
  23. /**
  24. * Creates a new DragnnCytoscapeGraphBuilder.
  25. */
  26. constructor() {
  27. this.graph = {nodes: [], edges: []};
  28. this.nodesWithoutCaptions = 0;
  29. }
  30. /**
  31. * Adds a new component node to the graph. Component nodes represent
  32. * components in DRAGNN, which often resemble tasks (tagging, parsing, etc.).
  33. *
  34. * @param {!Object} component Component descriptor from the master spec.
  35. */
  36. addComponentNode(component) {
  37. this.graph.nodes.push({
  38. 'data': {
  39. 'id': component.name,
  40. 'idx': component.idx,
  41. 'componentColor': '#' + component.color,
  42. 'text': component.name,
  43. 'type': 'component',
  44. },
  45. 'classes': 'component',
  46. });
  47. }
  48. /**
  49. * Adds a new step node to the graph. Step nodes are generated by unrolling a
  50. * component on a concrete example.
  51. *
  52. * @param {!Object} component Component descriptor from the master spec.
  53. * @param {!Object} step Step descriptor from the master spec.
  54. * @return {string} ID fo the node created.
  55. */
  56. addNode(component, step) {
  57. const graphId = component.name + '-' + step.idx;
  58. this.graph.nodes.push({
  59. 'data': {
  60. 'id': graphId,
  61. 'componentIdx': component.idx,
  62. 'stepIdx': step.idx,
  63. 'parent': component.name,
  64. 'text': step.caption || graphId,
  65. 'componentColor': '#' + component.color,
  66. 'type': 'step',
  67. // Shown in the mouse-over (node_info.jsx).
  68. 'stateInfo': step.html_representation,
  69. 'fixedFeatures': step.fixed_feature_trace,
  70. },
  71. 'classes': 'step',
  72. });
  73. return graphId;
  74. }
  75. /**
  76. * Adds a list of components from the master spec.
  77. *
  78. * This function generates colors, and calls addComponent().
  79. *
  80. * @param {!Object} masterTrace Master trace proto from DRAGNN.
  81. */
  82. addComponents(masterTrace) {
  83. const colors = palette('tol', masterTrace.component_trace.length);
  84. if (colors == null) {
  85. // Apparently palette.js can fail with > 12 components or such.
  86. window.alert('FAILURE -- YOU HAVE TOO MANY COMPONENTS FOR palette.js.');
  87. return;
  88. }
  89. _.each(masterTrace.component_trace, function(component, idx) {
  90. component.idx = idx;
  91. component.color = colors[idx];
  92. this.addComponent(component);
  93. }.bind(this));
  94. console.log('' + this.nodesWithoutCaptions + ' nodes without captions');
  95. }
  96. /**
  97. * Adds one component from the master spec. This generates component nodes,
  98. * step nodes, and edges.
  99. *
  100. * @param {!Object} component Component descriptor from the master spec.
  101. */
  102. addComponent(component) {
  103. this.addComponentNode(component);
  104. _.each(component.step_trace, (step, idx) => {
  105. step.idx = idx;
  106. if (!step.caption) {
  107. this.nodesWithoutCaptions += 1;
  108. return;
  109. }
  110. const graphId = this.addNode(component, step);
  111. _.each(step.linked_feature_trace, (linkedFeature) => {
  112. // Each feature can take multiple values.
  113. _.each(linkedFeature.value_trace, (linkedValue) => {
  114. const srcGraphId =
  115. linkedFeature.source_component + '-' + linkedValue.step_idx;
  116. this.graph.edges.push({
  117. 'data': {
  118. 'source': srcGraphId,
  119. 'target': graphId,
  120. 'curvature': 0,
  121. 'featureName': linkedValue.feature_name,
  122. 'featureValue': linkedValue.feature_value,
  123. }
  124. });
  125. });
  126. });
  127. });
  128. }
  129. }
  130. /**
  131. * Component for a graph and its controls. Currently this just manually
  132. * calls DOM methods, but we'll switch it out for something more modern in a
  133. * sec.
  134. */
  135. class InteractiveDragnnGraph {
  136. /**
  137. * Controller for the entire DRAGNN graph element on a page.
  138. *
  139. * @param {!Object} masterTrace Master trace proto from DRAGNN.
  140. * @param {!Object} element Container DOM element to populate.
  141. * @param {?Object} masterSpec Master spec proto from DRAGNN; if provided,
  142. * used to improve the layout.
  143. */
  144. constructor(masterTrace, element, masterSpec) {
  145. this.masterTrace = masterTrace;
  146. this.element = element;
  147. this.masterSpec = masterSpec || null;
  148. }
  149. /**
  150. * Initializes the controls and the graph.
  151. */
  152. initDomElements() {
  153. this.element.style.position = 'relative';
  154. this.element.style.overflow = 'hidden';
  155. const elt = preact.h(InteractiveGraph, {
  156. onmount: (view, graph_elt) => {
  157. this.view = view;
  158. this.initializeGraph(view, graph_elt);
  159. },
  160. onfilter: this.onFilter.bind(this),
  161. });
  162. preact.render(elt, this.element);
  163. }
  164. /**
  165. * Handler for when filtering text is entered.
  166. *
  167. * Future features: Make the number of neighbors customizable.
  168. *
  169. * @param {string} text Regular expression text to filter with. Currently
  170. * applied to node labels.
  171. */
  172. onFilter(text) {
  173. // Show relevant nodes (and parent components).
  174. const re = new RegExp(text);
  175. let sel = this.cy.$('node.step').filter(function(i, node) {
  176. return !text || node.data('text').match(re);
  177. });
  178. sel = sel.union(sel.neighborhood());
  179. sel = sel.union(sel.parents());
  180. sel.nodes().show();
  181. sel.abscomp().nodes().hide();
  182. // Redo layout.
  183. this.cy.layout({name: 'dragnn', masterSpec: this.masterSpec});
  184. }
  185. /**
  186. * Initializes the Cytoscape graph.
  187. */
  188. initializeGraph(view, domElement) {
  189. const builder = new DragnnCytoscapeGraphBuilder();
  190. builder.addComponents(this.masterTrace);
  191. const cy = cytoscape({
  192. container: domElement,
  193. boxSelectionEnabled: true,
  194. autounselectify: true,
  195. // We'll do more custom layout later.
  196. layout: {name: 'dragnn', masterSpec: this.masterSpec},
  197. style: [
  198. {
  199. selector: 'node',
  200. style: {
  201. 'background-color': 'data(componentColor)',
  202. 'content': 'data(text)',
  203. 'text-halign': 'center',
  204. 'text-opacity': 1.0,
  205. 'text-valign': 'center',
  206. }
  207. },
  208. {
  209. selector: 'node.step',
  210. style: {
  211. 'text-outline-width': 2,
  212. 'text-outline-color': '#ffffff',
  213. 'text-outline-opacity': 0.3,
  214. }
  215. },
  216. {
  217. selector: ':parent',
  218. style: {
  219. 'background-opacity': 0.1,
  220. 'text-halign': 'right',
  221. 'text-margin-x': 5,
  222. 'text-margin-y': 5,
  223. 'text-valign': 'bottom',
  224. }
  225. },
  226. {
  227. selector: 'edge',
  228. style: {
  229. 'control-point-distance': 'data(curvature)',
  230. 'curve-style': 'unbundled-bezier',
  231. 'line-color': '#666666',
  232. 'opacity': 0.4,
  233. 'target-arrow-color': '#666666',
  234. 'target-arrow-shape': 'triangle',
  235. 'width': 3,
  236. }
  237. },
  238. {selector: 'edge.faded-near', style: {'opacity': 0.2}},
  239. {selector: 'node.step.faded-near', style: {'opacity': 0.5}},
  240. {
  241. selector: 'node.step.faded-far, edge.faded-far',
  242. style: {'opacity': 0.1}
  243. },
  244. // Overall, override stuff for mouse-overs, but don't make the far edges
  245. // too dark (that looks jarring).
  246. {
  247. selector: 'edge.highlighted-edge',
  248. style: {'line-color': '#333333', 'opacity': 1.0}
  249. },
  250. {
  251. selector: 'edge.highlighted-edge.faded-far',
  252. style: {
  253. 'opacity': 0.4,
  254. }
  255. },
  256. ],
  257. elements: builder.graph,
  258. });
  259. this.cy = cy;
  260. setupTraceInteractionHandlers(cy, view, this.element);
  261. }
  262. }
  263. /**
  264. * This is the external interface. See "index.html" for the development example,
  265. * which downloads graph data from a JSON file. In most iPython notebook
  266. * situations, the script tag containing the graph definition will be generated
  267. * inline.
  268. *
  269. * @param {!Object} masterTrace Master trace proto from DRAGNN.
  270. * @param {string} divId ID of the page element to populate with the graph.
  271. * @param {?Object} masterSpec Master spec proto from DRAGNN; if provided, used
  272. * to improve the layout.
  273. */
  274. const visualizeToDiv = function(masterTrace, divId, masterSpec) {
  275. const interactiveGraph = new InteractiveDragnnGraph(
  276. masterTrace, document.getElementById(divId), masterSpec);
  277. interactiveGraph.initDomElements();
  278. };
  279. if (window !== undefined) {
  280. window.visualizeToDiv = visualizeToDiv;
  281. }