UNPKG

240 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var d3 = require('d3');
6
7function _interopNamespace(e) {
8 if (e && e.__esModule) return e;
9 var n = Object.create(null);
10 if (e) {
11 Object.keys(e).forEach(function (k) {
12 if (k !== 'default') {
13 var d = Object.getOwnPropertyDescriptor(e, k);
14 Object.defineProperty(n, k, d.get ? d : {
15 enumerable: true,
16 get: function () {
17 return e[k];
18 }
19 });
20 }
21 });
22 }
23 n['default'] = e;
24 return Object.freeze(n);
25}
26
27var d3__namespace = /*#__PURE__*/_interopNamespace(d3);
28
29var version = "3.0.2";
30
31var dataModel = {};
32
33dataModel.idGen = 0;
34
35dataModel.generateId = function () {
36 return dataModel.idGen++;
37};
38
39dataModel.nodes = [];
40dataModel.links = [];
41
42dataModel.getRootNode = function () {
43 return dataModel.nodes[0];
44};
45
46/**
47 * logger module.
48 * @module logger
49 */
50
51// LOGGER -----------------------------------------------------------------------------------------------------------
52var logger = {};
53logger.LogLevels = Object.freeze({DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, NONE: 4});
54logger.LEVEL = logger.LogLevels.NONE;
55logger.TRACE = false;
56
57/**
58 * Log a message on console depending on configured log levels.
59 * Level is define in popoto.logger.LEVEL property.
60 * If popoto.logger.TRACE is set to true, the stack trace is also added in log.
61 * @param logLevel Level of the message from popoto.logger.LogLevels.
62 * @param message Message to log.
63 */
64logger.log = function (logLevel, message) {
65 if (console && logLevel >= logger.LEVEL) {
66 if (logger.TRACE) {
67 message = message + "\n" + new Error().stack;
68 }
69 switch (logLevel) {
70 case logger.LogLevels.DEBUG:
71 console.log(message);
72 break;
73 case logger.LogLevels.INFO:
74 console.log(message);
75 break;
76 case logger.LogLevels.WARN:
77 console.warn(message);
78 break;
79 case logger.LogLevels.ERROR:
80 console.error(message);
81 break;
82 }
83 }
84};
85
86/**
87 * Log a message in DEBUG level.
88 * @param message to log.
89 */
90logger.debug = function (message) {
91 logger.log(logger.LogLevels.DEBUG, message);
92};
93
94/**
95 * Log a message in INFO level.
96 * @param message to log.
97 */
98logger.info = function (message) {
99 logger.log(logger.LogLevels.INFO, message);
100};
101
102/**
103 * Log a message in WARN level.
104 * @param message to log.
105 */
106logger.warn = function (message) {
107 logger.log(logger.LogLevels.WARN, message);
108};
109
110/**
111 * Log a message in ERROR level.
112 * @param message to log.
113 */
114logger.error = function (message) {
115 logger.log(logger.LogLevels.ERROR, message);
116};
117
118var query = {};
119
120/**
121 * Define the number of results displayed in result list.
122 */
123query.MAX_RESULTS_COUNT = 100;
124// query.RESULTS_PAGE_NUMBER = 1;
125query.VALUE_QUERY_LIMIT = 100;
126query.USE_PARENT_RELATION = false;
127query.USE_RELATION_DIRECTION = true;
128query.RETURN_LABELS = false;
129query.COLLECT_RELATIONS_WITH_VALUES = false;
130query.prefilter = "";
131query.prefilterParameters = {};
132
133query.applyPrefilters = function (queryStructure) {
134 queryStructure.statement = query.prefilter + queryStructure.statement;
135
136 Object.keys(query.prefilterParameters).forEach(function (key) {
137 queryStructure.parameters[key] = query.prefilterParameters[key];
138 });
139
140 return queryStructure;
141};
142
143/**
144 * Immutable constant object to identify Neo4j internal ID
145 */
146query.NEO4J_INTERNAL_ID = Object.freeze({queryInternalName: "NEO4JID"});
147
148/**
149 * Function used to filter returned relations
150 * return false if the result should be filtered out.
151 *
152 * @param d relation returned object
153 * @returns {boolean}
154 */
155query.filterRelation = function (d) {
156 return true;
157};
158
159/**
160 * Generate the query to count nodes of a label.
161 * If the label is defined as distinct in configuration the query will count only distinct values on constraint attribute.
162 */
163query.generateTaxonomyCountQuery = function (label) {
164 var constraintAttr = provider$1.node.getConstraintAttribute(label);
165
166 var whereElements = [];
167
168 var predefinedConstraints = provider$1.node.getPredefinedConstraints(label);
169 predefinedConstraints.forEach(function (predefinedConstraint) {
170 whereElements.push(predefinedConstraint.replace(new RegExp("\\$identifier", 'g'), "n"));
171 });
172
173 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
174 return "MATCH (n:`" + label + "`)" + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN count(DISTINCT ID(n)) as count"
175 } else {
176 return "MATCH (n:`" + label + "`)" + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN count(DISTINCT n." + constraintAttr + ") as count"
177 }
178};
179
180query.generateNegativeQueryElements = function () {
181 var whereElements = [];
182 var parameters = {};
183
184 var negativeNodes = dataModel.nodes.filter(function (n) {
185 return n.isNegative === true;
186 });
187
188 negativeNodes.forEach(
189 function (n) {
190 if (provider$1.node.getGenerateNegativeNodeValueConstraints(n) !== undefined) {
191 var custom = provider$1.node.getGenerateNegativeNodeValueConstraints(n)(n);
192 whereElements = whereElements.concat(custom.whereElements);
193 for (var prop in custom.parameters) {
194 if (custom.parameters.hasOwnProperty(prop)) {
195 parameters[prop] = custom.parameters[prop];
196 }
197 }
198 } else {
199 var linksToRoot = query.getLinksToRoot(n, dataModel.links);
200
201 var i = linksToRoot.length - 1;
202 var statement = "(NOT exists(";
203
204 statement += "(" + dataModel.getRootNode().internalLabel + ")";
205
206 while (i >= 0) {
207 var l = linksToRoot[i];
208 var targetNode = l.target;
209
210 if (targetNode.isParentRelReverse === true && query.USE_RELATION_DIRECTION === true) {
211 statement += "<-";
212 } else {
213 statement += "-";
214 }
215
216 statement += "[:`" + l.label + "`]";
217
218 if (targetNode.isParentRelReverse !== true && query.USE_RELATION_DIRECTION === true) {
219 statement += "->";
220 } else {
221 statement += "-";
222 }
223
224 if (targetNode === n && targetNode.value !== undefined && targetNode.value.length > 0) {
225 var constraintAttr = provider$1.node.getConstraintAttribute(targetNode.label);
226 var paramName = targetNode.internalLabel + "_" + constraintAttr;
227
228 if (targetNode.value.length > 1) {
229 for (var pid = 0; pid < targetNode.value.length; pid++) {
230 parameters[paramName + "_" + pid] = targetNode.value[pid].attributes[constraintAttr];
231 }
232
233 statement += "(:`" + targetNode.label + "`{" + constraintAttr + ":$x$})";
234 } else {
235 parameters[paramName] = targetNode.value[0].attributes[constraintAttr];
236 statement += "(:`" + targetNode.label + "`{" + constraintAttr + ":$" + paramName + "})";
237 }
238 } else {
239 statement += "(:`" + targetNode.label + "`)";
240 }
241
242 i--;
243 }
244
245 statement += "))";
246
247 if (n.value !== undefined && n.value.length > 1) {
248 var cAttr = provider$1.node.getConstraintAttribute(n.label);
249 var pn = n.internalLabel + "_" + cAttr;
250
251 for (var nid = 0; nid < targetNode.value.length; nid++) {
252 whereElements.push(statement.replace("$x$", "$" + pn + "_" + nid));
253 }
254 } else {
255 whereElements.push(statement);
256 }
257 }
258 }
259 );
260
261 return {
262 "whereElements": whereElements,
263 "parameters": parameters
264 };
265};
266
267/**
268 * Generate Cypher query match and where elements from root node, selected node and a set of the graph links.
269 *
270 * @param rootNode root node in the graph.
271 * @param selectedNode graph target node.
272 * @param links list of links subset of the graph.
273 * @returns {{matchElements: Array, whereElements: Array}} list of match and where elements.
274 * @param isConstraintNeeded (used only for relation query)
275 * @param useCustomConstraints define whether to use the custom constraints (actually it is used only for results)
276 */
277query.generateQueryElements = function (rootNode, selectedNode, links, isConstraintNeeded, useCustomConstraints) {
278 var matchElements = [];
279 var whereElements = [];
280 var relationElements = [];
281 var returnElements = [];
282 var parameters = {};
283
284 var rootPredefinedConstraints = provider$1.node.getPredefinedConstraints(rootNode.label);
285
286 rootPredefinedConstraints.forEach(function (predefinedConstraint) {
287 whereElements.push(predefinedConstraint.replace(new RegExp("\\$identifier", 'g'), rootNode.internalLabel));
288 });
289
290 matchElements.push("(" + rootNode.internalLabel + ":`" + rootNode.label + "`)");
291
292 // Generate root node match element
293 if (isConstraintNeeded || rootNode.immutable) {
294 var rootValueConstraints = query.generateNodeValueConstraints(rootNode, useCustomConstraints);
295 whereElements = whereElements.concat(rootValueConstraints.whereElements);
296 for (var param in rootValueConstraints.parameters) {
297 if (rootValueConstraints.parameters.hasOwnProperty(param)) {
298 parameters[param] = rootValueConstraints.parameters[param];
299 }
300 }
301 }
302
303 var relId = 0;
304
305 // Generate match elements for each links
306 links.forEach(function (l) {
307 var sourceNode = l.source;
308 var targetNode = l.target;
309
310 var sourceRel = "";
311 var targetRel = "";
312
313 if (!query.USE_RELATION_DIRECTION) {
314 sourceRel = "-";
315 targetRel = "-";
316 } else {
317 if (targetNode.isParentRelReverse === true) {
318 sourceRel = "<-";
319 targetRel = "-";
320 } else {
321 sourceRel = "-";
322 targetRel = "->";
323 }
324 }
325
326 var relIdentifier = "r" + relId++;
327 relationElements.push(relIdentifier);
328 var predefinedConstraints = provider$1.node.getPredefinedConstraints(targetNode.label);
329
330 predefinedConstraints.forEach(function (predefinedConstraint) {
331 whereElements.push(predefinedConstraint.replace(new RegExp("\\$identifier", 'g'), targetNode.internalLabel));
332 });
333
334 if (query.COLLECT_RELATIONS_WITH_VALUES && targetNode === selectedNode) {
335 returnElements.push("COLLECT(" + relIdentifier + ") AS incomingRels");
336 }
337
338 var sourceLabelStatement = "";
339
340 if (!useCustomConstraints || provider$1.node.getGenerateNodeValueConstraints(sourceNode) === undefined) {
341 sourceLabelStatement = ":`" + sourceNode.label + "`";
342 }
343
344 var targetLabelStatement = "";
345
346 if (!useCustomConstraints || provider$1.node.getGenerateNodeValueConstraints(targetNode) === undefined) {
347 targetLabelStatement = ":`" + targetNode.label + "`";
348 }
349
350 matchElements.push("(" + sourceNode.internalLabel + sourceLabelStatement + ")" + sourceRel + "[" + relIdentifier + ":`" + l.label + "`]" + targetRel + "(" + targetNode.internalLabel + targetLabelStatement + ")");
351
352 if (targetNode !== selectedNode && (isConstraintNeeded || targetNode.immutable)) {
353 var nodeValueConstraints = query.generateNodeValueConstraints(targetNode, useCustomConstraints);
354 whereElements = whereElements.concat(nodeValueConstraints.whereElements);
355 for (var param in nodeValueConstraints.parameters) {
356 if (nodeValueConstraints.parameters.hasOwnProperty(param)) {
357 parameters[param] = nodeValueConstraints.parameters[param];
358 }
359 }
360 }
361 });
362
363 return {
364 "matchElements": matchElements,
365 "whereElements": whereElements,
366 "relationElements": relationElements,
367 "returnElements": returnElements,
368 "parameters": parameters
369 };
370};
371
372/**
373 * Generate the where and parameter statements for the nodes with value
374 *
375 * @param node the node to generate value constraints
376 * @param useCustomConstraints define whether to use custom generation in popoto config
377 */
378query.generateNodeValueConstraints = function (node, useCustomConstraints) {
379 if (useCustomConstraints && provider$1.node.getGenerateNodeValueConstraints(node) !== undefined) {
380 return provider$1.node.getGenerateNodeValueConstraints(node)(node);
381 } else {
382 var parameters = {}, whereElements = [];
383 if (node.value !== undefined && node.value.length > 0) {
384 var constraintAttr = provider$1.node.getConstraintAttribute(node.label);
385 var paramName;
386 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
387 paramName = node.internalLabel + "_internalID";
388 } else {
389 paramName = node.internalLabel + "_" + constraintAttr;
390 }
391
392 if (node.value.length > 1) { // Generate IN constraint
393 parameters[paramName] = [];
394
395 node.value.forEach(function (value) {
396 var constraintValue;
397 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
398 constraintValue = value.internalID;
399 } else {
400 constraintValue = value.attributes[constraintAttr];
401 }
402
403 parameters[paramName].push(constraintValue);
404 });
405
406 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
407 whereElements.push("ID(" + node.internalLabel + ") IN " + "$" + paramName);
408 } else {
409 whereElements.push(node.internalLabel + "." + constraintAttr + " IN " + "$" + paramName);
410 }
411 } else { // Generate = constraint
412 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
413 parameters[paramName] = node.value[0].internalID;
414 } else {
415 parameters[paramName] = node.value[0].attributes[constraintAttr];
416 }
417
418 var operator = "=";
419
420 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
421 whereElements.push("ID(" + node.internalLabel + ") " + operator + " " + "$" + paramName);
422 } else {
423 whereElements.push(node.internalLabel + "." + constraintAttr + " " + operator + " " + "$" + paramName);
424 }
425 }
426 }
427
428 return {
429 parameters: parameters,
430 whereElements: whereElements
431 }
432 }
433};
434
435/**
436 * Filter links to get only paths from root to leaf containing a value or being the selectedNode.
437 * All other paths in the graph containing no value are ignored.
438 *
439 * @param rootNode root node of the graph.
440 * @param targetNode node in the graph target of the query.
441 * @param initialLinks list of links representing the graph to filter.
442 * @returns {Array} list of relevant links.
443 */
444query.getRelevantLinks = function (rootNode, targetNode, initialLinks) {
445 var links = initialLinks.slice();
446 var finalLinks = [];
447
448 // Filter all links to keep only those containing a value or being the selected node.
449 // Negatives nodes are handled separately.
450 var filteredLinks = links.filter(function (l) {
451 return l.target === targetNode || ((l.target.value !== undefined && l.target.value.length > 0) && (!l.target.isNegative === true));
452 });
453
454 // All the filtered links are removed from initial links list.
455 filteredLinks.forEach(function (l) {
456 links.splice(links.indexOf(l), 1);
457 });
458
459 // Then all the intermediate links up to the root node are added to get only the relevant links.
460 filteredLinks.forEach(function (fl) {
461 var sourceNode = fl.source;
462 var search = true;
463
464 while (search) {
465 var intermediateLink = null;
466 links.forEach(function (l) {
467 if (l.target === sourceNode) {
468 intermediateLink = l;
469 }
470 });
471
472 if (intermediateLink === null) { // no intermediate links needed
473 search = false;
474 } else {
475 if (intermediateLink.source === rootNode) {
476 finalLinks.push(intermediateLink);
477 links.splice(links.indexOf(intermediateLink), 1);
478 search = false;
479 } else {
480 finalLinks.push(intermediateLink);
481 links.splice(links.indexOf(intermediateLink), 1);
482 sourceNode = intermediateLink.source;
483 }
484 }
485 }
486 });
487
488 return filteredLinks.concat(finalLinks);
489};
490
491/**
492 * Get the list of link defining the complete path from node to root.
493 * All other links are ignored.
494 *
495 * @param node The node where to start in the graph.
496 * @param links
497 */
498query.getLinksToRoot = function (node, links) {
499 var pathLinks = [];
500 var targetNode = node;
501
502 while (targetNode !== dataModel.getRootNode()) {
503 var nodeLink;
504
505 for (var i = 0; i < links.length; i++) {
506 var link = links[i];
507 if (link.target === targetNode) {
508 nodeLink = link;
509 break;
510 }
511 }
512
513 if (nodeLink) {
514 pathLinks.push(nodeLink);
515 targetNode = nodeLink.source;
516 }
517 }
518
519 return pathLinks;
520};
521
522/**
523 * Generate a Cypher query to retrieve the results matching the current graph.
524 *
525 * @param isGraph
526 * @returns {{statement: string, parameters: (*|{})}}
527 */
528query.generateResultQuery = function (isGraph) {
529 var rootNode = dataModel.getRootNode();
530 var negativeElements = query.generateNegativeQueryElements();
531 var queryElements = query.generateQueryElements(rootNode, rootNode, query.getRelevantLinks(rootNode, rootNode, dataModel.links), true, true);
532 var queryMatchElements = queryElements.matchElements,
533 queryWhereElements = queryElements.whereElements.concat(negativeElements.whereElements),
534 queryRelationElements = queryElements.relationElements,
535 queryReturnElements = [],
536 queryEndElements = [],
537 queryParameters = queryElements.parameters;
538
539 for (var prop in negativeElements.parameters) {
540 if (negativeElements.parameters.hasOwnProperty(prop)) {
541 queryParameters[prop] = negativeElements.parameters[prop];
542 }
543 }
544
545 // Sort results by specified attribute
546 var resultOrderByAttribute = provider$1.node.getResultOrderByAttribute(rootNode.label);
547
548 if (resultOrderByAttribute !== undefined && resultOrderByAttribute !== null) {
549 var sorts = [];
550 var order = provider$1.node.isResultOrderAscending(rootNode.label);
551
552 var orders = [];
553 if (Array.isArray(order)) {
554 orders = order.map(function (v) {
555 return v ? "ASC" : "DESC";
556 });
557 } else {
558 orders.push(order ? "ASC" : "DESC");
559 }
560
561 if (Array.isArray(resultOrderByAttribute)) {
562 sorts = resultOrderByAttribute.map(function (ra) {
563 var index = resultOrderByAttribute.indexOf(ra);
564
565 if (index < orders.length) {
566 return ra + " " + orders[index];
567 } else {
568 return ra + " " + orders[orders.length - 1];
569 }
570 });
571
572 } else {
573 sorts.push(resultOrderByAttribute + " " + orders[0]);
574 }
575
576 queryEndElements.push("ORDER BY " + sorts.join(", "));
577 }
578
579 queryEndElements.push("LIMIT " + query.MAX_RESULTS_COUNT);
580
581 if (isGraph) {
582 // Only return relations
583 queryReturnElements.push(rootNode.internalLabel);
584 queryRelationElements.forEach(
585 function (el) {
586 queryReturnElements.push(el);
587 }
588 );
589 } else {
590 var resultAttributes = provider$1.node.getReturnAttributes(rootNode.label);
591
592 queryReturnElements = resultAttributes.map(function (attribute) {
593 if (attribute === query.NEO4J_INTERNAL_ID) {
594 return "ID(" + rootNode.internalLabel + ") AS " + query.NEO4J_INTERNAL_ID.queryInternalName;
595 } else {
596 return rootNode.internalLabel + "." + attribute + " AS " + attribute;
597 }
598 });
599
600 if (query.RETURN_LABELS === true) {
601 var element = "labels(" + rootNode.internalLabel + ")";
602
603 if (resultAttributes.indexOf("labels") < 0) {
604 element = element + " AS labels";
605 }
606
607 queryReturnElements.push(element);
608 }
609 }
610
611 var queryStatement = "MATCH " + queryMatchElements.join(", ") + ((queryWhereElements.length > 0) ? " WHERE " + queryWhereElements.join(" AND ") : "") + " RETURN DISTINCT " + queryReturnElements.join(", ") + " " + queryEndElements.join(" ");
612
613 // Filter the query if defined in config
614 var queryStructure = provider$1.node.filterResultQuery(rootNode.label, {
615 statement: queryStatement,
616 matchElements: queryMatchElements,
617 whereElements: queryWhereElements,
618 withElements: [],
619 returnElements: queryReturnElements,
620 endElements: queryEndElements,
621 parameters: queryParameters
622 });
623
624 return query.applyPrefilters(queryStructure);
625};
626
627/**
628 * Generate a cypher query to the get the node count, set as parameter matching the current graph.
629 *
630 * @param countedNode the counted node
631 * @returns {string} the node count cypher query
632 */
633query.generateNodeCountQuery = function (countedNode) {
634 var negativeElements = query.generateNegativeQueryElements();
635 var queryElements = query.generateQueryElements(dataModel.getRootNode(), countedNode, query.getRelevantLinks(dataModel.getRootNode(), countedNode, dataModel.links), true, true);
636 var queryMatchElements = queryElements.matchElements,
637 queryWhereElements = queryElements.whereElements.concat(negativeElements.whereElements),
638 queryReturnElements = [],
639 queryEndElements = [],
640 queryParameters = queryElements.parameters;
641
642 for (var prop in negativeElements.parameters) {
643 if (negativeElements.parameters.hasOwnProperty(prop)) {
644 queryParameters[prop] = negativeElements.parameters[prop];
645 }
646 }
647
648 var countAttr = provider$1.node.getConstraintAttribute(countedNode.label);
649
650 if (countAttr === query.NEO4J_INTERNAL_ID) {
651 queryReturnElements.push("count(DISTINCT ID(" + countedNode.internalLabel + ")) as count");
652 } else {
653 queryReturnElements.push("count(DISTINCT " + countedNode.internalLabel + "." + countAttr + ") as count");
654 }
655
656 var queryStatement = "MATCH " + queryMatchElements.join(", ") + ((queryWhereElements.length > 0) ? " WHERE " + queryWhereElements.join(" AND ") : "") + " RETURN " + queryReturnElements.join(", ");
657
658 // Filter the query if defined in config
659 var queryStructure = provider$1.node.filterNodeCountQuery(countedNode, {
660 statement: queryStatement,
661 matchElements: queryMatchElements,
662 whereElements: queryWhereElements,
663 returnElements: queryReturnElements,
664 endElements: queryEndElements,
665 parameters: queryParameters
666 });
667
668 return query.applyPrefilters(queryStructure);
669};
670
671/**
672 * Generate a Cypher query from the graph model to get all the possible values for the targetNode element.
673 *
674 * @param targetNode node in the graph to get the values.
675 * @returns {string} the query to execute to get all the values of targetNode corresponding to the graph.
676 */
677query.generateNodeValueQuery = function (targetNode) {
678 var negativeElements = query.generateNegativeQueryElements();
679 var rootNode = dataModel.getRootNode();
680 var queryElements = query.generateQueryElements(rootNode, targetNode, query.getRelevantLinks(rootNode, targetNode, dataModel.links), true, false);
681 var queryMatchElements = queryElements.matchElements,
682 queryWhereElements = queryElements.whereElements.concat(negativeElements.whereElements),
683 queryReturnElements = [],
684 queryEndElements = [],
685 queryParameters = queryElements.parameters;
686
687 for (var prop in negativeElements.parameters) {
688 if (negativeElements.parameters.hasOwnProperty(prop)) {
689 queryParameters[prop] = negativeElements.parameters[prop];
690 }
691 }
692
693 // Sort results by specified attribute
694 var valueOrderByAttribute = provider$1.node.getValueOrderByAttribute(targetNode.label);
695 if (valueOrderByAttribute) {
696 var order = provider$1.node.isValueOrderAscending(targetNode.label) ? "ASC" : "DESC";
697 queryEndElements.push("ORDER BY " + valueOrderByAttribute + " " + order);
698 }
699
700 queryEndElements.push("LIMIT " + query.VALUE_QUERY_LIMIT);
701
702 var resultAttributes = provider$1.node.getReturnAttributes(targetNode.label);
703 provider$1.node.getConstraintAttribute(targetNode.label);
704
705 for (var i = 0; i < resultAttributes.length; i++) {
706 if (resultAttributes[i] === query.NEO4J_INTERNAL_ID) {
707 queryReturnElements.push("ID(" + targetNode.internalLabel + ") AS " + query.NEO4J_INTERNAL_ID.queryInternalName);
708 } else {
709 queryReturnElements.push(targetNode.internalLabel + "." + resultAttributes[i] + " AS " + resultAttributes[i]);
710 }
711 }
712
713 // Add count return attribute on root node
714 var rootConstraintAttr = provider$1.node.getConstraintAttribute(rootNode.label);
715
716 if (rootConstraintAttr === query.NEO4J_INTERNAL_ID) {
717 queryReturnElements.push("count(DISTINCT ID(" + rootNode.internalLabel + ")) AS count");
718 } else {
719 queryReturnElements.push("count(DISTINCT " + rootNode.internalLabel + "." + rootConstraintAttr + ") AS count");
720 }
721
722 if (query.COLLECT_RELATIONS_WITH_VALUES) {
723 queryElements.returnElements.forEach(function (re) {
724 queryReturnElements.push(re);
725 });
726 }
727
728 var queryStatement = "MATCH " + queryMatchElements.join(", ") + ((queryWhereElements.length > 0) ? " WHERE " + queryWhereElements.join(" AND ") : "") + " RETURN " + queryReturnElements.join(", ") + " " + queryEndElements.join(" ");
729
730 // Filter the query if defined in config
731 var queryStructure = provider$1.node.filterNodeValueQuery(targetNode, {
732 statement: queryStatement,
733 matchElements: queryMatchElements,
734 whereElements: queryWhereElements,
735 returnElements: queryReturnElements,
736 endElements: queryEndElements,
737 parameters: queryParameters
738 });
739
740 return query.applyPrefilters(queryStructure);
741};
742
743/**
744 * Generate a Cypher query to retrieve all the relation available for a given node.
745 *
746 * @param targetNode
747 * @returns {string}
748 */
749query.generateNodeRelationQuery = function (targetNode) {
750
751 var linksToRoot = query.getLinksToRoot(targetNode, dataModel.links);
752
753 var queryElements = query.generateQueryElements(dataModel.getRootNode(), targetNode, linksToRoot, false, false);
754 var queryMatchElements = queryElements.matchElements,
755 queryWhereElements = queryElements.whereElements,
756 queryReturnElements = [],
757 queryEndElements = [],
758 queryParameters = queryElements.parameters;
759
760 var rel = query.USE_RELATION_DIRECTION ? "->" : "-";
761
762 queryMatchElements.push("(" + targetNode.internalLabel + ":`" + targetNode.label + "`)-[r]" + rel + "(x)");
763 queryReturnElements.push("type(r) AS label");
764 if (query.USE_PARENT_RELATION) {
765 queryReturnElements.push("head(labels(x)) AS target");
766 } else {
767 queryReturnElements.push("last(labels(x)) AS target");
768 }
769 queryReturnElements.push("count(r) AS count");
770 queryEndElements.push("ORDER BY count(r) DESC");
771
772 var queryStatement = "MATCH " + queryMatchElements.join(", ") + ((queryWhereElements.length > 0) ? " WHERE " + queryWhereElements.join(" AND ") : "") + " RETURN " + queryReturnElements.join(", ") + " " + queryEndElements.join(" ");
773 // Filter the query if defined in config
774 var queryStructure = provider$1.node.filterNodeRelationQuery(targetNode, {
775 statement: queryStatement,
776 matchElements: queryMatchElements,
777 whereElements: queryWhereElements,
778 returnElements: queryReturnElements,
779 endElements: queryEndElements,
780 parameters: queryParameters
781 });
782
783 return query.applyPrefilters(queryStructure);
784};
785
786/**
787 * runner module.
788 * @module runner
789 */
790
791var runner = {};
792
793runner.createSession = function () {
794 if (runner.DRIVER !== undefined) {
795 return runner.DRIVER.session({defaultAccessMode: "READ"})
796 } else {
797 throw new Error("popoto.runner.DRIVER must be defined");
798 }
799};
800
801runner.run = function (statements) {
802 logger.info("STATEMENTS:" + JSON.stringify(statements));
803 var session = runner.createSession();
804
805 return session.readTransaction(function (transaction) {
806 return Promise.all(
807 statements.statements.map(function (s) {
808 return transaction.run({text: s.statement, parameters: s.parameters});
809 })
810 )
811 })
812 .finally(function () {
813 session.close();
814 })
815};
816
817runner.toObject = function (results) {
818 return results.map(function (rs) {
819 return rs.records.map(function (r) {
820 return r.toObject();
821 })
822 })
823};
824
825var result = {};
826result.containerId = "popoto-results";
827result.hasChanged = true;
828result.resultCountListeners = [];
829result.resultListeners = [];
830result.graphResultListeners = [];
831result.RESULTS_PAGE_SIZE = 10;
832result.TOTAL_COUNT = false;
833
834/**
835 * Register a listener to the result count event.
836 * This listener will be called on evry result change with total result count.
837 */
838result.onTotalResultCount = function (listener) {
839 result.resultCountListeners.push(listener);
840};
841
842result.onResultReceived = function (listener) {
843 result.resultListeners.push(listener);
844};
845
846result.onGraphResultReceived = function (listener) {
847 result.graphResultListeners.push(listener);
848};
849
850/**
851 * Parse REST returned Graph data and generate a list of nodes and edges.
852 *
853 * @param data
854 * @returns {{nodes: Array, edges: Array}}
855 */
856result.parseGraphResultData = function (data) {
857
858 var nodes = {}, edges = {};
859
860 data.results[1].data.forEach(function (row) {
861 row.graph.nodes.forEach(function (n) {
862 if (!nodes.hasOwnProperty(n.id)) {
863 nodes[n.id] = n;
864 }
865 });
866
867 row.graph.relationships.forEach(function (r) {
868 if (!edges.hasOwnProperty(r.id)) {
869 edges[r.id] = r;
870 }
871 });
872 });
873
874 var nodesArray = [], edgesArray = [];
875
876 for (var n in nodes) {
877 if (nodes.hasOwnProperty(n)) {
878 nodesArray.push(nodes[n]);
879 }
880 }
881
882 for (var e in edges) {
883 if (edges.hasOwnProperty(e)) {
884 edgesArray.push(edges[e]);
885 }
886 }
887
888 return {nodes: nodesArray, edges: edgesArray};
889};
890
891result.updateResults = function () {
892 if (result.hasChanged) {
893 var resultsIndex = {};
894 var index = 0;
895
896 var resultQuery = query.generateResultQuery();
897 result.lastGeneratedQuery = resultQuery;
898
899 var postData = {
900 "statements": [
901 {
902 "statement": resultQuery.statement,
903 "parameters": resultQuery.parameters,
904 }
905 ]
906 };
907 resultsIndex["results"] = index++;
908
909 // Add Graph result query if listener found
910 if (result.graphResultListeners.length > 0) {
911 var graphQuery = query.generateResultQuery(true);
912 result.lastGeneratedQuery = graphQuery;
913
914 postData.statements.push(
915 {
916 "statement": graphQuery.statement,
917 "parameters": graphQuery.parameters,
918 });
919 resultsIndex["graph"] = index++;
920 }
921
922 if (result.TOTAL_COUNT === true && result.resultCountListeners.length > 0) {
923 var nodeCountQuery = query.generateNodeCountQuery(dataModel.getRootNode());
924 postData.statements.push(
925 {
926 "statement": nodeCountQuery.statement,
927 "parameters": nodeCountQuery.parameters
928 }
929 );
930 resultsIndex["total"] = index++;
931 }
932
933 logger.info("Results ==>");
934
935 runner.run(postData)
936 .then(function (res) {
937 logger.info("<== Results");
938
939 var parsedData = runner.toObject(res);
940
941 var resultObjects = parsedData[resultsIndex["results"]].map(function (d, i) {
942 return {
943 "resultIndex": i,
944 "label": dataModel.getRootNode().label,
945 "attributes": d
946 };
947 });
948
949 result.lastResults = resultObjects;
950
951 if (resultsIndex.hasOwnProperty("total")) {
952 var count = parsedData[resultsIndex["total"]][0].count;
953
954 // Notify listeners
955 result.resultCountListeners.forEach(function (listener) {
956 listener(count);
957 });
958 }
959
960 // Notify listeners
961 result.resultListeners.forEach(function (listener) {
962 listener(resultObjects);
963 });
964
965 if (result.graphResultListeners.length > 0) {
966 var graphResultObjects = result.parseGraphResultData(response);
967 result.graphResultListeners.forEach(function (listener) {
968 listener(graphResultObjects);
969 });
970 }
971
972 // Update displayed results only if needed ()
973 if (result.isActive) {
974 // Clear all results
975 var results = d3__namespace.select("#" + result.containerId).selectAll(".ppt-result").data([]);
976 results.exit().remove();
977 // Update data
978 results = d3__namespace.select("#" + result.containerId).selectAll(".ppt-result").data(resultObjects.slice(0, result.RESULTS_PAGE_SIZE), function (d) {
979 return d.resultIndex;
980 });
981
982 // Add new elements
983 var pElmt = results.enter()
984 .append("div")
985 .attr("class", "ppt-result")
986 .attr("id", function (d) {
987 return "popoto-result-" + d.resultIndex;
988 });
989
990 // Generate results with providers
991 pElmt.each(function (d) {
992 provider$1.node.getDisplayResults(d.label)(d3__namespace.select(this));
993 });
994 }
995
996 result.hasChanged = false;
997 })
998 .catch(function (error) {
999 logger.error(error);
1000
1001 // Notify listeners
1002 result.resultListeners.forEach(function (listener) {
1003 listener([]);
1004 });
1005 });
1006 }
1007};
1008
1009result.updateResultsCount = function () {
1010 // Update result counts with root node count
1011 if (result.resultCountListeners.length > 0) {
1012 result.resultCountListeners.forEach(function (listener) {
1013 listener(dataModel.getRootNode().count);
1014 });
1015 }
1016};
1017
1018result.generatePreQuery = function () {
1019 var p = {"ids": []};
1020
1021 result.lastResults.forEach(function (d) {
1022 p.ids.push(d.attributes.id);
1023 });
1024
1025 return {
1026 query: "MATCH (d) WHERE d.id IN $ids WITH d",
1027 param: p
1028 };
1029};
1030
1031/**
1032 * Main function to call to use Popoto.js.
1033 * This function will create all the HTML content based on available IDs in the page.
1034 *
1035 * @param startParam Root label or graph schema to use in the graph query builder.
1036 */
1037function start(startParam) {
1038 logger.info("Popoto " + version + " start on " + startParam);
1039
1040 graph$1.mainLabel = startParam;
1041
1042 checkHtmlComponents();
1043
1044 if (taxonomy.isActive) {
1045 taxonomy.createTaxonomyPanel();
1046 }
1047
1048 if (graph$1.isActive) {
1049 graph$1.createGraphArea();
1050 graph$1.createForceLayout();
1051
1052 if (typeof startParam === 'string' || startParam instanceof String) {
1053 var labelSchema = provider$1.node.getSchema(startParam);
1054 if (labelSchema !== undefined) {
1055 graph$1.addSchema(labelSchema);
1056 } else {
1057 graph$1.addRootNode(startParam);
1058 }
1059 } else {
1060 graph$1.loadSchema(startParam);
1061 }
1062 }
1063
1064 if (queryviewer.isActive) {
1065 queryviewer.createQueryArea();
1066 }
1067
1068 if (cypherviewer.isActive) {
1069 cypherviewer.createQueryArea();
1070 }
1071
1072 if (graph$1.USE_VORONOI_LAYOUT === true) {
1073 graph$1.voronoi.extent([[-popoto.graph.getSVGWidth(), -popoto.graph.getSVGWidth()], [popoto.graph.getSVGWidth() * 2, popoto.graph.getSVGHeight() * 2]]);
1074 }
1075
1076 update();
1077}
1078
1079/**
1080 * Check in the HTML page the components to generate.
1081 */
1082function checkHtmlComponents() {
1083 var graphHTMLContainer = d3__namespace.select("#" + graph$1.containerId);
1084 var taxonomyHTMLContainer = d3__namespace.select("#" + taxonomy.containerId);
1085 var queryHTMLContainer = d3__namespace.select("#" + queryviewer.containerId);
1086 var cypherHTMLContainer = d3__namespace.select("#" + cypherviewer.containerId);
1087 var resultsHTMLContainer = d3__namespace.select("#" + result.containerId);
1088
1089 if (graphHTMLContainer.empty()) {
1090 logger.debug("The page doesn't contain a container with ID = \"" + graph$1.containerId + "\" no graph area will be generated. This ID is defined in graph.containerId property.");
1091 graph$1.isActive = false;
1092 } else {
1093 graph$1.isActive = true;
1094 }
1095
1096 if (taxonomyHTMLContainer.empty()) {
1097 logger.debug("The page doesn't contain a container with ID = \"" + taxonomy.containerId + "\" no taxonomy filter will be generated. This ID is defined in taxonomy.containerId property.");
1098 taxonomy.isActive = false;
1099 } else {
1100 taxonomy.isActive = true;
1101 }
1102
1103 if (queryHTMLContainer.empty()) {
1104 logger.debug("The page doesn't contain a container with ID = \"" + queryviewer.containerId + "\" no query viewer will be generated. This ID is defined in queryviewer.containerId property.");
1105 queryviewer.isActive = false;
1106 } else {
1107 queryviewer.isActive = true;
1108 }
1109
1110 if (cypherHTMLContainer.empty()) {
1111 logger.debug("The page doesn't contain a container with ID = \"" + cypherviewer.containerId + "\" no cypher query viewer will be generated. This ID is defined in cypherviewer.containerId property.");
1112 cypherviewer.isActive = false;
1113 } else {
1114 cypherviewer.isActive = true;
1115 }
1116
1117 if (resultsHTMLContainer.empty()) {
1118 logger.debug("The page doesn't contain a container with ID = \"" + result.containerId + "\" no result area will be generated. This ID is defined in result.containerId property.");
1119 result.isActive = false;
1120 } else {
1121 result.isActive = true;
1122 }
1123}
1124
1125/**
1126 * Function to call to update all the generated elements including svg graph, query viewer and generated results.
1127 */
1128function update() {
1129 updateGraph();
1130
1131 // Do not update if rootNode is not valid.
1132 var root = dataModel.getRootNode();
1133
1134 if (!root || root.label === undefined) {
1135 return;
1136 }
1137
1138 if (queryviewer.isActive) {
1139 queryviewer.updateQuery();
1140 }
1141 if (cypherviewer.isActive) {
1142 cypherviewer.updateQuery();
1143 }
1144 // Results are updated only if needed.
1145 // If id found in html page or if result listeners have been added.
1146 // In this case the query must be executed.
1147 if (result.isActive || result.resultListeners.length > 0 || result.resultCountListeners.length > 0 || result.graphResultListeners.length > 0) {
1148 result.updateResults();
1149 }
1150}
1151
1152/**
1153 * Function to call to update the graph only.
1154 */
1155function updateGraph() {
1156 if (graph$1.isActive) {
1157 // Starts the D3.js force simulation.
1158 // This method must be called when the layout is first created, after assigning the nodes and links.
1159 // In addition, it should be called again whenever the nodes or links change.
1160 graph$1.link.updateLinks();
1161 graph$1.node.updateNodes();
1162
1163 // Force simulation restart
1164 graph$1.force.nodes(dataModel.nodes);
1165 graph$1.force.force("link").links(dataModel.links);
1166 graph$1.force.alpha(1).restart();
1167 }
1168}
1169
1170var taxonomy = {};
1171taxonomy.containerId = "popoto-taxonomy";
1172
1173/**
1174 * Create the taxonomy panel HTML elements.
1175 */
1176taxonomy.createTaxonomyPanel = function () {
1177 var htmlContainer = d3__namespace.select("#" + taxonomy.containerId);
1178
1179 var taxoUL = htmlContainer.append("ul")
1180 .attr("class", "ppt-taxo-ul");
1181
1182 var data = taxonomy.generateTaxonomiesData();
1183
1184 var taxos = taxoUL.selectAll(".taxo").data(data);
1185
1186 var taxoli = taxos.enter().append("li")
1187 .attr("id", function (d) {
1188 return d.id
1189 })
1190 .attr("class", "ppt-taxo-li")
1191 .attr("value", function (d) {
1192 return d.label;
1193 });
1194
1195 taxoli.append("span")
1196 .attr("class", function (d) {
1197 return "ppt-icon " + provider$1.taxonomy.getCSSClass(d.label, "span-icon");
1198 })
1199 .html("&nbsp;");
1200
1201 taxoli.append("span")
1202 .attr("class", "ppt-label")
1203 .text(function (d) {
1204 return provider$1.taxonomy.getTextValue(d.label);
1205 });
1206
1207 taxoli.append("span")
1208 .attr("class", "ppt-count");
1209
1210 // Add an on click event on the taxonomy to clear the graph and set this label as root
1211 taxoli.on("click", taxonomy.onClick);
1212
1213 taxonomy.addTaxonomyChildren(taxoli);
1214
1215 // The count is updated for each labels.
1216 var flattenData = [];
1217 data.forEach(function (d) {
1218 flattenData.push(d);
1219 if (d.children) {
1220 taxonomy.flattenChildren(d, flattenData);
1221 }
1222 });
1223
1224 if (!graph$1.DISABLE_COUNT) {
1225 taxonomy.updateCount(flattenData);
1226 }
1227};
1228
1229/**
1230 * Recursive function to flatten data content.
1231 *
1232 */
1233taxonomy.flattenChildren = function (d, vals) {
1234 d.children.forEach(function (c) {
1235 vals.push(c);
1236 if (c.children) {
1237 vals.concat(taxonomy.flattenChildren(c, vals));
1238 }
1239 });
1240};
1241
1242/**
1243 * Updates the count number on a taxonomy.
1244 *
1245 * @param taxonomyData
1246 */
1247taxonomy.updateCount = function (taxonomyData) {
1248 var statements = [];
1249
1250 taxonomyData.forEach(function (taxo) {
1251 statements.push(
1252 {
1253 "statement": query.generateTaxonomyCountQuery(taxo.label)
1254 }
1255 );
1256 });
1257
1258 (function (taxonomies) {
1259 logger.info("Count taxonomies ==>");
1260 runner.run(
1261 {
1262 "statements": statements
1263 })
1264 .then(function (results) {
1265 logger.info("<== Count taxonomies");
1266
1267 for (var i = 0; i < taxonomies.length; i++) {
1268 var count = results[i].records[0].get('count').toString();
1269 d3__namespace.select("#" + taxonomies[i].id)
1270 .select(".ppt-count")
1271 .text(" (" + count + ")");
1272 }
1273 }, function (error) {
1274 logger.error(error);
1275 d3__namespace.select("#popoto-taxonomy")
1276 .selectAll(".ppt-count")
1277 .text(" (0)");
1278 })
1279 .catch(function (error) {
1280 logger.error(error);
1281 d3__namespace.select("#popoto-taxonomy")
1282 .selectAll(".ppt-count")
1283 .text(" (0)");
1284 });
1285 })(taxonomyData);
1286};
1287
1288/**
1289 * Recursively generate the taxonomy children elements.
1290 *
1291 * @param selection
1292 */
1293taxonomy.addTaxonomyChildren = function (selection) {
1294 selection.each(function (d) {
1295 var li = d3__namespace.select(this);
1296
1297 var children = d.children;
1298 if (d.children) {
1299 var childLi = li.append("ul")
1300 .attr("class", "ppt-taxo-sub-ul")
1301 .selectAll("li")
1302 .data(children)
1303 .enter()
1304 .append("li")
1305 .attr("id", function (d) {
1306 return d.id
1307 })
1308 .attr("class", "ppt-taxo-sub-li")
1309 .attr("value", function (d) {
1310 return d.label;
1311 });
1312
1313 childLi.append("span")
1314 .attr("class", function (d) {
1315 return "ppt-icon " + provider$1.taxonomy.getCSSClass(d.label, "span-icon");
1316 })
1317 .html("&nbsp;");
1318
1319 childLi.append("span")
1320 .attr("class", "ppt-label")
1321 .text(function (d) {
1322 return provider$1.taxonomy.getTextValue(d.label);
1323 });
1324
1325 childLi.append("span")
1326 .attr("class", "ppt-count");
1327
1328 childLi.on("click", taxonomy.onClick);
1329
1330 taxonomy.addTaxonomyChildren(childLi);
1331 }
1332
1333 });
1334};
1335
1336taxonomy.onClick = function () {
1337 d3__namespace.event.stopPropagation();
1338
1339 var label = this.attributes.value.value;
1340
1341 dataModel.nodes.length = 0;
1342 dataModel.links.length = 0;
1343
1344 // Reinitialize internal label generator
1345 graph$1.node.internalLabels = {};
1346
1347 update();
1348 graph$1.mainLabel = label;
1349 if (provider$1.node.getSchema(label) !== undefined) {
1350 graph$1.addSchema(provider$1.node.getSchema(label));
1351 } else {
1352 graph$1.addRootNode(label);
1353 }
1354 graph$1.hasGraphChanged = true;
1355 result.hasChanged = true;
1356 graph$1.ignoreCount = false;
1357 update();
1358 tools.center();
1359};
1360
1361/**
1362 * Parse the list of label providers and return a list of data object containing only searchable labels.
1363 * @returns {Array}
1364 */
1365taxonomy.generateTaxonomiesData = function () {
1366 var id = 0;
1367 var data = [];
1368 // Retrieve root providers (searchable and without parent)
1369 for (var label in provider$1.node.Provider) {
1370 if (provider$1.node.Provider.hasOwnProperty(label)) {
1371 if (provider$1.node.getProperty(label, "isSearchable") && !provider$1.node.Provider[label].parent) {
1372 data.push({
1373 "label": label,
1374 "id": "popoto-lbl-" + id++
1375 });
1376 }
1377 }
1378 }
1379
1380 // Add children data for each provider with children.
1381 data.forEach(function (d) {
1382 if (provider$1.node.getProvider(d.label).hasOwnProperty("children")) {
1383 id = taxonomy.addChildrenData(d, id);
1384 }
1385 });
1386
1387 return data;
1388};
1389
1390/**
1391 * Add children providers data.
1392 * @param parentData
1393 * @param id
1394 */
1395taxonomy.addChildrenData = function (parentData, id) {
1396 parentData.children = [];
1397
1398 provider$1.node.getProvider(parentData.label).children.forEach(function (d) {
1399 var childProvider = provider$1.node.getProvider(d);
1400 var childData = {
1401 "label": d,
1402 "id": "popoto-lbl-" + id++
1403 };
1404 if (childProvider.hasOwnProperty("children")) {
1405 id = taxonomy.addChildrenData(childData, id);
1406 }
1407 if (provider$1.node.getProperty(d, "isSearchable")) {
1408 parentData.children.push(childData);
1409 }
1410 });
1411
1412 return id;
1413};
1414
1415// TOOLS -----------------------------------------------------------------------------------------------------------
1416
1417var tools = {};
1418// TODO introduce plugin mechanism to add tools
1419tools.CENTER_GRAPH = true;
1420tools.RESET_GRAPH = true;
1421tools.SAVE_GRAPH = false;
1422tools.TOGGLE_TAXONOMY = false;
1423tools.TOGGLE_FULL_SCREEN = true;
1424tools.TOGGLE_VIEW_RELATION = true;
1425tools.TOGGLE_FIT_TEXT = true;
1426
1427/**
1428 * Reset the graph to display the root node only.
1429 */
1430tools.reset = function () {
1431 dataModel.nodes.length = 0;
1432 dataModel.links.length = 0;
1433
1434 // Reinitialize internal label generator
1435 graph$1.node.internalLabels = {};
1436
1437 if (typeof graph$1.mainLabel === 'string' || graph$1.mainLabel instanceof String) {
1438 if (provider$1.node.getSchema(graph$1.mainLabel) !== undefined) {
1439 graph$1.addSchema(provider$1.node.getSchema(graph$1.mainLabel));
1440 } else {
1441 graph$1.addRootNode(graph$1.mainLabel);
1442 }
1443 } else {
1444 graph$1.loadSchema(graph$1.mainLabel);
1445 }
1446
1447 graph$1.hasGraphChanged = true;
1448 result.hasChanged = true;
1449 update();
1450 tools.center();
1451};
1452
1453/**
1454 * Reset zoom and center the view on svg center.
1455 */
1456tools.center = function () {
1457 graph$1.svgTag.transition().call(graph$1.zoom.transform, d3__namespace.zoomIdentity);
1458};
1459
1460/**
1461 * Show, hide taxonomy panel.
1462 */
1463tools.toggleTaxonomy = function () {
1464 var taxo = d3__namespace.select("#" + taxonomy.containerId);
1465 if (taxo.filter(".disabled").empty()) {
1466 taxo.classed("disabled", true);
1467 } else {
1468 taxo.classed("disabled", false);
1469 }
1470
1471 graph$1.centerRootNode();
1472};
1473
1474/**
1475 * Enable, disable text fitting on nodes.
1476 */
1477tools.toggleFitText = function () {
1478 graph$1.USE_FIT_TEXT = !graph$1.USE_FIT_TEXT;
1479 graph$1.node.updateNodes();
1480};
1481
1482/**
1483 * Show, hide relation donuts.
1484 */
1485tools.toggleViewRelation = function () {
1486 graph$1.DISABLE_RELATION = !graph$1.DISABLE_RELATION;
1487 d3__namespace.selectAll(".ppt-g-node-background").classed("hide", graph$1.DISABLE_RELATION);
1488 graph$1.tick();
1489};
1490
1491tools.toggleFullScreen = function () {
1492
1493 var elem = document.getElementById(graph$1.containerId);
1494
1495 if (!document.fullscreenElement && // alternative standard method
1496 !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) { // current working methods
1497 if (elem.requestFullscreen) {
1498 elem.requestFullscreen();
1499 } else if (elem.msRequestFullscreen) {
1500 elem.msRequestFullscreen();
1501 } else if (elem.mozRequestFullScreen) {
1502 elem.mozRequestFullScreen();
1503 } else if (elem.webkitRequestFullscreen) {
1504 elem.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
1505 }
1506 } else {
1507 if (document.exitFullscreen) {
1508 document.exitFullscreen();
1509 } else if (document.msExitFullscreen) {
1510 document.msExitFullscreen();
1511 } else if (document.mozCancelFullScreen) {
1512 document.mozCancelFullScreen();
1513 } else if (document.webkitExitFullscreen) {
1514 document.webkitExitFullscreen();
1515 }
1516 }
1517};
1518
1519var toolbar = {};
1520
1521toolbar.TOOL_TAXONOMY = "Show/hide taxonomy panel";
1522toolbar.TOOL_RELATION = "Show/hide relation";
1523toolbar.TOOL_CENTER = "Center view";
1524toolbar.TOOL_FULL_SCREEN = "Full screen";
1525toolbar.TOOL_RESET = "Reset graph";
1526toolbar.TOOL_SAVE = "Save graph";
1527toolbar.TOOL_FIT_TEXT = "Fit text in nodes";
1528
1529toolbar.render = function (container) {
1530 var toolbar = container
1531 .append("div")
1532 .attr("class", "ppt-toolbar");
1533
1534 if (tools.TOGGLE_VIEW_RELATION) {
1535 toolbar.append("span")
1536 .attr("id", "popoto-toggle-relation")
1537 .attr("class", "ppt-icon ppt-menu relation")
1538 .attr("title", toolbar.TOOL_RELATION)
1539 .on("click", function () {
1540 tools.toggleViewRelation();
1541 });
1542 }
1543
1544 if (tools.RESET_GRAPH) {
1545 toolbar.append("span")
1546 .attr("id", "popoto-reset-menu")
1547 .attr("class", "ppt-icon ppt-menu reset")
1548 .attr("title", toolbar.TOOL_RESET)
1549 .on("click", function () {
1550 graph$1.notifyListeners(graph$1.Events.GRAPH_RESET, []);
1551 tools.reset();
1552 });
1553 }
1554
1555 if (tools.TOGGLE_TAXONOMY) {
1556 toolbar.append("span")
1557 .attr("id", "popoto-taxonomy-menu")
1558 .attr("class", "ppt-icon ppt-menu taxonomy")
1559 .attr("title", toolbar.TOOL_TAXONOMY)
1560 .on("click", tools.toggleTaxonomy);
1561 }
1562
1563 if (tools.CENTER_GRAPH) {
1564 toolbar.append("span")
1565 .attr("id", "popoto-center-menu")
1566 .attr("class", "ppt-icon ppt-menu center")
1567 .attr("title", toolbar.TOOL_CENTER)
1568 .on("click", tools.center);
1569 }
1570
1571 if (tools.TOGGLE_FULL_SCREEN) {
1572 toolbar.append("span")
1573 .attr("id", "popoto-fullscreen-menu")
1574 .attr("class", "ppt-icon ppt-menu fullscreen")
1575 .attr("title", toolbar.TOOL_FULL_SCREEN)
1576 .on("click", tools.toggleFullScreen);
1577 }
1578
1579 if (tools.SAVE_GRAPH) {
1580 toolbar.append("span")
1581 .attr("id", "popoto-save-menu")
1582 .attr("class", "ppt-icon ppt-menu save")
1583 .attr("title", toolbar.TOOL_SAVE)
1584 .on("click", function () {
1585 graph$1.notifyListeners(graph$1.Events.GRAPH_SAVE, [graph$1.getSchema()]);
1586 });
1587 }
1588
1589 if (tools.TOGGLE_FIT_TEXT) {
1590 toolbar.append("span")
1591 .attr("id", "popoto-fit-text-menu")
1592 .attr("class", "ppt-icon ppt-menu fit-text")
1593 .attr("title", toolbar.TOOL_FIT_TEXT)
1594 .on("click", tools.toggleFitText);
1595 }
1596};
1597
1598var link = {};
1599
1600/**
1601 * Defines the different type of link.
1602 * RELATION is a relation link between two nodes.
1603 * VALUE is a link between a generic node and a value.
1604 */
1605link.LinkTypes = Object.freeze({RELATION: 0, VALUE: 1, SEGMENT: 2});
1606
1607/**
1608 * Offset added to text displayed on links.
1609 * @type {number}
1610 */
1611link.TEXT_DY = -4;
1612
1613/**
1614 * Define whether or not to display path markers.
1615 */
1616link.SHOW_MARKER = false;
1617
1618// ID of the g element in SVG graph containing all the link elements.
1619link.gID = "popoto-glinks";
1620
1621/**
1622 * Function to call to update the links after modification in the model.
1623 * This function will update the graph with all removed, modified or added links using d3.js mechanisms.
1624 */
1625link.updateLinks = function () {
1626 var data = link.updateData();
1627 link.removeElements(data.exit());
1628 link.addNewElements(data.enter());
1629 link.updateElements();
1630};
1631
1632/**
1633 * Update the links element with data coming from dataModel.links.
1634 */
1635link.updateData = function () {
1636 return graph$1.svg.select("#" + link.gID).selectAll(".ppt-glink").data(dataModel.links, function (d) {
1637 return d.id;
1638 });
1639};
1640
1641/**
1642 * Clean links elements removed from the list.
1643 */
1644link.removeElements = function (exitingData) {
1645 exitingData.remove();
1646};
1647
1648/**
1649 * Create new elements.
1650 */
1651link.addNewElements = function (enteringData) {
1652
1653 var newLinkElements = enteringData.append("g")
1654 .attr("class", "ppt-glink")
1655 .on("click", link.clickLink)
1656 .on("mouseover", link.mouseOverLink)
1657 .on("mouseout", link.mouseOutLink);
1658
1659 newLinkElements.append("path")
1660 .attr("class", "ppt-link");
1661
1662 newLinkElements.append("text")
1663 .attr("text-anchor", "middle")
1664 .attr("dy", link.TEXT_DY)
1665 .append("textPath")
1666 .attr("class", "ppt-textPath")
1667 .attr("startOffset", "50%");
1668};
1669
1670/**
1671 * Update all the elements (new + modified)
1672 */
1673link.updateElements = function () {
1674 var toUpdateElem = graph$1.svg.select("#" + link.gID).selectAll(".ppt-glink");
1675
1676 toUpdateElem
1677 .attr("id", function (d) {
1678 return "ppt-glink_" + d.id;
1679 });
1680
1681 toUpdateElem.selectAll(".ppt-link")
1682 .attr("id", function (d) {
1683 return "ppt-path_" + d.id
1684 })
1685 .attr("stroke", function (d) {
1686 return provider$1.link.getColor(d, "path", "stroke");
1687 })
1688 .attr("class", function (link) {
1689 return "ppt-link " + provider$1.link.getCSSClass(link, "path")
1690 });
1691
1692 // Due to a bug on webkit browsers (as of 30/01/2014) textPath cannot be selected
1693 // To workaround this issue the selection is done with its associated css class
1694 toUpdateElem.selectAll("text")
1695 .attr("id", function (d) {
1696 return "ppt-text_" + d.id
1697 })
1698 .attr("class", function (link) {
1699 return provider$1.link.getCSSClass(link, "text")
1700 })
1701 .attr("fill", function (d) {
1702 return provider$1.link.getColor(d, "text", "fill");
1703 })
1704 .selectAll(".ppt-textPath")
1705 .attr("id", function (d) {
1706 return "ppt-textpath_" + d.id;
1707 })
1708 .attr("class", function (link) {
1709 return "ppt-textpath " + provider$1.link.getCSSClass(link, "text-path")
1710 })
1711 .attr("xlink:href", function (d) {
1712 return "#ppt-path_" + d.id;
1713 })
1714 .text(function (d) {
1715 return provider$1.link.getTextValue(d);
1716 });
1717};
1718
1719/**
1720 * Function called when mouse is over the link.
1721 * This function is used to change the CSS class on hover of the link and query viewer element.
1722 *
1723 * TODO try to introduce event instead of directly access query spans here. This could be used in future extensions.
1724 */
1725link.mouseOverLink = function () {
1726 d3__namespace.select(this)
1727 .select("path")
1728 .attr("class", function (link) {
1729 return "ppt-link " + provider$1.link.getCSSClass(link, "path--hover")
1730 });
1731
1732 d3__namespace.select(this).select("text")
1733 .attr("class", function (link) {
1734 return provider$1.link.getCSSClass(link, "text--hover")
1735 });
1736
1737 var hoveredLink = d3__namespace.select(this).data()[0];
1738
1739 if (queryviewer.isActive) {
1740 queryviewer.queryConstraintSpanElements.filter(function (d) {
1741 return d.ref === hoveredLink;
1742 }).classed("hover", true);
1743 queryviewer.querySpanElements.filter(function (d) {
1744 return d.ref === hoveredLink;
1745 }).classed("hover", true);
1746 }
1747
1748 if (cypherviewer.isActive) {
1749 cypherviewer.querySpanElements.filter(function (d) {
1750 return d.link === hoveredLink;
1751 }).classed("hover", true);
1752 }
1753};
1754
1755/**
1756 * Function called when mouse goes out of the link.
1757 * This function is used to reinitialize the CSS class of the link and query viewer element.
1758 */
1759link.mouseOutLink = function () {
1760 d3__namespace.select(this)
1761 .select("path")
1762 .attr("class", function (link) {
1763 return "ppt-link " + provider$1.link.getCSSClass(link, "path")
1764 });
1765
1766 d3__namespace.select(this).select("text")
1767 .attr("class", function (link) {
1768 return provider$1.link.getCSSClass(link, "text")
1769 });
1770
1771 var hoveredLink = d3__namespace.select(this).data()[0];
1772
1773 if (queryviewer.isActive) {
1774 queryviewer.queryConstraintSpanElements.filter(function (d) {
1775 return d.ref === hoveredLink;
1776 }).classed("hover", false);
1777 queryviewer.querySpanElements.filter(function (d) {
1778 return d.ref === hoveredLink;
1779 }).classed("hover", false);
1780 }
1781
1782 if (cypherviewer.isActive) {
1783 cypherviewer.querySpanElements.filter(function (d) {
1784 return d.link === hoveredLink;
1785 }).classed("hover", false);
1786 }
1787};
1788
1789// Delete all related nodes from this link
1790link.clickLink = function () {
1791 var clickedLink = d3__namespace.select(this).data()[0];
1792
1793 if (clickedLink.type !== link.LinkTypes.VALUE) {
1794 // Collapse all expanded choose nodes first to avoid having invalid displayed value node if collapsed relation contains a value.
1795 graph$1.node.collapseAllNode();
1796
1797 var willChangeResults = graph$1.node.removeNode(clickedLink.target);
1798
1799 graph$1.hasGraphChanged = true;
1800 result.hasChanged = willChangeResults;
1801 update();
1802 }
1803
1804};
1805
1806/**
1807 * Convert the parameter to a function returning the parameter if it is not already a function.
1808 *
1809 * @param param
1810 * @return {*}
1811 */
1812
1813function toFunction(param) {
1814 if (typeof param === "function") {
1815 return param;
1816 } else {
1817 return function () {
1818 return param;
1819 };
1820 }
1821}
1822
1823var CONTEXT_2D = document.createElement("canvas").getContext("2d");
1824var DEFAULT_CANVAS_LINE_HEIGHT = 12;
1825
1826function measureTextWidth(text) {
1827 return CONTEXT_2D.measureText(text).width;
1828}
1829
1830/**
1831 * Compute the radius of the circle wrapping all the lines.
1832 *
1833 * @param lines array of text lines
1834 * @return {number}
1835 */
1836function computeTextRadius(lines) {
1837 var textRadius = 0;
1838
1839 for (var i = 0, n = lines.length; i < n; ++i) {
1840 var dx = lines[i].width / 2;
1841 var dy = (Math.abs(i - n / 2 + 0.5) + 0.5) * DEFAULT_CANVAS_LINE_HEIGHT;
1842 textRadius = Math.max(textRadius, Math.sqrt(dx * dx + dy * dy));
1843 }
1844
1845 return textRadius;
1846}
1847
1848function computeLines(words, targetWidth) {
1849 var line;
1850 var lineWidth0 = Infinity;
1851 var lines = [];
1852
1853 for (var i = 0, n = words.length; i < n; ++i) {
1854 var lineText1 = (line ? line.text + " " : "") + words[i];
1855 var lineWidth1 = measureTextWidth(lineText1);
1856 if ((lineWidth0 + lineWidth1) / 2 < targetWidth) {
1857 line.width = lineWidth0 = lineWidth1;
1858 line.text = lineText1;
1859 } else {
1860 lineWidth0 = measureTextWidth(words[i]);
1861 line = {width: lineWidth0, text: words[i]};
1862 lines.push(line);
1863 }
1864 }
1865
1866 return lines;
1867}
1868
1869function computeTargetWidth(text) {
1870 return Math.sqrt(measureTextWidth(text.trim()) * DEFAULT_CANVAS_LINE_HEIGHT);
1871}
1872
1873/**
1874 * Split text into words.
1875 *
1876 * @param text
1877 * @return {*|string[]}
1878 */
1879function computeWords(text) {
1880 var words = text.split(/\s+/g); // To hyphenate: /\s+|(?<=-)/
1881 if (!words[words.length - 1]) words.pop();
1882 if (!words[0]) words.shift();
1883 return words;
1884}
1885
1886/**
1887 * Inspired by https://beta.observablehq.com/@mbostock/fit-text-to-circle
1888 * Extract words from the text and group them by lines to fit a circle.
1889 *
1890 * @param text
1891 * @returns {*}
1892 */
1893function extractLines(text) {
1894 if (text === undefined || text === null) {
1895 return [];
1896 }
1897
1898 var textString = String(text);
1899
1900 var words = computeWords(textString);
1901 var targetWidth = computeTargetWidth(textString);
1902
1903 return computeLines(words, targetWidth);
1904}
1905
1906function createTextElements(selection, getClass) {
1907 selection.each(function (d) {
1908 var text = d3__namespace.select(this)
1909 .selectAll(".fitted-text")
1910 .data([{}]);
1911
1912 text.enter()
1913 .append("text")
1914 .merge(text)
1915 .attr("class", "fitted-text" + (getClass !== undefined ? " " + getClass(d) : ""))
1916 .attr("style", "text-anchor: middle; font: 10px sans-serif");
1917 });
1918}
1919
1920function createSpanElements(text, getText) {
1921 text.each(function (fitData) {
1922 var lines = extractLines(getText(fitData));
1923 var span = d3__namespace.select(this).selectAll("tspan")
1924 .data(lines);
1925
1926 span.exit().remove();
1927
1928 span.enter()
1929 .append("tspan")
1930 .merge(span)
1931 .attr("x", 0)
1932 .attr("y", function (d, i) {
1933 var lineCount = lines.length;
1934 return (i - lineCount / 2 + 0.8) * DEFAULT_CANVAS_LINE_HEIGHT
1935 })
1936 .text(function (d) {
1937 return d.text
1938 });
1939 });
1940}
1941
1942/**
1943 * Create the text representation of a node by slicing the text into lines to fit the node.
1944 *
1945 * @param selection
1946 * @param textParam
1947 * @param radiusParam
1948 * @param classParam
1949 */
1950function appendFittedText(selection, textParam, radiusParam, classParam) {
1951 var getRadius = toFunction(radiusParam);
1952 var getText = toFunction(textParam);
1953 var getClass = classParam ? toFunction(classParam) : classParam;
1954
1955 createTextElements(selection, getClass);
1956
1957 var text = selection.select(".fitted-text");
1958
1959 createSpanElements(text, getText);
1960
1961 text.attr("transform", function (d) {
1962 var lines = extractLines(getText(d));
1963 var textRadius = computeTextRadius(lines);
1964
1965 var scale = 1;
1966 if (textRadius !== 0 && textRadius) {
1967 scale = getRadius(d) / textRadius;
1968 }
1969 return "translate(" + 0 + "," + 0 + ")" + " scale(" + scale + ")"
1970 });
1971}
1972
1973var fitTextRenderer = {};
1974
1975fitTextRenderer.getNodeBoundingBox = function(node) {
1976 return node.getBBox();
1977};
1978
1979/**
1980 * Create the text representation of a node by slicing the text into lines to fit the node.
1981 *
1982 * TODO: Clean getLines return and corresponding data.
1983 * @param nodeSelection
1984 */
1985fitTextRenderer.render = function (nodeSelection) {
1986
1987 var backgroundRectSelection = nodeSelection
1988 .append("rect")
1989 .attr("fill", function (node) {
1990 return provider$1.node.getColor(node, "back-text", "fill");
1991 })
1992 .attr("class", function (node) {
1993 return provider$1.node.getCSSClass(node, "back-text")
1994 });
1995
1996 appendFittedText(nodeSelection,
1997 function (d) { return provider$1.node.getTextValue(d)},
1998 function (d) { return provider$1.node.getSize(d) },
1999 function (d) { return provider$1.node.getCSSClass(d, "text") });
2000
2001 backgroundRectSelection
2002 .attr("x", function (d) {
2003 var bbox = fitTextRenderer.getNodeBoundingBox(d3__namespace.select(this.parentNode).select("text").node());
2004 return bbox.x - 3;
2005 })
2006 .attr("y", function (d) {
2007 var bbox = fitTextRenderer.getNodeBoundingBox(d3__namespace.select(this.parentNode).select("text").node());
2008 return bbox.y;
2009 })
2010 .attr("rx", "5")
2011 .attr("ry", "5")
2012 .attr("width", function (d) {
2013 var bbox = fitTextRenderer.getNodeBoundingBox(d3__namespace.select(this.parentNode).select("text").node());
2014 return bbox.width + 6;
2015 })
2016 .attr("height", function (d) {
2017 var bbox = fitTextRenderer.getNodeBoundingBox(d3__namespace.select(this.parentNode).select("text").node());
2018 return bbox.height;
2019 })
2020 .attr("transform", function (d) {
2021 return d3__namespace.select(this.parentNode).select("text").attr("transform")
2022 });
2023};
2024
2025var textRenderer = {};
2026textRenderer.TEXT_Y = 8; // TODO move this in dedicated config
2027
2028textRenderer.getNodeBoundingBox = function(node) {
2029 return node.getBBox();
2030};
2031
2032/**
2033 * Create the text representation of a node with a SVG rect element as background.
2034 *
2035 * TODO: clean mouseover text because this renderer change parent clip-path attribute
2036 * @param nodeSelection
2037 */
2038textRenderer.render = function (nodeSelection) {
2039
2040 var backgroundRectSelection = nodeSelection
2041 .append("rect")
2042 .attr("fill", function (node) {
2043 return provider$1.node.getColor(node, "back-text", "fill");
2044 })
2045 .attr("class", function (node) {
2046 return provider$1.node.getCSSClass(node, "back-text")
2047 });
2048
2049 nodeSelection.append('text')
2050 .attr('x', 0)
2051 .attr('y', textRenderer.TEXT_Y)
2052 .attr('text-anchor', 'middle')
2053 .attr("class", function (node) {
2054 return provider$1.node.getCSSClass(node, "text")
2055 })
2056 .on("mouseover", function (d) {
2057 d3__namespace.select(this.parentNode).attr("clip-path", null);
2058 })
2059 .on("mouseout", function (d) {
2060 d3__namespace.select(this.parentNode)
2061 .attr("clip-path", function (node) {
2062 return "url(#node-view" + node.id + ")";
2063 });
2064 })
2065 .text(function (d) {
2066 return provider$1.node.getTextValue(d);
2067 });
2068
2069 backgroundRectSelection
2070 .attr("x", function (d) {
2071 var bbox = textRenderer.getNodeBoundingBox(d3__namespace.select(this.parentNode).select("text").node());
2072 return bbox.x - 3;
2073 })
2074 .attr("y", function (d) {
2075 var bbox = textRenderer.getNodeBoundingBox(d3__namespace.select(this.parentNode).select("text").node());
2076 return bbox.y;
2077 })
2078 .attr("rx", "5")
2079 .attr("ry", "5")
2080 .attr("width", function (d) {
2081 var bbox = textRenderer.getNodeBoundingBox(d3__namespace.select(this.parentNode).select("text").node());
2082 return bbox.width + 6;
2083 })
2084 .attr("height", function (d) {
2085 var bbox = textRenderer.getNodeBoundingBox(d3__namespace.select(this.parentNode).select("text").node());
2086 return bbox.height;
2087 });
2088};
2089
2090var node = {};
2091
2092// ID of the g element in SVG graph containing all the link elements.
2093node.gID = "popoto-gnodes";
2094
2095// Node ellipse size used by default for text nodes.
2096node.DONUTS_MARGIN = 0;
2097node.DONUT_WIDTH = 20;
2098
2099// Define the max number of character displayed in node.
2100node.NODE_MAX_CHARS = 11;
2101node.NODE_TITLE_MAX_CHARS = 100;
2102
2103// Number of nodes displayed per page during value selection.
2104node.PAGE_SIZE = 10;
2105
2106// Count box default size
2107node.CountBox = {x: 16, y: 33, w: 52, h: 19};
2108
2109// Store choose node state to avoid multiple node expand at the same time
2110node.chooseWaiting = false;
2111
2112node.getDonutInnerRadius = function (n) {
2113 return provider$1.node.getSize(n) + node.DONUTS_MARGIN;
2114};
2115
2116node.getDonutOuterRadius = function (n) {
2117 return provider$1.node.getSize(n) + node.DONUTS_MARGIN + node.DONUT_WIDTH;
2118};
2119
2120node.pie = d3__namespace.pie()
2121 .sort(null)
2122 .value(function (d) {
2123 return 1;
2124 });
2125
2126/**
2127 * Defines the list of possible nodes.
2128 * ROOT: Node used as graph root. It is the target of the query. Only one node of this type should be available in graph.
2129 * CHOOSE: Nodes defining a generic node label. From these node is is possible to select a value or explore relations.
2130 * VALUE: Unique node containing a value constraint. Usually replace CHOOSE nodes once a value as been selected.
2131 * GROUP: Empty node used to group relations. No value can be selected but relations can be explored. These nodes doesn't have count.
2132 */
2133node.NodeTypes = Object.freeze({ROOT: 0, CHOOSE: 1, VALUE: 2, GROUP: 3});
2134
2135// Used to generate unique internal labels used for example as identifier in Cypher query.
2136node.internalLabels = {};
2137
2138/**
2139 * Create a normalized identifier from a node label.
2140 * Multiple calls with the same node label will generate different unique identifier.
2141 *
2142 * @param nodeLabel
2143 * @returns {string}
2144 */
2145node.generateInternalLabel = function (nodeLabel) {
2146 var label = nodeLabel ? nodeLabel.toLowerCase().replace(/ /g, '') : "n";
2147
2148 if (label in node.internalLabels) {
2149 node.internalLabels[label] = node.internalLabels[label] + 1;
2150 } else {
2151 node.internalLabels[label] = 0;
2152 return label;
2153 }
2154
2155 return label + node.internalLabels[label];
2156};
2157
2158/**
2159 * Update Nodes SVG elements using D3.js update mechanisms.
2160 */
2161node.updateNodes = function () {
2162 var data = node.updateData();
2163 node.removeElements(data.exit());
2164 node.addNewElements(data.enter());
2165 node.updateElements();
2166};
2167
2168/**
2169 * Update node data with changes done in dataModel.nodes model.
2170 */
2171node.updateData = function () {
2172 var data = graph$1.svg.select("#" + node.gID).selectAll(".ppt-gnode").data(dataModel.nodes, function (d) {
2173 return d.id;
2174 });
2175
2176 if (graph$1.hasGraphChanged) {
2177 node.updateAutoLoadValues();
2178
2179 if (!graph$1.DISABLE_COUNT && !graph$1.ignoreCount) {
2180 node.updateCount();
2181 }
2182 }
2183 graph$1.hasGraphChanged = false;
2184
2185 return data;
2186};
2187
2188/**
2189 * Update nodes and result counts by executing a query for every nodes with the new graph structure.
2190 */
2191node.updateCount = function () {
2192 var statements = [];
2193
2194 var countedNodes = dataModel.nodes
2195 .filter(function (d) {
2196 return d.type !== node.NodeTypes.VALUE && d.type !== node.NodeTypes.GROUP && (!d.hasOwnProperty("isNegative") || !d.isNegative);
2197 });
2198
2199 countedNodes.forEach(function (n) {
2200 var nodeCountQuery = query.generateNodeCountQuery(n);
2201 statements.push(
2202 {
2203 "statement": nodeCountQuery.statement,
2204 "parameters": nodeCountQuery.parameters
2205 }
2206 );
2207 });
2208
2209 logger.info("Count nodes ==>");
2210 runner.run(
2211 {
2212 "statements": statements
2213 })
2214 .then(function (results) {
2215 logger.info("<== Count nodes");
2216 var data = runner.toObject(results);
2217
2218 for (var i = 0; i < countedNodes.length; i++) {
2219 countedNodes[i].count = data[i][0].count;
2220 }
2221
2222 // Update result count with root node new count
2223 if (result.resultCountListeners.length > 0) {
2224 result.updateResultsCount();
2225 }
2226
2227 node.updateElements();
2228 graph$1.link.updateElements();
2229 })
2230 .catch(function (error) {
2231 logger.error(error);
2232 countedNodes.forEach(function (n) {
2233 n.count = 0;
2234 });
2235 node.updateElements();
2236 graph$1.link.updateElements();
2237 });
2238};
2239
2240/**
2241 * Update values for nodes having preloadData property
2242 */
2243node.updateAutoLoadValues = function () {
2244 var statements = [];
2245
2246 var nodesToLoadData = node.getAutoLoadValueNodes();
2247
2248 for (var i = 0; i < nodesToLoadData.length; i++) {
2249 var nodeToQuery = nodesToLoadData[i];
2250 var nodeValueQuery = query.generateNodeValueQuery(nodeToQuery);
2251 statements.push(
2252 {
2253 "statement": nodeValueQuery.statement,
2254 "parameters": nodeValueQuery.parameters
2255 }
2256 );
2257 }
2258
2259 if (statements.length > 0) {
2260 logger.info("AutoLoadValue ==>");
2261 runner.run(
2262 {
2263 "statements": statements
2264 })
2265 .then(function (results) {
2266 logger.info("<== AutoLoadValue");
2267
2268 var data = runner.toObject(results);
2269
2270 for (var i = 0; i < nodesToLoadData.length; i++) {
2271 var nodeToQuery = nodesToLoadData[i];
2272 var constraintAttr = provider$1.node.getConstraintAttribute(nodeToQuery.label);
2273 // Here results are parsed and values already selected are filtered out
2274 nodeToQuery.data = data[i].filter(function (dataToFilter) {
2275 var keepData = true;
2276 if (nodeToQuery.hasOwnProperty("value") && nodeToQuery.value.length > 0) {
2277 nodeToQuery.value.forEach(function (value) {
2278 if (value.attributes[constraintAttr] === dataToFilter[constraintAttr]) {
2279 keepData = false;
2280 }
2281 });
2282 }
2283 return keepData;
2284 });
2285
2286 nodeToQuery.page = 1;
2287 }
2288
2289 graph$1.notifyListeners(graph$1.Events.GRAPH_NODE_DATA_LOADED, [nodesToLoadData]);
2290 })
2291 .catch(function (error) {
2292 logger.error(error);
2293 });
2294 }
2295};
2296
2297/**
2298 * Remove old elements.
2299 * Should be called after updateData.
2300 */
2301node.removeElements = function (exitingData) {
2302 // Nodes without parent are simply removed.
2303 exitingData.filter(function (d) {
2304 return !d.parent;
2305 }).remove();
2306
2307 // Nodes with a parent are removed with an animation (nodes are collapsed to their parents before being removed)
2308 exitingData.filter(function (d) {
2309 return d.parent;
2310 }).transition().duration(300).attr("transform", function (d) {
2311 return "translate(" + d.parent.x + "," + d.parent.y + ")";
2312 }).remove();
2313};
2314
2315/**
2316 * Add all new elements.
2317 * Only the skeleton of new nodes are added custom data will be added during the element update phase.
2318 * Should be called after updateData and before updateElements.
2319 */
2320node.addNewElements = function (enteringData) {
2321
2322 var gNewNodeElements = enteringData
2323 .append("g")
2324 .attr("class", "ppt-gnode");
2325
2326 gNewNodeElements.on("click", node.nodeClick)
2327 .on("mouseover", node.mouseOverNode)
2328 // .on("mousemove", nUdeXXX.mouseMoveNode)
2329 .on("mouseout", node.mouseOutNode);
2330
2331 // Add right click on all nodes except value
2332 gNewNodeElements.filter(function (d) {
2333 return d.type !== node.NodeTypes.VALUE;
2334 }).on("contextmenu", node.clearSelection);
2335
2336 // Disable right click context menu on value nodes
2337 gNewNodeElements.filter(function (d) {
2338 return d.type === node.NodeTypes.VALUE;
2339 }).on("contextmenu", function () {
2340 // Disable context menu on
2341 d3__namespace.event.preventDefault();
2342 });
2343
2344 var nodeDefs = gNewNodeElements.append("defs");
2345
2346 // Circle clipPath using node radius size
2347 nodeDefs.append("clipPath")
2348 .attr("id", function (n) {
2349 return "node-view" + n.id;
2350 })
2351 .append("circle")
2352 .attr("cx", 0)
2353 .attr("cy", 0);
2354
2355 // Nodes are composed of 3 layouts and skeleton are created here.
2356 node.addBackgroundElements(gNewNodeElements);
2357 node.addMiddlegroundElements(gNewNodeElements);
2358 node.addForegroundElements(gNewNodeElements);
2359};
2360
2361/**
2362 * Create the background for a new node element.
2363 * The background of a node is defined by a circle not visible by default (fill-opacity set to 0) but can be used to highlight a node with animation on this attribute.
2364 * This circle also define the node zone that can receive events like mouse clicks.
2365 *
2366 * @param gNewNodeElements
2367 */
2368node.addBackgroundElements = function (gNewNodeElements) {
2369 var background = gNewNodeElements
2370 .append("g")
2371 .attr("class", "ppt-g-node-background")
2372 .classed("hide", graph$1.DISABLE_RELATION);
2373
2374 background.append("g")
2375 .attr("class", "ppt-donut-labels");
2376 background.append("g")
2377 .attr("class", "ppt-donut-segments");
2378};
2379
2380/**
2381 * Create the node main elements.
2382 *
2383 * @param gNewNodeElements
2384 */
2385node.addMiddlegroundElements = function (gNewNodeElements) {
2386 gNewNodeElements
2387 .append("g")
2388 .attr("class", "ppt-g-node-middleground");
2389};
2390
2391/**
2392 * Create the node foreground elements.
2393 * It contains node additional elements, count or tools like navigation arrows.
2394 *
2395 * @param gNewNodeElements
2396 */
2397node.addForegroundElements = function (gNewNodeElements) {
2398 var foreground = gNewNodeElements
2399 .append("g")
2400 .attr("class", "ppt-g-node-foreground");
2401
2402 // Arrows icons added only for root and choose nodes
2403 var gArrow = foreground.filter(function (d) {
2404 return d.type === node.NodeTypes.ROOT || d.type === node.NodeTypes.CHOOSE;
2405 })
2406 .append("g")
2407 .attr("class", "ppt-node-foreground-g-arrows");
2408
2409 var glArrow = gArrow.append("g");
2410 //glArrow.append("polygon")
2411 //.attr("points", "-53,-23 -33,-33 -33,-13");
2412 glArrow.append("circle")
2413 .attr("class", "ppt-larrow")
2414 .attr("cx", "-43")
2415 .attr("cy", "-23")
2416 .attr("r", "17");
2417
2418 glArrow.append("path")
2419 .attr("class", "ppt-arrow")
2420 .attr("d", "m -44.905361,-23 6.742,-6.742 c 0.81,-0.809 0.81,-2.135 0,-2.944 l -0.737,-0.737 c -0.81,-0.811 -2.135,-0.811 -2.945,0 l -8.835,8.835 c -0.435,0.434 -0.628,1.017 -0.597,1.589 -0.031,0.571 0.162,1.154 0.597,1.588 l 8.835,8.834 c 0.81,0.811 2.135,0.811 2.945,0 l 0.737,-0.737 c 0.81,-0.808 0.81,-2.134 0,-2.943 l -6.742,-6.743 z");
2421
2422 glArrow.on("click", function (clickedNode) {
2423 d3__namespace.event.stopPropagation(); // To avoid click event on svg element in background
2424
2425 // On left arrow click page number is decreased and node expanded to display the new page
2426 if (clickedNode.page > 1) {
2427 clickedNode.page--;
2428 node.collapseNode(clickedNode);
2429 node.expandNode(clickedNode);
2430 }
2431 });
2432
2433 var grArrow = gArrow.append("g");
2434 //grArrow.append("polygon")
2435 //.attr("points", "53,-23 33,-33 33,-13");
2436
2437 grArrow.append("circle")
2438 .attr("class", "ppt-rarrow")
2439 .attr("cx", "43")
2440 .attr("cy", "-23")
2441 .attr("r", "17");
2442
2443 grArrow.append("path")
2444 .attr("class", "ppt-arrow")
2445 .attr("d", "m 51.027875,-24.5875 -8.835,-8.835 c -0.811,-0.811 -2.137,-0.811 -2.945,0 l -0.738,0.737 c -0.81,0.81 -0.81,2.136 0,2.944 l 6.742,6.742 -6.742,6.742 c -0.81,0.81 -0.81,2.136 0,2.943 l 0.737,0.737 c 0.81,0.811 2.136,0.811 2.945,0 l 8.835,-8.836 c 0.435,-0.434 0.628,-1.017 0.597,-1.588 0.032,-0.569 -0.161,-1.152 -0.596,-1.586 z");
2446
2447 grArrow.on("click", function (clickedNode) {
2448 d3__namespace.event.stopPropagation(); // To avoid click event on svg element in background
2449
2450 if (clickedNode.page * node.PAGE_SIZE < clickedNode.count) {
2451 clickedNode.page++;
2452 node.collapseNode(clickedNode);
2453 node.expandNode(clickedNode);
2454 }
2455 });
2456
2457 // Count box
2458 if (!graph$1.DISABLE_COUNT) {
2459 var countForeground = foreground.filter(function (d) {
2460 return d.type !== node.NodeTypes.GROUP;
2461 });
2462
2463 countForeground
2464 .append("rect")
2465 .attr("x", node.CountBox.x)
2466 .attr("y", node.CountBox.y)
2467 .attr("width", node.CountBox.w)
2468 .attr("height", node.CountBox.h)
2469 .attr("class", "ppt-count-box");
2470
2471 countForeground
2472 .append("text")
2473 .attr("x", 42)
2474 .attr("y", 48)
2475 .attr("text-anchor", "middle")
2476 .attr("class", "ppt-count-text");
2477 }
2478
2479 foreground.filter(function (d) {
2480 return d.type === node.NodeTypes.CHOOSE;
2481 }).append("g")
2482 .attr("class", "ppt-g-node-ban")
2483 .append("path")
2484 .attr("d", "M89.1 19.2C88 17.7 86.6 16.2 85.2 14.8 83.8 13.4 82.3 12 80.8 10.9 72 3.9 61.3 0 50 0 36.7 0 24.2 5.4 14.8 14.8 5.4 24.2 0 36.7 0 50c0 11.4 3.9 22.1 10.9 30.8 1.2 1.5 2.5 3 3.9 4.4 1.4 1.4 2.9 2.7 4.4 3.9C27.9 96.1 38.6 100 50 100 63.3 100 75.8 94.6 85.2 85.2 94.6 75.8 100 63.3 100 50 100 38.7 96.1 28 89.1 19.2ZM11.9 50c0-10.2 4-19.7 11.1-27C30.3 15.9 39.8 11.9 50 11.9c8.2 0 16 2.6 22.4 7.3L19.3 72.4C14.5 66 11.9 58.2 11.9 50Zm65 27c-7.2 7.1-16.8 11.1-27 11.1-8.2 0-16-2.6-22.4-7.4L80.8 27.6C85.5 34 88.1 41.8 88.1 50c0 10.2-4 19.7-11.1 27z");
2485};
2486
2487/**
2488 * Updates all elements.
2489 */
2490node.updateElements = function () {
2491 var toUpdateElem = graph$1.svg.select("#" + node.gID).selectAll(".ppt-gnode");
2492
2493 toUpdateElem.attr("id", function (d) {
2494 return "popoto-gnode_" + d.id;
2495 });
2496
2497 if (graph$1.USE_VORONOI_LAYOUT) {
2498 toUpdateElem.attr("clip-path", function (d) {
2499 return "url(#voroclip-" + d.id + ")";
2500 });
2501 }
2502
2503 toUpdateElem.select("defs")
2504 .select("clipPath")
2505 .attr("id", function (n) {
2506 return "node-view" + n.id;
2507 }).selectAll("circle")
2508 .attr("r", function (n) {
2509 return provider$1.node.getSize(n);
2510 });
2511
2512 // Display voronoi paths
2513 // TODO ZZZ re|move
2514 // nUdeXXX.selectAllData.selectAll(".gra").data(["unique"]).enter().append("g").attr("class", "gra").append("use");
2515 // nUdeXXX.selectAllData.selectAll("use").attr("xlink:href",function(d){
2516 // console.log("#pvoroclip-"+d3.select(this.parentNode.parentNode).datum().id);
2517 // return "#pvoroclip-"+d3.select(this.parentNode.parentNode).datum().id;
2518 // }).attr("fill","none").attr("stroke","red").attr("stroke-width","1px");
2519
2520 // TODO ZZZ move functions?
2521 toUpdateElem.filter(function (n) {
2522 return n.type !== node.NodeTypes.ROOT
2523 }).call(d3__namespace.drag()
2524 .on("start", dragstarted)
2525 .on("drag", dragged)
2526 .on("end", dragended));
2527
2528 function dragstarted(d) {
2529 if (!d3__namespace.event.active) graph$1.force.alphaTarget(0.3).restart();
2530 d.fx = d.x;
2531 d.fy = d.y;
2532 }
2533
2534 function dragged(d) {
2535 d.fx = d3__namespace.event.x;
2536 d.fy = d3__namespace.event.y;
2537 }
2538
2539 function dragended(d) {
2540 if (!d3__namespace.event.active) graph$1.force.alphaTarget(0);
2541 if (d.fixed === false) {
2542 d.fx = null;
2543 d.fy = null;
2544 }
2545
2546 }
2547
2548 node.updateBackgroundElements();
2549 node.updateMiddlegroundElements();
2550 node.updateForegroundElements();
2551};
2552
2553node.updateBackgroundElements = function () {
2554 var nodeBackgroundElements = graph$1.svg.select("#" + node.gID).selectAll(".ppt-gnode").selectAll(".ppt-g-node-background");
2555
2556 nodeBackgroundElements.select(".ppt-donut-labels").selectAll("*").remove();
2557 nodeBackgroundElements.select(".ppt-donut-segments").selectAll("*").remove();
2558
2559 var gSegment = nodeBackgroundElements.select(".ppt-donut-segments").selectAll(".ppt-segment-container")
2560 .data(function (d) {
2561 var relationships = [];
2562 if (d.hasOwnProperty("relationships")) {
2563 relationships = d.relationships;
2564 }
2565 return relationships;
2566 }, function (d) {
2567 return d.id;
2568 })
2569 .enter()
2570 .append("g")
2571 .attr("class", ".ppt-segment-container")
2572 .on("click", node.segmentClick)
2573 .on("mouseover", function (d) {
2574 d3__namespace.select(this).select(".ppt-text-arc").classed("hover", true);
2575 })
2576 .on("mouseout", function (d) {
2577 d3__namespace.select(this).select(".ppt-text-arc").classed("hover", false);
2578 });
2579
2580 gSegment.append("title").attr("class", "ppt-svg-title")
2581 .text(function (d) {
2582 return d.label + " " + d.target;
2583 });
2584
2585 var gLabel = nodeBackgroundElements.select(".ppt-donut-labels").selectAll(".ppt-segment-container")
2586 .data(function (n) {
2587 var relationships = [];
2588 if (n.hasOwnProperty("relationships")) {
2589 relationships = n.relationships;
2590 }
2591 return relationships;
2592 }, function (relationship) {
2593 return relationship.id;
2594 })
2595 .enter()
2596 .append("g")
2597 .attr("class", ".ppt-segment-container")
2598 .on("click", node.segmentClick)
2599 .on("mouseover", function (d) {
2600 d3__namespace.select(this).select(".ppt-text-arc").classed("hover", true);
2601 })
2602 .on("mouseout", function (d) {
2603 d3__namespace.select(this).select(".ppt-text-arc").classed("hover", false);
2604 });
2605
2606 gLabel.append("path")
2607 .attr("class", "ppt-hidden-arc")
2608 .attr("id", function (d, i) {
2609 var n = d3__namespace.select(this.parentNode.parentNode).datum();
2610 return "arc_" + n.id + "_" + i;
2611 })
2612 .attr("d", function (relationship) {
2613 var n = d3__namespace.select(this.parentNode.parentNode).datum();
2614
2615 //A regular expression that captures all in between the start of a string (denoted by ^)
2616 //and the first capital letter L
2617 var firstArcSection = /(^.+?)L/;
2618 var singleArcSection = /(^.+?)M/;
2619
2620 var intermediateArc = {
2621 startAngle: relationship.directionAngle - (Math.PI - 0.1),
2622 endAngle: relationship.directionAngle + (Math.PI - 0.1)
2623 };
2624
2625 var arcPath = d3__namespace.arc()
2626 .innerRadius(node.getDonutInnerRadius(n))
2627 .outerRadius(node.getDonutOuterRadius(n))(intermediateArc);
2628
2629 //The [1] gives back the expression between the () (thus not the L as well)
2630 //which is exactly the arc statement
2631 var res = firstArcSection.exec(arcPath);
2632 var newArc = "";
2633 if (res && res.length > 1) {
2634 newArc = res[1];
2635 } else {
2636 newArc = singleArcSection.exec(arcPath)[1];
2637 }
2638
2639 //Replace all the comma's so that IE can handle it -_-
2640 //The g after the / is a modifier that "find all matches rather than stopping after the first match"
2641 newArc = newArc.replace(/,/g, " ");
2642
2643 return newArc;
2644 })
2645 .style("fill", "none")
2646 .style("stroke", "none");
2647
2648 gSegment.append("text")
2649 .attr("text-anchor", "middle")
2650 .attr("class", function (d) {
2651 var n = d3__namespace.select(this.parentNode.parentNode).datum();
2652 if (n.hasOwnProperty("count") && n.count === 0) {
2653 return "ppt-text-arc disabled";
2654 } else {
2655 return "ppt-text-arc";
2656 }
2657 })
2658 .attr("fill", function (d) {
2659 var n = d3__namespace.select(this.parentNode.parentNode).datum();
2660
2661 return provider$1.link.getColor({
2662 label: d.label,
2663 type: graph$1.link.LinkTypes.SEGMENT,
2664 source: n,
2665 target: {label: d.target}
2666 }, "segment", "fill");
2667 })
2668 .attr("dy", graph$1.link.TEXT_DY)
2669 .append("textPath")
2670 .attr("startOffset", "50%")
2671 .attr("xlink:href", function (d, i) {
2672 var n = d3__namespace.select(this.parentNode.parentNode.parentNode).datum();
2673 return "#arc_" + n.id + "_" + i;
2674 })
2675 .text(function (d) {
2676 var n = d3__namespace.select(this.parentNode.parentNode.parentNode).datum();
2677
2678 return provider$1.link.getTextValue({
2679 source: n,
2680 target: {label: d.target},
2681 label: d.label,
2682 type: graph$1.link.LinkTypes.SEGMENT
2683 });
2684 });
2685
2686 gSegment.append("path")
2687 .attr("class", function (d) {
2688 var n = d3__namespace.select(this.parentNode.parentNode).datum();
2689 if (n.hasOwnProperty("count") && n.count === 0) {
2690 return "ppt-segment disabled";
2691 } else {
2692 return "ppt-segment";
2693 }
2694 })
2695 .attr("d", function (d) {
2696 var n = d3__namespace.select(this.parentNode.parentNode).datum();
2697 return d3__namespace.arc()
2698 .innerRadius(node.getDonutInnerRadius(n))
2699 .outerRadius(node.getDonutOuterRadius(n))(d)
2700 })
2701 .attr("fill", function (d) {
2702 var n = d3__namespace.select(this.parentNode.parentNode).datum();
2703 return provider$1.link.getColor({
2704 label: d.label,
2705 type: graph$1.link.LinkTypes.RELATION,
2706 source: n,
2707 target: {label: d.target}
2708 }, "path", "fill");
2709 })
2710 .attr("stroke", function (d) {
2711 var n = d3__namespace.select(this.parentNode.parentNode).datum();
2712
2713 return provider$1.link.getColor({
2714 label: d.label,
2715 type: graph$1.link.LinkTypes.RELATION,
2716 source: n,
2717 target: {label: d.target}
2718 }, "path", "stroke");
2719 })
2720 ;
2721
2722};
2723
2724/**
2725 * Update the middle layer of nodes.
2726 * TODO refactor node generation to allow future extensions (for example add plugin with new node types...)
2727 */
2728node.updateMiddlegroundElements = function () {
2729 var middleG = graph$1.svg.select("#" + node.gID).selectAll(".ppt-gnode").selectAll(".ppt-g-node-middleground");
2730
2731 middleG.attr("clip-path", function (n) {
2732 return "url(#node-view" + n.id + ")";
2733 });
2734
2735 // Clear all content in case node type has changed
2736 middleG.selectAll("*").remove();
2737
2738
2739 node.updateMiddlegroundElementsTooltip(middleG);
2740
2741 node.updateMiddlegroundElementsText(middleG.filter(function (d) {
2742 return provider$1.node.getNodeDisplayType(d) === provider$1.node.DisplayTypes.TEXT;
2743 }));
2744
2745 node.updateMiddlegroundElementsImage(middleG.filter(function (d) {
2746 return provider$1.node.getNodeDisplayType(d) === provider$1.node.DisplayTypes.IMAGE;
2747 }));
2748
2749 node.updateMiddlegroundElementsSymbol(middleG.filter(function (d) {
2750 return provider$1.node.getNodeDisplayType(d) === provider$1.node.DisplayTypes.SYMBOL;
2751 }));
2752
2753 node.updateMiddlegroundElementsSVG(middleG.filter(function (d) {
2754 return provider$1.node.getNodeDisplayType(d) === provider$1.node.DisplayTypes.SVG;
2755 }));
2756
2757 node.updateMiddlegroundElementsDisplayedText(middleG.filter(function (d) {
2758 return provider$1.node.isTextDisplayed(d);
2759 }));
2760
2761};
2762
2763node.updateMiddlegroundElementsTooltip = function (middleG) {
2764 // Most browser will generate a tooltip if a title is specified for the SVG element
2765 // TODO Introduce an SVG tooltip instead?
2766 middleG.append("title")
2767 .attr("class", function (n) {
2768 return provider$1.node.getCSSClass(n, "title")
2769 })
2770 .text(function (d) {
2771 return provider$1.node.getTextValue(d, node.NODE_TITLE_MAX_CHARS);
2772 });
2773
2774};
2775
2776node.updateMiddlegroundElementsText = function (gMiddlegroundTextNodes) {
2777 var circle = gMiddlegroundTextNodes.append("circle").attr("r", function (n) {
2778 return provider$1.node.getSize(n);
2779 });
2780
2781 // Set class according to node type
2782 circle
2783 .attr("class", function (n) {
2784 return provider$1.node.getCSSClass(n, "circle")
2785 })
2786 .attr("fill", function (n) {
2787 return provider$1.node.getColor(n, "circle", "fill");
2788 })
2789 .attr("stroke", function (n) {
2790 return provider$1.node.getColor(n, "circle", "stroke");
2791 });
2792};
2793
2794node.updateMiddlegroundElementsImage = function (gMiddlegroundImageNodes) {
2795 gMiddlegroundImageNodes.append("circle").attr("r", function (n) {
2796 return provider$1.node.getSize(n);
2797 })
2798 .attr("class", function (n) {
2799 return provider$1.node.getCSSClass(n, "image-background-circle")
2800 });
2801
2802 gMiddlegroundImageNodes.append("image")
2803 .attr("class", function (n) {
2804 return provider$1.node.getCSSClass(n, "image")
2805 })
2806 .attr("width", function (d) {
2807 return provider$1.node.getImageWidth(d);
2808 })
2809 .attr("height", function (d) {
2810 return provider$1.node.getImageHeight(d);
2811 })
2812 // Center the image on node
2813 .attr("transform", function (d) {
2814 return "translate(" + (-provider$1.node.getImageWidth(d) / 2) + "," + (-provider$1.node.getImageHeight(d) / 2) + ")";
2815 })
2816 .attr("xlink:href", function (d) {
2817 return provider$1.node.getImagePath(d);
2818 });
2819};
2820
2821node.updateMiddlegroundElementsSymbol = function (gMiddlegroundSymbolNodes) {
2822 gMiddlegroundSymbolNodes.append("circle").attr("r", function (n) {
2823 return provider$1.node.getSize(n);
2824 })
2825 .attr("class", function (n) {
2826 return provider$1.node.getCSSClass(n, "symbol-background-circle")
2827 })
2828 .attr("fill", function (n) {
2829 return provider$1.node.getColor(n, "circle", "fill");
2830 })
2831 .attr("stroke", function (n) {
2832 return provider$1.node.getColor(n, "circle", "stroke");
2833 });
2834
2835 gMiddlegroundSymbolNodes.append("use")
2836 .attr("class", function (n) {
2837 return provider$1.node.getCSSClass(n, "symbol")
2838 })
2839 .attr("width", function (d) {
2840 return provider$1.node.getImageWidth(d);
2841 })
2842 .attr("height", function (d) {
2843 return provider$1.node.getImageHeight(d);
2844 })
2845 // Center the image on node
2846 .attr("transform", function (d) {
2847 return "translate(" + (-provider$1.node.getImageWidth(d) / 2) + "," + (-provider$1.node.getImageHeight(d) / 2) + ")";
2848 })
2849 .attr("xlink:href", function (d) {
2850 return provider$1.node.getImagePath(d);
2851 })
2852 .attr("fill", function (n) {
2853 return provider$1.node.getColor(n, "circle", "fill");
2854 })
2855 .attr("stroke", function (n) {
2856 return provider$1.node.getColor(n, "circle", "stroke");
2857 });
2858};
2859
2860node.updateMiddlegroundElementsSVG = function (gMiddlegroundSVGNodes) {
2861 var SVGmiddleG = gMiddlegroundSVGNodes.append("g");
2862
2863 SVGmiddleG.append("circle").attr("r", function (n) {
2864 return provider$1.node.getSize(n);
2865 }).attr("class", "ppt-svg-node-background");
2866
2867 var svgMiddlePaths = SVGmiddleG.selectAll("path").data(function (d) {
2868 return provider$1.node.getSVGPaths(d);
2869 });
2870
2871 // Update nested data elements
2872 svgMiddlePaths.exit().remove();
2873 svgMiddlePaths.enter().append("path");
2874
2875 SVGmiddleG
2876 .selectAll("path")
2877 .attr("class", function (d) {
2878 var n = d3__namespace.select(this.parentNode).datum();
2879 return provider$1.node.getCSSClass(n, "path")
2880 })
2881 .each(function (d, i) {
2882 for (var prop in d) {
2883 if (d.hasOwnProperty(prop)) {
2884 d3__namespace.select(this).attr(prop, d[prop]);
2885 }
2886 }
2887 });
2888};
2889
2890node.updateMiddlegroundElementsDisplayedText = function (middleG) {
2891 var textDisplayed = middleG.filter(function (d) {
2892 return provider$1.node.isTextDisplayed(d);
2893 });
2894
2895 if (graph$1.USE_FIT_TEXT) {
2896 fitTextRenderer.render(textDisplayed);
2897 } else {
2898 textRenderer.render(textDisplayed);
2899 }
2900};
2901
2902/**
2903 * Updates the foreground elements
2904 */
2905node.updateForegroundElements = function () {
2906
2907 // Updates browse arrows status
2908 // TODO ZZZ extract variable?
2909 var gArrows = graph$1.svg.select("#" + node.gID).selectAll(".ppt-gnode").selectAll(".ppt-g-node-foreground")
2910 .selectAll(".ppt-node-foreground-g-arrows");
2911 gArrows.classed("active", function (d) {
2912 return d.valueExpanded && d.data && d.data.length > node.PAGE_SIZE;
2913 });
2914
2915 gArrows.selectAll(".ppt-larrow").classed("enabled", function (d) {
2916 return d.page > 1;
2917 });
2918
2919 gArrows.selectAll(".ppt-rarrow").classed("enabled", function (d) {
2920 if (d.data) {
2921 var count = d.data.length;
2922 return d.page * node.PAGE_SIZE < count;
2923 } else {
2924 return false;
2925 }
2926 });
2927
2928 // Update count box class depending on node type
2929 var gForegrounds = graph$1.svg.select("#" + node.gID).selectAll(".ppt-gnode").selectAll(".ppt-g-node-foreground");
2930
2931 gForegrounds.selectAll(".ppt-count-box").filter(function (d) {
2932 return d.type !== node.NodeTypes.CHOOSE;
2933 }).classed("root", true);
2934
2935 gForegrounds.selectAll(".ppt-count-box").filter(function (d) {
2936 return d.type === node.NodeTypes.CHOOSE;
2937 }).classed("value", true);
2938
2939 gForegrounds.selectAll(".ppt-count-box").classed("disabled", function (d) {
2940 return d.count === 0;
2941 });
2942
2943 if (!graph$1.DISABLE_COUNT) {
2944 gForegrounds.selectAll(".ppt-count-text")
2945 .text(function (d) {
2946 if (d.count !== null) {
2947 return d.count;
2948 } else {
2949 return "...";
2950 }
2951 })
2952 .classed("disabled", function (d) {
2953 return d.count === 0;
2954 });
2955 }
2956
2957 graph$1.svg.select("#" + node.gID).selectAll(".ppt-gnode").selectAll(".ppt-g-node-foreground").filter(function (n) {
2958 return n.isNegative === true;
2959 }).selectAll(".ppt-g-node-ban")
2960 .attr("transform", function (d) {
2961 return "translate(" + (-provider$1.node.getSize(d)) + "," + (-provider$1.node.getSize(d)) + ") " +
2962 "scale(" + ((provider$1.node.getSize(d) * 2) / 100) + ")"; // 100 is the size of the image drawn with the path
2963 })
2964 .attr("stroke-width", function (d) {
2965 return (2 / ((provider$1.node.getSize(d) * 2) / 100)) + "px";
2966 });
2967
2968
2969 graph$1.svg.select("#" + node.gID).selectAll(".ppt-gnode").selectAll(".ppt-g-node-foreground").selectAll(".ppt-g-node-ban")
2970 .classed("active", function (n) {
2971 return n.isNegative === true;
2972 });
2973};
2974
2975node.segmentClick = function (d) {
2976 d3__namespace.event.preventDefault();
2977
2978 var n = d3__namespace.select(this.parentNode.parentNode).datum();
2979
2980 graph$1.ignoreCount = true;
2981
2982 graph$1.addRelationshipData(n, d, function (targetNode) {
2983 graph$1.notifyListeners(graph$1.Events.GRAPH_NODE_RELATION_ADD, [
2984 dataModel.links.filter(function (l) {
2985 return l.target === targetNode;
2986 })
2987 ]);
2988 graph$1.ignoreCount = false;
2989 graph$1.hasGraphChanged = true;
2990 update();
2991 });
2992};
2993
2994/**
2995 * Handle the mouse over event on nodes.
2996 */
2997node.mouseOverNode = function () {
2998 d3__namespace.event.preventDefault();
2999
3000 // TODO don't work on IE (nodes unstable) find another way to move node in foreground on mouse over?
3001 // d3.select(this).moveToFront();
3002
3003 // tootip.div.style("display", "inline");
3004
3005 var hoveredNode = d3__namespace.select(this).data()[0];
3006
3007 if (queryviewer.isActive) {
3008 // Hover the node in query
3009 queryviewer.queryConstraintSpanElements.filter(function (d) {
3010 return d.ref === hoveredNode;
3011 }).classed("hover", true);
3012 queryviewer.querySpanElements.filter(function (d) {
3013 return d.ref === hoveredNode;
3014 }).classed("hover", true);
3015 }
3016
3017 if (cypherviewer.isActive) {
3018 cypherviewer.querySpanElements.filter(function (d) {
3019 return d.node === hoveredNode;
3020 }).classed("hover", true);
3021 }
3022};
3023
3024// nUdeXXX.mouseMoveNode = function () {
3025// d3.event.preventDefault();
3026//
3027// var hoveredNode = d3.select(this).data()[0];
3028//
3029// tootip.div
3030// .text(provider.node.getTextValue(hoveredNode, nUdeXXX.NODE_TITLE_MAX_CHARS))
3031// .style("left", (d3.event.pageX - 34) + "px")
3032// .style("top", (d3.event.pageY - 12) + "px");
3033// };
3034
3035/**
3036 * Handle mouse out event on nodes.
3037 */
3038node.mouseOutNode = function () {
3039 d3__namespace.event.preventDefault();
3040
3041 // tootip.div.style("display", "none");
3042
3043 var hoveredNode = d3__namespace.select(this).data()[0];
3044
3045 if (queryviewer.isActive) {
3046 // Remove hover class on node.
3047 queryviewer.queryConstraintSpanElements.filter(function (d) {
3048 return d.ref === hoveredNode;
3049 }).classed("hover", false);
3050 queryviewer.querySpanElements.filter(function (d) {
3051 return d.ref === hoveredNode;
3052 }).classed("hover", false);
3053 }
3054
3055 if (cypherviewer.isActive) {
3056 cypherviewer.querySpanElements.filter(function (d) {
3057 return d.node === hoveredNode;
3058 }).classed("hover", false);
3059 }
3060};
3061
3062/**
3063 * Handle the click event on nodes.
3064 */
3065node.nodeClick = function () {
3066 if (!d3__namespace.event.defaultPrevented) { // To avoid click on drag end
3067 var clickedNode = d3__namespace.select(this).data()[0]; // Clicked node data
3068 logger.debug("nodeClick (" + clickedNode.label + ")");
3069
3070 if (clickedNode.type === node.NodeTypes.VALUE) {
3071 node.valueNodeClick(clickedNode);
3072 } else if (clickedNode.type === node.NodeTypes.CHOOSE || clickedNode.type === node.NodeTypes.ROOT) {
3073 if (d3__namespace.event.ctrlKey) {
3074 if (clickedNode.type === node.NodeTypes.CHOOSE) {
3075 clickedNode.isNegative = !clickedNode.hasOwnProperty("isNegative") || !clickedNode.isNegative;
3076
3077 node.collapseAllNode();
3078
3079 if (clickedNode.hasOwnProperty("value") && clickedNode.value.length > 0) ; else {
3080
3081 if (clickedNode.isNegative) {
3082 // Remove all related nodes
3083 for (var i = dataModel.links.length - 1; i >= 0; i--) {
3084 if (dataModel.links[i].source === clickedNode) {
3085 node.removeNode(dataModel.links[i].target);
3086 }
3087 }
3088
3089 clickedNode.count = 0;
3090 }
3091 }
3092
3093 result.hasChanged = true;
3094 graph$1.hasGraphChanged = true;
3095 update();
3096 } // negation not supported on root node
3097 } else {
3098 if (clickedNode.valueExpanded) {
3099 node.collapseNode(clickedNode);
3100 } else {
3101 node.chooseNodeClick(clickedNode);
3102 }
3103 }
3104 }
3105 }
3106};
3107
3108/**
3109 * Remove all the value node directly linked to clicked node.
3110 *
3111 * @param clickedNode
3112 */
3113node.collapseNode = function (clickedNode) {
3114 if (clickedNode.valueExpanded) { // node is collapsed only if it has been expanded first
3115 logger.debug("collapseNode (" + clickedNode.label + ")");
3116
3117 graph$1.notifyListeners(graph$1.Events.GRAPH_NODE_VALUE_COLLAPSE, [clickedNode]);
3118
3119 var linksToRemove = dataModel.links.filter(function (l) {
3120 return l.source === clickedNode && l.type === graph$1.link.LinkTypes.VALUE;
3121 });
3122
3123 // Remove children nodes from model
3124 linksToRemove.forEach(function (l) {
3125 dataModel.nodes.splice(dataModel.nodes.indexOf(l.target), 1);
3126 });
3127
3128 // Remove links from model
3129 for (var i = dataModel.links.length - 1; i >= 0; i--) {
3130 if (linksToRemove.indexOf(dataModel.links[i]) >= 0) {
3131 dataModel.links.splice(i, 1);
3132 }
3133 }
3134
3135 // Node has been fixed when expanded so we unfix it back here.
3136 if (clickedNode.type !== node.NodeTypes.ROOT) {
3137 clickedNode.fixed = false;
3138 clickedNode.fx = null;
3139 clickedNode.fy = null;
3140 }
3141
3142 // Parent node too if not root
3143 if (clickedNode.parent && clickedNode.parent.type !== node.NodeTypes.ROOT) {
3144 clickedNode.parent.fixed = false;
3145 clickedNode.parent.fx = null;
3146 clickedNode.parent.fy = null;
3147 }
3148
3149 clickedNode.valueExpanded = false;
3150 update();
3151
3152 } else {
3153 logger.debug("collapseNode called on an unexpanded node");
3154 }
3155};
3156
3157/**
3158 * Collapse all nodes with value expanded.
3159 *
3160 */
3161node.collapseAllNode = function () {
3162 dataModel.nodes.forEach(function (n) {
3163 if ((n.type === node.NodeTypes.CHOOSE || n.type === node.NodeTypes.ROOT) && n.valueExpanded) {
3164 node.collapseNode(n);
3165 }
3166 });
3167};
3168
3169/**
3170 * Function called on a value node click.
3171 * In this case the value is added in the parent node and all the value nodes are collapsed.
3172 *
3173 * @param clickedNode
3174 */
3175node.valueNodeClick = function (clickedNode) {
3176 logger.debug("valueNodeClick (" + clickedNode.label + ")");
3177
3178 graph$1.notifyListeners(graph$1.Events.GRAPH_NODE_ADD_VALUE, [clickedNode]);
3179
3180 if (clickedNode.parent.value === undefined) {
3181 clickedNode.parent.value = [];
3182 }
3183 clickedNode.parent.value.push(clickedNode);
3184 result.hasChanged = true;
3185 graph$1.hasGraphChanged = true;
3186
3187 node.collapseNode(clickedNode.parent);
3188};
3189
3190/**
3191 * Function called on choose node click.
3192 * In this case a query is executed to get all the possible value
3193 * @param clickedNode
3194 * TODO optimize with cached data?
3195 */
3196node.chooseNodeClick = function (clickedNode) {
3197 logger.debug("chooseNodeClick (" + clickedNode.label + ") with waiting state set to " + node.chooseWaiting);
3198 if (!node.chooseWaiting && !clickedNode.immutable && !(clickedNode.count === 0)) {
3199
3200 // Collapse all expanded nodes first
3201 node.collapseAllNode();
3202
3203 // Set waiting state to true to avoid multiple call on slow query execution
3204 node.chooseWaiting = true;
3205
3206 // Don't run query to get value if node isAutoLoadValue is set to true
3207 if (clickedNode.data !== undefined && clickedNode.isAutoLoadValue) {
3208 clickedNode.page = 1;
3209 node.expandNode(clickedNode);
3210 node.chooseWaiting = false;
3211 } else {
3212 logger.info("Values (" + clickedNode.label + ") ==>");
3213 var nodeValueQuery = query.generateNodeValueQuery(clickedNode);
3214 runner.run(
3215 {
3216 "statements": [
3217 {
3218 "statement": nodeValueQuery.statement,
3219 "parameters": nodeValueQuery.parameters
3220 }]
3221 })
3222 .then(function (results) {
3223 logger.info("<== Values (" + clickedNode.label + ")");
3224 var parsedData = runner.toObject(results);
3225 var constraintAttr = provider$1.node.getConstraintAttribute(clickedNode.label);
3226
3227 clickedNode.data = parsedData[0].filter(function (dataToFilter) {
3228 var keepData = true;
3229 if (clickedNode.hasOwnProperty("value") && clickedNode.value.length > 0) {
3230 clickedNode.value.forEach(function (value) {
3231 if (value.attributes[constraintAttr] === dataToFilter[constraintAttr]) {
3232 keepData = false;
3233 }
3234 });
3235 }
3236 return keepData;
3237 });
3238
3239 clickedNode.page = 1;
3240 node.expandNode(clickedNode);
3241 node.chooseWaiting = false;
3242 })
3243 .catch(function (error) {
3244 node.chooseWaiting = false;
3245 logger.error(error);
3246 });
3247 }
3248 }
3249};
3250
3251/**
3252 * Add in all expanded choose nodes the value containing the specified value for the given attribute.
3253 * And remove it from the nodes data.
3254 *
3255 * @param attribute
3256 * @param value
3257 */
3258node.addExpandedValue = function (attribute, value) {
3259
3260 var isAnyChangeDone = false;
3261
3262 // For each expanded nodes
3263 for (var i = dataModel.nodes.length - 1; i >= 0; i--) {
3264 if (dataModel.nodes[i].valueExpanded) {
3265
3266 // Look in node data if value can be found in reverse order to be able to remove value without effect on iteration index
3267 for (var j = dataModel.nodes[i].data.length - 1; j >= 0; j--) {
3268 if (dataModel.nodes[i].data[j][attribute] === value) {
3269 isAnyChangeDone = true;
3270
3271 // Create field value if needed
3272 if (!dataModel.nodes[i].hasOwnProperty("value")) {
3273 dataModel.nodes[i].value = [];
3274 }
3275
3276 // Add value
3277 dataModel.nodes[i].value.push({
3278 attributes: dataModel.nodes[i].data[j]
3279 });
3280
3281 // Remove data added in value
3282 dataModel.nodes[i].data.splice(j, 1);
3283 }
3284 }
3285
3286 // Refresh node
3287 node.collapseNode(dataModel.nodes[i]);
3288 node.expandNode(dataModel.nodes[i]);
3289 }
3290 }
3291
3292 if (isAnyChangeDone) {
3293 result.hasChanged = true;
3294 graph$1.hasGraphChanged = true;
3295 update();
3296 }
3297};
3298
3299/**
3300 * Get all nodes that contains a value.
3301 *
3302 * @param label If set return only node of this label.
3303 * @return {Array} Array of nodes containing at least one value.
3304 */
3305node.getContainingValue = function (label) {
3306 var nodesWithValue = [];
3307 var links = dataModel.links, nodes = dataModel.nodes;
3308
3309 if (nodes.length > 0) {
3310
3311 var rootNode = nodes[0];
3312
3313 // Add root value
3314 if (rootNode.value !== undefined && rootNode.value.length > 0) {
3315 if (label === undefined || label === rootNode.label) {
3316 nodesWithValue.push(rootNode);
3317 }
3318 }
3319
3320 links.forEach(function (l) {
3321 var targetNode = l.target;
3322 if (l.type === graph$1.link.LinkTypes.RELATION && targetNode.value !== undefined && targetNode.value.length > 0) {
3323 if (label === undefined || label === targetNode.label) {
3324 nodesWithValue.push(targetNode);
3325 }
3326 }
3327 });
3328 }
3329
3330 return nodesWithValue;
3331};
3332
3333
3334/**
3335 * Add value in all CHOOSE nodes with specified label.
3336 *
3337 * @param label nodes where to insert
3338 * @param value
3339 */
3340node.addValueForLabel = function (label, value) {
3341 var isAnyChangeDone = false;
3342
3343 // Find choose node with label
3344 for (var i = dataModel.nodes.length - 1; i >= 0; i--) {
3345 if (dataModel.nodes[i].type === node.NodeTypes.CHOOSE && dataModel.nodes[i].label === label) {
3346
3347 // Create field value if needed
3348 if (!dataModel.nodes[i].hasOwnProperty("value")) {
3349 dataModel.nodes[i].value = [];
3350 }
3351
3352 // check if value already exists
3353 var isValueFound = false;
3354 var constraintAttr = provider$1.node.getConstraintAttribute(label);
3355 dataModel.nodes[i].value.forEach(function (val) {
3356 if (val.attributes.hasOwnProperty(constraintAttr) && val.attributes[constraintAttr] === value.attributes[constraintAttr]) {
3357 isValueFound = true;
3358 }
3359 });
3360
3361 if (!isValueFound) {
3362 // Add value
3363 dataModel.nodes[i].value.push(value);
3364 isAnyChangeDone = true;
3365 }
3366 }
3367 }
3368
3369 return isAnyChangeDone;
3370};
3371
3372/**
3373 * Add a value in a node with the given id and the value of the first attribute if found in its data.
3374 *
3375 * @param nodeIds a list of node ids where to add the value.
3376 * @param displayAttributeValue the value to find in data and to add if found
3377 */
3378node.addValue = function (nodeIds, displayAttributeValue) {
3379 var isAnyChangeDone = false;
3380
3381 // Find choose node with label
3382 for (var i = 0; i < dataModel.nodes.length; i++) {
3383 var n = dataModel.nodes[i];
3384 if (nodeIds.indexOf(n.id) >= 0) {
3385
3386 // Create field value in node if needed
3387 if (!n.hasOwnProperty("value")) {
3388 n.value = [];
3389 }
3390
3391 var displayAttr = provider$1.node.getReturnAttributes(n.label)[0];
3392
3393 // Find data for this node and add value
3394 n.data.forEach(function (d) {
3395 if (d.hasOwnProperty(displayAttr) && d[displayAttr] === displayAttributeValue) {
3396 isAnyChangeDone = true;
3397 n.value.push({attributes: d});
3398 }
3399 });
3400 }
3401 }
3402
3403 if (isAnyChangeDone) {
3404 result.hasChanged = true;
3405 graph$1.hasGraphChanged = true;
3406 update();
3407 }
3408};
3409
3410/**
3411 * Remove a value from a node.
3412 * If the value is not found nothing is done.
3413 *
3414 * @param n
3415 * @param value
3416 */
3417node.removeValue = function (n, value) {
3418 var isAnyChangeDone = false;
3419
3420 node.collapseNode(n);
3421
3422 for (var j = n.value.length - 1; j >= 0; j--) {
3423 if (n.value[j] === value) {
3424 n.value.splice(j, 1);
3425
3426 isAnyChangeDone = true;
3427 }
3428 }
3429 return isAnyChangeDone;
3430};
3431
3432node.removeValues = function (n) {
3433 var isAnyChangeDone = false;
3434
3435 node.collapseNode(n);
3436
3437 if (n.value !== undefined && n.value.length > 0) {
3438 n.value.length = 0;
3439 isAnyChangeDone = true;
3440 }
3441
3442 return isAnyChangeDone
3443};
3444
3445/**
3446 * Get the value in the provided nodeId for a specific value id.
3447 *
3448 * @param nodeId
3449 * @param constraintAttributeValue
3450 */
3451node.getValue = function (nodeId, constraintAttributeValue) {
3452 for (var i = 0; i < dataModel.nodes.length; i++) {
3453 var n = dataModel.nodes[i];
3454
3455 if (n.id === nodeId) {
3456 var constraintAttribute = provider$1.node.getConstraintAttribute(n.label);
3457
3458 for (var j = n.value.length - 1; j >= 0; j--) {
3459 if (n.value[j].attributes[constraintAttribute] === constraintAttributeValue) {
3460 return n.value[j]
3461 }
3462 }
3463 }
3464 }
3465};
3466
3467/**
3468 * Remove in all expanded nodes the value containing the specified value for the given attribute.
3469 * And move it back to nodes data.
3470 *
3471 * @param attribute
3472 * @param value
3473 */
3474node.removeExpandedValue = function (attribute, value) {
3475 var isAnyChangeDone = false;
3476
3477 // For each expanded nodes in reverse order as some values can be removed
3478 for (var i = dataModel.nodes.length - 1; i >= 0; i--) {
3479 if (dataModel.nodes[i].valueExpanded) {
3480
3481 var removedValues = [];
3482
3483 // Remove values
3484 for (var j = dataModel.nodes[i].value.length - 1; j >= 0; j--) {
3485 if (dataModel.nodes[i].value[j].attributes[attribute] === value) {
3486 isAnyChangeDone = true;
3487
3488 removedValues = removedValues.concat(dataModel.nodes[i].value.splice(j, 1));
3489 }
3490 }
3491
3492 //And add them back in data
3493 for (var k = 0; k < removedValues.length; k++) {
3494 dataModel.nodes[i].data.push(removedValues[k].attributes);
3495 }
3496
3497 // Refresh node
3498 node.collapseNode(dataModel.nodes[i]);
3499 node.expandNode(dataModel.nodes[i]);
3500 }
3501 }
3502
3503 if (isAnyChangeDone) {
3504 result.hasChanged = true;
3505 graph$1.hasGraphChanged = true;
3506 update();
3507 }
3508};
3509
3510/**
3511 * Return all nodes with isAutoLoadValue property set to true.
3512 */
3513node.getAutoLoadValueNodes = function () {
3514 return dataModel.nodes
3515 .filter(function (d) {
3516 return d.hasOwnProperty("isAutoLoadValue") && d.isAutoLoadValue === true && !(d.isNegative === true);
3517 });
3518};
3519
3520/**
3521 * Add a list of related value if not already found in node.
3522 * A value is defined with the following structure
3523 * {
3524 * id,
3525 * rel,
3526 * label
3527 * }
3528 *
3529 * @param n
3530 * @param values
3531 * @param isNegative
3532 */
3533node.addRelatedValues = function (n, values, isNegative) {
3534 var valuesToAdd = node.filterExistingValues(n, values);
3535
3536 if (valuesToAdd.length <= 0) {
3537 return;
3538 }
3539
3540 var statements = [];
3541
3542 valuesToAdd.forEach(function (v) {
3543 var constraintAttr = provider$1.node.getConstraintAttribute(v.label);
3544
3545 var statement = "MATCH ";
3546 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
3547 statement += "(v:`" + v.label + "`) WHERE (ID(v) = $p)";
3548 } else {
3549 statement += "(v:`" + v.label + "`) WHERE (v." + constraintAttr + " = $p)";
3550 }
3551
3552 var resultAttributes = provider$1.node.getReturnAttributes(v.label);
3553 var sep = "";
3554
3555 statement += " RETURN DISTINCT \"" + v.rel + "\" AS rel, \"" + v.label + "\" AS label, {" + resultAttributes.reduce(function (a, attr) {
3556 a += sep + attr + ":v." + attr;
3557 sep = ", ";
3558 return a
3559 }, "") + "} AS value LIMIT 1";
3560
3561 statements.push(
3562 {
3563 "statement": statement,
3564 "parameters": {p: v.id},
3565 "resultDataContents": ["row"]
3566 }
3567 );
3568 });
3569
3570 logger.info("addRelatedValues ==>");
3571 runner.run(
3572 {
3573 "statements": statements
3574 })
3575 .then(function (results) {
3576 logger.info("<== addRelatedValues");
3577
3578 var parsedData = runner.toObject(results);
3579 var count = 0;
3580 parsedData.forEach(function (data) {
3581 if (data.length > 0) {
3582 var dataLabel = data[0].label;
3583 var dataValue = data[0].value;
3584 var dataRel = data[0].rel;
3585
3586 var value = {
3587 "id": dataModel.generateId(),
3588 "parent": n,
3589 "attributes": dataValue,
3590 "type": node.NodeTypes.VALUE,
3591 "label": dataLabel
3592 };
3593 graph$1.ignoreCount = true;
3594
3595 var nodeRelationships = n.relationships;
3596 var nodeRels = nodeRelationships.filter(function (r) {
3597 return r.label === dataRel && r.target === dataLabel
3598 });
3599
3600 var nodeRel = {label: dataRel, target: dataLabel};
3601 if (nodeRels.length > 0) {
3602 nodeRel = nodeRels[0];
3603 }
3604
3605 graph$1.addRelationshipData(n, nodeRel, function () {
3606 count++;
3607
3608 if (count === parsedData.length) {
3609 graph$1.ignoreCount = false;
3610 graph$1.hasGraphChanged = true;
3611 result.hasChanged = true;
3612 update();
3613 }
3614 }, [value], isNegative);
3615 }
3616 });
3617 })
3618 .catch(function (error) {
3619 logger.error(error);
3620 });
3621};
3622
3623/**
3624 * Add a list of related value prefixed by a path of nodes.
3625 * A value is defined with the following structure
3626 * {
3627 * id,
3628 * rel,
3629 * label
3630 * }
3631 *
3632 * @param n
3633 * @param relPath
3634 * @param values
3635 * @param isNegative
3636 */
3637node.addRelatedBranch = function (n, relPath, values, isNegative) {
3638 if (relPath.length > 0) {
3639 var rel = relPath[0];
3640 relPath = relPath.slice(1);
3641
3642 var relationships = n.relationships.filter(function (r) {
3643 return r.label === rel.type && r.target === rel.target;
3644 });
3645
3646 if (relationships.length > 0) {
3647 graph$1.addRelationshipData(n, relationships[0], function (createdNode) {
3648 node.addRelatedBranch(createdNode, relPath, values, isNegative);
3649 });
3650 }
3651 } else {
3652 node.addRelatedValues(n, values, isNegative);
3653 }
3654};
3655
3656/**
3657 * A value is defined with the following structure
3658 * {
3659 * id,
3660 * rel,
3661 * label
3662 * }
3663 *
3664 * @param n
3665 * @param values
3666 */
3667node.filterExistingValues = function (n, values) {
3668 var notFoundValues = [];
3669 var possibleValueNodes = dataModel.nodes.filter(function (n) {
3670 return n.parent === n && n.hasOwnProperty("value") && n.value.length > 0;
3671 });
3672
3673 values.forEach(function (v) {
3674 var found = false;
3675 var constraintAttr = provider$1.node.getConstraintAttribute(v.label);
3676
3677 possibleValueNodes.forEach(function (n) {
3678 if (n.label === v.label) {
3679 n.value.forEach(function (nv) {
3680 if (nv.attributes[constraintAttr] === v.id) {
3681 found = true;
3682 }
3683 });
3684 }
3685 });
3686
3687 if (!found) {
3688 notFoundValues.push(v);
3689 }
3690 });
3691
3692 return notFoundValues;
3693};
3694
3695/**
3696 * Function called to expand a node containing values.
3697 * This function will create the value nodes with the clicked node internal data.
3698 * Only nodes corresponding to the current page index will be generated.
3699 *
3700 * @param clickedNode
3701 */
3702node.expandNode = function (clickedNode) {
3703
3704 graph$1.notifyListeners(graph$1.Events.GRAPH_NODE_VALUE_EXPAND, [clickedNode]);
3705
3706 // Get subset of node corresponding to the current node page and page size
3707 var lIndex = clickedNode.page * node.PAGE_SIZE;
3708 var sIndex = lIndex - node.PAGE_SIZE;
3709
3710 var dataToAdd = clickedNode.data.slice(sIndex, lIndex);
3711 var parentAngle = graph$1.computeParentAngle(clickedNode);
3712
3713 // Then each node are created and dispatched around the clicked node using computed coordinates.
3714 var i = 1;
3715 dataToAdd.forEach(function (d) {
3716 var angleDeg;
3717 if (clickedNode.parent) {
3718 angleDeg = (((360 / (dataToAdd.length + 1)) * i));
3719 } else {
3720 angleDeg = (((360 / (dataToAdd.length)) * i));
3721 }
3722
3723 var nx = clickedNode.x + (100 * Math.cos((angleDeg * (Math.PI / 180)) - parentAngle)),
3724 ny = clickedNode.y + (100 * Math.sin((angleDeg * (Math.PI / 180)) - parentAngle));
3725
3726 var n = {
3727 "id": dataModel.generateId(),
3728 "parent": clickedNode,
3729 "attributes": d,
3730 "type": node.NodeTypes.VALUE,
3731 "label": clickedNode.label,
3732 "count": d.count,
3733 "x": nx,
3734 "y": ny,
3735 "internalID": d[query.NEO4J_INTERNAL_ID.queryInternalName]
3736 };
3737
3738 dataModel.nodes.push(n);
3739 dataModel.links.push(
3740 {
3741 id: "l" + dataModel.generateId(),
3742 source: clickedNode,
3743 target: n,
3744 type: graph$1.link.LinkTypes.VALUE
3745 }
3746 );
3747
3748 i++;
3749 });
3750
3751 // Pin clicked node and its parent to avoid the graph to move for selection, only new value nodes will blossom around the clicked node.
3752 clickedNode.fixed = true;
3753 clickedNode.fx = clickedNode.x;
3754 clickedNode.fy = clickedNode.y;
3755 if (clickedNode.parent && clickedNode.parent.type !== node.NodeTypes.ROOT) {
3756 clickedNode.parent.fixed = true;
3757 clickedNode.parent.fx = clickedNode.parent.x;
3758 clickedNode.parent.fy = clickedNode.parent.y;
3759 }
3760 // Change node state
3761 clickedNode.valueExpanded = true;
3762 update();
3763};
3764
3765/**
3766 * Fetches the list of relationships of a node and store them in the relationships property.
3767 *
3768 * @param n the node to fetch the relationships.
3769 * @param callback
3770 * @param directionAngle
3771 */
3772node.loadRelationshipData = function (n, callback, directionAngle) {
3773 var schema = provider$1.node.getSchema(n.label);
3774
3775 if (schema !== undefined) {
3776 if (schema.hasOwnProperty("rel") && schema.rel.length > 0) {
3777 callback(node.pie.startAngle(directionAngle - Math.PI).endAngle(directionAngle + Math.PI)(schema.rel).map(function (d) {
3778 var data = {
3779 id: d.data.label + d.data.target.label,
3780 label: d.data.label,
3781 target: d.data.target.label,
3782 count: 0,
3783 startAngle: d.startAngle,
3784 endAngle: d.endAngle,
3785 directionAngle: (d.startAngle + d.endAngle) / 2
3786 };
3787
3788 if (d.data.isReverse === true) {
3789 data.isReverse = true;
3790 }
3791
3792 return data;
3793 }));
3794 } else {
3795 callback([]);
3796 }
3797 } else {
3798 var nodeRelationQuery = query.generateNodeRelationQuery(n);
3799
3800 logger.info("Relations (" + n.label + ") ==>");
3801 runner.run(
3802 {
3803 "statements": [
3804 {
3805 "statement": nodeRelationQuery.statement,
3806 "parameters": nodeRelationQuery.parameters
3807 }]
3808 })
3809 .then(function (results) {
3810 logger.info("<== Relations (" + n.label + ")");
3811 var parsedData = runner.toObject(results);
3812
3813 // Filter data to eventually remove relations if a filter has been defined in config.
3814 var filteredData = parsedData[0].filter(function (d) {
3815 return query.filterRelation(d);
3816 });
3817
3818 filteredData = node.pie.startAngle(directionAngle - Math.PI).endAngle(directionAngle + Math.PI)(filteredData).map(function (d) {
3819 return {
3820 id: d.data.label + d.data.target,
3821 label: d.data.label,
3822 target: d.data.target,
3823 count: d.data.count.toString(),
3824 startAngle: d.startAngle,
3825 endAngle: d.endAngle,
3826 directionAngle: (d.startAngle + d.endAngle) / 2
3827 }
3828 });
3829
3830 callback(filteredData);
3831 })
3832 .catch(function (error) {
3833 logger.error(error);
3834 callback([]);
3835 });
3836 }
3837};
3838
3839/**
3840 * Expands all the relationships available in node.
3841 *
3842 * @param n
3843 * @param callback
3844 */
3845node.expandRelationships = function (n, callback) {
3846 var callbackCount = 0;
3847
3848 if (n.hasOwnProperty("relationships") && n.relationships.length > 0) {
3849
3850 for (var i = 0; i < n.relationships.length; i++) {
3851 graph$1.addRelationshipData(n, n.relationships[i], function () {
3852 callbackCount++;
3853
3854 if (callbackCount === n.relationships.length) {
3855 callback();
3856 }
3857 });
3858 }
3859 } else {
3860 callback();
3861 }
3862};
3863
3864/**
3865 * Remove a node and its relationships (recursively) from the graph.
3866 *
3867 * @param n the node to remove.
3868 */
3869node.removeNode = function (n) {
3870 var willChangeResults = n.hasOwnProperty("value") && n.value.length > 0;
3871
3872 var linksToRemove = dataModel.links.filter(function (l) {
3873 return l.source === n;
3874 });
3875
3876 // Remove children nodes from model
3877 linksToRemove.forEach(function (l) {
3878 var rc = node.removeNode(l.target);
3879 willChangeResults = willChangeResults || rc;
3880 });
3881
3882 // Remove links to nodes from model
3883 for (var i = dataModel.links.length - 1; i >= 0; i--) {
3884 if (dataModel.links[i].target === n) {
3885 dataModel.links.splice(i, 1);
3886 }
3887 }
3888
3889 dataModel.nodes.splice(dataModel.nodes.indexOf(n), 1);
3890
3891 return willChangeResults;
3892};
3893
3894/**
3895 * Remove empty branches containing a node.
3896 *
3897 * @param n the node to remove.
3898 * @return true if node have been removed
3899 */
3900node.removeEmptyBranches = function (n) {
3901 var hasValues = n.hasOwnProperty("value") && n.value.length > 0;
3902
3903 var childrenLinks = dataModel.links.filter(function (l) {
3904 return l.source === n;
3905 });
3906
3907 childrenLinks.forEach(function (l) {
3908 var hasRemainingNodes = !node.removeEmptyBranches(l.target);
3909 hasValues = hasValues || hasRemainingNodes;
3910 });
3911
3912 if (!hasValues) {
3913 // Remove links to node from model
3914 for (var i = dataModel.links.length - 1; i >= 0; i--) {
3915 if (dataModel.links[i].target === n) {
3916 dataModel.links.splice(i, 1);
3917 }
3918 }
3919
3920 dataModel.nodes.splice(dataModel.nodes.indexOf(n), 1);
3921 }
3922
3923 return !hasValues;
3924};
3925
3926/**
3927 * Get in the parent nodes the closest one to the root.
3928 *
3929 * @param n the node to start from.
3930 * @return {*} the trunk node or the node in parameters if not found.
3931 */
3932node.getTrunkNode = function (n) {
3933
3934 for (var i = 0; i < dataModel.links.length; i++) {
3935 var l = dataModel.links[i];
3936 if (l.target === n) {
3937 if (l.source !== graph$1.getRootNode()) {
3938 return node.getTrunkNode(l.source);
3939 }
3940 }
3941 }
3942
3943 return n;
3944};
3945
3946/**
3947 * Function to add on node event to clear the selection.
3948 * Call to this function on a node will remove the selected value and trigger a graph update.
3949 */
3950node.clearSelection = function () {
3951 // Prevent default event like right click opening menu.
3952 d3__namespace.event.preventDefault();
3953
3954 // Get clicked node.
3955 var clickedNode = d3__namespace.select(this).data()[0];
3956
3957 // Collapse all expanded choose nodes first
3958 node.collapseAllNode();
3959
3960 if (clickedNode.value !== undefined && clickedNode.value.length > 0 && !clickedNode.immutable) {
3961 // Remove last value of chosen node
3962 clickedNode.value.pop();
3963
3964 if (clickedNode.isNegative === true) {
3965 if (clickedNode.value.length === 0) {
3966 // Remove all related nodes
3967 for (var i = dataModel.links.length - 1; i >= 0; i--) {
3968 if (dataModel.links[i].source === clickedNode) {
3969 node.removeNode(dataModel.links[i].target);
3970 }
3971 }
3972
3973 clickedNode.count = 0;
3974 }
3975 }
3976
3977 result.hasChanged = true;
3978 graph$1.hasGraphChanged = true;
3979 update();
3980 }
3981};
3982
3983var graph = {};
3984
3985graph.link = link;
3986graph.node = node;
3987
3988graph.DISABLE_RELATION = false;
3989graph.DISABLE_COUNT = false;
3990
3991/**
3992 * ID of the HTML component where the graph query builder elements will be generated in.
3993 * @type {string}
3994 */
3995graph.containerId = "popoto-graph";
3996graph.hasGraphChanged = true;
3997// Defines the min and max level of zoom available in graph query builder.
3998graph.zoom = d3__namespace.zoom().scaleExtent([0.1, 10]);
3999graph.WHEEL_ZOOM_ENABLED = true;
4000graph.USE_DONUT_FORCE = false;
4001graph.USE_VORONOI_LAYOUT = false;
4002graph.USE_FIT_TEXT = false;
4003
4004/**
4005 * Define the list of listenable events on graph.
4006 */
4007graph.Events = Object.freeze({
4008 NODE_ROOT_ADD: "root.node.add",
4009 NODE_EXPAND_RELATIONSHIP: "node.expandRelationship",
4010 GRAPH_SAVE: "graph.save",
4011 GRAPH_RESET: "graph.reset",
4012 GRAPH_NODE_RELATION_ADD: "graph.node.relation_add",
4013 GRAPH_NODE_VALUE_EXPAND: "graph.node.value_expand",
4014 GRAPH_NODE_VALUE_COLLAPSE: "graph.node.value_collapse",
4015 GRAPH_NODE_ADD_VALUE: "graph.node.add_value",
4016 GRAPH_NODE_DATA_LOADED: "graph.node.data_loaded"
4017});
4018
4019graph.listeners = {};
4020
4021/**
4022 * Add a listener to the specified event.
4023 *
4024 * @param event name of the event to add the listener.
4025 * @param listener the listener to add.
4026 */
4027graph.on = function (event, listener) {
4028 if (!graph.listeners.hasOwnProperty(event)) {
4029 graph.listeners[event] = [];
4030 }
4031
4032 graph.listeners[event].push(listener);
4033};
4034
4035/**
4036 * Notify the listeners.
4037 *
4038 * @param event
4039 * @param parametersArray
4040 */
4041graph.notifyListeners = function (event, parametersArray) {
4042 if (graph.listeners.hasOwnProperty(event)) {
4043 graph.listeners[event].forEach(function (listener) {
4044 listener.apply(event, parametersArray);
4045 });
4046 }
4047};
4048
4049/**
4050 * Add a listener on graph save event.
4051 * @param listener
4052 */
4053graph.onSave = function (listener) {
4054 graph.on(graph.Events.GRAPH_SAVE, listener);
4055};
4056
4057/**
4058 * Add a listener on graph reset event.
4059 * @param listener
4060 */
4061graph.onReset = function (listener) {
4062 graph.on(graph.Events.GRAPH_RESET, listener);
4063};
4064
4065/**
4066 * Set default graph to a predefined value.
4067 * @param graph
4068 */
4069graph.setDefaultGraph = function (graph) {
4070 graph.mainLabel = graph;
4071};
4072
4073/**
4074 * Generates all the HTML and SVG element needed to display the graph query builder.
4075 * Everything will be generated in the container with id defined by graph.containerId.
4076 */
4077graph.createGraphArea = function () {
4078
4079 var htmlContainer = d3__namespace.select("#" + graph.containerId);
4080
4081 toolbar.render(htmlContainer);
4082
4083 graph.svgTag = htmlContainer.append("svg")
4084 .attr("class", "ppt-svg-graph")
4085 // .attr("viewBox", "0 0 800 600") TODO to avoid need of windows resize event
4086 .call(graph.zoom.on("zoom", graph.rescale));
4087
4088 graph.svgTag.on("dblclick.zoom", null);
4089
4090 if (!graph.WHEEL_ZOOM_ENABLED) {
4091 // Disable mouse wheel events.
4092 graph.svgTag.on("wheel.zoom", null)
4093 .on("mousewheel.zoom", null);
4094 }
4095
4096 // Objects created inside a <defs> element are not rendered immediately; instead, think of them as templates or macros created for future use.
4097 graph.svgdefs = graph.svgTag.append("defs");
4098
4099 // Cross marker for path with id #cross -X-
4100 graph.svgdefs.append("marker")
4101 .attr("id", "cross")
4102 .attr("refX", 10)
4103 .attr("refY", 10)
4104 .attr("markerWidth", 20)
4105 .attr("markerHeight", 20)
4106 .attr("markerUnits", "strokeWidth")
4107 .attr("orient", "auto")
4108 .append("path")
4109 .attr("class", "ppt-marker-cross")
4110 .attr("d", "M5,5 L15,15 M15,5 L5,15");
4111
4112 // Triangle marker for paths with id #arrow --|>
4113 graph.svgdefs.append("marker")
4114 .attr("id", "arrow")
4115 .attr("refX", 9)
4116 .attr("refY", 3)
4117 .attr("markerWidth", 10)
4118 .attr("markerHeight", 10)
4119 .attr("markerUnits", "strokeWidth")
4120 .attr("orient", "auto")
4121 .append("path")
4122 .attr("class", "ppt-marker-arrow")
4123 .attr("d", "M0,0 L0,6 L9,3 z");
4124
4125 // Reversed triangle marker for paths with id #reverse-arrow <|--
4126 graph.svgdefs.append("marker")
4127 .attr("id", "reverse-arrow")
4128 .attr("refX", 0)
4129 .attr("refY", 3)
4130 .attr("markerWidth", 10)
4131 .attr("markerHeight", 10)
4132 .attr("markerUnits", "strokeWidth")
4133 .attr("orient", "auto")
4134 .append("path")
4135 .attr("class", "ppt-marker-reverse-arrow")
4136 .attr("d", "M0,3 L9,6 L9,0 z");
4137
4138 // Gray scale filter for images.
4139 var grayscaleFilter = graph.svgdefs.append("filter")
4140 .attr("id", "grayscale");
4141
4142 grayscaleFilter.append("feColorMatrix")
4143 .attr("type", "saturate")
4144 .attr("values", "0");
4145
4146 // to change brightness
4147 // var feCT = grayscaleFilter
4148 // .append("feComponentTransfer");
4149 //
4150 // feCT.append("feFuncR")
4151 // .attr("type", "linear")
4152 // .attr("slope", "0.2");
4153 //
4154 // feCT.append("feFuncG")
4155 // .attr("type", "linear")
4156 // .attr("slope", "0.2");
4157 //
4158 // feCT.append("feFuncB")
4159 // .attr("type", "linear")
4160 // .attr("slope", "0.2");
4161
4162 // gooey
4163 var filter = graph.svgdefs.append("filter").attr("id", "gooey");
4164 filter.append("feGaussianBlur")
4165 .attr("in", "SourceGraphic")
4166 .attr("stdDeviation", "10")
4167 //to fix safari: http://stackoverflow.com/questions/24295043/svg-gaussian-blur-in-safari-unexpectedly-lightens-image
4168 .attr("color-interpolation-filters", "sRGB")
4169 .attr("result", "blur");
4170 filter.append("feColorMatrix")
4171 .attr("class", "blurValues")
4172 .attr("in", "blur")
4173 .attr("mode", "matrix")
4174 .attr("values", "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -6")
4175 .attr("result", "gooey");
4176 filter.append("feBlend")
4177 .attr("in", "SourceGraphic")
4178 .attr("in2", "gooey")
4179 .attr("operator", "atop");
4180
4181
4182 graph.svgdefs.append("g")
4183 .attr("id", "voronoi-clip-path");
4184
4185 graph.svg = graph.svgTag.append("svg:g");
4186
4187 // Create two separated area for links and nodes
4188 // Links and nodes are separated in a dedicated "g" element
4189 // and nodes are generated after links to ensure that nodes are always on foreground.
4190 graph.svg.append("g").attr("id", graph.link.gID);
4191 graph.svg.append("g").attr("id", graph.node.gID);
4192
4193 // This listener is used to center the root node in graph during a window resize.
4194 // TODO can the listener be limited on the parent container only?
4195 window.addEventListener('resize', graph.centerRootNode);
4196};
4197
4198graph.getRootNode = function () {
4199 return dataModel.getRootNode();
4200};
4201
4202graph.centerRootNode = function () {
4203 dataModel.getRootNode().fx = graph.getSVGWidth() / 2;
4204 dataModel.getRootNode().fy = graph.getSVGHeight() / 2;
4205 update();
4206};
4207
4208/**
4209 * Get the actual width of the SVG element containing the graph query builder.
4210 * @returns {number}
4211 */
4212graph.getSVGWidth = function () {
4213 if (typeof graph.svg == 'undefined' || graph.svg.empty()) {
4214 logger.debug("graph.svg is undefined or empty.");
4215 return 0;
4216 } else {
4217 return document.getElementById(graph.containerId).clientWidth;
4218 }
4219};
4220
4221/**
4222 * Get the actual height of the SVG element containing the graph query builder.
4223 * @returns {number}
4224 */
4225graph.getSVGHeight = function () {
4226 if (typeof graph.svg == 'undefined' || graph.svg.empty()) {
4227 logger.debug("graph.svg is undefined or empty.");
4228 return 0;
4229 } else {
4230 return document.getElementById(graph.containerId).clientHeight;
4231 }
4232};
4233
4234/**
4235 * Function to call on SVG zoom event to update the svg transform attribute.
4236 */
4237graph.rescale = function () {
4238 var transform = d3__namespace.event.transform;
4239 if (isNaN(transform.x) || isNaN(transform.y) || isNaN(transform.k)) {
4240 graph.svg.attr("transform", d3__namespace.zoomIdentity);
4241 } else {
4242 graph.svg.attr("transform", transform);
4243 }
4244};
4245
4246graph.CHARGE = -500;
4247
4248/**
4249 * Create the D3.js force simultation for the graph query builder.
4250 */
4251// TODO ZZZ rename to create createForceSimulation
4252graph.createForceLayout = function () {
4253
4254 graph.force = d3__namespace.forceSimulation()
4255 .force("charge", d3__namespace.forceManyBody()
4256 .strength(function (d) {
4257 if (d.charge) {
4258 return d.charge;
4259 } else {
4260 return graph.CHARGE;
4261 }
4262 }
4263 )
4264 )
4265 .force(
4266 "link",
4267 d3__namespace.forceLink().id(
4268 function (d) {
4269 return d.id;
4270 }
4271 ).distance(provider$1.link.getDistance)
4272 );
4273
4274
4275 graph.force.nodes(dataModel.nodes);
4276 graph.force.force("link").links(dataModel.links);
4277
4278 graph.force.on("tick", graph.tick);
4279};
4280
4281/**
4282 * Adds graph root nodes using the label set as parameter.
4283 * All the other nodes should have been removed first to avoid inconsistent data.
4284 *
4285 * @param label label of the node to add as root.
4286 */
4287graph.addRootNode = function (label) {
4288 if (dataModel.nodes.length > 0) {
4289 logger.warn("graph.addRootNode is called but the graph is not empty.");
4290 }
4291 if (provider$1.node.getSchema(label) !== undefined) {
4292 graph.addSchema(provider$1.node.getSchema(label));
4293 } else {
4294
4295 var n = {
4296 "id": dataModel.generateId(),
4297 "type": graph.node.NodeTypes.ROOT,
4298 // x and y coordinates are set to the center of the SVG area.
4299 // These coordinate will never change at runtime except if the window is resized.
4300 "x": graph.getSVGWidth() / 2,
4301 "y": graph.getSVGHeight() / 2,
4302 "fx": graph.getSVGWidth() / 2,
4303 "fy": graph.getSVGHeight() / 2,
4304 "tx": graph.getSVGWidth() / 2,
4305 "ty": graph.getSVGHeight() / 2,
4306 "label": label,
4307 // The node is fixed to always remain in the center of the svg area.
4308 // This property should not be changed at runtime to avoid issues with the zoom and pan.
4309 "fixed": true,
4310 // Label used internally to identify the node.
4311 // This label is used for example as cypher query identifier.
4312 "internalLabel": graph.node.generateInternalLabel(label),
4313 // List of relationships
4314 "relationships": [],
4315 "isAutoLoadValue": provider$1.node.getIsAutoLoadValue(label) === true
4316 };
4317
4318 dataModel.nodes.push(n);
4319 graph.notifyListeners(graph.Events.NODE_ROOT_ADD, [n]);
4320
4321 graph.node.loadRelationshipData(n, function (relationships) {
4322 n.relationships = relationships;
4323
4324 if (provider$1.node.getIsAutoExpandRelations(label)) {
4325
4326 graph.ignoreCount = true;
4327 graph.node.expandRelationships(n, function () {
4328
4329 graph.ignoreCount = false;
4330 graph.hasGraphChanged = true;
4331 updateGraph();
4332 });
4333 } else {
4334 graph.hasGraphChanged = true;
4335 updateGraph();
4336 }
4337 }, Math.PI / 2);
4338 }
4339};
4340
4341graph.loadSchema = function (graphToLoad) {
4342 if (dataModel.nodes.length > 0) {
4343 logger.warn("graph.loadSchema is called but the graph is not empty.");
4344 }
4345
4346 var rootNodeSchema = graphToLoad;
4347 var rootNode = {
4348 "id": dataModel.generateId(),
4349 "type": graph.node.NodeTypes.ROOT,
4350 // x and y coordinates are set to the center of the SVG area.
4351 // These coordinate will never change at runtime except if the window is resized.
4352 "x": graph.getSVGWidth() / 2,
4353 "y": graph.getSVGHeight() / 2,
4354 "fx": graph.getSVGWidth() / 2,
4355 "fy": graph.getSVGHeight() / 2,
4356 "tx": graph.getSVGWidth() / 2,
4357 "ty": graph.getSVGHeight() / 2,
4358 "label": rootNodeSchema.label,
4359 // The node is fixed to always remain in the center of the svg area.
4360 // This property should not be changed at runtime to avoid issues with the zoom and pan.
4361 "fixed": true,
4362 // Label used internally to identify the node.
4363 // This label is used for example as cypher query identifier.
4364 "internalLabel": graph.node.generateInternalLabel(rootNodeSchema.label),
4365 // List of relationships
4366 "relationships": [],
4367 "isAutoLoadValue": provider$1.node.getIsAutoLoadValue(rootNodeSchema.label) === true
4368 };
4369 dataModel.nodes.push(rootNode);
4370 graph.notifyListeners(graph.Events.NODE_ROOT_ADD, [rootNode]);
4371
4372 var labelSchema = provider$1.node.getSchema(graphToLoad.label);
4373
4374 // Add relationship from schema if defined
4375 if (labelSchema !== undefined && labelSchema.hasOwnProperty("rel") && labelSchema.rel.length > 0) {
4376 var directionAngle = (Math.PI / 2);
4377
4378 rootNode.relationships = graph.node.pie.startAngle(directionAngle - Math.PI).endAngle(directionAngle + Math.PI)(labelSchema.rel).map(function (d) {
4379
4380 var data = {
4381 id: d.data.label + d.data.target.label,
4382 label: d.data.label,
4383 target: d.data.target.label,
4384 count: 0,
4385 startAngle: d.startAngle,
4386 endAngle: d.endAngle,
4387 directionAngle: (d.startAngle + d.endAngle) / 2
4388 };
4389
4390 if (d.data.isReverse === true) {
4391 data.isReverse = true;
4392 }
4393
4394 return data;
4395 });
4396 } else {
4397 graph.node.loadRelationshipData(rootNode, function (relationships) {
4398 rootNode.relationships = relationships;
4399 graph.hasGraphChanged = true;
4400 updateGraph();
4401 }, Math.PI / 2);
4402 }
4403
4404 if (rootNodeSchema.hasOwnProperty("value")) {
4405 var nodeSchemaValue = [].concat(rootNodeSchema.value);
4406 rootNode.value = [];
4407 nodeSchemaValue.forEach(function (value) {
4408 rootNode.value.push(
4409 {
4410 "id": dataModel.generateId(),
4411 "parent": rootNode,
4412 "attributes": value,
4413 "type": graph.node.NodeTypes.VALUE,
4414 "label": rootNode.label
4415 }
4416 );
4417 });
4418 }
4419
4420 if (rootNodeSchema.hasOwnProperty("rel")) {
4421 for (var linkIndex = 0; linkIndex < rootNodeSchema.rel.length; linkIndex++) {
4422 graph.loadSchemaRelation(rootNodeSchema.rel[linkIndex], rootNode, linkIndex + 1, rootNodeSchema.rel.length);
4423 }
4424 }
4425};
4426
4427graph.loadSchemaRelation = function (relationSchema, parentNode, linkIndex, parentLinkTotalCount) {
4428 var targetNodeSchema = relationSchema.target;
4429 var target = graph.loadSchemaNode(targetNodeSchema, parentNode, linkIndex, parentLinkTotalCount, relationSchema.label, (relationSchema.hasOwnProperty("isReverse") && relationSchema.isReverse === true));
4430
4431 var newLink = {
4432 id: "l" + dataModel.generateId(),
4433 source: parentNode,
4434 target: target,
4435 type: graph.link.LinkTypes.RELATION,
4436 label: relationSchema.label,
4437 schema: relationSchema
4438 };
4439
4440 dataModel.links.push(newLink);
4441
4442 var labelSchema = provider$1.node.getSchema(targetNodeSchema.label);
4443
4444 // Add relationship from schema if defined
4445 if (labelSchema !== undefined && labelSchema.hasOwnProperty("rel") && labelSchema.rel.length > 0) {
4446 var directionAngle = (Math.PI / 2);
4447
4448 target.relationships = graph.node.pie.startAngle(directionAngle - Math.PI).endAngle(directionAngle + Math.PI)(labelSchema.rel).map(function (d) {
4449 var data = {
4450 id: d.data.label + d.data.target.label,
4451 label: d.data.label,
4452 target: d.data.target.label,
4453 count: 0,
4454 startAngle: d.startAngle,
4455 endAngle: d.endAngle,
4456 directionAngle: (d.startAngle + d.endAngle) / 2
4457 };
4458
4459 if (d.data.isReverse === true) {
4460 data.isReverse = true;
4461 }
4462
4463 return data;
4464 });
4465 } else {
4466 graph.node.loadRelationshipData(target, function (relationships) {
4467 target.relationships = relationships;
4468 graph.hasGraphChanged = true;
4469 updateGraph();
4470 }, Math.PI / 2);
4471 }
4472
4473 if (targetNodeSchema.hasOwnProperty("rel")) {
4474 for (var linkIndex2 = 0; linkIndex2 < targetNodeSchema.rel.length; linkIndex2++) {
4475 graph.loadSchemaRelation(targetNodeSchema.rel[linkIndex2], target, linkIndex2 + 1, targetNodeSchema.rel.length);
4476 }
4477 }
4478};
4479
4480graph.loadSchemaNode = function (nodeSchema, parentNode, index, parentLinkTotalCount, parentRel, isReverse) {
4481 var isGroupNode = provider$1.node.getIsGroup(nodeSchema);
4482 var parentAngle = graph.computeParentAngle(parentNode);
4483
4484 var angleDeg;
4485 if (parentAngle) {
4486 angleDeg = (((360 / (parentLinkTotalCount + 1)) * index));
4487 } else {
4488 angleDeg = (((360 / (parentLinkTotalCount)) * index));
4489 }
4490
4491 var nx = parentNode.x + (200 * Math.cos((angleDeg * (Math.PI / 180)) - parentAngle)),
4492 ny = parentNode.y + (200 * Math.sin((angleDeg * (Math.PI / 180)) - parentAngle));
4493
4494 // TODO add force coordinate X X X
4495 // var tx = nx + ((provider.link.getDistance(newLink)) * Math.cos(link.directionAngle - Math.PI / 2));
4496 // var ty = ny + ((provider.link.getDistance(newLink)) * Math.sin(link.directionAngle - Math.PI / 2));
4497
4498 var n = {
4499 "id": dataModel.generateId(),
4500 "parent": parentNode,
4501 "parentRel": parentRel,
4502 "type": (isGroupNode) ? graph.node.NodeTypes.GROUP : graph.node.NodeTypes.CHOOSE,
4503 "label": nodeSchema.label,
4504 "fixed": false,
4505 "internalLabel": graph.node.generateInternalLabel(nodeSchema.label),
4506 "x": nx,
4507 "y": ny,
4508 "schema": nodeSchema,
4509 "isAutoLoadValue": provider$1.node.getIsAutoLoadValue(nodeSchema.label) === true,
4510 "relationships": []
4511 };
4512
4513 if (isReverse === true) {
4514 n.isParentRelReverse = true;
4515 }
4516
4517 if (nodeSchema.hasOwnProperty("isNegative") && nodeSchema.isNegative === true) {
4518 n.isNegative = true;
4519 n.count = 0;
4520 }
4521
4522 dataModel.nodes.push(n);
4523
4524 if (nodeSchema.hasOwnProperty("value")) {
4525 var nodeSchemaValue = [].concat(nodeSchema.value);
4526 n.value = [];
4527 nodeSchemaValue.forEach(function (value) {
4528 n.value.push(
4529 {
4530 "id": dataModel.generateId(),
4531 "parent": n,
4532 "attributes": value,
4533 "type": graph.node.NodeTypes.VALUE,
4534 "label": n.label
4535 }
4536 );
4537 });
4538 }
4539
4540 return n;
4541};
4542
4543/**
4544 * Adds a complete graph from schema.
4545 * All the other nodes should have been removed first to avoid inconsistent data.
4546 *
4547 * @param graphSchema schema of the graph to add.
4548 */
4549graph.addSchema = function (graphSchema) {
4550 if (dataModel.nodes.length > 0) {
4551 logger.warn("graph.addSchema is called but the graph is not empty.");
4552 }
4553
4554 var rootNodeSchema = graphSchema;
4555
4556 var rootNode = {
4557 "id": dataModel.generateId(),
4558 "type": graph.node.NodeTypes.ROOT,
4559 // x and y coordinates are set to the center of the SVG area.
4560 // These coordinate will never change at runtime except if the window is resized.
4561 "x": graph.getSVGWidth() / 2,
4562 "y": graph.getSVGHeight() / 2,
4563 "fx": graph.getSVGWidth() / 2,
4564 "fy": graph.getSVGHeight() / 2,
4565 "tx": graph.getSVGWidth() / 2,
4566 "ty": graph.getSVGHeight() / 2,
4567 "label": rootNodeSchema.label,
4568 // The node is fixed to always remain in the center of the svg area.
4569 // This property should not be changed at runtime to avoid issues with the zoom and pan.
4570 "fixed": true,
4571 // Label used internally to identify the node.
4572 // This label is used for example as cypher query identifier.
4573 "internalLabel": graph.node.generateInternalLabel(rootNodeSchema.label),
4574 // List of relationships
4575 "relationships": [],
4576 "isAutoLoadValue": provider$1.node.getIsAutoLoadValue(rootNodeSchema.label) === true
4577 };
4578 dataModel.nodes.push(rootNode);
4579 graph.notifyListeners(graph.Events.NODE_ROOT_ADD, [rootNode]);
4580
4581 if (rootNodeSchema.hasOwnProperty("rel") && rootNodeSchema.rel.length > 0) {
4582 var directionAngle = (Math.PI / 2);
4583
4584 rootNode.relationships = graph.node.pie.startAngle(directionAngle - Math.PI).endAngle(directionAngle + Math.PI)(rootNodeSchema.rel).map(function (d) {
4585 var data = {
4586 id: d.data.label + d.data.target.label,
4587 label: d.data.label,
4588 target: d.data.target.label,
4589 count: 0,
4590 startAngle: d.startAngle,
4591 endAngle: d.endAngle,
4592 directionAngle: (d.startAngle + d.endAngle) / 2
4593 };
4594
4595 if (d.data.isReverse === true) {
4596 data.isReverse = true;
4597 }
4598
4599 return data;
4600 });
4601 }
4602
4603 // if (!isCollapsed && rootNodeSchema.hasOwnProperty("rel")) {
4604 // for (var linkIndex = 0; linkIndex < rootNodeSchema.rel.length; linkIndex++) {
4605 // graph.addSchemaRelation(rootNodeSchema.rel[linkIndex], rootNode, linkIndex + 1, rootNodeSchema.rel.length);
4606 // }
4607 // }
4608};
4609
4610graph.addSchemaRelation = function (relationSchema, parentNode, linkIndex, parentLinkTotalCount) {
4611 var targetNodeSchema = relationSchema.target;
4612 var target = graph.addSchemaNode(targetNodeSchema, parentNode, linkIndex, parentLinkTotalCount, relationSchema.label);
4613
4614 var newLink = {
4615 id: "l" + dataModel.generateId(),
4616 source: parentNode,
4617 target: target,
4618 type: graph.link.LinkTypes.RELATION,
4619 label: relationSchema.label,
4620 schema: relationSchema
4621 };
4622
4623 dataModel.links.push(newLink);
4624};
4625
4626graph.addSchemaNode = function (nodeSchema, parentNode, index, parentLinkTotalCount, parentRel) {
4627 var isGroupNode = provider$1.node.getIsGroup(nodeSchema);
4628 var isCollapsed = nodeSchema.hasOwnProperty("collapsed") && nodeSchema.collapsed === true;
4629
4630 var parentAngle = graph.computeParentAngle(parentNode);
4631
4632 var angleDeg;
4633 if (parentAngle) {
4634 angleDeg = (((360 / (parentLinkTotalCount + 1)) * index));
4635 } else {
4636 angleDeg = (((360 / (parentLinkTotalCount)) * index));
4637 }
4638
4639 var nx = parentNode.x + (200 * Math.cos((angleDeg * (Math.PI / 180)) - parentAngle)),
4640 ny = parentNode.y + (200 * Math.sin((angleDeg * (Math.PI / 180)) - parentAngle));
4641
4642 // TODO add force coordinate X X X
4643 // var tx = nx + ((provider.link.getDistance(newLink)) * Math.cos(link.directionAngle - Math.PI / 2));
4644 // var ty = ny + ((provider.link.getDistance(newLink)) * Math.sin(link.directionAngle - Math.PI / 2));
4645
4646 var n = {
4647 "id": dataModel.generateId(),
4648 "parent": parentNode,
4649 "parentRel": parentRel,
4650 "type": (isGroupNode) ? graph.node.NodeTypes.GROUP : graph.node.NodeTypes.CHOOSE,
4651 "label": nodeSchema.label,
4652 "fixed": false,
4653 "internalLabel": graph.node.generateInternalLabel(nodeSchema.label),
4654 "x": nx,
4655 "y": ny,
4656 "schema": nodeSchema,
4657 "isAutoLoadValue": provider$1.node.getIsAutoLoadValue(nodeSchema.label) === true,
4658 "relationships": []
4659 };
4660
4661 if (nodeSchema.hasOwnProperty("rel") && nodeSchema.rel.length > 0) {
4662 var relMap = {};
4663 var relSegments = [];
4664
4665 for (var i = 0; i < nodeSchema.rel.length; i++) {
4666 var rel = nodeSchema.rel[i];
4667 var id = rel.label + rel.target.label;
4668
4669 if (!relMap.hasOwnProperty(id)) {
4670 relMap[id] = rel;
4671 relSegments.push(rel);
4672 }
4673
4674 }
4675
4676 n.relationships = graph.node.pie(relSegments).map(function (d) {
4677 return {
4678 id: d.data.label + d.data.target.label,
4679 count: d.data.count || 0,
4680 label: d.data.label,
4681 target: d.data.target.label,
4682 startAngle: d.startAngle,
4683 endAngle: d.endAngle,
4684 directionAngle: (d.startAngle + d.endAngle) / 2
4685 }
4686 });
4687
4688 }
4689
4690 if (nodeSchema.hasOwnProperty("value")) {
4691 var nodeSchemaValue = [].concat(nodeSchema.value);
4692 n.value = [];
4693 nodeSchemaValue.forEach(function (value) {
4694 n.value.push(
4695 {
4696 "id": dataModel.generateId(),
4697 "parent": n,
4698 "attributes": value,
4699 "type": graph.node.NodeTypes.VALUE,
4700 "label": n.label
4701 }
4702 );
4703 });
4704 }
4705
4706 dataModel.nodes.push(n);
4707
4708 if (!isCollapsed && nodeSchema.hasOwnProperty("rel")) {
4709 for (var linkIndex = 0; linkIndex < nodeSchema.rel.length; linkIndex++) {
4710 graph.addSchemaRelation(nodeSchema.rel[linkIndex], n, linkIndex + 1, nodeSchema.rel.length);
4711 }
4712 }
4713
4714 return n;
4715};
4716
4717/**
4718 * Get the current schema of the graph.
4719 * @returns {{}}
4720 */
4721graph.getSchema = function () {
4722 var nodesMap = {};
4723
4724 var rootNode = dataModel.getRootNode();
4725
4726 nodesMap[rootNode.id] = {
4727 label: rootNode.label
4728 };
4729
4730 if (rootNode.hasOwnProperty("value")) {
4731 nodesMap[rootNode.id].value = [];
4732 rootNode.value.forEach(function (value) {
4733 nodesMap[rootNode.id].value.push(value.attributes);
4734 });
4735 }
4736
4737 var links = dataModel.links;
4738 if (links.length > 0) {
4739 links.forEach(function (l) {
4740 if (l.type === graph.link.LinkTypes.RELATION) {
4741 var sourceNode = l.source;
4742 var targetNode = l.target;
4743
4744 if (!nodesMap.hasOwnProperty(sourceNode.id)) {
4745 nodesMap[sourceNode.id] = {
4746 label: sourceNode.label
4747 };
4748 if (sourceNode.hasOwnProperty("isNegative") && sourceNode.isNegative === true) {
4749 nodesMap[sourceNode.id].isNegative = true;
4750 }
4751 if (sourceNode.hasOwnProperty("value")) {
4752 nodesMap[sourceNode.id].value = [];
4753 sourceNode.value.forEach(function (value) {
4754 nodesMap[sourceNode.id].value.push(value.attributes);
4755 });
4756 }
4757 }
4758
4759 if (!nodesMap.hasOwnProperty(targetNode.id)) {
4760 nodesMap[targetNode.id] = {
4761 label: targetNode.label
4762 };
4763 if (targetNode.hasOwnProperty("isNegative") && targetNode.isNegative === true) {
4764 nodesMap[targetNode.id].isNegative = true;
4765 }
4766 if (targetNode.hasOwnProperty("value")) {
4767 nodesMap[targetNode.id].value = [];
4768 targetNode.value.forEach(function (value) {
4769 nodesMap[targetNode.id].value.push(value.attributes);
4770 });
4771 }
4772 }
4773
4774 if (!nodesMap[sourceNode.id].hasOwnProperty("rel")) {
4775 nodesMap[sourceNode.id].rel = [];
4776 }
4777
4778 var rel = {
4779 label: l.label,
4780 target: nodesMap[targetNode.id]
4781 };
4782
4783 if (targetNode.hasOwnProperty("isParentRelReverse") && targetNode.isParentRelReverse === true) {
4784 rel.isReverse = true;
4785 }
4786
4787 nodesMap[sourceNode.id].rel.push(rel);
4788 }
4789 });
4790
4791 }
4792
4793 return nodesMap[rootNode.id];
4794};
4795
4796/**
4797 * Function to call on D3.js force layout tick event.
4798 * This function will update the position of all links and nodes elements in the graph with the force layout computed coordinate.
4799 */
4800graph.tick = function () {
4801 var paths = graph.svg.selectAll("#" + graph.link.gID + " > g");
4802
4803 // Update link paths
4804 paths.selectAll(".ppt-link")
4805 .attr("d", function (d) {
4806 var linkSource = d.source;
4807 var linkTarget = d.target;
4808 var parentAngle = graph.computeParentAngle(linkTarget);
4809 var sourceMargin = provider$1.node.getSize(linkSource);
4810 var targetMargin = provider$1.node.getSize(linkTarget);
4811
4812 if (!graph.DISABLE_RELATION && linkSource.hasOwnProperty("relationships") && linkSource.relationships.length > 0) {
4813 sourceMargin = graph.node.getDonutOuterRadius(linkSource);
4814 }
4815
4816 if (!graph.DISABLE_RELATION && linkTarget.hasOwnProperty("relationships") && linkTarget.relationships.length > 0) {
4817 targetMargin = graph.node.getDonutOuterRadius(linkTarget);
4818 }
4819
4820 var targetX = linkTarget.x + ((targetMargin) * Math.cos(parentAngle)),
4821 targetY = linkTarget.y - ((targetMargin) * Math.sin(parentAngle));
4822
4823 var sourceX = linkSource.x - ((sourceMargin) * Math.cos(parentAngle)),
4824 sourceY = linkSource.y + ((sourceMargin) * Math.sin(parentAngle));
4825
4826 // Add an intermediate point in path center
4827 var middleX = (targetX + sourceX) / 2,
4828 middleY = (targetY + sourceY) / 2;
4829
4830 if (linkSource.x <= linkTarget.x || graph.ignoreMirroLinkLabels === true) {
4831 return "M" + sourceX + " " + sourceY + "L" + middleX + " " + middleY + "L" + targetX + " " + targetY;
4832 } else {
4833 return "M" + targetX + " " + targetY + "L" + middleX + " " + middleY + "L" + sourceX + " " + sourceY;
4834 }
4835 })
4836 .attr("marker-end", function (d) {
4837 if (graph.link.SHOW_MARKER) {
4838 if (d.target.isParentRelReverse === true) {
4839 if (d.source.x > d.target.x) {
4840 return "url(#arrow)";
4841 }
4842 } else {
4843 if (d.source.x <= d.target.x) {
4844 return "url(#arrow)";
4845 }
4846 }
4847 }
4848 return null;
4849 })
4850 .attr("marker-start", function (d) {
4851 if (graph.link.SHOW_MARKER) {
4852 if (d.target.isParentRelReverse === true) {
4853 if (d.source.x <= d.target.x) {
4854 return "url(#reverse-arrow)";
4855 }
4856 } else {
4857 if (d.source.x > d.target.x) {
4858 return "url(#reverse-arrow)";
4859 }
4860 }
4861 }
4862 return null;
4863 });
4864
4865 // Workaround to WebKit browsers:
4866 // Updating a path element by itself does not trigger redraw on dependent elements that reference this path.
4867 // So, even though we update the path, the referencing textPath element will not be redrawn.
4868 // To workaround this update bug, the xlink:href attribute to “#path” is updated.
4869 paths.selectAll(".ppt-textPath")
4870 .attr("xlink:href", function (d) {
4871 return "#ppt-path_" + d.id;
4872 });
4873
4874 graph.svg.selectAll("#" + graph.node.gID + " > g")
4875 .attr("transform", function (d) {
4876 return "translate(" + (d.x) + "," + (d.y) + ")";
4877 });
4878
4879 if (graph.USE_VORONOI_LAYOUT === true) {
4880
4881 var clip = d3__namespace.select("#voronoi-clip-path").selectAll('.voroclip')
4882 .data(graph.recenterVoronoi(dataModel.nodes), function (d) {
4883 return d.point.id;
4884 });
4885
4886 clip.enter().append('clipPath')
4887 .attr('id', function (d) {
4888 return 'voroclip-' + d.point.id;
4889 })
4890 .attr('class', 'voroclip');
4891
4892 clip.exit().remove();
4893
4894 clip.selectAll('path').remove();
4895
4896 clip.append('path')
4897 .attr('id', function (d) {
4898 return 'pvoroclip-' + d.point.id;
4899 })
4900 .attr('d', function (d) {
4901 return 'M' + d.join(',') + 'Z';
4902 });
4903 }
4904};
4905
4906/**
4907 * Compute the angle in radian between the node and its parent.
4908 * TODO: clean or add comments to explain the code...
4909 *
4910 * @param n node to compute angle.
4911 * @returns {number} angle in radian.
4912 */
4913graph.computeParentAngle = function (n) {
4914 var angleRadian = 0;
4915 var r = 100;
4916 if (n.parent) {
4917 var xp = n.parent.x;
4918 var yp = n.parent.y;
4919 var x0 = n.x;
4920 var y0 = n.y;
4921 var dist = Math.sqrt(Math.pow(xp - x0, 2) + Math.pow(yp - y0, 2));
4922
4923 var k = r / (dist - r);
4924 var xc = (x0 + (k * xp)) / (1 + k);
4925
4926 var val = (xc - x0) / r;
4927 if (val < -1) {
4928 val = -1;
4929 }
4930 if (val > 1) {
4931 val = 1;
4932 }
4933
4934 angleRadian = Math.acos(val);
4935
4936 if (yp > y0) {
4937 angleRadian = 2 * Math.PI - angleRadian;
4938 }
4939 }
4940 return angleRadian;
4941};
4942
4943/**
4944 *
4945 * @param n
4946 * @param l
4947 * @param callback
4948 * @param values
4949 * @param isNegative
4950 */
4951graph.addRelationshipData = function (n, l, callback, values, isNegative) {
4952 var targetNode = {
4953 "id": "" + dataModel.generateId(),
4954 "parent": n,
4955 "parentRel": l.label,
4956 "type": graph.node.NodeTypes.CHOOSE,
4957 "label": l.target,
4958 "fixed": false,
4959 "internalLabel": graph.node.generateInternalLabel(l.target),
4960 "relationships": []
4961 };
4962
4963 if (l.isReverse === true) {
4964 targetNode.isParentRelReverse = true;
4965 }
4966
4967 if (values !== undefined && values.length > 0) {
4968 targetNode.value = values;
4969 }
4970 if (isNegative !== undefined && isNegative === true) {
4971 targetNode.isNegative = true;
4972 }
4973
4974 var newLink = {
4975 id: "l" + dataModel.generateId(),
4976 source: n,
4977 target: targetNode,
4978 type: graph.link.LinkTypes.RELATION,
4979 label: l.label
4980 };
4981
4982 targetNode.x = n.x + ((provider$1.link.getDistance(newLink) * 2 / 3) * Math.cos(l.directionAngle - Math.PI / 2)) + Math.random() * 10;
4983 targetNode.y = n.y + ((provider$1.link.getDistance(newLink) * 2 / 3) * Math.sin(l.directionAngle - Math.PI / 2)) + Math.random() * 10;
4984
4985 targetNode.tx = n.tx + ((provider$1.link.getDistance(newLink)) * Math.cos(l.directionAngle - Math.PI / 2));
4986 targetNode.ty = n.ty + ((provider$1.link.getDistance(newLink)) * Math.sin(l.directionAngle - Math.PI / 2));
4987
4988 dataModel.nodes.push(targetNode);
4989 dataModel.links.push(newLink);
4990
4991 graph.hasGraphChanged = true;
4992 updateGraph();
4993
4994 graph.node.loadRelationshipData(targetNode, function (relationships) {
4995 targetNode.relationships = relationships;
4996
4997 graph.hasGraphChanged = true;
4998 updateGraph();
4999
5000 if (provider$1.node.getIsAutoExpandRelations(targetNode.label)) {
5001 graph.node.expandRelationships(targetNode, function () {
5002 callback(targetNode);
5003 });
5004 } else {
5005 callback(targetNode);
5006 }
5007 },
5008 l.directionAngle
5009 );
5010
5011
5012};
5013
5014graph.voronoi = d3__namespace.voronoi()
5015 .x(function (d) {
5016 return d.x;
5017 })
5018 .y(function (d) {
5019 return d.y;
5020 });
5021
5022graph.recenterVoronoi = function (nodes) {
5023 var shapes = [];
5024
5025 var voronois = graph.voronoi.polygons(nodes.map(function (d) {
5026 d.x = d.x || 0;
5027 d.y = d.y || 0;
5028 return d
5029 }));
5030
5031 voronois.forEach(function (d) {
5032 if (!d.length) {
5033 return;
5034 }
5035
5036 var n = [];
5037 d.forEach(function (c) {
5038 n.push([c[0] - d.data.x, c[1] - d.data.y]);
5039 });
5040
5041 n.point = d.data;
5042 shapes.push(n);
5043 });
5044 return shapes;
5045};
5046
5047var graph$1 = graph;
5048
5049var provider = {};
5050
5051/**
5052 * Default color scale generator.
5053 * Used in getColor link and node providers.
5054 */
5055provider.colorScale = d3__namespace.scaleOrdinal(d3__namespace.schemeCategory10);
5056provider.link = {};
5057provider.link.Provider = {};
5058provider.taxonomy = {};
5059provider.taxonomy.Provider = {};
5060provider.node = {};
5061provider.node.Provider = {};
5062
5063//------------------------------------------------
5064// LINKS
5065
5066/**
5067 * Get the text representation of a link.
5068 *
5069 * @param link the link to get the text representation.
5070 * @returns {string} the text representation of the link.
5071 */
5072provider.link.getTextValue = function (link) {
5073 if (provider.link.Provider.hasOwnProperty("getTextValue")) {
5074 return provider.link.Provider.getTextValue(link);
5075 } else {
5076 if (provider.link.DEFAULT_PROVIDER.hasOwnProperty("getTextValue")) {
5077 return provider.link.DEFAULT_PROVIDER.getTextValue(link);
5078 } else {
5079 logger.error("No provider defined for link getTextValue");
5080 }
5081 }
5082};
5083
5084provider.link.getColor = function (link, element, attribute) {
5085 if (provider.link.Provider.hasOwnProperty("getColor")) {
5086 return provider.link.Provider.getColor(link, element, attribute);
5087 } else {
5088 if (provider.link.DEFAULT_PROVIDER.hasOwnProperty("getColor")) {
5089 return provider.link.DEFAULT_PROVIDER.getColor(link, element, attribute);
5090 } else {
5091 logger.error("No provider defined for getColor");
5092 }
5093 }
5094};
5095
5096provider.link.getCSSClass = function (link, element) {
5097 if (provider.link.Provider.hasOwnProperty("getCSSClass")) {
5098 return provider.link.Provider.getCSSClass(link, element);
5099 } else {
5100 if (provider.link.DEFAULT_PROVIDER.hasOwnProperty("getCSSClass")) {
5101 return provider.link.DEFAULT_PROVIDER.getCSSClass(link, element);
5102 } else {
5103 logger.error("No provider defined for getCSSClass");
5104 }
5105 }
5106};
5107
5108provider.link.getDistance = function (link) {
5109 if (provider.link.Provider.hasOwnProperty("getDistance")) {
5110 return provider.link.Provider.getDistance(link);
5111 } else {
5112 if (provider.link.DEFAULT_PROVIDER.hasOwnProperty("getDistance")) {
5113 return provider.link.DEFAULT_PROVIDER.getDistance(link);
5114 } else {
5115 logger.error("No provider defined for getDistance");
5116 }
5117 }
5118};
5119
5120/**
5121 * Get the semantic text representation of a link.
5122 *
5123 * @param link the link to get the semantic text representation.
5124 * @returns {string} the semantic text representation of the link.
5125 */
5126provider.link.getSemanticValue = function (link) {
5127 if (provider.link.Provider.hasOwnProperty("getSemanticValue")) {
5128 return provider.link.Provider.getSemanticValue(link);
5129 } else {
5130 if (provider.link.DEFAULT_PROVIDER.hasOwnProperty("getSemanticValue")) {
5131 return provider.link.DEFAULT_PROVIDER.getSemanticValue(link);
5132 } else {
5133 logger.error("No provider defined for getSemanticValue");
5134 }
5135 }
5136};
5137
5138provider.colorLuminance = function (hex, lum) {
5139
5140 // validate hex string
5141 hex = String(hex).replace(/[^0-9a-f]/gi, '');
5142 if (hex.length < 6) {
5143 hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
5144 }
5145 lum = lum || 0;
5146
5147 // convert to decimal and change luminosity
5148 var rgb = "#", c, i;
5149 for (i = 0; i < 3; i++) {
5150 c = parseInt(hex.substr(i * 2, 2), 16);
5151 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
5152 rgb += ("00" + c).substr(c.length);
5153 }
5154
5155 return rgb;
5156};
5157
5158/**
5159 * Label provider used by default if none have been defined for a label.
5160 * This provider can be changed if needed to customize default behavior.
5161 * If some properties are not found in user customized providers, default values will be extracted from this provider.
5162 */
5163provider.link.DEFAULT_PROVIDER = {
5164 /**
5165 * Function used to return the text representation of a link.
5166 *
5167 * The default behavior is to return the internal relation name as text for relation links.
5168 * And return the target node text value for links between a node and its expanded values but only if text is not displayed on value node.
5169 *
5170 * @param link the link to represent as text.
5171 * @returns {string} the text representation of the link.
5172 */
5173 "getTextValue": function (link) {
5174 if (link.type === graph$1.link.LinkTypes.VALUE) {
5175 // Links between node and list of values.
5176
5177 if (provider.node.isTextDisplayed(link.target)) {
5178 // Don't display text on link if text is displayed on target node.
5179 return "";
5180 } else {
5181 // No text is displayed on target node then the text is displayed on link.
5182 return provider.node.getTextValue(link.target);
5183 }
5184
5185 } else {
5186 var targetName = "";
5187 if (link.type === graph$1.link.LinkTypes.SEGMENT) {
5188 targetName = " " + provider.node.getTextValue(link.target);
5189 }
5190 return link.label + targetName;
5191 }
5192 },
5193
5194
5195 /**
5196 *
5197 * @param link
5198 */
5199 "getDistance": function (link) {
5200 if (link.type === graph$1.link.LinkTypes.VALUE) {
5201 return (13 / 8) * (provider.node.getSize(link.source) + provider.node.getSize(link.target));
5202 } else {
5203 return (20 / 8) * (provider.node.getSize(link.source) + provider.node.getSize(link.target));
5204 }
5205 },
5206
5207 /**
5208 * Return the color to use on links and relation donut segments.
5209 *
5210 *
5211 * Return null or undefined
5212 * @param link
5213 * @param element
5214 * @param attribute
5215 * @return {*}
5216 */
5217 "getColor": function (link, element, attribute) {
5218 if (link.type === graph$1.link.LinkTypes.VALUE) {
5219 return "#525863";
5220 } else {
5221 var colorId = link.source.label + link.label + link.target.label;
5222
5223 var color = provider.colorScale(colorId);
5224 if (attribute === "stroke") {
5225 return provider.colorLuminance(color, -0.2);
5226 }
5227 return color;
5228 }
5229 },
5230
5231 /**
5232 *
5233 * @param link
5234 * @param element
5235 * @return {string}
5236 */
5237 "getCSSClass": function (link, element) {
5238 var cssClass = "ppt-link__" + element;
5239
5240 if (link.type === graph$1.link.LinkTypes.VALUE) {
5241 cssClass = cssClass + "--value";
5242 } else {
5243 var labelAsCSSName = "ppt-" + link.label.replace(/[^0-9a-z\-_]/gi, '');
5244 if (link.type === graph$1.link.LinkTypes.RELATION) {
5245 cssClass = cssClass + "--relation";
5246
5247 if (link.target.count === 0) {
5248 cssClass = cssClass + "--disabled";
5249 }
5250
5251 cssClass = cssClass + " " + labelAsCSSName;
5252 }
5253 }
5254
5255 return cssClass;
5256 },
5257
5258 /**
5259 * Function used to return a descriptive text representation of a link.
5260 * This representation should be more complete than getLinkTextValue and can contain semantic data.
5261 * This function is used for example to generate the label in the query viewer.
5262 *
5263 * The default behavior is to return the getLinkTextValue.
5264 *
5265 * @param link the link to represent as text.
5266 * @returns {string} the text semantic representation of the link.
5267 */
5268 "getSemanticValue": function (link) {
5269 return provider.link.getTextValue(link);
5270 }
5271};
5272
5273provider.link.Provider = provider.link.DEFAULT_PROVIDER;
5274
5275//------------------------------------------------
5276// TAXONOMY
5277
5278/**
5279 * Get the text representation of a taxonomy.
5280 *
5281 * @param label the label used for the taxonomy.
5282 * @returns {string} the text representation of the taxonomy.
5283 */
5284provider.taxonomy.getTextValue = function (label) {
5285 if (provider.taxonomy.Provider.hasOwnProperty("getTextValue")) {
5286 return provider.taxonomy.Provider.getTextValue(label);
5287 } else {
5288 if (provider.taxonomy.DEFAULT_PROVIDER.hasOwnProperty("getTextValue")) {
5289 return provider.taxonomy.DEFAULT_PROVIDER.getTextValue(label);
5290 } else {
5291 logger.error("No provider defined for taxonomy getTextValue");
5292 }
5293 }
5294};
5295
5296/**
5297 *
5298 * @param label
5299 * @param element
5300 * @return {*}
5301 */
5302provider.taxonomy.getCSSClass = function (label, element) {
5303 if (provider.taxonomy.Provider.hasOwnProperty("getCSSClass")) {
5304 return provider.taxonomy.Provider.getCSSClass(label, element);
5305 } else {
5306 if (provider.taxonomy.DEFAULT_PROVIDER.hasOwnProperty("getCSSClass")) {
5307 return provider.taxonomy.DEFAULT_PROVIDER.getCSSClass(label, element);
5308 } else {
5309 logger.error("No provider defined for taxonomy getCSSClass");
5310 }
5311 }
5312};
5313
5314/**
5315 * Label provider used by default if none have been defined for a label.
5316 * This provider can be changed if needed to customize default behavior.
5317 * If some properties are not found in user customized providers, default values will be extracted from this provider.
5318 */
5319provider.taxonomy.DEFAULT_PROVIDER = {
5320 /**
5321 * Function used to return the text representation of a taxonomy.
5322 *
5323 * The default behavior is to return the label without changes.
5324 *
5325 * @param label the label used to represent the taxonomy.
5326 * @returns {string} the text representation of the taxonomy.
5327 */
5328 "getTextValue": function (label) {
5329 return label;
5330 },
5331
5332 /**
5333 *
5334 * @param label
5335 * @return {string}
5336 */
5337 "getCSSClass": function (label, element) {
5338 var labelAsCSSName = label.replace(/[^0-9a-z\-_]/gi, '');
5339
5340 var cssClass = "ppt-taxo__" + element;
5341
5342 return cssClass + " " + labelAsCSSName;
5343 }
5344
5345};
5346
5347provider.taxonomy.Provider = provider.taxonomy.DEFAULT_PROVIDER;
5348
5349/**
5350 * Define the different type of rendering of a node for a given label.
5351 * TEXT: default rendering type, the node will be displayed with an ellipse and a text in it.
5352 * IMAGE: the node is displayed as an image using the image tag in the svg graph.
5353 * In this case an image path is required.
5354 * SVG: the node is displayed using a list of svg path, each path can contain its own color.
5355 */
5356provider.node.DisplayTypes = Object.freeze({TEXT: 0, IMAGE: 1, SVG: 2, SYMBOL: 3});
5357
5358/**
5359 * Get the label provider for the given label.
5360 * If no provider is defined for the label:
5361 * First search in parent provider.
5362 * Then if not found will create one from default provider.
5363 *
5364 * @param label to retrieve the corresponding label provider.
5365 * @returns {object} corresponding label provider.
5366 */
5367provider.node.getProvider = function (label) {
5368 if (label === undefined) {
5369 logger.error("Node label is undefined, no label provider can be found.");
5370 } else {
5371 if (provider.node.Provider.hasOwnProperty(label)) {
5372 return provider.node.Provider[label];
5373 } else {
5374 logger.debug("No direct provider found for label " + label);
5375
5376 // Search in all children list definitions to find the parent provider.
5377 for (var p in provider.node.Provider) {
5378 if (provider.node.Provider.hasOwnProperty(p)) {
5379 var nProvider = provider.node.Provider[p];
5380 if (nProvider.hasOwnProperty("children")) {
5381 if (nProvider["children"].indexOf(label) > -1) {
5382 logger.debug("No provider is defined for label (" + label + "), parent (" + p + ") will be used");
5383 // A provider containing the required label in its children definition has been found it will be cloned.
5384
5385 var newProvider = {"parent": p};
5386 for (var pr in nProvider) {
5387 if (nProvider.hasOwnProperty(pr) && pr !== "children" && pr !== "parent") {
5388 newProvider[pr] = nProvider[pr];
5389 }
5390 }
5391
5392 provider.node.Provider[label] = newProvider;
5393 return provider.node.Provider[label];
5394 }
5395 }
5396 }
5397 }
5398
5399 logger.debug("No label provider defined for label (" + label + ") default one will be created from provider.node.DEFAULT_PROVIDER");
5400
5401 provider.node.Provider[label] = {};
5402 // Clone default provider properties in new provider.
5403 for (var prop in provider.node.DEFAULT_PROVIDER) {
5404 if (provider.node.DEFAULT_PROVIDER.hasOwnProperty(prop)) {
5405 provider.node.Provider[label][prop] = provider.node.DEFAULT_PROVIDER[prop];
5406 }
5407 }
5408 return provider.node.Provider[label];
5409 }
5410 }
5411};
5412
5413/**
5414 * Get the property or function defined in node label provider.
5415 * If the property is not found search is done in parents.
5416 * If not found in parent, property defined in provider.node.DEFAULT_PROVIDER is returned.
5417 * If not found in default provider, defaultValue is set and returned.
5418 *
5419 * @param label node label to get the property in its provider.
5420 * @param name name of the property to retrieve.
5421 * @returns {*} node property defined in its label provider.
5422 */
5423provider.node.getProperty = function (label, name) {
5424 var nProvider = provider.node.getProvider(label);
5425
5426 if (!nProvider.hasOwnProperty(name)) {
5427 var providerIterator = nProvider;
5428
5429 // Check parents
5430 var isPropertyFound = false;
5431 while (providerIterator.hasOwnProperty("parent") && !isPropertyFound) {
5432 providerIterator = provider.node.getProvider(providerIterator.parent);
5433 if (providerIterator.hasOwnProperty(name)) {
5434
5435 // Set attribute in child to optimize next call.
5436 nProvider[name] = providerIterator[name];
5437 isPropertyFound = true;
5438 }
5439 }
5440
5441 if (!isPropertyFound) {
5442 logger.debug("No \"" + name + "\" property found for node label provider (" + label + "), default value will be used");
5443 if (provider.node.DEFAULT_PROVIDER.hasOwnProperty(name)) {
5444 nProvider[name] = provider.node.DEFAULT_PROVIDER[name];
5445 } else {
5446 logger.debug("No default value for \"" + name + "\" property found for label provider (" + label + ")");
5447 }
5448 }
5449 }
5450 return nProvider[name];
5451};
5452
5453/**
5454 *
5455 * @param label
5456 */
5457provider.node.getIsAutoLoadValue = function (label) {
5458 return provider.node.getProperty(label, "isAutoLoadValue");
5459};
5460
5461/**
5462 * Return the "isSearchable" property for the node label provider.
5463 * Is Searchable defines whether the label can be used as graph query builder root.
5464 * If true the label can be displayed in the taxonomy filter.
5465 *
5466 * @param label
5467 * @returns boolean
5468 */
5469provider.node.getIsSearchable = function (label) {
5470 return provider.node.getProperty(label, "isSearchable");
5471};
5472
5473/**
5474 * Return the "autoExpandRelations" property for the node label provider.
5475 * Auto expand relations defines whether the label will automatically add its relations when displayed on graph.
5476 *
5477 * @param label
5478 * @returns boolean
5479 */
5480provider.node.getIsAutoExpandRelations = function (label) {
5481 return provider.node.getProperty(label, "autoExpandRelations");
5482};
5483
5484provider.node.getSchema = function (label) {
5485 return provider.node.getProperty(label, "schema");
5486};
5487
5488/**
5489 * Return the list of attributes defined in node label provider.
5490 * Parents return attributes are also returned.
5491 *
5492 * @param label used to retrieve parent attributes.
5493 * @returns {Array} list of return attributes for a node.
5494 */
5495provider.node.getReturnAttributes = function (label) {
5496 var nProvider = provider.node.getProvider(label);
5497 var attributes = {}; // Object is used as a Set to merge possible duplicate in parents
5498
5499 if (nProvider.hasOwnProperty("returnAttributes")) {
5500 for (var i = 0; i < nProvider.returnAttributes.length; i++) {
5501 if (nProvider.returnAttributes[i] === query.NEO4J_INTERNAL_ID) {
5502 attributes[query.NEO4J_INTERNAL_ID.queryInternalName] = true;
5503 } else {
5504 attributes[nProvider.returnAttributes[i]] = true;
5505 }
5506 }
5507 }
5508
5509 // Add parent attributes
5510 while (nProvider.hasOwnProperty("parent")) {
5511 nProvider = provider.node.getProvider(nProvider.parent);
5512 if (nProvider.hasOwnProperty("returnAttributes")) {
5513 for (var j = 0; j < nProvider.returnAttributes.length; j++) {
5514 if (nProvider.returnAttributes[j] === query.NEO4J_INTERNAL_ID) {
5515 attributes[query.NEO4J_INTERNAL_ID.queryInternalName] = true;
5516 } else {
5517 attributes[nProvider.returnAttributes[j]] = true;
5518 }
5519 }
5520 }
5521 }
5522
5523 // Add default provider attributes if any but not internal id as this id is added only if none has been found.
5524 if (provider.node.DEFAULT_PROVIDER.hasOwnProperty("returnAttributes")) {
5525 for (var k = 0; k < provider.node.DEFAULT_PROVIDER.returnAttributes.length; k++) {
5526 if (provider.node.DEFAULT_PROVIDER.returnAttributes[k] !== query.NEO4J_INTERNAL_ID) {
5527 attributes[provider.node.DEFAULT_PROVIDER.returnAttributes[k]] = true;
5528 }
5529 }
5530 }
5531
5532 // Add constraint attribute in the list
5533 var constraintAttribute = provider.node.getConstraintAttribute(label);
5534 if (constraintAttribute === query.NEO4J_INTERNAL_ID) {
5535 attributes[query.NEO4J_INTERNAL_ID.queryInternalName] = true;
5536 } else {
5537 attributes[constraintAttribute] = true;
5538 }
5539
5540
5541 // Add all in array
5542 var attrList = [];
5543 for (var attr in attributes) {
5544 if (attributes.hasOwnProperty(attr)) {
5545 if (attr === query.NEO4J_INTERNAL_ID.queryInternalName) {
5546 attrList.push(query.NEO4J_INTERNAL_ID);
5547 } else {
5548 attrList.push(attr);
5549 }
5550 }
5551 }
5552
5553 // If no attributes have been found internal ID is used
5554 if (attrList.length <= 0) {
5555 attrList.push(query.NEO4J_INTERNAL_ID);
5556 }
5557 return attrList;
5558};
5559
5560/**
5561 * Return the attribute to use as constraint attribute for a node defined in its label provider.
5562 *
5563 * @param label
5564 * @returns {*}
5565 */
5566provider.node.getConstraintAttribute = function (label) {
5567 return provider.node.getProperty(label, "constraintAttribute");
5568};
5569
5570provider.node.getDisplayAttribute = function (label) {
5571 var displayAttribute = provider.node.getProperty(label, "displayAttribute");
5572
5573 if (displayAttribute === undefined) {
5574 var returnAttributes = provider.node.getReturnAttributes(label);
5575 if (returnAttributes.length > 0) {
5576 displayAttribute = returnAttributes[0];
5577 } else {
5578 displayAttribute = provider.node.getConstraintAttribute(label);
5579 }
5580 }
5581
5582 return displayAttribute
5583};
5584
5585/**
5586 * Return a list of predefined constraint defined in the node label configuration.
5587 *
5588 * @param label
5589 * @returns {*}
5590 */
5591provider.node.getPredefinedConstraints = function (label) {
5592 return provider.node.getProperty(label, "getPredefinedConstraints")();
5593};
5594
5595provider.node.filterResultQuery = function (label, initialQuery) {
5596 return provider.node.getProperty(label, "filterResultQuery")(initialQuery);
5597};
5598
5599provider.node.getValueOrderByAttribute = function (label) {
5600 return provider.node.getProperty(label, "valueOrderByAttribute");
5601};
5602
5603provider.node.isValueOrderAscending = function (label) {
5604 return provider.node.getProperty(label, "isValueOrderAscending");
5605};
5606
5607provider.node.getResultOrderByAttribute = function (label) {
5608 return provider.node.getProperty(label, "resultOrderByAttribute");
5609};
5610
5611/**
5612 *
5613 * @param label
5614 */
5615provider.node.isResultOrderAscending = function (label) {
5616 return provider.node.getProperty(label, "isResultOrderAscending");
5617};
5618
5619/**
5620 * Return the value of the getTextValue function defined in the label provider corresponding to the parameter node.
5621 * If no "getTextValue" function is defined in the provider, search is done in parents.
5622 * If none is found in parent default provider method is used.
5623 *
5624 * @param node
5625 * @param parameter
5626 */
5627provider.node.getTextValue = function (node, parameter) {
5628 return provider.node.getProperty(node.label, "getTextValue")(node, parameter);
5629};
5630
5631
5632/**
5633 * Return the value of the getSemanticValue function defined in the label provider corresponding to the parameter node.
5634 * The semantic value is a more detailed description of the node used for example in the query viewer.
5635 * If no "getTextValue" function is defined in the provider, search is done in parents.
5636 * If none is found in parent default provider method is used.
5637 *
5638 * @param node
5639 * @returns {*}
5640 */
5641provider.node.getSemanticValue = function (node) {
5642 return provider.node.getProperty(node.label, "getSemanticValue")(node);
5643};
5644
5645/**
5646 * Return a list of SVG paths objects, each defined by a "d" property containing the path and "f" property for the color.
5647 *
5648 * @param node
5649 * @returns {*}
5650 */
5651provider.node.getSVGPaths = function (node) {
5652 return provider.node.getProperty(node.label, "getSVGPaths")(node);
5653};
5654
5655/**
5656 * Check in label provider if text must be displayed with images nodes.
5657 * @param node
5658 * @returns {*}
5659 */
5660provider.node.isTextDisplayed = function (node) {
5661 return provider.node.getProperty(node.label, "getIsTextDisplayed")(node);
5662};
5663
5664/**
5665 *
5666 * @param node
5667 */
5668provider.node.getSize = function (node) {
5669 return provider.node.getProperty(node.label, "getSize")(node);
5670};
5671
5672/**
5673 * Return the getColor property.
5674 *
5675 * @param node
5676 * @param style
5677 * @returns {*}
5678 */
5679provider.node.getColor = function (node, style) {
5680 return provider.node.getProperty(node.label, "getColor")(node, style);
5681};
5682
5683/**
5684 *
5685 * @param node
5686 * @param element
5687 */
5688provider.node.getCSSClass = function (node, element) {
5689 return provider.node.getProperty(node.label, "getCSSClass")(node, element);
5690};
5691
5692/**
5693 * Return the getIsGroup property.
5694 *
5695 * @param node
5696 * @returns {*}
5697 */
5698provider.node.getIsGroup = function (node) {
5699 return provider.node.getProperty(node.label, "getIsGroup")(node);
5700};
5701
5702/**
5703 * Return the node display type.
5704 * can be TEXT, IMAGE, SVG or GROUP.
5705 *
5706 * @param node
5707 * @returns {*}
5708 */
5709provider.node.getNodeDisplayType = function (node) {
5710 return provider.node.getProperty(node.label, "getDisplayType")(node);
5711};
5712
5713/**
5714 * Return the file path of the image defined in the provider.
5715 *
5716 * @param node the node to get the image path.
5717 * @returns {string} the path of the node image.
5718 */
5719provider.node.getImagePath = function (node) {
5720 return provider.node.getProperty(node.label, "getImagePath")(node);
5721};
5722
5723/**
5724 * Return the width size of the node image.
5725 *
5726 * @param node the node to get the image width.
5727 * @returns {int} the image width.
5728 */
5729provider.node.getImageWidth = function (node) {
5730 return provider.node.getProperty(node.label, "getImageWidth")(node);
5731};
5732
5733/**
5734 * Return the height size of the node image.
5735 *
5736 * @param node the node to get the image height.
5737 * @returns {int} the image height.
5738 */
5739provider.node.getImageHeight = function (node) {
5740 return provider.node.getProperty(node.label, "getImageHeight")(node);
5741};
5742
5743provider.node.filterNodeValueQuery = function (node, initialQuery) {
5744 return provider.node.getProperty(node.label, "filterNodeValueQuery")(node, initialQuery);
5745};
5746
5747provider.node.filterNodeCountQuery = function (node, initialQuery) {
5748 return provider.node.getProperty(node.label, "filterNodeCountQuery")(node, initialQuery);
5749};
5750
5751provider.node.filterNodeRelationQuery = function (node, initialQuery) {
5752 return provider.node.getProperty(node.label, "filterNodeRelationQuery")(node, initialQuery);
5753};
5754
5755provider.node.getGenerateNodeValueConstraints = function (node) {
5756 return provider.node.getProperty(node.label, "generateNodeValueConstraints");
5757};
5758
5759provider.node.getGenerateNegativeNodeValueConstraints = function (node) {
5760 return provider.node.getProperty(node.label, "generateNegativeNodeValueConstraints");
5761};
5762
5763/**
5764 * Return the displayResults function defined in label parameter's provider.
5765 *
5766 * @param label
5767 * @returns {*}
5768 */
5769provider.node.getDisplayResults = function (label) {
5770 return provider.node.getProperty(label, "displayResults");
5771};
5772
5773/**
5774 * Label provider used by default if none have been defined for a label.
5775 * This provider can be changed if needed to customize default behavior.
5776 * If some properties are not found in user customized providers, default
5777 * values will be extracted from this provider.
5778 */
5779provider.node.DEFAULT_PROVIDER = (
5780 {
5781 /**********************************************************************
5782 * Label specific parameters:
5783 *
5784 * These attributes are specific to a node label and will be used for
5785 * every node having this label.
5786 **********************************************************************/
5787
5788 /**
5789 * Defines whether this label can be used as root element of the graph
5790 * query builder.
5791 * This property is also used to determine whether the label can be
5792 * displayed in the taxonomy filter.
5793 *
5794 * The default value is true.
5795 */
5796 "isSearchable": true,
5797
5798 /**
5799 * Defines whether this label will automatically expend its relations
5800 * when displayed on graph.
5801 * If set to true, once displayed additional request will be sent on
5802 * the database to retrieve its relations.
5803 *
5804 * The default value is false.
5805 */
5806 "autoExpandRelations": false,
5807
5808 /**
5809 * Defines whether this label will automatically load its available
5810 * data displayed on graph.
5811 * If set to true, once displayed additional request will be sent on
5812 * the database to retrieve its possible values.
5813 *
5814 * The default value is false.
5815 */
5816 "isAutoLoadValue": false,
5817
5818 /**
5819 * Defines the list of attribute to return for node of this label.
5820 * All the attributes listed here will be added in generated cypher
5821 * queries and available in result list and in node provider's
5822 * functions.
5823 *
5824 * The default value contains only the Neo4j internal id.
5825 * This id is used by default because it is a convenient way to identify
5826 * a node when nothing is known about its attributes.
5827 * But you should really consider using your application attributes
5828 * instead, it is a bad practice to rely on this attribute.
5829 */
5830 "returnAttributes": [query.NEO4J_INTERNAL_ID],
5831
5832 /**
5833 * Defines the attribute used to order the value displayed on node.
5834 *
5835 * Default value is "count" attribute.
5836 */
5837 "valueOrderByAttribute": "count",
5838
5839 /**
5840 * Defines whether the value query order by is ascending, if false order
5841 * by will be descending.
5842 *
5843 * Default value is false (descending)
5844 */
5845 "isValueOrderAscending": false,
5846
5847 /**
5848 * Defines the attributes used to order the results.
5849 * It can be an attribute name or a list of attribute names.
5850 *
5851 * Default value is "null" to disable order by.
5852 */
5853 "resultOrderByAttribute": null,
5854
5855 /**
5856 * Defines whether the result query order by is ascending, if false
5857 * order by will be descending.
5858 * It can be a boolean value or a list of boolean to match the resultOrderByAttribute.
5859 * If size of isResultOrderAscending < size of resultOrderByAttribute last value is used.
5860 *
5861 * Default value is true (ascending)
5862 */
5863 "isResultOrderAscending": true,
5864
5865 /**
5866 * Defines the attribute of the node to use in query constraint for
5867 * nodes of this label.
5868 * This attribute is used in the generated cypher query to build the
5869 * constraints with selected values.
5870 *
5871 * The default value is the Neo4j internal id.
5872 * This id is used by default because it is a convenient way to
5873 * identify a node when nothing is known about its attributes.
5874 * But you should really consider using your application attributes
5875 * instead, it is a bad practice to rely on this attribute.
5876 */
5877 "constraintAttribute": query.NEO4J_INTERNAL_ID,
5878
5879 /**
5880 * Defines the attribute of the node to use by default to display the node.
5881 * This attribute must be present in returnAttributes list.
5882 *
5883 * The default value is undefined.
5884 * If undefined the first attribute of the returnAttributes will be used or
5885 * constraintAttribute if the list is empty.
5886 */
5887 "displayAttribute": undefined,
5888
5889 /**
5890 * Return the list of predefined constraints to add for the given label.
5891 * These constraints will be added in every generated Cypher query.
5892 *
5893 * For example if the returned list contain ["$identifier.born > 1976"]
5894 * for "Person" nodes everywhere in popoto.js the generated Cypher
5895 * query will add the constraint "WHERE person.born > 1976"
5896 *
5897 * @returns {Array}
5898 */
5899 "getPredefinedConstraints": function () {
5900 return [];
5901 },
5902
5903 /**
5904 * Filters the query generated to retrieve the queries.
5905 *
5906 * @param initialQuery contains the query as an object structure.
5907 * @returns {*}
5908 */
5909 "filterResultQuery": function (initialQuery) {
5910 return initialQuery;
5911 },
5912
5913 /**********************************************************************
5914 * Node specific parameters:
5915 *
5916 * These attributes are specific to nodes (in graph or query viewer)
5917 * for a given label.
5918 * But they can be customized for nodes of the same label.
5919 * The parameters are defined by a function that will be called with
5920 * the node as parameter.
5921 * In this function the node internal attributes can be used to
5922 * customize the value to return.
5923 **********************************************************************/
5924
5925 /**
5926 * Function returning the display type of a node.
5927 * This type defines how the node will be drawn in the graph.
5928 *
5929 * The result must be one of the following values:
5930 *
5931 * provider.node.DisplayTypes.IMAGE
5932 * In this case the node will be drawn as an image and "getImagePath"
5933 * function is required to return the node image path.
5934 *
5935 * provider.node.DisplayTypes.SVG
5936 * In this case the node will be drawn as SVG paths and "getSVGPaths"
5937 *
5938 * provider.node.DisplayTypes.TEXT
5939 * In this case the node will be drawn as a simple circle.
5940 *
5941 * Default value is TEXT.
5942 *
5943 * @param node the node to extract its type.
5944 * @returns {number} one value from provider.node.DisplayTypes
5945 */
5946 "getDisplayType": function (node) {
5947 return provider.node.DisplayTypes.TEXT;
5948 },
5949
5950 /**
5951 * Function defining the size of the node in graph.
5952 *
5953 * The size define the radius of the circle defining the node.
5954 * other elements (menu, counts...) will scale on this size.
5955 *
5956 * Default value is 50.
5957 *
5958 * @param node
5959 */
5960 "getSize": function (node) {
5961 return 50;
5962 },
5963
5964 /**
5965 * Return a color for the node.
5966 *
5967 * @param node
5968 * @returns {*}
5969 */
5970 "getColor": function (node) {
5971 if (node.type === graph$1.node.NodeTypes.VALUE) {
5972 return provider.node.getColor(node.parent);
5973 } else {
5974 var parentLabel = "";
5975 if (node.hasOwnProperty("parent")) {
5976 parentLabel = node.parent.label;
5977 }
5978
5979 var incomingRelation = node.parentRel || "";
5980
5981 var colorId = parentLabel + incomingRelation + node.label;
5982 return provider.colorScale(colorId);
5983 }
5984 },
5985
5986 /**
5987 * Generate a CSS class for the node depending on its type.
5988 *
5989 * @param node
5990 * @param element
5991 * @return {string}
5992 */
5993 "getCSSClass": function (node, element) {
5994 var labelAsCSSName = node.label.replace(/[^0-9a-z\-_]/gi, '');
5995
5996 var cssClass = "ppt-node__" + element;
5997
5998 if (node.type === graph$1.node.NodeTypes.ROOT) {
5999 cssClass = cssClass + "--root";
6000 }
6001 if (node.type === graph$1.node.NodeTypes.CHOOSE) {
6002 cssClass = cssClass + "--choose";
6003 }
6004 if (node.type === graph$1.node.NodeTypes.GROUP) {
6005 cssClass = cssClass + "--group";
6006 }
6007 if (node.type === graph$1.node.NodeTypes.VALUE) {
6008 cssClass = cssClass + "--value";
6009 }
6010 if (node.value !== undefined && node.value.length > 0) {
6011 cssClass = cssClass + "--value-selected";
6012 }
6013 if (node.count === 0) {
6014 cssClass = cssClass + "--disabled";
6015 }
6016
6017 return cssClass + " " + labelAsCSSName;
6018 },
6019
6020 /**
6021 * Function defining whether the node is a group node.
6022 * In this case no count are displayed and no value can be selected on
6023 * the node.
6024 *
6025 * Default value is false.
6026 */
6027 "getIsGroup": function (node) {
6028 return false;
6029 },
6030
6031 /**
6032 * Function defining whether the node text representation must be
6033 * displayed on graph.
6034 * If true the value returned for getTextValue on node will be displayed
6035 * on graph.
6036 *
6037 * This text will be added in addition to the getDisplayType
6038 * representation.
6039 * It can be displayed on all type of node display, images, SVG or text.
6040 *
6041 * Default value is true
6042 *
6043 * @param node the node to display on graph.
6044 * @returns {boolean} true if text must be displayed on graph for the
6045 * node.
6046 */
6047 "getIsTextDisplayed": function (node) {
6048 return true;
6049 },
6050
6051 /**
6052 * Function used to return the text representation of a node.
6053 *
6054 * The default behavior is to return the label of the node
6055 * or the value of constraint attribute of the node if it contains
6056 * value.
6057 *
6058 * The returned value is truncated using
6059 * graph.node.NODE_MAX_CHARS property.
6060 *
6061 * @param node the node to represent as text.
6062 * @param maxLength used to truncate the text.
6063 * @returns {string} the text representation of the node.
6064 */
6065 "getTextValue": function (node, maxLength) {
6066 var text = "";
6067 var displayAttr = provider.node.getDisplayAttribute(node.label);
6068 if (node.type === graph$1.node.NodeTypes.VALUE) {
6069 if (displayAttr === query.NEO4J_INTERNAL_ID) {
6070 text = "" + node.internalID;
6071 } else {
6072 text = "" + node.attributes[displayAttr];
6073 }
6074 } else {
6075 if (node.value !== undefined && node.value.length > 0) {
6076 if (displayAttr === query.NEO4J_INTERNAL_ID) {
6077 var separator = "";
6078 node.value.forEach(function (value) {
6079 text += separator + value.internalID;
6080 separator = " or ";
6081 });
6082 } else {
6083 var separator = "";
6084 node.value.forEach(function (value) {
6085 text += separator + value.attributes[displayAttr];
6086 separator = " or ";
6087 });
6088 }
6089 } else {
6090 text = node.label;
6091 }
6092 }
6093
6094 return text;
6095 },
6096
6097 /**
6098 * Function used to return a descriptive text representation of a link.
6099 * This representation should be more complete than getTextValue and can
6100 * contain semantic data.
6101 * This function is used for example to generate the label in the query
6102 * viewer.
6103 *
6104 * The default behavior is to return the getTextValue not truncated.
6105 *
6106 * @param node the node to represent as text.
6107 * @returns {string} the text semantic representation of the node.
6108 */
6109 "getSemanticValue": function (node) {
6110 var text = "";
6111 var displayAttr = provider.node.getDisplayAttribute(node.label);
6112 if (node.type === graph$1.node.NodeTypes.VALUE) {
6113 if (displayAttr === query.NEO4J_INTERNAL_ID) {
6114 text = "" + node.internalID;
6115 } else {
6116 text = "" + node.attributes[displayAttr];
6117 }
6118 } else {
6119 if (node.value !== undefined && node.value.length > 0) {
6120 if (displayAttr === query.NEO4J_INTERNAL_ID) {
6121 var separator = "";
6122 node.value.forEach(function (value) {
6123 text += separator + value.internalID;
6124 separator = " or ";
6125 });
6126 } else {
6127 var separator = "";
6128 node.value.forEach(function (value) {
6129 text += separator + value.attributes[displayAttr];
6130 separator = " or ";
6131 });
6132 }
6133 } else {
6134 text = node.label;
6135 }
6136 }
6137 return text;
6138 },
6139
6140 /**
6141 * Function returning the image file path to use for a node.
6142 * This function is only used for provider.node.DisplayTypes.IMAGE
6143 * type nodes.
6144 *
6145 * @param node
6146 * @returns {string}
6147 */
6148 "getImagePath": function (node) {
6149 // if (node.type === graph.node.NodeTypes.VALUE) {
6150 // var constraintAttribute = provider.node.getConstraintAttribute(node.label);
6151 // return "image/node/value/" + node.label.toLowerCase() + "/" + node.attributes[constraintAttribute] + ".svg";
6152 // } else {
6153 return "image/node/" + node.label.toLowerCase() + "/" + node.label.toLowerCase() + ".svg";
6154 // }
6155 },
6156
6157 /**
6158 * Function returning a array of path objects to display in the node.
6159 *
6160 * @param node
6161 * @returns {*[]}
6162 */
6163 "getSVGPaths": function (node) {
6164 var size = provider.node.getSize(node);
6165 return [
6166 {
6167 "d": "M 0, 0 m -" + size + ", 0 a " + size + "," + size + " 0 1,0 " + 2 * size + ",0 a " + size + "," + size + " 0 1,0 -" + 2 * size + ",0",
6168 "fill": "transparent",
6169 "stroke": provider.node.getColor(node),
6170 "stroke-width": "2px"
6171 }
6172 ];
6173 },
6174
6175 /**
6176 * Function returning the image width of the node.
6177 * This function is only used for provider.node.DisplayTypes.IMAGE
6178 * type nodes.
6179 *
6180 * @param node
6181 * @returns {number}
6182 */
6183 "getImageWidth": function (node) {
6184 return provider.node.getSize(node) * 2;
6185 },
6186
6187 /**
6188 * Function returning the image height of the node.
6189 * This function is only used for provider.node.DisplayTypes.IMAGE
6190 * type nodes.
6191 *
6192 * @param node
6193 * @returns {number}
6194 */
6195 "getImageHeight": function (node) {
6196 return provider.node.getSize(node) * 2;
6197 },
6198
6199 /**
6200 * Filters the query generated to retrieve the values on a node.
6201 *
6202 * @param node
6203 * @param initialQuery contains the query as an object structure.
6204 * @returns {*}
6205 */
6206 "filterNodeValueQuery": function (node, initialQuery) {
6207 return initialQuery;
6208 },
6209 /**
6210 * Filters the query generated to retrieve the values on a node.
6211 *
6212 * @param node
6213 * @param initialQuery contains the query as an object structure.
6214 * @returns {*}
6215 */
6216 "filterNodeCountQuery": function (node, initialQuery) {
6217 return initialQuery;
6218 },
6219 /**
6220 * Filters the query used to retrieve the values on a node.
6221 *
6222 * @param node
6223 * @param initialQuery contains the query as an object structure.
6224 * @returns {*}
6225 */
6226 "filterNodeRelationQuery": function (node, initialQuery) {
6227 return initialQuery;
6228 },
6229
6230 /**
6231 * Customize, in query, the generated constraint for the node.
6232 *
6233 * If undefined use default constraint generation.
6234 */
6235 "generateNodeValueConstraints": undefined,
6236
6237 /**
6238 * Customize, in query, the generated negative constraint for the node.
6239 *
6240 * If undefined use default negative constraint generation.
6241 */
6242 "generateNegativeNodeValueConstraints": undefined,
6243
6244 /**********************************************************************
6245 * Results specific parameters:
6246 *
6247 * These attributes are specific to result display.
6248 **********************************************************************/
6249
6250 /**
6251 * Generate the result entry content using d3.js mechanisms.
6252 *
6253 * The parameter of the function is the &lt;p&gt; selected with d3.js
6254 *
6255 * The default behavior is to generate a &lt;table&gt; containing all
6256 * the return attributes in a &lt;th&gt; and their value in a &lt;td&gt;.
6257 *
6258 * @param pElmt the &lt;p&gt; element generated in the result list.
6259 */
6260 "displayResults": function (pElmt) {
6261 var result = pElmt.data()[0];
6262 var returnAttributes = provider.node.getReturnAttributes(result.label);
6263
6264 returnAttributes.forEach(function (attribute) {
6265 var div = pElmt.append("div").attr("class", "ppt-result-attribute-div");
6266 var attributeName = attribute;
6267
6268 if (query.NEO4J_INTERNAL_ID === attribute) {
6269 attributeName = query.NEO4J_INTERNAL_ID.queryInternalName;
6270 }
6271
6272 var span = div.append("span");
6273 span.text(function () {
6274 if (attribute === query.NEO4J_INTERNAL_ID) {
6275 return "internal ID:"
6276 } else {
6277 return attribute + ":";
6278 }
6279 });
6280 if (result.attributes[attributeName] !== undefined) {
6281 div.append("span").text(function (result) {
6282 return result.attributes[attributeName];
6283 });
6284 }
6285 });
6286 }
6287 });
6288
6289var provider$1 = provider;
6290
6291var queryviewer = {};
6292
6293queryviewer.containerId = "popoto-query";
6294queryviewer.QUERY_STARTER = "I'm looking for";
6295queryviewer.CHOOSE_LABEL = "choose";
6296
6297/**
6298 * Create the query viewer area.
6299 *
6300 */
6301queryviewer.createQueryArea = function () {
6302 var id = "#" + queryviewer.containerId;
6303
6304 queryviewer.queryConstraintSpanElements = d3__namespace.select(id).append("p").attr("class", "ppt-query-constraint-elements").selectAll(".queryConstraintSpan");
6305 queryviewer.querySpanElements = d3__namespace.select(id).append("p").attr("class", "ppt-query-elements").selectAll(".querySpan");
6306};
6307
6308/**
6309 * Update all the elements displayed on the query viewer based on current graph.
6310 */
6311queryviewer.updateQuery = function () {
6312
6313 // Remove all query span elements
6314 queryviewer.queryConstraintSpanElements = queryviewer.queryConstraintSpanElements.data([]);
6315 queryviewer.querySpanElements = queryviewer.querySpanElements.data([]);
6316
6317 queryviewer.queryConstraintSpanElements.exit().remove();
6318 queryviewer.querySpanElements.exit().remove();
6319
6320 // Update data
6321 queryviewer.queryConstraintSpanElements = queryviewer.queryConstraintSpanElements.data(queryviewer.generateConstraintData(dataModel.links, dataModel.nodes));
6322 queryviewer.querySpanElements = queryviewer.querySpanElements.data(queryviewer.generateData(dataModel.links, dataModel.nodes));
6323
6324 // Remove old span (not needed as all have been cleaned before)
6325 // queryviewer.querySpanElements.exit().remove();
6326
6327 // Add new span
6328 queryviewer.queryConstraintSpanElements = queryviewer.queryConstraintSpanElements.enter().append("span")
6329 .on("contextmenu", queryviewer.rightClickSpan)
6330 .on("click", queryviewer.clickSpan)
6331 .on("mouseover", queryviewer.mouseOverSpan)
6332 .on("mouseout", queryviewer.mouseOutSpan)
6333 .merge(queryviewer.queryConstraintSpanElements);
6334
6335 queryviewer.querySpanElements = queryviewer.querySpanElements.enter().append("span")
6336 .on("contextmenu", queryviewer.rightClickSpan)
6337 .on("click", queryviewer.clickSpan)
6338 .on("mouseover", queryviewer.mouseOverSpan)
6339 .on("mouseout", queryviewer.mouseOutSpan)
6340 .merge(queryviewer.querySpanElements);
6341
6342 // Update all span
6343 queryviewer.queryConstraintSpanElements
6344 .attr("id", function (d) {
6345 return d.id
6346 })
6347 .attr("class", function (d) {
6348 if (d.isLink) {
6349 return "ppt-span-link";
6350 } else {
6351 if (d.type === graph$1.node.NodeTypes.ROOT) {
6352 return "ppt-span-root";
6353 } else if (d.type === graph$1.node.NodeTypes.CHOOSE) {
6354 if (d.ref.value !== undefined && d.ref.value.length > 0) {
6355 return "ppt-span-value";
6356 } else {
6357 return "ppt-span-choose";
6358 }
6359 } else if (d.type === graph$1.node.NodeTypes.VALUE) {
6360 return "ppt-span-value";
6361 } else if (d.type === graph$1.node.NodeTypes.GROUP) {
6362 return "ppt-span-group";
6363 } else {
6364 return "ppt-span";
6365 }
6366 }
6367 })
6368 .text(function (d) {
6369 return d.term + " ";
6370 });
6371
6372 queryviewer.querySpanElements
6373 .attr("id", function (d) {
6374 return d.id
6375 })
6376 .attr("class", function (d) {
6377 if (d.isLink) {
6378 return "ppt-span-link";
6379 } else {
6380 if (d.type === graph$1.node.NodeTypes.ROOT) {
6381 return "ppt-span-root";
6382 } else if (d.type === graph$1.node.NodeTypes.CHOOSE) {
6383 if (d.ref.value !== undefined && d.ref.value.length > 0) {
6384 return "ppt-span-value";
6385 } else {
6386 return "ppt-span-choose";
6387 }
6388 } else if (d.type === graph$1.node.NodeTypes.VALUE) {
6389 return "ppt-span-value";
6390 } else if (d.type === graph$1.node.NodeTypes.GROUP) {
6391 return "ppt-span-group";
6392 } else {
6393 return "ppt-span";
6394 }
6395 }
6396 })
6397 .text(function (d) {
6398 return d.term + " ";
6399 });
6400};
6401
6402queryviewer.generateConstraintData = function (links, nodes) {
6403 var elmts = [], id = 0;
6404
6405 // Add query starter
6406 elmts.push(
6407 {id: id++, term: queryviewer.QUERY_STARTER}
6408 );
6409
6410 // Add the root node as query term
6411 if (nodes.length > 0) {
6412 elmts.push(
6413 {id: id++, type: nodes[0].type, term: provider$1.node.getSemanticValue(nodes[0]), ref: nodes[0]}
6414 );
6415 }
6416
6417 // Add a span for each link and its target node
6418 links.forEach(function (l) {
6419
6420 var sourceNode = l.source;
6421 var targetNode = l.target;
6422 if (l.type === graph$1.link.LinkTypes.RELATION && targetNode.type !== graph$1.node.NodeTypes.GROUP && targetNode.value !== undefined && targetNode.value.length > 0) {
6423 if (sourceNode.type === graph$1.node.NodeTypes.GROUP) {
6424 elmts.push(
6425 {
6426 id: id++,
6427 type: sourceNode.type,
6428 term: provider$1.node.getSemanticValue(sourceNode),
6429 ref: sourceNode
6430 }
6431 );
6432 }
6433
6434 elmts.push({id: id++, isLink: true, term: provider$1.link.getSemanticValue(l), ref: l});
6435
6436 if (targetNode.type !== graph$1.node.NodeTypes.GROUP) {
6437 if (targetNode.value !== undefined && targetNode.value.length > 0) {
6438 elmts.push(
6439 {
6440 id: id++,
6441 type: targetNode.type,
6442 term: provider$1.node.getSemanticValue(targetNode),
6443 ref: targetNode
6444 }
6445 );
6446 } else {
6447 elmts.push(
6448 {
6449 id: id++,
6450 type: targetNode.type,
6451 term: "<" + queryviewer.CHOOSE_LABEL + " " + provider$1.node.getSemanticValue(targetNode) + ">",
6452 ref: targetNode
6453 }
6454 );
6455 }
6456 }
6457 }
6458 });
6459
6460 return elmts;
6461};
6462
6463// TODO add option nodes in generated query when no value is available
6464queryviewer.generateData = function (links, nodes) {
6465 var elmts = [], options = [], id = 0;
6466
6467 // Add a span for each link and its target node
6468 links.forEach(function (l) {
6469
6470 var sourceNode = l.source;
6471 var targetNode = l.target;
6472
6473 if (targetNode.type === graph$1.node.NodeTypes.GROUP) {
6474 options.push(
6475 {
6476 id: id++,
6477 type: targetNode.type,
6478 term: provider$1.node.getSemanticValue(targetNode),
6479 ref: targetNode
6480 }
6481 );
6482 }
6483
6484 if (l.type === graph$1.link.LinkTypes.RELATION && targetNode.type !== graph$1.node.NodeTypes.GROUP && (targetNode.value === undefined || targetNode.value.length === 0)) {
6485 if (sourceNode.type === graph$1.node.NodeTypes.GROUP) {
6486 elmts.push(
6487 {
6488 id: id++,
6489 type: sourceNode.type,
6490 term: provider$1.node.getSemanticValue(sourceNode),
6491 ref: sourceNode
6492 }
6493 );
6494 }
6495
6496 elmts.push({id: id++, isLink: true, term: provider$1.link.getSemanticValue(l), ref: l});
6497
6498 if (targetNode.type !== graph$1.node.NodeTypes.GROUP) {
6499 elmts.push(
6500 {
6501 id: id++,
6502 type: targetNode.type,
6503 term: "<" + queryviewer.CHOOSE_LABEL + " " + provider$1.node.getSemanticValue(targetNode) + ">",
6504 ref: targetNode
6505 }
6506 );
6507 }
6508 }
6509 });
6510
6511 return elmts.concat(options);
6512};
6513
6514/**
6515 *
6516 */
6517queryviewer.mouseOverSpan = function () {
6518 d3__namespace.select(this).classed("hover", function (d) {
6519 return d.ref;
6520 });
6521
6522 var hoveredSpan = d3__namespace.select(this).data()[0];
6523
6524 if (hoveredSpan.ref) {
6525 var linkElmt = graph$1.svg.selectAll("#" + graph$1.link.gID + " > g").filter(function (d) {
6526 return d === hoveredSpan.ref;
6527 });
6528 linkElmt.select("path").classed("ppt-link-hover", true);
6529 linkElmt.select("text").classed("ppt-link-hover", true);
6530
6531 var nodeElmt = graph$1.svg.selectAll("#" + graph$1.node.gID + " > g").filter(function (d) {
6532 return d === hoveredSpan.ref;
6533 });
6534
6535 nodeElmt.select(".ppt-g-node-background").selectAll("circle").transition().style("fill-opacity", 0.5);
6536
6537 if (cypherviewer.isActive) {
6538 cypherviewer.querySpanElements.filter(function (d) {
6539 return d.node === hoveredSpan.ref || d.link === hoveredSpan.ref;
6540 }).classed("hover", true);
6541 }
6542 }
6543};
6544
6545queryviewer.rightClickSpan = function () {
6546 var clickedSpan = d3__namespace.select(this).data()[0];
6547
6548 if (!clickedSpan.isLink && clickedSpan.ref) {
6549 var nodeElmt = graph$1.svg.selectAll("#" + graph$1.node.gID + " > g").filter(function (d) {
6550 return d === clickedSpan.ref;
6551 });
6552
6553 nodeElmt.on("contextmenu").call(nodeElmt.node(), clickedSpan.ref);
6554 }
6555};
6556
6557queryviewer.clickSpan = function () {
6558 var clickedSpan = d3__namespace.select(this).data()[0];
6559
6560 if (!clickedSpan.isLink && clickedSpan.ref) {
6561 var nodeElmt = graph$1.svg.selectAll("#" + graph$1.node.gID + " > g").filter(function (d) {
6562 return d === clickedSpan.ref;
6563 });
6564
6565 nodeElmt.on("click").call(nodeElmt.node(), clickedSpan.ref);
6566 }
6567};
6568
6569/**
6570 *
6571 */
6572queryviewer.mouseOutSpan = function () {
6573 d3__namespace.select(this).classed("hover", false);
6574
6575 var hoveredSpan = d3__namespace.select(this).data()[0];
6576
6577 if (hoveredSpan.ref) {
6578 var linkElmt = graph$1.svg.selectAll("#" + graph$1.link.gID + " > g").filter(function (d) {
6579 return d === hoveredSpan.ref;
6580 });
6581 linkElmt.select("path").classed("ppt-link-hover", false);
6582 linkElmt.select("text").classed("ppt-link-hover", false);
6583
6584 var nodeElmt = graph$1.svg.selectAll("#" + graph$1.node.gID + " > g").filter(function (d) {
6585 return d === hoveredSpan.ref;
6586 });
6587 nodeElmt.select(".ppt-g-node-background").selectAll("circle").transition().style("fill-opacity", 0);
6588
6589 if (cypherviewer.isActive) {
6590 cypherviewer.querySpanElements.filter(function (d) {
6591 return d.node === hoveredSpan.ref || d.link === hoveredSpan.ref;
6592 }).classed("hover", false);
6593 }
6594 }
6595};
6596
6597var cypherviewer = {};
6598cypherviewer.containerId = "popoto-cypher";
6599cypherviewer.MATCH = "MATCH";
6600cypherviewer.RETURN = "RETURN";
6601cypherviewer.WHERE = "WHERE";
6602cypherviewer.QueryElementTypes = Object.freeze({
6603 KEYWORD: 0,
6604 NODE: 1,
6605 SEPARATOR: 2,
6606 SOURCE: 3,
6607 LINK: 4,
6608 TARGET: 5,
6609 RETURN: 6,
6610 WHERE: 7
6611});
6612
6613/**
6614 * Create the Cypher viewer area.
6615 *
6616 */
6617cypherviewer.createQueryArea = function () {
6618 var id = "#" + cypherviewer.containerId;
6619
6620 cypherviewer.querySpanElements = d3__namespace.select(id).append("p").attr("class", "ppt-query-constraint-elements").selectAll(".queryConstraintSpan");
6621};
6622
6623/**
6624 * Update all the elements displayed on the cypher viewer based on current graph.
6625 */
6626cypherviewer.updateQuery = function () {
6627
6628 // Remove all query span elements
6629 cypherviewer.querySpanElements = cypherviewer.querySpanElements.data([]);
6630
6631 cypherviewer.querySpanElements.exit().remove();
6632
6633 // Update data
6634 cypherviewer.querySpanElements = cypherviewer.querySpanElements.data(cypherviewer.generateData(dataModel.links, dataModel.nodes));
6635
6636 // Remove old span (not needed as all have been cleaned before)
6637 // queryviewer.querySpanElements.exit().remove();
6638
6639 // Add new span
6640 cypherviewer.querySpanElements = cypherviewer.querySpanElements.enter().append("span")
6641 .attr("id", function (d) {
6642 return "cypher-" + d.id;
6643 })
6644 .on("mouseover", cypherviewer.mouseOverSpan)
6645 .on("mouseout", cypherviewer.mouseOutSpan)
6646 .on("contextmenu", cypherviewer.rightClickSpan)
6647 .on("click", cypherviewer.clickSpan)
6648 .merge(cypherviewer.querySpanElements);
6649
6650 // Update all spans:
6651 cypherviewer.querySpanElements.filter(function (d) {
6652 return d.type === cypherviewer.QueryElementTypes.KEYWORD;
6653 })
6654 .attr("class", "ppt-span")
6655 .text(function (d) {
6656 return " " + d.value + " ";
6657 });
6658
6659 cypherviewer.querySpanElements.filter(function (d) {
6660 return d.type === cypherviewer.QueryElementTypes.SEPARATOR;
6661 })
6662 .attr("class", "ppt-span")
6663 .text(function (d) {
6664 return d.value + " ";
6665 });
6666
6667 cypherviewer.querySpanElements.filter(function (d) {
6668 return d.type === cypherviewer.QueryElementTypes.NODE;
6669 })
6670 .attr("class", function (d) {
6671 if (d.node.value !== undefined && d.node.value.length > 0) {
6672 return "ppt-span-root-value";
6673 } else {
6674 return "ppt-span-root";
6675 }
6676 })
6677 .text(function (d) {
6678 return "(" + d.node.internalLabel + ":`" + d.node.label + "`)";
6679 });
6680
6681 cypherviewer.querySpanElements.filter(function (d) {
6682 return d.type === cypherviewer.QueryElementTypes.SOURCE;
6683 })
6684 .attr("class", function (d) {
6685 if (d.node === dataModel.getRootNode()) {
6686 if (d.node.value !== undefined && d.node.value.length > 0) {
6687 return "ppt-span-root-value";
6688 } else {
6689 return "ppt-span-root";
6690 }
6691 } else {
6692 if (d.node.value !== undefined && d.node.value.length > 0) {
6693 return "ppt-span-value";
6694 } else {
6695 return "ppt-span-choose";
6696 }
6697 }
6698 })
6699 .text(function (d) {
6700 var sourceNode = d.node;
6701 return "(" + sourceNode.internalLabel + ":`" + sourceNode.label + "`)";
6702 });
6703
6704 cypherviewer.querySpanElements.filter(function (d) {
6705 return d.type === cypherviewer.QueryElementTypes.LINK;
6706 })
6707 .attr("class", "ppt-span-link")
6708 .text(function (d) {
6709 if (d.link.target.isParentRelReverse === true) {
6710 return "<-[:`" + d.link.label + "`]-";
6711 } else {
6712 return "-[:`" + d.link.label + "`]-" + (query.USE_RELATION_DIRECTION ? ">" : "");
6713 }
6714 });
6715
6716 cypherviewer.querySpanElements.filter(function (d) {
6717 return d.type === cypherviewer.QueryElementTypes.TARGET;
6718 })
6719 .attr("class", function (d) {
6720 if (d.node.value !== undefined && d.node.value.length > 0) {
6721 return "ppt-span-value";
6722 } else {
6723 return "ppt-span-choose";
6724 }
6725 })
6726 .text(function (d) {
6727 return "(" + d.node.internalLabel + ":`" + d.node.label + "`)";
6728 });
6729
6730 cypherviewer.querySpanElements.filter(function (d) {
6731 return d.type === cypherviewer.QueryElementTypes.WHERE;
6732 })
6733 .attr("class", function (d) {
6734 if (d.node === dataModel.getRootNode()) {
6735 return "ppt-span-root-value";
6736 } else {
6737 return "ppt-span-value";
6738 }
6739 })
6740 .text(function (d) {
6741 var node = d.node;
6742 if (node.isNegative === true) {
6743 if (!node.hasOwnProperty("value") || node.value.length <= 0) {
6744 return "(NOT (" + d.link.source.internalLabel + ":`" + d.link.source.label + "`)-[:`" + d.link.label + "`]->(:`" + d.link.target.label + "`))";
6745 } else {
6746 var clauses = [];
6747 var constAttr = provider$1.node.getConstraintAttribute(node.label);
6748 node.value.forEach(function (value) {
6749 clauses.push(
6750 "(NOT (" + d.link.source.internalLabel + ":`" + d.link.source.label + "`)-[:`" + d.link.label + "`]->(:`" + d.link.target.label + "`{" + constAttr + ":" + value.attributes[constAttr] + "}))"
6751 );
6752 });
6753
6754 return clauses.join(" AND ");
6755 }
6756 } else {
6757 var constraintAttr = provider$1.node.getConstraintAttribute(node.label);
6758
6759 var text = "";
6760 var separator = "";
6761
6762 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
6763 text += node.internalLabel + ".id";
6764 } else {
6765 text += node.internalLabel + "." + constraintAttr;
6766 }
6767
6768 if (node.hasOwnProperty("value") && node.value.length > 1) {
6769 text += " IN [";
6770 } else {
6771 text += " = ";
6772 }
6773
6774 node.value.forEach(function (value) {
6775 text += separator;
6776 separator = ", ";
6777 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
6778 text += value.internalID;
6779 } else {
6780 var constraintValue = value.attributes[constraintAttr];
6781 if (typeof constraintValue === "boolean" || typeof constraintValue === "number") {
6782 text += constraintValue;
6783 } else {
6784 text += "\"" + constraintValue + "\"";
6785 }
6786 }
6787 });
6788
6789 if (node.value.length > 1) {
6790 text += "]";
6791 }
6792
6793 return "(" + text + ")";
6794 }
6795 });
6796
6797 cypherviewer.querySpanElements.filter(function (d) {
6798 return d.type === cypherviewer.QueryElementTypes.RETURN;
6799 })
6800 .attr("class", function (d) {
6801 if (d.node.value !== undefined && d.node.value.length > 0) {
6802 return "ppt-span-root-value";
6803 } else {
6804 return "ppt-span-root";
6805 }
6806 })
6807 .text(function (d) {
6808 return d.node.internalLabel;
6809 });
6810
6811};
6812
6813/**
6814 * Generate the data from graph to use in cypher query viewer.
6815 * The returned data is a list of elements representing the current query.
6816 * Example:
6817 *
6818 * MATCH
6819 * (person:`Person`),
6820 * (person:`Person`)-[:`FOLLOWS`]->(person1:`Person`{`name`:\"Jessica Thompson\"}),
6821 * (person1:`Person`)-[:`REVIEWED`]->(movie5:`Movie`{`title`:\"The Replacements\"})
6822 * RETURN person
6823 *
6824 * @param links
6825 * @returns {Array}
6826 */
6827cypherviewer.generateData = function (links) {
6828 var elmts = [], id = 0;
6829 var rootNode = dataModel.getRootNode();
6830 var relevantLinks = query.getRelevantLinks(rootNode, rootNode, links);
6831 var negativeLinks = relevantLinks.filter(function (rl) {
6832 return rl.target.isNegative === true && (!rl.target.hasOwnProperty("value") || rl.target.value.length <= 0)
6833 });
6834
6835 elmts.push(
6836 {
6837 id: id++,
6838 type: cypherviewer.QueryElementTypes.KEYWORD,
6839 value: cypherviewer.MATCH
6840 }
6841 );
6842
6843 if (rootNode) {
6844 elmts.push(
6845 {
6846 id: id++,
6847 type: cypherviewer.QueryElementTypes.NODE,
6848 node: rootNode
6849 }
6850 );
6851 }
6852
6853 if (relevantLinks.length > 0 && relevantLinks.length > negativeLinks.length) {
6854 elmts.push(
6855 {
6856 id: id++,
6857 type: cypherviewer.QueryElementTypes.SEPARATOR,
6858 value: ","
6859 }
6860 );
6861 }
6862
6863 for (var i = 0; i < relevantLinks.length; i++) {
6864 var relevantLink = relevantLinks[i];
6865
6866 if (relevantLink.target.isNegative === true && (!relevantLink.target.hasOwnProperty("value") || relevantLink.target.value.length <= 0)) ; else {
6867 elmts.push(
6868 {
6869 id: id++,
6870 type: cypherviewer.QueryElementTypes.SOURCE,
6871 node: relevantLink.source
6872 }
6873 );
6874
6875 elmts.push(
6876 {
6877 id: id++,
6878 type: cypherviewer.QueryElementTypes.LINK,
6879 link: relevantLink
6880 }
6881 );
6882
6883 elmts.push(
6884 {
6885 id: id++,
6886 type: cypherviewer.QueryElementTypes.TARGET,
6887 node: relevantLink.target
6888 }
6889 );
6890
6891 // Add separator except for last element
6892 if (i < (relevantLinks.length - 1)) {
6893 elmts.push(
6894 {
6895 id: id++,
6896 type: cypherviewer.QueryElementTypes.SEPARATOR,
6897 value: ","
6898 }
6899 );
6900 }
6901 }
6902 }
6903
6904 if ((rootNode && rootNode.value !== undefined && rootNode.value.length > 0) || (relevantLinks.length > 0)) {
6905 elmts.push(
6906 {
6907 id: id++,
6908 type: cypherviewer.QueryElementTypes.KEYWORD,
6909 value: cypherviewer.WHERE
6910 }
6911 );
6912 }
6913
6914 if (rootNode && rootNode.value !== undefined && rootNode.value.length > 0) {
6915 elmts.push(
6916 {
6917 id: id++,
6918 type: cypherviewer.QueryElementTypes.WHERE,
6919 node: rootNode
6920 }
6921 );
6922
6923 if (relevantLinks.length > 0) {
6924 elmts.push(
6925 {
6926 id: id++,
6927 type: cypherviewer.QueryElementTypes.SEPARATOR,
6928 value: " AND "
6929 }
6930 );
6931 }
6932 }
6933
6934 var needSeparator = false;
6935 for (var i = 0; i < relevantLinks.length; i++) {
6936 var relevantLink = relevantLinks[i];
6937 if (relevantLink.target.isNegative === true) {
6938 if (needSeparator) {
6939 elmts.push(
6940 {
6941 id: id++,
6942 type: cypherviewer.QueryElementTypes.SEPARATOR,
6943 value: " AND "
6944 }
6945 );
6946 }
6947 elmts.push(
6948 {
6949 id: id++,
6950 type: cypherviewer.QueryElementTypes.WHERE,
6951 node: relevantLink.target,
6952 link: relevantLink
6953 }
6954 );
6955 needSeparator = true;
6956 } else {
6957 if (relevantLink.target.value !== undefined && relevantLink.target.value.length > 0) {
6958
6959 if (needSeparator) {
6960 elmts.push(
6961 {
6962 id: id++,
6963 type: cypherviewer.QueryElementTypes.SEPARATOR,
6964 value: " AND "
6965 }
6966 );
6967 }
6968
6969 elmts.push(
6970 {
6971 id: id++,
6972 type: cypherviewer.QueryElementTypes.WHERE,
6973 node: relevantLink.target
6974 }
6975 );
6976
6977 needSeparator = true;
6978 }
6979 }
6980 }
6981
6982 elmts.push(
6983 {
6984 id: id++,
6985 type: cypherviewer.QueryElementTypes.KEYWORD,
6986 value: cypherviewer.RETURN
6987 }
6988 );
6989
6990 if (rootNode) {
6991 elmts.push(
6992 {
6993 id: id++,
6994 type: cypherviewer.QueryElementTypes.RETURN,
6995 node: rootNode
6996 }
6997 );
6998 }
6999
7000 return elmts;
7001};
7002
7003/**
7004 *
7005 */
7006cypherviewer.mouseOverSpan = function () {
7007 var hoveredSpan = d3__namespace.select(this).data()[0];
7008 if (hoveredSpan.node) {
7009 // Hover all spans with same node data
7010 cypherviewer.querySpanElements.filter(function (d) {
7011 return d.node === hoveredSpan.node;
7012 }).classed("hover", true);
7013
7014 // Highlight node in graph
7015 var nodeElmt = graph$1.svg.selectAll("#" + graph$1.node.gID + " > g").filter(function (d) {
7016 return d === hoveredSpan.node;
7017 });
7018 nodeElmt.select(".ppt-g-node-background").selectAll("circle").transition().style("fill-opacity", 0.5);
7019
7020 // Highlight query viewer
7021 if (queryviewer.isActive) {
7022 queryviewer.queryConstraintSpanElements.filter(function (d) {
7023 return d.ref === hoveredSpan.node;
7024 }).classed("hover", true);
7025
7026 queryviewer.querySpanElements.filter(function (d) {
7027 return d.ref === hoveredSpan.node;
7028 }).classed("hover", true);
7029 }
7030 } else if (hoveredSpan.link) {
7031 d3__namespace.select(this).classed("hover", true);
7032
7033 // Highlight link in graph
7034 var linkElmt = graph$1.svg.selectAll("#" + graph$1.link.gID + " > g").filter(function (d) {
7035 return d === hoveredSpan.link;
7036 });
7037 linkElmt.select("path").classed("ppt-link-hover", true);
7038 linkElmt.select("text").classed("ppt-link-hover", true);
7039 }
7040};
7041
7042cypherviewer.mouseOutSpan = function () {
7043 var hoveredSpan = d3__namespace.select(this).data()[0];
7044 if (hoveredSpan.node) {
7045 // Remove hover on all spans with same node data
7046 cypherviewer.querySpanElements.filter(function (d) {
7047 return d.node === hoveredSpan.node;
7048 }).classed("hover", false);
7049
7050 // Remove highlight on node in graph
7051 var nodeElmt = graph$1.svg.selectAll("#" + graph$1.node.gID + " > g").filter(function (d) {
7052 return d === hoveredSpan.node;
7053 });
7054 nodeElmt.select(".ppt-g-node-background").selectAll("circle").transition().style("fill-opacity", 0);
7055
7056 if (queryviewer.isActive) {
7057 queryviewer.queryConstraintSpanElements.filter(function (d) {
7058 return d.ref === hoveredSpan.node;
7059 }).classed("hover", false);
7060
7061 queryviewer.querySpanElements.filter(function (d) {
7062 return d.ref === hoveredSpan.node;
7063 }).classed("hover", false);
7064 }
7065 } else if (hoveredSpan.link) {
7066 d3__namespace.select(this).classed("hover", false);
7067
7068 // Remove highlight on link in graph
7069 var linkElmt = graph$1.svg.selectAll("#" + graph$1.link.gID + " > g").filter(function (d) {
7070 return d === hoveredSpan.link;
7071 });
7072 linkElmt.select("path").classed("ppt-link-hover", false);
7073 linkElmt.select("text").classed("ppt-link-hover", false);
7074 }
7075};
7076
7077cypherviewer.clickSpan = function () {
7078 var clickedSpan = d3__namespace.select(this).data()[0];
7079
7080 if (clickedSpan.node) {
7081 var nodeElmt = graph$1.svg.selectAll("#" + graph$1.node.gID + " > g").filter(function (d) {
7082 return d === clickedSpan.node;
7083 });
7084
7085 nodeElmt.on("click").call(nodeElmt.node(), clickedSpan.node);
7086 }
7087};
7088
7089cypherviewer.rightClickSpan = function () {
7090 var clickedSpan = d3__namespace.select(this).data()[0];
7091
7092 if (clickedSpan.node) {
7093 var nodeElmt = graph$1.svg.selectAll("#" + graph$1.node.gID + " > g").filter(function (d) {
7094 return d === clickedSpan.node;
7095 });
7096
7097 nodeElmt.on("contextmenu").call(nodeElmt.node(), clickedSpan.node);
7098 }
7099};
7100
7101exports.appendFittedText = appendFittedText;
7102exports.cypherviewer = cypherviewer;
7103exports.dataModel = dataModel;
7104exports.graph = graph$1;
7105exports.logger = logger;
7106exports.provider = provider$1;
7107exports.query = query;
7108exports.queryviewer = queryviewer;
7109exports.result = result;
7110exports.runner = runner;
7111exports.start = start;
7112exports.taxonomy = taxonomy;
7113exports.tools = tools;
7114exports.update = update;
7115exports.updateGraph = updateGraph;
7116exports.version = version;