view.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. /*jshint esversion: 6 */
  2. class View {
  3. constructor(host) {
  4. this._host = host;
  5. this._model = null;
  6. this._sidebar = new Sidebar();
  7. this._host.initialize(this);
  8. document.documentElement.style.overflow = 'hidden';
  9. document.body.scroll = 'no';
  10. var navigationButton = document.getElementById('navigation-button');
  11. if (navigationButton) {
  12. navigationButton.addEventListener('click', (e) => {
  13. this.showSummary();
  14. });
  15. }
  16. }
  17. show(page) {
  18. if (!page) {
  19. page = (!this._model && !this._graph) ? 'welcome' : 'graph';
  20. }
  21. this._sidebar.close();
  22. var welcomeElement = document.getElementById('welcome');
  23. var openFileButton = document.getElementById('open-file-button');
  24. var spinnerElement = document.getElementById('spinner');
  25. var graphElement = document.getElementById('graph');
  26. var navigationElement = document.getElementById('navigation-button');
  27. if (page == 'welcome') {
  28. document.body.style.cursor = 'default';
  29. welcomeElement.style.display = 'block';
  30. var offsetHeight1 = welcomeElement.offsetHeight;
  31. openFileButton.style.display = 'block';
  32. openFileButton.style.opacity = 1;
  33. spinnerElement.style.display = 'none';
  34. graphElement.style.display = 'none';
  35. graphElement.style.opacity = 0;
  36. navigationElement.style.display = 'none';
  37. this._model = null;
  38. this._graph = false;
  39. }
  40. if (page == 'spinner') {
  41. document.body.style.cursor = 'wait';
  42. welcomeElement.style.display = 'block';
  43. openFileButton.style.opacity = 0;
  44. spinnerElement.style.display = 'block';
  45. var offsetHeight2 = spinnerElement.offsetHeight;
  46. graphElement.style.display = 'block';
  47. graphElement.style.opacity = 0;
  48. navigationElement.style.display = 'none';
  49. }
  50. if (page == 'graph') {
  51. welcomeElement.style.display = 'none';
  52. openFileButton.style.display = 'none';
  53. spinnerElement.style.display = 'none';
  54. graphElement.style.display = 'block';
  55. graphElement.style.opacity = 1;
  56. navigationElement.style.display = 'block';
  57. document.body.style.cursor = 'default';
  58. }
  59. }
  60. loadBuffer(buffer, identifier, callback) {
  61. var modelFactoryRegistry = [
  62. new OnnxModelFactory(),
  63. new MXNetModelFactory(),
  64. new KerasModelFactory(),
  65. new CoreMLModelFactory(),
  66. new CaffeModelFactory(),
  67. new Caffe2ModelFactory(),
  68. new TensorFlowLiteModelFactory(),
  69. new TensorFlowModelFactory()
  70. ];
  71. var matches = modelFactoryRegistry.filter((factory) => factory.match(buffer, identifier));
  72. var next = () => {
  73. if (matches.length > 0) {
  74. var modelFactory = matches.shift();
  75. modelFactory.open(buffer, identifier, this._host, (err, model) => {
  76. if (model || matches.length == 0) {
  77. callback(err, model);
  78. }
  79. else {
  80. next();
  81. }
  82. });
  83. }
  84. else {
  85. var extension = identifier.split('.').pop();
  86. callback(new Error('Unsupported file extension \'.' + extension + '\'.'), null);
  87. }
  88. };
  89. next();
  90. }
  91. openBuffer(buffer, identifier, callback) {
  92. this._sidebar.close();
  93. setTimeout(() => {
  94. this.loadBuffer(buffer, identifier, (err, model) => {
  95. if (err) {
  96. callback(err);
  97. }
  98. else {
  99. setTimeout(() => {
  100. this._graph = false;
  101. try {
  102. var graph = model.graphs.length > 0 ? model.graphs[0] : null;
  103. this.updateGraph(model, graph);
  104. this._model = model;
  105. this._activeGraph = graph;
  106. callback(null);
  107. }
  108. catch (err) {
  109. try {
  110. this.updateGraph(this._model, this._activeGraph);
  111. }
  112. catch (obj) {
  113. this._model = null;
  114. this._activeGraph = null;
  115. }
  116. callback(err);
  117. }
  118. }, 2);
  119. }
  120. });
  121. }, 2);
  122. }
  123. showError(err) {
  124. this._sidebar.close();
  125. this._host.showError(err.toString());
  126. this.show('welcome');
  127. }
  128. updateActiveGraph(name) {
  129. this._sidebar.close();
  130. if (this._model) {
  131. var model = this._model;
  132. var graph = model.graphs.filter(graph => graph.name).shift();
  133. if (graph) {
  134. this.show('spinner');
  135. setTimeout(() => {
  136. try {
  137. this.updateGraph(model, graph);
  138. this._model = model;
  139. this._activeGraph = graph;
  140. }
  141. catch (obj) {
  142. this._model = null;
  143. this._activeGraph = null;
  144. }
  145. }, 2);
  146. }
  147. }
  148. }
  149. updateGraph(model, graph) {
  150. if (!graph) {
  151. this.show('graph');
  152. return;
  153. }
  154. var svgElement = document.getElementById('graph');
  155. while (svgElement.lastChild) {
  156. svgElement.removeChild(svgElement.lastChild);
  157. }
  158. var groups = graph.groups;
  159. var g = new dagre.graphlib.Graph({ compound: groups });
  160. g.setGraph({});
  161. // g.setGraph({ align: 'DR' });
  162. // g.setGraph({ ranker: 'network-simplex' });
  163. // g.setGraph({ ranker: 'tight-tree' });
  164. // g.setGraph({ ranker: 'longest-path' });
  165. // g.setGraph({ acyclicer: 'greedy' });
  166. g.setDefaultEdgeLabel(() => { return {}; });
  167. var nodeId = 0;
  168. var edgeMap = {};
  169. var clusterMap = {};
  170. var clusterParentMap = {};
  171. if (groups) {
  172. graph.nodes.forEach((node) => {
  173. if (node.group) {
  174. var path = node.group.split('/');
  175. while (path.length > 0) {
  176. var name = path.join('/');
  177. path.pop();
  178. clusterParentMap[name] = path.join('/');
  179. }
  180. }
  181. });
  182. }
  183. graph.nodes.forEach((node) => {
  184. var formatter = new NodeFormatter();
  185. function addOperator(viewService, formatter, node) {
  186. if (node) {
  187. var styles = [ 'node-item-operator' ];
  188. var category = node.category;
  189. if (category) {
  190. styles.push('node-item-operator-' + category.toLowerCase());
  191. }
  192. formatter.addItem(node.primitive ? node.primitive : node.operator, styles, node.name, () => {
  193. viewService.showNode(node);
  194. });
  195. }
  196. }
  197. addOperator(this, formatter, node);
  198. addOperator(this, formatter, node.inner);
  199. var primitive = node.primitive;
  200. var hiddenInputs = false;
  201. var hiddenInitializers = false;
  202. node.inputs.forEach((input) => {
  203. // TODO what about mixed input & initializer
  204. if (input.connections.length > 0) {
  205. var initializers = input.connections.filter(connection => connection.initializer);
  206. var inputClass = 'node-item-input';
  207. if (initializers.length == 0) {
  208. inputClass = 'node-item-input';
  209. if (input.hidden) {
  210. hiddenInputs = true;
  211. }
  212. }
  213. else {
  214. if (initializers.length == input.connections.length) {
  215. inputClass = 'node-item-constant';
  216. if (input.hidden) {
  217. hiddenInitializers = true;
  218. }
  219. }
  220. else {
  221. inputClass = 'node-item-constant';
  222. if (input.hidden) {
  223. hiddenInputs = true;
  224. }
  225. }
  226. }
  227. if (!input.hidden) {
  228. var types = input.connections.map(connection => connection.type ? connection.type : '').join('\n');
  229. formatter.addItem(input.name, [ inputClass ], types, () => {
  230. this.showNodeInput(node, input);
  231. });
  232. }
  233. input.connections.forEach((connection) => {
  234. if (!connection.initializer) {
  235. var tuple = edgeMap[connection.id];
  236. if (!tuple) {
  237. tuple = { from: null, to: [] };
  238. edgeMap[connection.id] = tuple;
  239. }
  240. tuple.to.push({
  241. node: nodeId,
  242. name: input.name
  243. });
  244. }
  245. });
  246. }
  247. });
  248. if (hiddenInputs) {
  249. formatter.addItem('...', [ 'node-item-input' ], '', () => {
  250. this.showNode(node);
  251. });
  252. }
  253. if (hiddenInitializers) {
  254. formatter.addItem('...', [ 'node-item-constant' ], '', () => {
  255. this.showNode(node);
  256. });
  257. }
  258. node.outputs.forEach((output) => {
  259. output.connections.forEach((connection) => {
  260. var tuple = edgeMap[connection.id];
  261. if (!tuple) {
  262. tuple = { from: null, to: [] };
  263. edgeMap[connection.id] = tuple;
  264. }
  265. tuple.from = {
  266. node: nodeId,
  267. name: output.name
  268. };
  269. });
  270. });
  271. var dependencies = node.dependencies;
  272. if (dependencies && dependencies.length > 0) {
  273. formatter.setControlDependencies();
  274. }
  275. var attributes = node.attributes;
  276. if (attributes && !primitive) {
  277. formatter.setAttributeHandler(() => {
  278. this.showNode(node);
  279. });
  280. attributes.forEach((attribute) => {
  281. if (attribute.visible) {
  282. var attributeValue = '';
  283. if (attribute.tensor) {
  284. attributeValue = '[...]';
  285. }
  286. else {
  287. attributeValue = attribute.value;
  288. if (attributeValue.length > 25) {
  289. attributeValue = attributeValue.substring(0, 25) + '...';
  290. }
  291. }
  292. formatter.addAttribute(attribute.name, attributeValue, attribute.type);
  293. }
  294. });
  295. }
  296. g.setNode(nodeId, { label: formatter.format(svgElement) });
  297. function createCluster(name) {
  298. if (!clusterMap[name]) {
  299. g.setNode(name, { rx: 5, ry: 5});
  300. clusterMap[name] = true;
  301. var parent = clusterParentMap[name];
  302. if (parent) {
  303. createCluster(parent);
  304. g.setParent(name, parent);
  305. }
  306. }
  307. }
  308. if (groups) {
  309. var name = node.group;
  310. if (name && name.length > 0) {
  311. if (!clusterParentMap.hasOwnProperty(name)) {
  312. var lastIndex = name.lastIndexOf('/');
  313. if (lastIndex != -1) {
  314. name = name.substring(0, lastIndex);
  315. if (!clusterParentMap.hasOwnProperty(name)) {
  316. name = null;
  317. }
  318. }
  319. else {
  320. name = null;
  321. }
  322. }
  323. if (name) {
  324. createCluster(name);
  325. g.setParent(nodeId, name);
  326. }
  327. }
  328. }
  329. nodeId++;
  330. });
  331. graph.inputs.forEach((input) => {
  332. var tuple = edgeMap[input.id];
  333. if (!tuple) {
  334. tuple = { from: null, to: [] };
  335. edgeMap[input.id] = tuple;
  336. }
  337. tuple.from = {
  338. node: nodeId,
  339. };
  340. var formatter = new NodeFormatter();
  341. formatter.addItem(input.name, [ 'graph-item-input' ], input.type, () => {
  342. this.showSummary();
  343. });
  344. g.setNode(nodeId++, { label: formatter.format(svgElement), class: 'graph-input' } );
  345. });
  346. graph.outputs.forEach((output) => {
  347. var outputId = output.id;
  348. var outputName = output.name;
  349. var tuple = edgeMap[outputId];
  350. if (!tuple) {
  351. tuple = { from: null, to: [] };
  352. edgeMap[outputId] = tuple;
  353. }
  354. tuple.to.push({
  355. node: nodeId,
  356. // name: valueInfo.name
  357. });
  358. var formatter = new NodeFormatter();
  359. formatter.addItem(output.name, [ 'graph-item-output' ], output.type, () => {
  360. this.showSummary();
  361. });
  362. g.setNode(nodeId++, { label: formatter.format(svgElement) } );
  363. });
  364. Object.keys(edgeMap).forEach((edge) => {
  365. var tuple = edgeMap[edge];
  366. if (tuple.from != null) {
  367. tuple.to.forEach((to) => {
  368. var text = '';
  369. if (tuple.from.name && to.name) {
  370. text = tuple.from.name + ' => ' + to.name;
  371. }
  372. else if (tuple.from.name) {
  373. text = tuple.from.name;
  374. }
  375. else {
  376. text = to.name;
  377. }
  378. if (to.dependency) {
  379. g.setEdge(tuple.from.node, to.node, { label: text, arrowhead: 'vee', curve: d3.curveBasis, class: 'edge-path-control' } );
  380. }
  381. else {
  382. g.setEdge(tuple.from.node, to.node, { label: text, arrowhead: 'vee', curve: d3.curveBasis } );
  383. }
  384. });
  385. }
  386. // else {
  387. // console.log('?');
  388. // }
  389. // if (tuple.from == null || tuple.to.length == 0) {
  390. // console.log(edge);
  391. // }
  392. });
  393. // Workaround for Safari background drag/zoom issue:
  394. // https://stackoverflow.com/questions/40887193/d3-js-zoom-is-not-working-with-mousewheel-in-safari
  395. var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  396. rect.setAttribute('width', '100%');
  397. rect.setAttribute('height', '100%');
  398. rect.setAttribute('fill', 'none');
  399. rect.setAttribute('pointer-events', 'all');
  400. svgElement.appendChild(rect);
  401. var outputGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  402. svgElement.appendChild(outputGroup);
  403. // Set up zoom support
  404. var zoom = d3.zoom();
  405. zoom.scaleExtent([0.1, 2]);
  406. zoom.on('zoom', (e) => {
  407. d3.select(outputGroup).attr('transform', d3.event.transform);
  408. });
  409. var svg = d3.select(svgElement);
  410. svg.call(zoom);
  411. svg.call(zoom.transform, d3.zoomIdentity);
  412. setTimeout(() => {
  413. var graphRenderer = new GraphRenderer(outputGroup);
  414. graphRenderer.render(g);
  415. var svgSize = svgElement.getBoundingClientRect();
  416. var inputElements = svgElement.getElementsByClassName('graph-input');
  417. if (inputElements && inputElements.length > 0) {
  418. // Center view based on input elements
  419. var x = 0;
  420. var y = 0;
  421. for (var i = 0; i < inputElements.length; i++) {
  422. var inputTransform = inputElements[i].transform.baseVal.consolidate().matrix;
  423. x += inputTransform.e;
  424. y += inputTransform.f;
  425. }
  426. x = x / inputElements.length;
  427. y = y / inputElements.length;
  428. svg.call(zoom.transform, d3.zoomIdentity.translate((svgSize.width / 2) - x, (svgSize.height / 4) - y));
  429. }
  430. else {
  431. svg.call(zoom.transform, d3.zoomIdentity.translate((svgSize.width - g.graph().width) / 2, (svgSize.height - g.graph().height) / 2));
  432. }
  433. this.show('graph');
  434. }, 2);
  435. }
  436. showSummary() {
  437. if (this._model) {
  438. var template = Handlebars.compile(summaryTemplate, 'utf-8');
  439. var data = template(this._model);
  440. this._sidebar.open(data, 'Summary');
  441. }
  442. }
  443. showNode(node) {
  444. if (node) {
  445. var documentationHandler = () => {
  446. this.showDocumentation(node);
  447. };
  448. var view = new NodeView(node, documentationHandler);
  449. this._sidebar.open(view.elements, 'Node');
  450. }
  451. }
  452. showNodeInput(node, input) {
  453. if (input) {
  454. var documentationHandler = () => {
  455. this.showDocumentation(node);
  456. };
  457. var view = new NodeView(node, documentationHandler);
  458. view.toggleInput(input.name);
  459. this._sidebar.open(view.elements, 'Node');
  460. }
  461. }
  462. showDocumentation(node) {
  463. var documentation = node.documentation;
  464. if (documentation) {
  465. this._sidebar.open(documentation, 'Documentation');
  466. var documentationElement = document.getElementById('documentation');
  467. if (documentationElement) {
  468. documentationElement.addEventListener('click', (e) => {
  469. if (e.target && e.target.href) {
  470. var link = e.target.href;
  471. if (link.startsWith('http://') || link.startsWith('https://')) {
  472. this._host.openURL(link);
  473. e.preventDefault();
  474. }
  475. }
  476. });
  477. }
  478. }
  479. }
  480. }
  481. class Sidebar {
  482. constructor() {
  483. this._closeSidebarHandler = (e) => {
  484. this.close();
  485. };
  486. this._closeSidebarKeyDownHandler = (e) => {
  487. if (e.keyCode == 27) {
  488. e.preventDefault();
  489. this.close();
  490. }
  491. };
  492. this._resizeSidebarHandler = (e) => {
  493. var contentElement = document.getElementById('sidebar-content');
  494. if (contentElement) {
  495. contentElement.style.height = window.innerHeight - 60;
  496. }
  497. };
  498. }
  499. open(content, title, width) {
  500. var sidebarElement = document.getElementById('sidebar');
  501. var titleElement = document.getElementById('sidebar-title');
  502. var contentElement = document.getElementById('sidebar-content');
  503. var closeButtonElement = document.getElementById('sidebar-closebutton');
  504. if (sidebarElement && contentElement && closeButtonElement && titleElement) {
  505. titleElement.innerHTML = title ? title.toUpperCase() : '';
  506. window.addEventListener('resize', this._resizeSidebarHandler);
  507. document.addEventListener('keydown', this._closeSidebarKeyDownHandler);
  508. closeButtonElement.addEventListener('click', this._closeSidebarHandler);
  509. closeButtonElement.style.color = '#818181';
  510. contentElement.style.height = window.innerHeight - 60;
  511. while (contentElement.firstChild) {
  512. contentElement.removeChild(contentElement.firstChild);
  513. }
  514. if (typeof content == 'string') {
  515. contentElement.innerHTML = content;
  516. }
  517. else if (content instanceof Array) {
  518. content.forEach((element) => {
  519. contentElement.appendChild(element);
  520. });
  521. }
  522. else {
  523. contentElement.appendChild(content);
  524. }
  525. sidebarElement.style.width = width ? width : '500px';
  526. if (width && width.endsWith('%')) {
  527. contentElement.style.width = '100%';
  528. }
  529. else {
  530. contentElement.style.width = 'calc(' + sidebarElement.style.width + ' - 40px)';
  531. }
  532. }
  533. }
  534. close() {
  535. var sidebarElement = document.getElementById('sidebar');
  536. var contentElement = document.getElementById('sidebar-content');
  537. var closeButtonElement = document.getElementById('sidebar-closebutton');
  538. if (sidebarElement && contentElement && closeButtonElement) {
  539. document.removeEventListener('keydown', this._closeSidebarKeyDownHandler);
  540. sidebarElement.removeEventListener('resize', this._resizeSidebarHandler);
  541. closeButtonElement.removeEventListener('click', this._closeSidebarHandler);
  542. closeButtonElement.style.color = '#f8f8f8';
  543. sidebarElement.style.width = '0';
  544. }
  545. }
  546. }
  547. window.view = new View(window.host);
  548. function updateActiveGraph(name) {
  549. window.view.updateActiveGraph(name);
  550. }
  551. class Int64 {
  552. constructor(buffer) {
  553. this._buffer = buffer;
  554. }
  555. toString(radix) {
  556. var high = this.readInt32(4);
  557. var low = this.readInt32(0);
  558. var str = '';
  559. var sign = high & 0x80000000;
  560. if (sign) {
  561. high = ~high;
  562. low = 0x100000000 - low;
  563. }
  564. radix = radix || 10;
  565. while (true) {
  566. var mod = (high % radix) * 0x100000000 + low;
  567. high = Math.floor(high / radix);
  568. low = Math.floor(mod / radix);
  569. str = (mod % radix).toString(radix) + str;
  570. if (!high && !low)
  571. {
  572. break;
  573. }
  574. }
  575. if (sign) {
  576. str = "-" + str;
  577. }
  578. return str;
  579. }
  580. readInt32(offset) {
  581. return (this._buffer[offset + 3] * 0x1000000) + (this._buffer[offset + 2] << 16) + (this._buffer[offset + 1] << 8) + this._buffer[offset + 0];
  582. }
  583. }
  584. class Uint64 {
  585. constructor(buffer) {
  586. this._buffer = buffer;
  587. }
  588. toString(radix) {
  589. var high = this.readInt32(4);
  590. var low = this.readInt32(0);
  591. var str = '';
  592. radix = radix || 10;
  593. while (true) {
  594. var mod = (high % radix) * 0x100000000 + low;
  595. high = Math.floor(high / radix);
  596. low = Math.floor(mod / radix);
  597. str = (mod % radix).toString(radix) + str;
  598. if (!high && !low)
  599. {
  600. break;
  601. }
  602. }
  603. return str;
  604. }
  605. readInt32(offset) {
  606. return (this._buffer[offset + 3] * 0x1000000) + (this._buffer[offset + 2] << 16) + (this._buffer[offset + 1] << 8) + this._buffer[offset + 0];
  607. }
  608. }