view popoto_dev/src/js/popoto.js @ 12:d67c5ad47709

implementation with dropdown popup, unfinished
author alistair
date Fri, 02 Oct 2015 01:08:46 -0400
parents
children
line wrap: on
line source

/**
 * Popoto.js is a JavaScript library built with D3.js providing a graph based search interface generated in HTML and SVG usable on any modern browser.
 * This library generates an interactive graph query builder into any website or web based application to create dynamic queries on Neo4j databases and display the results.
 *
 * Copyright (C) 2014-2015 Frederic Ciminera
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * contact@popotojs.com
 */
popoto = function () {
    var popoto = {
        version: "0.0-a6"
    };

    /**
     * Main function to call to use Popoto.js.
     * This function will create all the HTML content based on available IDs in the page.
     * popoto.graph.containerId for the graph query builder.
     * popoto.queryviewer.containerId for the query viewer.
     *
     * @param label Root label to use in the graph query builder.
     */
    popoto.start = function (label) {
        popoto.logger.info("Popoto " + popoto.version + " start.");

        if (typeof popoto.rest.CYPHER_URL == 'undefined') {
            popoto.logger.error("popoto.rest.CYPHER_URL is not set but this property is required.");
        } else {
            // TODO introduce component generator mechanism instead for future plugin extensions
            popoto.checkHtmlComponents();

            if (popoto.taxonomy.isActive) {
                popoto.taxonomy.createTaxonomyPanel();
            }

            if (popoto.graph.isActive) {
                popoto.graph.createGraphArea();
                popoto.graph.createForceLayout();
                popoto.graph.addRootNode(label);
            }

            if (popoto.queryviewer.isActive) {
                popoto.queryviewer.createQueryArea();
            }

            popoto.update();
        }
    };

    /**
     * Check in the HTML page the components to generate.
     */
    popoto.checkHtmlComponents = function () {
        var graphHTMLContainer = d3.select("#" + popoto.graph.containerId);
        var taxonomyHTMLContainer = d3.select("#" + popoto.taxonomy.containerId);
        var queryHTMLContainer = d3.select("#" + popoto.queryviewer.containerId);
        var cypherHTMLContainer = d3.select("#" + popoto.cypherviewer.containerId);
        var resultsHTMLContainer = d3.select("#" + popoto.result.containerId);

        if (graphHTMLContainer.empty()) {
            popoto.logger.debug("The page doesn't contain a container with ID = \"" + popoto.graph.containerId + "\" no graph area will be generated. This ID is defined in popoto.graph.containerId property.");
            popoto.graph.isActive = false;
        } else {
            popoto.graph.isActive = true;
        }

        if (taxonomyHTMLContainer.empty()) {
            popoto.logger.debug("The page doesn't contain a container with ID = \"" + popoto.taxonomy.containerId + "\" no taxonomy filter will be generated. This ID is defined in popoto.taxonomy.containerId property.");
            popoto.taxonomy.isActive = false;
        } else {
            popoto.taxonomy.isActive = true;
        }

        if (queryHTMLContainer.empty()) {
            popoto.logger.debug("The page doesn't contain a container with ID = \"" + popoto.queryviewer.containerId + "\" no query viewer will be generated. This ID is defined in popoto.queryviewer.containerId property.");
            popoto.queryviewer.isActive = false;
        } else {
            popoto.queryviewer.isActive = true;
        }

        if (cypherHTMLContainer.empty()) {
            popoto.logger.debug("The page doesn't contain a container with ID = \"" + popoto.cypherviewer.containerId + "\" no cypher query viewer will be generated. This ID is defined in popoto.cypherviewer.containerId property.");
            popoto.cypherviewer.isActive = false;
        } else {
            popoto.cypherviewer.isActive = true;
        }

        if (resultsHTMLContainer.empty()) {
            popoto.logger.debug("The page doesn't contain a container with ID = \"" + popoto.result.containerId + "\" no result area will be generated. This ID is defined in popoto.result.containerId property.");
            popoto.result.isActive = false;
        } else {
            popoto.result.isActive = true;
        }
    };

    /**
     * Function to call to update all the generated elements including svg graph, query viewer and generated results.
     */
    popoto.update = function () {
        popoto.updateGraph();

        if (popoto.queryviewer.isActive) {
            popoto.queryviewer.updateQuery();
        }
        // Results are updated only if needed.
        // If id found in html page or if result listeners have been added.
        // In this case the query must be executed.
        if (popoto.result.isActive || popoto.result.resultListeners.length > 0 || popoto.result.resultCountListeners.length > 0) {
            popoto.result.updateResults();
        }
    };

    /**
     * Function to call to update the graph only.
     */
    popoto.updateGraph = function () {
        if (popoto.graph.isActive) {
            // Starts the D3.js force simulation.
            // This method must be called when the layout is first created, after assigning the nodes and links.
            // In addition, it should be called again whenever the nodes or links change.
            popoto.graph.force.start();
            popoto.graph.link.updateLinks();
            popoto.graph.node.updateNodes();
        }
    };

    // REST ------------------------------------------------------------------------------------------------------------
    popoto.rest = {};

    /**
     * Default REST URL used to call Neo4j server with cypher queries to execute.
     * This property should be updated to access to your own server.
     * @type {string}
     */
    popoto.rest.CYPHER_URL = "http://localhost:7474/db/data/transaction/commit";

    /**
     * Create JQuery ajax POST request to access Neo4j REST API.
     *
     * @param data data object containing Cypher query
     * @returns {*} the JQuery ajax request object.
     */
    popoto.rest.post = function (data) {
        var strData = JSON.stringify(data);
        popoto.logger.info("REST POST:" + strData);

        return $.ajax({
            type: "POST",
            beforeSend: function (request) {
                if (popoto.rest.AUTHORIZATION) {
                    request.setRequestHeader("Authorization", popoto.rest.AUTHORIZATION);
                }
            },
            url: popoto.rest.CYPHER_URL,
            contentType: "application/json",
            data: strData
        });
    };

    // LOGGER -----------------------------------------------------------------------------------------------------------
    popoto.logger = {};
    popoto.logger.LogLevels = Object.freeze({DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, NONE: 4});
    popoto.logger.LEVEL = popoto.logger.LogLevels.NONE;
    popoto.logger.TRACE = false;

    /**
     * Log a message on console depending on configured log levels.
     * Level is define in popoto.logger.LEVEL property.
     * If popoto.logger.TRACE is set to true, the stack trace is also added in log.
     * @param logLevel Level of the message from popoto.logger.LogLevels.
     * @param message Message to log.
     */
    popoto.logger.log = function (logLevel, message) {
        if (console && logLevel >= popoto.logger.LEVEL) {
            if (popoto.logger.TRACE) {
                message = message + "\n" + new Error().stack
            }
            switch (logLevel) {
                case popoto.logger.LogLevels.DEBUG:
                    console.log(message);
                    break;
                case popoto.logger.LogLevels.INFO:
                    console.log(message);
                    break;
                case popoto.logger.LogLevels.WARN:
                    console.warn(message);
                    break;
                case popoto.logger.LogLevels.ERROR:
                    console.error(message);
                    break;
            }
        }
    };

    /**
     * Log a message in DEBUG level.
     * @param message to log.
     */
    popoto.logger.debug = function (message) {
        popoto.logger.log(popoto.logger.LogLevels.DEBUG, message);
    };

    /**
     * Log a message in INFO level.
     * @param message to log.
     */
    popoto.logger.info = function (message) {
        popoto.logger.log(popoto.logger.LogLevels.INFO, message);
    };

    /**
     * Log a message in WARN level.
     * @param message to log.
     */
    popoto.logger.warn = function (message) {
        popoto.logger.log(popoto.logger.LogLevels.WARN, message);
    };

    /**
     * Log a message in ERROR level.
     * @param message to log.
     */
    popoto.logger.error = function (message) {
        popoto.logger.log(popoto.logger.LogLevels.ERROR, message);
    };

    // TAXONOMIES  -----------------------------------------------------------------------------------------------------

    popoto.taxonomy = {};
    popoto.taxonomy.containerId = "popoto-taxonomy";

    /**
     * Create the taxonomy panel HTML elements.
     */
    popoto.taxonomy.createTaxonomyPanel = function () {
        var htmlContainer = d3.select("#" + popoto.taxonomy.containerId);

        var taxoUL = htmlContainer.append("ul");

        var data = popoto.taxonomy.generateTaxonomiesData();

        var taxos = taxoUL.selectAll(".taxo").data(data);

        var taxoli = taxos.enter().append("li")
            .attr("id", function (d) {
                return d.id
            })
            .attr("value", function (d) {
                return d.label;
            });

        taxoli.append("img")
            .attr("src", "css/image/category.png")
            .attr("width", "24")
            .attr("height", "24");

        taxoli.append("span")
            .attr("class", "ppt-label")
            .text(function (d) {
                return popoto.provider.getTaxonomyTextValue(d.label);
            });

        taxoli.append("span")
            .attr("class", "ppt-count");

        // Add an on click event on the taxonomy to clear the graph and set this label as root
        taxoli.on("click", popoto.taxonomy.onClick);

        popoto.taxonomy.addTaxonomyChildren(taxoli);

        // The count is updated for each labels.
        var flattenData = [];
        data.forEach(function (d) {
            flattenData.push(d);
            if (d.children) {
                popoto.taxonomy.flattenChildren(d, flattenData);
            }
        });

        popoto.taxonomy.updateCount(flattenData);
    };

    /**
     * Recursive function to flatten data content.
     *
     */
    popoto.taxonomy.flattenChildren = function (d, vals) {
        d.children.forEach(function (c) {
            vals.push(c);
            if (c.children) {
                vals.concat(popoto.taxonomy.flattenChildren(c, vals));
            }
        });
    };

    /**
     * Updates the count number on a taxonomy.
     *
     * @param taxonomyData
     */
    popoto.taxonomy.updateCount = function (taxonomyData) {
        var statements = [];

        taxonomyData.forEach(function (taxo) {
            statements.push(
                {
                    "statement": popoto.query.generateTaxonomyCountQuery(taxo.label)
                }
            );
        });

        (function (taxonomies) {
            popoto.logger.info("Count taxonomies ==> ");
            popoto.rest.post(
                {
                    "statements": statements
                })
                .done(function (returnedData) {
                    for (var i = 0; i < taxonomies.length; i++) {
                        var count = returnedData.results[i].data[0].row[0];
                        d3.select("#" + taxonomies[i].id)
                            .select(".ppt-count")
                            .text(" (" + count + ")");
                    }
                })
                .fail(function (xhr, textStatus, errorThrown) {
                    popoto.logger.error(textStatus + ": error while accessing Neo4j server on URL:\"" + popoto.rest.CYPHER_URL + "\" defined in \"popoto.rest.CYPHER_URL\" property: " + errorThrown);
                    d3.select("#popoto-taxonomy")
                        .selectAll(".ppt-count")
                        .text(" (0)");
                });
        })(taxonomyData);
    };

    /**
     * Recursively generate the taxonomy children elements.
     *
     * @param selection
     */
    popoto.taxonomy.addTaxonomyChildren = function (selection) {
        selection.each(function (d) {
            var li = d3.select(this);

            var children = d.children;
            if (d.children) {
                var childLi = li.append("ul")
                    .selectAll("li")
                    .data(children)
                    .enter()
                    .append("li")
                    .attr("id", function (d) {
                        return d.id
                    })
                    .attr("value", function (d) {
                        return d.label;
                    });

                childLi.append("img")
                    .attr("src", "css/image/category.png")
                    .attr("width", "24")
                    .attr("height", "24");

                childLi.append("span")
                    .attr("class", "ppt-label")
                    .text(function (d) {
                        return popoto.provider.getTaxonomyTextValue(d.label);
                    });

                childLi.append("span")
                    .attr("class", "ppt-count");

                childLi.on("click", popoto.taxonomy.onClick);

                popoto.taxonomy.addTaxonomyChildren(childLi);
            }

        });
    };

    popoto.taxonomy.onClick = function () {
        d3.event.stopPropagation();

        // Workaround to avoid click on taxonomies if root node has not yet been initialized
        // If it contains a count it mean all the initialization has been done
        var root = popoto.graph.getRootNode();
        if (root.count === undefined) {
            return;
        }

        var label = this.attributes.value.value;

        while (popoto.graph.force.nodes().length > 0) {
            popoto.graph.force.nodes().pop();
        }

        while (popoto.graph.force.links().length > 0) {
            popoto.graph.force.links().pop();
        }

        // Reinitialize internal label generator
        popoto.graph.node.internalLabels = {};

        popoto.update();
        popoto.graph.addRootNode(label);
        popoto.graph.hasGraphChanged = true;
        popoto.result.hasChanged = true;
        popoto.update();
        popoto.tools.center();
    };

    /**
     * Parse the list of label providers and return a list of data object containing only searchable labels.
     * @returns {Array}
     */
    popoto.taxonomy.generateTaxonomiesData = function () {
        var id = 0;
        var data = [];

        // Retrieve root providers (searchable and without parent)
        for (var label in popoto.provider.nodeProviders) {
            if (popoto.provider.nodeProviders.hasOwnProperty(label)) {
                if (popoto.provider.getProperty(label, "isSearchable") && !popoto.provider.nodeProviders[label].parent) {
                    data.push({
                        "label": label,
                        "id": "popoto-lbl-" + id++
                    });
                }
            }
        }

        // Add children data for each provider with children.
        data.forEach(function (d) {
            if (popoto.provider.getProvider(d.label).hasOwnProperty("children")) {
                id = popoto.taxonomy.addChildrenData(d, id);
            }
        });

        return data;
    };

    /**
     * Add children providers data.
     * @param parentData
     * @param id
     */
    popoto.taxonomy.addChildrenData = function (parentData, id) {
        parentData.children = [];

        popoto.provider.getProvider(parentData.label).children.forEach(function (d) {
            var childProvider = popoto.provider.getProvider(d);
            var childData = {
                "label": d,
                "id": "popoto-lbl-" + id++
            };
            if (childProvider.hasOwnProperty("children")) {
                id = popoto.taxonomy.addChildrenData(childData, id);
            }
            if (popoto.provider.getProperty(d, "isSearchable")) {
                parentData.children.push(childData);
            }
        });

        return id;
    };

    // TOOLS -----------------------------------------------------------------------------------------------------------

    popoto.tools = {};
    // TODO introduce plugin mechanism to add tools
    popoto.tools.CENTER_GRAPH = true;
    popoto.tools.RESET_GRAPH = true;
    popoto.tools.TOGGLE_TAXONOMY = true;
    popoto.tools.TOGGLE_FULL_SCREEN = true;

    /**
     * Reset all the graph to display the root node only.
     */
    popoto.tools.reset = function () {
        var label = popoto.graph.getRootNode().label;

        while (popoto.graph.force.nodes().length > 0) {
            popoto.graph.force.nodes().pop();
        }

        while (popoto.graph.force.links().length > 0) {
            popoto.graph.force.links().pop();
        }

        // Reinitialize internal label generator
        popoto.graph.node.internalLabels = {};

        popoto.update();
        popoto.graph.addRootNode(label);
        popoto.graph.hasGraphChanged = true;
        popoto.result.hasChanged = true;
        popoto.update();
        popoto.tools.center();
    };

    /**
     * Reset zoom and center the view on svg center.
     */
    popoto.tools.center = function () {
        popoto.graph.zoom.translate([0, 0]).scale(1);
        popoto.graph.svg.transition().attr("transform", "translate(" + popoto.graph.zoom.translate() + ")" + " scale(" + popoto.graph.zoom.scale() + ")");
    };

    /**
     * Show, hide taxonomy panel.
     */
    popoto.tools.toggleTaxonomy = function () {
        var taxo = d3.select("#" + popoto.taxonomy.containerId);
        if (taxo.filter(".disabled").empty()) {
            taxo.classed("disabled", true);
        } else {
            taxo.classed("disabled", false);
        }
    };

    popoto.tools.toggleFullScreen = function () {

        var elem = document.getElementById(popoto.graph.containerId);

        if (!document.fullscreenElement &&    // alternative standard method
            !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) {  // current working methods
            if (elem.requestFullscreen) {
                elem.requestFullscreen();
            } else if (elem.msRequestFullscreen) {
                elem.msRequestFullscreen();
            } else if (elem.mozRequestFullScreen) {
                elem.mozRequestFullScreen();
            } else if (elem.webkitRequestFullscreen) {
                elem.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
            }
        } else {
            if (document.exitFullscreen) {
                document.exitFullscreen();
            } else if (document.msExitFullscreen) {
                document.msExitFullscreen();
            } else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
            } else if (document.webkitExitFullscreen) {
                document.webkitExitFullscreen();
            }
        }
    };
    // GRAPH -----------------------------------------------------------------------------------------------------------

    popoto.graph = {};

    /**
     * ID of the HTML component where the graph query builder elements will be generated in.
     * @type {string}
     */
    popoto.graph.containerId = "popoto-graph";
    popoto.graph.hasGraphChanged = true;
    // Defines the min and max level of zoom available in graph query builder.
    popoto.graph.zoom = d3.behavior.zoom().scaleExtent([0.1, 10]);
    popoto.graph.WHEEL_ZOOM_ENABLED = true;
    popoto.graph.TOOL_TAXONOMY = "Show/hide taxonomy panel";
    popoto.graph.TOOL_CENTER = "Center view";
    popoto.graph.TOOL_FULL_SCREEN = "Full screen";
    popoto.graph.TOOL_RESET = "Reset graph";

    /**
     * Define the list of listenable events on graph.
     */
    popoto.graph.Events = Object.freeze({NODE_ROOT_ADD: "root.node.add", NODE_EXPAND_RELATIONSHIP: "node.expandRelationship"});

    /**
     * Generates all the HTML and SVG element needed to display the graph query builder.
     * Everything will be generated in the container with id defined by popoto.graph.containerId.
     */
    popoto.graph.createGraphArea = function () {

        var htmlContainer = d3.select("#" + popoto.graph.containerId);

        var toolbar = htmlContainer
            .append("div")
            .attr("class", "ppt-toolbar");

        if (popoto.tools.RESET_GRAPH) {
            toolbar.append("span")
                .attr("id", "popoto-reset-menu")
                .attr("class", "ppt-menu reset")
                .attr("title", popoto.graph.TOOL_RESET)
                .on("click", popoto.tools.reset);
        }

        if (popoto.taxonomy.isActive && popoto.tools.TOGGLE_TAXONOMY) {
            toolbar.append("span")
                .attr("id", "popoto-taxonomy-menu")
                .attr("class", "ppt-menu taxonomy")
                .attr("title", popoto.graph.TOOL_TAXONOMY)
                .on("click", popoto.tools.toggleTaxonomy);
        }

        if (popoto.tools.CENTER_GRAPH) {
            toolbar.append("span")
                .attr("id", "popoto-center-menu")
                .attr("class", "ppt-menu center")
                .attr("title", popoto.graph.TOOL_CENTER)
                .on("click", popoto.tools.center);
        }

        if (popoto.tools.TOGGLE_FULL_SCREEN) {
            toolbar.append("span")
                .attr("id", "popoto-fullscreen-menu")
                .attr("class", "ppt-menu fullscreen")
                .attr("title", popoto.graph.TOOL_FULL_SCREEN)
                .on("click", popoto.tools.toggleFullScreen);
        }

        var svgTag = htmlContainer.append("svg").call(popoto.graph.zoom.on("zoom", popoto.graph.rescale));

        svgTag.on("dblclick.zoom", null)
            .attr("class", "ppt-svg-graph");

        if (!popoto.graph.WHEEL_ZOOM_ENABLED) {
            // Disable mouse wheel events.
            svgTag.on("wheel.zoom", null)
                .on("mousewheel.zoom", null);
        }

        popoto.graph.svg = svgTag.append('svg:g');

        // Create two separated area for links and nodes
        // Links and nodes are separated in a dedicated "g" element
        // and nodes are generated after links to ensure that nodes are always on foreground.
        popoto.graph.svg.append("g").attr("id", popoto.graph.link.gID);
        popoto.graph.svg.append("g").attr("id", popoto.graph.node.gID);

        // This listener is used to center the root node in graph during a window resize.
        // TODO can the listener be limited on the parent container only?
        window.addEventListener('resize', popoto.graph.centerRootNode);
    };

    popoto.graph.centerRootNode = function () {
        popoto.graph.getRootNode().px = popoto.graph.getSVGWidth() / 2;
        popoto.graph.getRootNode().py = popoto.graph.getSVGHeight() / 2;
        popoto.update();
    };

    /**
     * Get the actual width of the SVG element containing the graph query builder.
     * @returns {number}
     */
    popoto.graph.getSVGWidth = function () {
        if (typeof popoto.graph.svg == 'undefined' || popoto.graph.svg.empty()) {
            popoto.logger.debug("popoto.graph.svg is undefined or empty.");
            return 0;
        } else {
            return document.getElementById(popoto.graph.containerId).clientWidth;
        }
    };

    /**
     * Get the actual height of the SVG element containing the graph query builder.
     * @returns {number}
     */
    popoto.graph.getSVGHeight = function () {
        if (typeof popoto.graph.svg == 'undefined' || popoto.graph.svg.empty()) {
            popoto.logger.debug("popoto.graph.svg is undefined or empty.");
            return 0;
        } else {
            return document.getElementById(popoto.graph.containerId).clientHeight;
        }
    };

    /**
     * Function to call on SVG zoom event to update the svg transform attribute.
     */
    popoto.graph.rescale = function () {
        var trans = d3.event.translate,
            scale = d3.event.scale;

        popoto.graph.svg.attr("transform",
            "translate(" + trans + ")"
            + " scale(" + scale + ")");
    };

    /******************************
     * Default parameters used to configure D3.js force layout.
     * These parameter can be modified to change graph behavior.
     ******************************/
    popoto.graph.LINK_DISTANCE = 150;
    popoto.graph.LINK_STRENGTH = 1;
    popoto.graph.FRICTION = 0.8;
    popoto.graph.CHARGE = -1400;
    popoto.graph.THETA = 0.8;
    popoto.graph.GRAVITY = 0.0;

    /**
     * Contains the list off root node add listeners.
     */
    popoto.graph.rootNodeAddListeners = [];
    popoto.graph.nodeExpandRelationsipListeners = [];

    /**
     *  Create the D3.js force layout for the graph query builder.
     */
    popoto.graph.createForceLayout = function () {

        popoto.graph.force = d3.layout.force()
            .size([popoto.graph.getSVGWidth(), popoto.graph.getSVGHeight()])
            .linkDistance(function (d) {
                if (d.type === popoto.graph.link.LinkTypes.RELATION) {
                    return ((3 * popoto.graph.LINK_DISTANCE) / 2);
                } else {
                    return popoto.graph.LINK_DISTANCE;
                }
            })
            .linkStrength(function (d) {
                if (d.linkStrength) {
                    return d.linkStrength;
                } else {
                    return popoto.graph.LINK_STRENGTH;
                }
            })
            .friction(popoto.graph.FRICTION)
            .charge(function (d) {
                if (d.charge) {
                    return d.charge;
                } else {
                    return popoto.graph.CHARGE;
                }
            })
            .theta(popoto.graph.THETA)
            .gravity(popoto.graph.GRAVITY)
            .on("tick", popoto.graph.tick); // Function called on every position update done by D3.js

        // Disable event propagation on drag to avoid zoom and pan issues
        popoto.graph.force.drag()
            .on("dragstart", function (d) {
                d3.event.sourceEvent.stopPropagation();
            })
            .on("dragend", function (d) {
                d3.event.sourceEvent.stopPropagation();
            });
    };

    /**
     * Add a listener to the specified event.
     *
     * @param event name of the event to add the listener.
     * @param listener the listener to add.
     */
    popoto.graph.on = function (event, listener) {
        if (event === popoto.graph.Events.NODE_ROOT_ADD) {
            popoto.graph.rootNodeAddListeners.push(listener);
        }
        if (event === popoto.graph.Events.NODE_EXPAND_RELATIONSHIP) {
            popoto.graph.nodeExpandRelationsipListeners.push(listener);
        }
    };

    /**
     * Adds graph root nodes using the label set as parameter.
     * All the other nodes should have been removed first to avoid inconsistent data.
     *
     * @param label label of the node to add as root.
     */
    popoto.graph.addRootNode = function (label) {
        if (popoto.graph.force.nodes().length > 0) {
            popoto.logger.debug("popoto.graph.addRootNode is called but the graph is not empty.");
        }

        popoto.graph.force.nodes().push({
            "id": "0",
            "type": popoto.graph.node.NodeTypes.ROOT,
            // x and y coordinates are set to the center of the SVG area.
            // These coordinate will never change at runtime except if the window is resized.
            "x": popoto.graph.getSVGWidth() / 2,
            "y": popoto.graph.getSVGHeight() / 2,
            "label": label,
            // The node is fixed to always remain in the center of the svg area.
            // This property should not be changed at runtime to avoid issues with the zoom and pan.
            "fixed": true,
            // Label used internally to identify the node.
            // This label is used for example as cypher query identifier.
            "internalLabel": popoto.graph.node.generateInternalLabel(label)
        });

        // Notify listeners
        popoto.graph.rootNodeAddListeners.forEach(function (listener) {
            listener(popoto.graph.getRootNode());
        });
    };

    /**
     * Get the graph root node.
     * @returns {*}
     */
    popoto.graph.getRootNode = function () {
        return popoto.graph.force.nodes()[0];
    };

    /**
     * Function to call on D3.js force layout tick event.
     * This function will update the position of all links and nodes elements in the graph with the force layout computed coordinate.
     */
    popoto.graph.tick = function () {
        popoto.graph.svg.selectAll("#" + popoto.graph.link.gID + " > g")
            .selectAll("path")
            .attr("d", function (d) {
                var parentAngle = popoto.graph.computeParentAngle(d.target);
                var targetX = d.target.x + (popoto.graph.link.RADIUS * Math.cos(parentAngle)),
                    targetY = d.target.y - (popoto.graph.link.RADIUS * Math.sin(parentAngle));

                var sourceX = d.source.x - (popoto.graph.link.RADIUS * Math.cos(parentAngle)),
                    sourceY = d.source.y + (popoto.graph.link.RADIUS * Math.sin(parentAngle));

                if (d.source.x <= d.target.x) {
                    return "M" + sourceX + " " + sourceY + "L" + targetX + " " + targetY;
                } else {
                    return "M" + targetX + " " + targetY + "L" + sourceX + " " + sourceY;
                }
            });

        popoto.graph.svg.selectAll("#" + popoto.graph.node.gID + " > g")
            .attr("transform", function (d) {
                return "translate(" + (d.x) + "," + (d.y) + ")";
            });
    };

    // LINKS -----------------------------------------------------------------------------------------------------------
    popoto.graph.link = {};

    /**
     * Defines the radius around the node to start link drawing.
     * If set to 0 links will start from the middle of the node.
     */
    popoto.graph.link.RADIUS = 25;

    // ID of the g element in SVG graph containing all the link elements.
    popoto.graph.link.gID = "popoto-glinks";

    /**
     * Defines the different type of link.
     * RELATION is a relation link between two nodes.
     * VALUE is a link between a generic node and a value.
     */
    popoto.graph.link.LinkTypes = Object.freeze({RELATION: 0, VALUE: 1});

    /**
     * Function to call to update the links after modification in the model.
     * This function will update the graph with all removed, modified or added links using d3.js mechanisms.
     */
    popoto.graph.link.updateLinks = function () {
        popoto.graph.link.svgLinkElements = popoto.graph.svg.select("#" + popoto.graph.link.gID).selectAll("g");
        popoto.graph.link.updateData();
        popoto.graph.link.removeElements();
        popoto.graph.link.addNewElements();
        popoto.graph.link.updateElements();
    };

    /**
     * Update the links element with data coming from popoto.graph.force.links().
     */
    popoto.graph.link.updateData = function () {
        popoto.graph.link.svgLinkElements = popoto.graph.link.svgLinkElements.data(popoto.graph.force.links(), function (d) {
            return d.id;
        });
    };

    /**
     * Clean links elements removed from the list.
     */
    popoto.graph.link.removeElements = function () {
        popoto.graph.link.svgLinkElements.exit().remove();
    };

    /**
     * Create new elements.
     */
    popoto.graph.link.addNewElements = function () {

        var newLinkElements = popoto.graph.link.svgLinkElements.enter().append("g")
            .attr("class", "ppt-glink")
            .on("mouseover", popoto.graph.link.mouseOverLink)
            .on("mouseout", popoto.graph.link.mouseOutLink);

        newLinkElements.append("path");

        newLinkElements.append("text")
            .attr("text-anchor", "middle")
            .attr("dy", "-4")
            .append("textPath")
            .attr("class", "ppt-textPath")
            .attr("startOffset", "50%");

    };

    /**
     * Update all the elements (new + modified)
     */
    popoto.graph.link.updateElements = function () {
        popoto.graph.link.svgLinkElements
            .attr("id", function (d) {
                return "ppt-glink_" + d.id;
            });

        popoto.graph.link.svgLinkElements.selectAll("path")
            .attr("id", function (d) {
                return "ppt-path_" + d.id
            })
            .attr("class", function (d) {
                if (d.type === popoto.graph.link.LinkTypes.VALUE) {
                    return "ppt-link-value";
                } else {
                    if (d.target.count == 0) {
                        return "ppt-link-relation disabled";
                    } else {
                        if (d.target.value !== undefined) {
                            return "ppt-link-relation value";
                        } else {
                            return "ppt-link-relation";
                        }
                    }
                }
            });

        // Due to a bug on webkit browsers (as of 30/01/2014) textPath cannot be selected
        // To workaround this issue the selection is done with its associated css class
        popoto.graph.link.svgLinkElements.selectAll("text")
            .attr("id", function (d) {
                return "ppt-text_" + d.id
            })
            .attr("class", function (d) {
                if (d.type === popoto.graph.link.LinkTypes.VALUE) {
                    return "ppt-link-text-value";
                } else {
                    if (d.target.count == 0) {
                        return "ppt-link-text-relation disabled";
                    } else {
                        if (d.target.value !== undefined) {
                            return "ppt-link-text-relation value";
                        } else {
                            return "ppt-link-text-relation";
                        }
                    }
                }
            })
            .selectAll(".ppt-textPath")
            .attr("id", function (d) {
                return "ppt-textpath_" + d.id
            })
            .attr("xlink:href", function (d) {
                return "#ppt-path_" + d.id
            })
            .text(function (d) {
                return popoto.provider.getLinkTextValue(d);
            });
    };

    /**
     * Function called when mouse is over the link.
     * This function is used to change the CSS class on hover of the link and query viewer element.
     *
     * TODO try to introduce event instead of directly access query spans here. This could be used in future extensions.
     */
    popoto.graph.link.mouseOverLink = function () {
        d3.select(this).select("path").classed("ppt-link-hover", true);
        d3.select(this).select("text").classed("ppt-link-hover", true);

        if (popoto.queryviewer.isActive) {
            var hoveredLink = d3.select(this).data()[0];

            popoto.queryviewer.queryConstraintSpanElements.filter(function (d) {
                return d.ref === hoveredLink;
            }).classed("hover", true);
            popoto.queryviewer.querySpanElements.filter(function (d) {
                return d.ref === hoveredLink;
            }).classed("hover", true);
        }
    };

    /**
     * Function called when mouse goes out of the link.
     * This function is used to reinitialize the CSS class of the link and query viewer element.
     */
    popoto.graph.link.mouseOutLink = function () {
        d3.select(this).select("path").classed("ppt-link-hover", false);
        d3.select(this).select("text").classed("ppt-link-hover", false);

        if (popoto.queryviewer.isActive) {
            var hoveredLink = d3.select(this).data()[0];

            popoto.queryviewer.queryConstraintSpanElements.filter(function (d) {
                return d.ref === hoveredLink;
            }).classed("hover", false);
            popoto.queryviewer.querySpanElements.filter(function (d) {
                return d.ref === hoveredLink;
            }).classed("hover", false);
        }
    };

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // NODES -----------------------------------------------------------------------------------------------------------

    popoto.graph.node = {};

    // ID of the g element in SVG graph containing all the link elements.
    popoto.graph.node.gID = "popoto-gnodes";

    // Node ellipse size used by default for text nodes.
    popoto.graph.node.ELLIPSE_RX = 50;
    popoto.graph.node.ELLIPSE_RY = 25;
    popoto.graph.node.TEXT_Y = 8;
    popoto.graph.node.BACK_CIRCLE_R = 70;
    // Define the max number of character displayed in ellipses.
    popoto.graph.node.NODE_MAX_CHARS = 11;

    // Number of nodes displayed per page during value selection.
    popoto.graph.node.PAGE_SIZE = 10;

    // Count box default size
    popoto.graph.node.CountBox = {x: 16, y: 33, w: 52, h: 19};

    // Store choose node state to avoid multiple node expand at the same time
    popoto.graph.node.chooseWaiting = false;

    /**
     * Defines the list of possible nodes.
     * ROOT: Node used as graph root. It is the target of the query. Only one node of this type should be available in graph.
     * CHOOSE: Nodes defining a generic node label. From these node is is possible to select a value or explore relations.
     * VALUE: Unique node containing a value constraint. Usually replace CHOOSE nodes once a value as been selected.
     * GROUP: Empty node used to group relations. No value can be selected but relations can be explored. These nodes doesn't have count.
     */
    popoto.graph.node.NodeTypes = Object.freeze({ROOT: 0, CHOOSE: 1, VALUE: 2, GROUP: 3});

    // Variable used to generate unique id for each new nodes.
    popoto.graph.node.idgen = 0;

    // Used to generate unique internal labels used for example as identifier in Cypher query.
    popoto.graph.node.internalLabels = {};

    /**
     * Create a normalized identifier from a node label.
     * Multiple calls with the same node label will generate different unique identifier.
     *
     * @param nodeLabel
     * @returns {string}
     */
    popoto.graph.node.generateInternalLabel = function (nodeLabel) {
        var label = nodeLabel.toLowerCase().replace(/ /g, '');

        if (label in popoto.graph.node.internalLabels) {
            popoto.graph.node.internalLabels[label] = popoto.graph.node.internalLabels[label] + 1;
        } else {
            popoto.graph.node.internalLabels[label] = 0;
            return label;
        }

        return label + popoto.graph.node.internalLabels[label];
    };

    /**
     * Update Nodes SVG elements using D3.js update mechanisms.
     */
    popoto.graph.node.updateNodes = function () {
        if (!popoto.graph.node.svgNodeElements) {
            popoto.graph.node.svgNodeElements = popoto.graph.svg.select("#" + popoto.graph.node.gID).selectAll("g");
        }
        popoto.graph.node.updateData();
        popoto.graph.node.removeElements();
        popoto.graph.node.addNewElements();
        popoto.graph.node.updateElements();
    };

    /**
     * Update node data with changes done in popoto.graph.force.nodes() model.
     */
    popoto.graph.node.updateData = function () {
        popoto.graph.node.svgNodeElements = popoto.graph.node.svgNodeElements.data(popoto.graph.force.nodes(), function (d) {
            return d.id;
        });

        if (popoto.graph.hasGraphChanged) {
            popoto.graph.node.updateCount();
            popoto.graph.hasGraphChanged = false;
        }
    };

    /**
     * Update nodes count by executing a query for every nodes with the new graph structure.
     */
    popoto.graph.node.updateCount = function () {

        var statements = [];

        var counterNodes = popoto.graph.force.nodes()
            .filter(function (d) {
                return d.type !== popoto.graph.node.NodeTypes.VALUE && d.type !== popoto.graph.node.NodeTypes.GROUP;
            });

        counterNodes.forEach(function (node) {
            var query = popoto.query.generateNodeCountCypherQuery(node);
            statements.push(
                {
                    "statement": query
                }
            );
        });

        popoto.logger.info("Count nodes ==> ");
        popoto.rest.post(
            {
                "statements": statements
            })
            .done(function (returnedData) {

                if (returnedData.errors && returnedData.errors.length > 0) {
                    popoto.logger.error("Cypher query error:" + JSON.stringify(returnedData.errors));
                }

                if (returnedData.results && returnedData.results.length > 0) {
                    for (var i = 0; i < counterNodes.length; i++) {
                        counterNodes[i].count = returnedData.results[i].data[0].row[0];
                    }
                } else {
                    counterNodes.forEach(function (node) {
                        node.count = 0;
                    });
                }
                popoto.graph.node.updateElements();
                popoto.graph.link.updateElements();
            })
            .fail(function (xhr, textStatus, errorThrown) {
                popoto.logger.error(textStatus + ": error while accessing Neo4j server on URL:\"" + popoto.rest.CYPHER_URL + "\" defined in \"popoto.rest.CYPHER_URL\" property: " + errorThrown);
                counterNodes.forEach(function (node) {
                    node.count = 0;
                });
                popoto.graph.node.updateElements();
                popoto.graph.link.updateElements();
            });
    };

    /**
     * Remove old elements.
     * Should be called after updateData.
     */
    popoto.graph.node.removeElements = function () {
        var toRemove = popoto.graph.node.svgNodeElements.exit();

        // Nodes without parent are simply removed.
        toRemove.filter(function (d) {
            return !d.parent;
        }).remove();

        // Nodes with a parent are removed with an animation (nodes are collapsed to their parents before being removed)
        toRemove.filter(function (d) {
            return d.parent;
        }).transition().duration(300).attr("transform", function (d) {
            return "translate(" + d.parent.x + "," + d.parent.y + ")";
        }).remove();
    };

    /**
     * Add all new elements.
     * Only the skeleton of new nodes are added custom data will be added during the element update phase.
     * Should be called after updateData and before updateElements.
     */
    popoto.graph.node.addNewElements = function () {
        var gNewNodeElements = popoto.graph.node.svgNodeElements.enter()
            .append("g")
            .on("click", popoto.graph.node.nodeClick)
            .on("mouseover", popoto.graph.node.mouseOverNode)
            .on("mouseout", popoto.graph.node.mouseOutNode);

        // Add right click on all nodes except value
        gNewNodeElements.filter(function (d) {
            return d.type !== popoto.graph.node.NodeTypes.VALUE;
        }).on("contextmenu", popoto.graph.node.clearSelection);

        // Disable right click context menu on value nodes
        gNewNodeElements.filter(function (d) {
            return d.type === popoto.graph.node.NodeTypes.VALUE;
        }).on("contextmenu", function () {
            // Disable context menu on
            d3.event.preventDefault();
        });

        // Most browser will generate a tooltip if a title is specified for the SVG element
        // TODO Introduce an SVG tooltip instead?
        gNewNodeElements.append("title").attr("class", "ppt-svg-title");

        // Nodes are composed of 3 layouts and skeleton are created here.
        popoto.graph.node.addBackgroundElements(gNewNodeElements);
        popoto.graph.node.addMiddlegroundElements(gNewNodeElements);
        popoto.graph.node.addForegroundElements(gNewNodeElements);
    };

    /**
     * Create the background for a new node element.
     * 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.
     * This circle also define the node zone that can receive events like mouse clicks.
     *
     * @param gNewNodeElements
     */
    popoto.graph.node.addBackgroundElements = function (gNewNodeElements) {
        var background = gNewNodeElements
            .append("g")
            .attr("class", "ppt-g-node-background");

        background.append("circle")
            .attr("class", function (d) {
                var cssClass = "ppt-node-background-circle";
                if (d.value !== undefined) {
                    cssClass = cssClass + " selected-value";
                } else if (d.type === popoto.graph.node.NodeTypes.ROOT) {
                    cssClass = cssClass + " root";
                } else if (d.type === popoto.graph.node.NodeTypes.CHOOSE) {
                    cssClass = cssClass + " choose";
                } else if (d.type === popoto.graph.node.NodeTypes.VALUE) {
                    cssClass = cssClass + " value";
                } else if (d.type === popoto.graph.node.NodeTypes.GROUP) {
                    cssClass = cssClass + " group";
                }

                return cssClass;
            })
            .style("fill-opacity", 0)
            .attr("r", popoto.graph.node.BACK_CIRCLE_R);
    };

    /**
     * Create the node main elements.
     *
     * @param gNewNodeElements
     */
    popoto.graph.node.addMiddlegroundElements = function (gNewNodeElements) {
        var middle = gNewNodeElements
            .append("g")
            .attr("class", "ppt-g-node-middleground");
    };

    /**
     * Create the node foreground elements.
     * It contains node additional elements, count or tools like navigation arrows.
     *
     * @param gNewNodeElements
     */
    popoto.graph.node.addForegroundElements = function (gNewNodeElements) {
        var foreground = gNewNodeElements
            .append("g")
            .attr("class", "ppt-g-node-foreground");

        // plus sign
        var gRelationship = foreground.filter(function (d) {
            return d.type !== popoto.graph.node.NodeTypes.VALUE;
        }).append("g").attr("class", "ppt-rel-plus-icon");

        gRelationship.append("title")
            .text("Add relationship");

        gRelationship
            .append("circle")
            .attr("class", "ppt-rel-plus-background")
            .attr("cx", "32")
            .attr("cy", "-43")
            .attr("r", "16");

        gRelationship
            .append("path")
            .attr("class", "ppt-rel-plus-path")
            .attr("d", "M 40,-45 35,-45 35,-50 30,-50 30,-45 25,-45 25,-40 30,-40 30,-35 35,-35 35,-40 40,-40 z");

        gRelationship
            .on("mouseover", function () {
                d3.select(this).select(".ppt-rel-plus-background").transition().style("fill-opacity", 0.5);
            })
            .on("mouseout", function () {
                d3.select(this).select(".ppt-rel-plus-background").transition().style("fill-opacity", 0);
            })
            .on("click", function () {
                d3.event.stopPropagation(); // To avoid click event on svg element in background
                popoto.graph.node.expandRelationship.call(this);
            });

        // Minus sign
        var gMinusRelationship = foreground.filter(function (d) {
            return d.type !== popoto.graph.node.NodeTypes.VALUE;
        }).append("g").attr("class", "ppt-rel-minus-icon");

        gMinusRelationship.append("title")
            .text("Remove relationship");

        gMinusRelationship
            .append("circle")
            .attr("class", "ppt-rel-minus-background")
            .attr("cx", "32")
            .attr("cy", "-43")
            .attr("r", "16");

        gMinusRelationship
            .append("path")
            .attr("class", "ppt-rel-minus-path")
            .attr("d", "M 40,-45 25,-45 25,-40 40,-40 z");

        gMinusRelationship
            .on("mouseover", function () {
                d3.select(this).select(".ppt-rel-minus-background").transition().style("fill-opacity", 0.5);
            })
            .on("mouseout", function () {
                d3.select(this).select(".ppt-rel-minus-background").transition().style("fill-opacity", 0);
            })
            .on("click", function () {
                d3.event.stopPropagation(); // To avoid click event on svg element in background
                popoto.graph.node.collapseRelationship.call(this);
            });

        // Arrows icons added only for root and choose nodes
        var gArrow = foreground.filter(function (d) {
            return d.type === popoto.graph.node.NodeTypes.ROOT || d.type === popoto.graph.node.NodeTypes.CHOOSE;
        })
            .append("g")
            .attr("class", "ppt-node-foreground-g-arrows");

        var glArrow = gArrow.append("g");
        //glArrow.append("polygon")
        //.attr("points", "-53,-23 -33,-33 -33,-13");
        glArrow.append("circle")
            .attr("class", "ppt-larrow")
            .attr("cx", "-43")
            .attr("cy", "-23")
            .attr("r", "17");

        glArrow.append("path")
            .attr("class", "ppt-arrow")
            .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");

        glArrow.on("click", function (clickedNode) {
            d3.event.stopPropagation(); // To avoid click event on svg element in background

            // On left arrow click page number is decreased and node expanded to display the new page
            if (clickedNode.page > 1) {
                clickedNode.page--;
                popoto.graph.node.collapseNode(clickedNode);
                popoto.graph.node.expandNode(clickedNode);
            }
        });

        var grArrow = gArrow.append("g");
        //grArrow.append("polygon")
        //.attr("points", "53,-23 33,-33 33,-13");

        grArrow.append("circle")
            .attr("class", "ppt-rarrow")
            .attr("cx", "43")
            .attr("cy", "-23")
            .attr("r", "17");

        grArrow.append("path")
            .attr("class", "ppt-arrow")
            .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");

        grArrow.on("click", function (clickedNode) {
            d3.event.stopPropagation(); // To avoid click event on svg element in background

            if (clickedNode.page * popoto.graph.node.PAGE_SIZE < clickedNode.count) {
                clickedNode.page++;
                popoto.graph.node.collapseNode(clickedNode);
                popoto.graph.node.expandNode(clickedNode);
            }
        });

        // Count box
        var countForeground = foreground.filter(function (d) {
            return d.type !== popoto.graph.node.NodeTypes.GROUP;
        });

        countForeground
            .append("rect")
            .attr("x", popoto.graph.node.CountBox.x)
            .attr("y", popoto.graph.node.CountBox.y)
            .attr("width", popoto.graph.node.CountBox.w)
            .attr("height", popoto.graph.node.CountBox.h)
            .attr("class", "ppt-count-box");

        countForeground
            .append("text")
            .attr("x", 42)
            .attr("y", 48)
            .attr("text-anchor", "middle")
            .attr("class", "ppt-count-text");
    };

    /**
     * Updates all elements.
     */
    popoto.graph.node.updateElements = function () {
        popoto.graph.node.svgNodeElements.attr("id", function (d) {
            return "popoto-gnode_" + d.id;
        });

        popoto.graph.node.svgNodeElements
            .selectAll(".ppt-svg-title")
            .text(function (d) {
                return popoto.provider.getTextValue(d);
            });

        popoto.graph.node.svgNodeElements.filter(function (n) {
            return n.type !== popoto.graph.node.NodeTypes.ROOT
        }).call(popoto.graph.force.drag);

        popoto.graph.node.updateBackgroundElements();
        popoto.graph.node.updateMiddlegroundElements();
        popoto.graph.node.updateForegroundElements();
    };

    popoto.graph.node.updateBackgroundElements = function () {
        popoto.graph.node.svgNodeElements.selectAll(".ppt-g-node-background")
            .selectAll(".ppt-node-background-circle")
            .attr("class", function (d) {
                var cssClass = "ppt-node-background-circle";

                if (d.type === popoto.graph.node.NodeTypes.VALUE) {
                    cssClass = cssClass + " value";
                } else if (d.type === popoto.graph.node.NodeTypes.GROUP) {
                    cssClass = cssClass + " group";
                } else {
                    if (d.value !== undefined) {
                        if (d.type === popoto.graph.node.NodeTypes.ROOT) {
                            cssClass = cssClass + " selected-root-value";
                        } else if (d.type === popoto.graph.node.NodeTypes.CHOOSE) {
                            cssClass = cssClass + " selected-value";
                        }
                    } else {
                        if (d.count == 0) {
                            cssClass = cssClass + " disabled";
                        } else {
                            if (d.type === popoto.graph.node.NodeTypes.ROOT) {
                                cssClass = cssClass + " root";
                            } else if (d.type === popoto.graph.node.NodeTypes.CHOOSE) {
                                cssClass = cssClass + " choose";
                            }
                        }
                    }
                }

                return cssClass;
            })
            .attr("r", popoto.graph.node.BACK_CIRCLE_R);
    };

    /**
     * Update the middle layer of nodes.
     * TODO refactor node generation to allow future extensions (for example add plugin with new node types...)
     */
    popoto.graph.node.updateMiddlegroundElements = function () {

        var middleG = popoto.graph.node.svgNodeElements.selectAll(".ppt-g-node-middleground");

        // Clear all content in case node type has changed
        middleG.selectAll("*").remove();

        //-------------------------------
        // Update IMAGE nodes
        var imageMiddle = middleG.filter(function (d) {
            return popoto.provider.getNodeDisplayType(d) === popoto.provider.NodeDisplayTypes.IMAGE;
        }).append("image").attr("class", "ppt-node-image");

        imageMiddle
            .attr("width", function (d) {
                return popoto.provider.getImageWidth(d);
            })
            .attr("height", function (d) {
                return popoto.provider.getImageHeight(d);
            })
            // Center the image on node
            .attr("transform", function (d) {
                return "translate(" + (-popoto.provider.getImageWidth(d) / 2) + "," + (-popoto.provider.getImageHeight(d) / 2) + ")";
            })
            .attr("xlink:href", function (d) {
                return popoto.provider.getImagePath(d);
            });

        //-------------------------
        // Update TEXT nodes
        var ellipseMiddle = middleG.filter(function (d) {
            return popoto.provider.getNodeDisplayType(d) === popoto.provider.NodeDisplayTypes.TEXT;
        }).append("ellipse").attr("rx", popoto.graph.node.ELLIPSE_RX).attr("ry", popoto.graph.node.ELLIPSE_RY);

        // Set class according to node type
        ellipseMiddle
            .attr("rx", popoto.graph.node.ELLIPSE_RX)
            .attr("ry", popoto.graph.node.ELLIPSE_RY)
            .attr("class", function (d) {
                if (d.type === popoto.graph.node.NodeTypes.ROOT) {
                    if (d.value) {
                        return "ppt-node-ellipse selected-root-value"
                    } else {
                        if (d.count == 0) {
                            return "ppt-node-ellipse root disabled";
                        } else {
                            return "ppt-node-ellipse root";
                        }
                    }
                } else if (d.type === popoto.graph.node.NodeTypes.CHOOSE) {
                    if (d.value) {
                        return "ppt-node-ellipse selected-value"
                    } else {
                        if (d.count == 0) {
                            return "ppt-node-ellipse choose disabled";
                        } else {
                            return "ppt-node-ellipse choose";
                        }
                    }
                } else if (d.type === popoto.graph.node.NodeTypes.VALUE) {
                    return "ppt-node-ellipse value";
                } else if (d.type === popoto.graph.node.NodeTypes.GROUP) {
                    return "ppt-node-ellipse group";
                }
            });

        //-------------------------
        // Update SVG nodes
        var svgMiddle = middleG.filter(function (d) {
            return popoto.provider.getNodeDisplayType(d) === popoto.provider.NodeDisplayTypes.SVG;
        }).append("g")
            // Add D3.js nested data with all paths required to render the svg element.
            .selectAll("path").data(function (d) {
                return popoto.provider.getSVGPaths(d);
            });

        // Update nested data elements
        svgMiddle.exit().remove();

        svgMiddle.enter().append("path");

        middleG
            .selectAll("path")
            .attr("d", function (d) {
                return d.d;
            })
            .attr("class", function (d) {
                return d["class"];
            });

        // Update text
        var textMiddle = middleG.filter(function (d) {
            return popoto.provider.isTextDisplayed(d);
        }).append('text')
            .attr('x', 0)
            .attr('y', popoto.graph.node.TEXT_Y)
            .attr('text-anchor', 'middle');
        textMiddle
            .attr('y', popoto.graph.node.TEXT_Y)
            .attr("class", function (d) {
                switch (d.type) {
                    case popoto.graph.node.NodeTypes.CHOOSE:
                        if (d.value === undefined) {
                            if (d.count == 0) {
                                return "ppt-node-text-choose disabled";
                            } else {
                                return "ppt-node-text-choose";
                            }
                        } else {
                            return "ppt-node-text-choose selected-value";
                        }
                    case popoto.graph.node.NodeTypes.GROUP:
                        return "ppt-node-text-group";
                    case popoto.graph.node.NodeTypes.ROOT:
                        if (d.value === undefined) {
                            if (d.count == 0) {
                                return "ppt-node-text-root disabled";
                            } else {
                                return "ppt-node-text-root";
                            }
                        } else {
                            return "ppt-node-text-root selected-value";
                        }
                    case popoto.graph.node.NodeTypes.VALUE:
                        return "ppt-node-text-value";
                }
            })
            .text(function (d) {
                if (popoto.provider.isTextDisplayed(d)) {
                    return popoto.provider.getTextValue(d);
                } else {
                    return "";
                }
            });
    };

    /**
     * Updates the foreground elements
     */
    popoto.graph.node.updateForegroundElements = function () {

        // Updates browse arrows status
        var gArrows = popoto.graph.node.svgNodeElements.selectAll(".ppt-g-node-foreground")
            .selectAll(".ppt-node-foreground-g-arrows");
        gArrows.classed("active", function (d) {
            return d.valueExpanded && d.data && d.data.length > popoto.graph.node.PAGE_SIZE;
        });

        gArrows.selectAll(".ppt-larrow").classed("enabled", function (d) {
            return d.page > 1;
        });

        gArrows.selectAll(".ppt-rarrow").classed("enabled", function (d) {
            if (d.data) {
                var count = d.data.length;
                return d.page * popoto.graph.node.PAGE_SIZE < count;
            } else {
                return false;
            }
        });

        // Update count box class depending on node type
        var gForegrounds = popoto.graph.node.svgNodeElements.selectAll(".ppt-g-node-foreground");

        gForegrounds.selectAll(".ppt-count-box").filter(function (d) {
            return d.type !== popoto.graph.node.NodeTypes.CHOOSE;
        }).classed("root", true);

        gForegrounds.selectAll(".ppt-count-box").filter(function (d) {
            return d.type === popoto.graph.node.NodeTypes.CHOOSE;
        }).classed("value", true);

        gForegrounds.selectAll(".ppt-count-box").classed("disabled", function (d) {
            return d.count == 0;
        });

        gForegrounds.selectAll(".ppt-count-text")
            .text(function (d) {
                if (d.count != null) {
                    return d.count;
                } else {
                    return "...";
                }
            })
            .classed("disabled", function (d) {
                return d.count == 0;
            });

        // Hide/Show plus icon (set disabled CSS class) if node already has been expanded.
        gForegrounds.selectAll(".ppt-rel-plus-icon")
            .classed("disabled", function (d) {
                return d.linkExpanded || d.count == 0 || d.linkCount == 0;
            });

        gForegrounds.selectAll(".ppt-rel-minus-icon")
            .classed("disabled", function (d) {
                return (!d.linkExpanded) || d.count == 0 || d.linkCount == 0;
            });

    };

    /**
     * Handle the mouse over event on nodes.
     */
    popoto.graph.node.mouseOverNode = function () {
        d3.event.preventDefault();

        // TODO don't work on IE (nodes unstable) find another way to move node in foreground on mouse over?
        // d3.select(this).moveToFront();

        d3.select(this).select(".ppt-g-node-background").selectAll("circle").transition().style("fill-opacity", 0.5);

        if (popoto.queryviewer.isActive) {
            // Get the hovered node data
            var hoveredNode = d3.select(this).data()[0];

            // Hover the node in query
            popoto.queryviewer.queryConstraintSpanElements.filter(function (d) {
                return d.ref === hoveredNode;
            }).classed("hover", true);
            popoto.queryviewer.querySpanElements.filter(function (d) {
                return d.ref === hoveredNode;
            }).classed("hover", true);
        }
    };

    /**
     * Handle mouse out event on nodes.
     */
    popoto.graph.node.mouseOutNode = function () {
        d3.event.preventDefault();

        d3.select(this).select(".ppt-g-node-background").selectAll("circle").transition().style("fill-opacity", 0);

        if (popoto.queryviewer.isActive) {
            // Get the hovered node data
            var hoveredNode = d3.select(this).data()[0];

            // Remove hover class on node.
            popoto.queryviewer.queryConstraintSpanElements.filter(function (d) {
                return d.ref === hoveredNode;
            }).classed("hover", false);
            popoto.queryviewer.querySpanElements.filter(function (d) {
                return d.ref === hoveredNode;
            }).classed("hover", false);
        }
    };

    /**
     * Handle the click event on nodes.
     */
    popoto.graph.node.nodeClick = function () {
        var clickedNode = d3.select(this).data()[0]; // Clicked node data
        popoto.logger.debug("nodeClick (" + clickedNode.label + ")");

        if (clickedNode.type === popoto.graph.node.NodeTypes.VALUE) {
            popoto.graph.node.valueNodeClick(clickedNode);
        } else if (clickedNode.type === popoto.graph.node.NodeTypes.CHOOSE || clickedNode.type === popoto.graph.node.NodeTypes.ROOT) {
            if (clickedNode.valueExpanded) {
                popoto.graph.node.collapseNode(clickedNode);
            } else {
                popoto.graph.node.chooseNodeClick(clickedNode);
            }
        }
    };

    /**
     * Remove all the value node directly linked to clicked node.
     *
     * @param clickedNode
     */
    popoto.graph.node.collapseNode = function (clickedNode) {
        if (clickedNode.valueExpanded) { // node is collapsed only if it has been expanded first
            popoto.logger.debug("collapseNode (" + clickedNode.label + ")");

            var linksToRemove = popoto.graph.force.links().filter(function (l) {
                return l.source === clickedNode && l.type === popoto.graph.link.LinkTypes.VALUE;
            });

            // Remove children nodes from model
            linksToRemove.forEach(function (l) {
                popoto.graph.force.nodes().splice(popoto.graph.force.nodes().indexOf(l.target), 1);
            });

            // Remove links from model
            for (var i = popoto.graph.force.links().length - 1; i >= 0; i--) {
                if (linksToRemove.indexOf(popoto.graph.force.links()[i]) >= 0) {
                    popoto.graph.force.links().splice(i, 1);
                }
            }

            // Node has been fixed when expanded so we unfix it back here.
            if (clickedNode.type !== popoto.graph.node.NodeTypes.ROOT) {
                clickedNode.fixed = false;
            }

            // Parent node too if not root
            if (clickedNode.parent && clickedNode.parent.type !== popoto.graph.node.NodeTypes.ROOT) {
                clickedNode.parent.fixed = false;
            }

            clickedNode.valueExpanded = false;
            popoto.update();

        } else {
            popoto.logger.debug("collapseNode called on an unexpanded node");
        }
    };

    /**
     * Function called on a value node click.
     * In this case the value is added in the parent node and all the value nodes are collapsed.
     *
     * @param clickedNode
     */
    popoto.graph.node.valueNodeClick = function (clickedNode) {
        popoto.logger.debug("valueNodeClick (" + clickedNode.label + ")");
        clickedNode.parent.value = clickedNode;
        popoto.result.hasChanged = true;
        popoto.graph.hasGraphChanged = true;

        popoto.graph.node.collapseNode(clickedNode.parent);
    };

    /**
     * Function called on choose node click.
     * In this case a query is executed to get all the possible value
     * @param clickedNode
     * TODO optimize with cached data?
     */
    popoto.graph.node.chooseNodeClick = function (clickedNode) {
        popoto.logger.debug("chooseNodeClick (" + clickedNode.label + ") with waiting state set to " + popoto.graph.node.chooseWaiting);
        if (!popoto.graph.node.chooseWaiting && !clickedNode.immutable) {

            // Collapse all expanded nodes first
            popoto.graph.force.nodes().forEach(function (n) {
                if ((n.type == popoto.graph.node.NodeTypes.ROOT || n.type == popoto.graph.node.NodeTypes.CHOOSE) && n.valueExpanded) {
                    popoto.graph.node.collapseNode(n);
                }
            });

            // Set waiting state to true to avoid multiple call on slow query execution
            popoto.graph.node.chooseWaiting = true;

            popoto.logger.info("Values (" + clickedNode.label + ") ==> ");
            popoto.rest.post(
                {
                    "statements": [
                        {
                            "statement": popoto.query.generateValueQuery(clickedNode)
                        }]
                })
                .done(function (data) {
                    clickedNode.id = (++popoto.graph.node.idgen);
                    clickedNode.data = popoto.graph.node.parseResultData(data);
                    clickedNode.page = 1;
                    popoto.graph.node.expandNode(clickedNode);
                    popoto.graph.node.chooseWaiting = false;
                })
                .fail(function (xhr, textStatus, errorThrown) {
                    popoto.graph.node.chooseWaiting = false;
                    popoto.logger.error(textStatus + ": error while accessing Neo4j server on URL:\"" + popoto.rest.CYPHER_URL + "\" defined in \"popoto.rest.CYPHER_URL\" property: " + errorThrown);
                });
        }
    };

    /**
     * Parse query execution result and generate an array of object.
     * These objects contains of a list of properties made of result attributes with their value.
     *
     * @param data query execution raw data
     * @returns {Array} array of structured object with result attributes.
     */
    popoto.graph.node.parseResultData = function (data) {
        var results = [];

        for (var x = 0; x < data.results[0].data.length; x++) {
            var obj = {};

            for (var i = 0; i < data.results[0].columns.length; i++) {
                obj[data.results[0].columns[i]] = data.results[0].data[x].row[i];
            }

            results.push(obj);
        }

        return results;
    };

    /**
     * Compute the angle in radian between the node and its parent.
     * TODO: clean or add comments to explain the code...
     *
     * @param node node to compute angle.
     * @returns {number} angle in radian.
     */
    popoto.graph.computeParentAngle = function (node) {
        var angleRadian = 0;
        var r = 100;
        if (node.parent) {
            var xp = node.parent.x;
            var yp = node.parent.y;
            var x0 = node.x;
            var y0 = node.y;
            var dist = Math.sqrt(Math.pow(xp - x0, 2) + Math.pow(yp - y0, 2));

            var k = r / (dist - r);
            var xc = (x0 + (k * xp)) / (1 + k);

            var val = (xc - x0) / r;
            if (val < -1) {
                val = -1;
            }
            if (val > 1) {
                val = 1;
            }

            angleRadian = Math.acos(val);

            if (yp > y0) {
                angleRadian = 2 * Math.PI - angleRadian;
            }
        }
        return angleRadian;
    };

    /**
     * Function called to expand a node containing values.
     * This function will create the value nodes with the clicked node internal data.
     * Only nodes corresponding to the current page index will be generated.
     *
     * @param clickedNode
     */
    popoto.graph.node.expandNode = function (clickedNode) {

        // Get subset of node corresponding to the current node page and page size
        var lIndex = clickedNode.page * popoto.graph.node.PAGE_SIZE;
        var sIndex = lIndex - popoto.graph.node.PAGE_SIZE;

        var dataToAdd = clickedNode.data.slice(sIndex, lIndex);
        var parentAngle = popoto.graph.computeParentAngle(clickedNode);

        // Then each node are created and dispatched around the clicked node using computed coordinates.
        var i = 1;
        dataToAdd.forEach(function (d) {
            var angleDeg;
            if (clickedNode.parent) {
                angleDeg = (((360 / (dataToAdd.length + 1)) * i));
            } else {
                angleDeg = (((360 / (dataToAdd.length)) * i));
            }

            var nx = clickedNode.x + (100 * Math.cos((angleDeg * (Math.PI / 180)) - parentAngle)),
                ny = clickedNode.y + (100 * Math.sin((angleDeg * (Math.PI / 180)) - parentAngle));

            var node = {
                "id": (++popoto.graph.node.idgen),
                "parent": clickedNode,
                "attributes": d,
                "type": popoto.graph.node.NodeTypes.VALUE,
                "label": clickedNode.label,
                "count": d.count,
                "x": nx,
                "y": ny,
                "internalID": d[popoto.query.NEO4J_INTERNAL_ID.queryInternalName]
            };

            popoto.graph.force.nodes().push(node);

            popoto.graph.force.links().push(
                {
                    id: "l" + (++popoto.graph.node.idgen),
                    source: clickedNode,
                    target: node,
                    type: popoto.graph.link.LinkTypes.VALUE
                }
            );

            i++;
        });

        // Pin clicked node and its parent to avoid the graph to move for selection, only new value nodes will blossom around the clicked node.
        clickedNode.fixed = true;
        if (clickedNode.parent && clickedNode.parent.type !== popoto.graph.node.NodeTypes.ROOT) {
            clickedNode.parent.fixed = true;
        }
        // Change node state
        clickedNode.valueExpanded = true;
        popoto.update();
    };

    /**
     * Function called on a right click on a node.
     *
     * In this case all expanded nodes in the graph will first be closed then if no relation have been added yet a Cypher query is executed to get all the related nodes.
     * A first coordinate pre-computation is done to dispatch the new node correctly around the parent node and the nodes are added with a link in the model.
     *
     * If no relation are found or relation were already added the right click event is used to remove the node current selection.
     *
     */
    popoto.graph.node.expandRelationship = function () {
        // Prevent default right click event opening menu.
        d3.event.preventDefault();

        // Notify listeners
        popoto.graph.nodeExpandRelationsipListeners.forEach(function (listener) {
            listener(this);
        });

        // Get clicked node.
        var clickedNode = d3.select(this).data()[0];

        if (!clickedNode.linkExpanded && !popoto.graph.node.linkWaiting && !clickedNode.valueExpanded) {
            popoto.graph.node.linkWaiting = true;

            popoto.logger.info("Relations (" + clickedNode.label + ") ==> ");
            popoto.rest.post(
                {
                    "statements": [
                        {
                            "statement": popoto.query.generateLinkQuery(clickedNode)
                        }]
                })
                .done(function (data) {
                    var parsedData = popoto.graph.node.parseResultData(data);

                    parsedData = parsedData.filter(function (d) {
                        return popoto.query.filterRelation(d);
                    });

                    if (parsedData.length <= 0) {
                        // Set linkExpanded to true to avoid a new query call on next right click
                        clickedNode.linkExpanded = true;
                        clickedNode.linkCount = 0;
                        popoto.graph.hasGraphChanged = true;
                        popoto.update();
                    } else {
                        var parentAngle = popoto.graph.computeParentAngle(clickedNode);

                        var i = 1;
                        parsedData.forEach(function (d) {
                            var angleDeg;
                            if (parentAngle) {
                                angleDeg = (((360 / (parsedData.length + 1)) * i));
                            } else {
                                angleDeg = (((360 / (parsedData.length)) * i));
                            }

                            var nx = clickedNode.x + (100 * Math.cos((angleDeg * (Math.PI / 180)) - parentAngle)),
                                ny = clickedNode.y + (100 * Math.sin((angleDeg * (Math.PI / 180)) - parentAngle));

                            var isGroupNode = popoto.provider.getIsGroup(d);
                            // filter multiple labels
                            var nodeLabel = popoto.provider.getLabelFilter(d.label);

                            var node = {
                                "id": "" + (++popoto.graph.node.idgen),
                                "parent": clickedNode,
                                "type": (isGroupNode) ? popoto.graph.node.NodeTypes.GROUP : popoto.graph.node.NodeTypes.CHOOSE,
                                "label": nodeLabel,
                                "fixed": false,
                                "internalLabel": popoto.graph.node.generateInternalLabel(nodeLabel),
                                "x": nx,
                                "y": ny
                            };

                            popoto.graph.force.nodes().push(node);

                            popoto.graph.force.links().push(
                                {
                                    id: "l" + (++popoto.graph.node.idgen),
                                    source: clickedNode,
                                    target: node,
                                    type: popoto.graph.link.LinkTypes.RELATION,
                                    label: d.relationship
                                }
                            );

                            i++;
                        });

                        popoto.graph.hasGraphChanged = true;
                        clickedNode.linkExpanded = true;
                        clickedNode.linkCount = parsedData.length;
                        popoto.update();
                    }
                    popoto.graph.node.linkWaiting = false;
                })
                .fail(function (xhr, textStatus, errorThrown) {
                    popoto.logger.error(textStatus + ": error while accessing Neo4j server on URL:\"" + popoto.rest.CYPHER_URL + "\" defined in \"popoto.rest.CYPHER_URL\" property: " + errorThrown);
                    popoto.graph.node.linkWaiting = false;
                });
        }
    };

    /**
     * Remove all relationships from context node (including children).
     */
    popoto.graph.node.collapseRelationship = function () {
        d3.event.preventDefault();

        // Get clicked node.
        var clickedNode = d3.select(this).data()[0];

        if (clickedNode.linkExpanded && clickedNode.linkCount > 0 && !popoto.graph.node.linkWaiting && !clickedNode.valueExpanded) {

            // Collapse all expanded choose nodes first to avoid having invalid displayed value node if collapsed relation contains a value.
            popoto.graph.force.nodes().forEach(function (n) {
                if ((n.type === popoto.graph.node.NodeTypes.CHOOSE || n.type === popoto.graph.node.NodeTypes.ROOT) && n.valueExpanded) {
                    popoto.graph.node.collapseNode(n);
                }
            });

            var linksToRemove = popoto.graph.force.links().filter(function (l) {
                return l.source === clickedNode && l.type === popoto.graph.link.LinkTypes.RELATION;
            });

            // Remove children nodes from model
            linksToRemove.forEach(function (l) {
                popoto.graph.node.removeNode(l.target);
            });

            // Remove links from model
            for (var i = popoto.graph.force.links().length - 1; i >= 0; i--) {
                if (linksToRemove.indexOf(popoto.graph.force.links()[i]) >= 0) {
                    popoto.graph.force.links().splice(i, 1);
                }
            }

            clickedNode.linkExpanded = false;
            popoto.result.hasChanged = true;
            popoto.graph.hasGraphChanged = true;
            popoto.update();
        }
    };

    /**
     * Remove a node and its relationships (recursively) from the graph.
     *
     * @param node the node to remove.
     */
    popoto.graph.node.removeNode = function (node) {

        var linksToRemove = popoto.graph.force.links().filter(function (l) {
            return l.source === node;
        });

        // Remove children nodes from model
        linksToRemove.forEach(function (l) {
            popoto.graph.node.removeNode(l.target);
        });

        // Remove links from model
        for (var i = popoto.graph.force.links().length - 1; i >= 0; i--) {
            if (linksToRemove.indexOf(popoto.graph.force.links()[i]) >= 0) {
                popoto.graph.force.links().splice(i, 1);
            }
        }

        popoto.graph.force.nodes().splice(popoto.graph.force.nodes().indexOf(node), 1);

    };

    /**
     * Function to add on node event to clear the selection.
     * Call to this function on a node will remove the selected value and triger a graph update.
     */
    popoto.graph.node.clearSelection = function () {
        // Prevent default event like right click  opening menu.
        d3.event.preventDefault();

        // Get clicked node.
        var clickedNode = d3.select(this).data()[0];

        // Collapse all expanded choose nodes first
        popoto.graph.force.nodes().forEach(function (n) {
            if ((n.type === popoto.graph.node.NodeTypes.CHOOSE || n.type === popoto.graph.node.NodeTypes.ROOT) && n.valueExpanded) {
                popoto.graph.node.collapseNode(n);
            }
        });

        if (clickedNode.value != null && !clickedNode.immutable) {
            // Remove selected value of choose node
            delete clickedNode.value;

            popoto.result.hasChanged = true;
            popoto.graph.hasGraphChanged = true;
            popoto.update();
        }
    };

// QUERY VIEWER -----------------------------------------------------------------------------------------------------
    popoto.queryviewer = {};
    popoto.queryviewer.containerId = "popoto-query";
    popoto.queryviewer.QUERY_STARTER = "I'm looking for";
    popoto.queryviewer.CHOOSE_LABEL = "choose";

    /**
     * Create the query viewer area.
     *
     */
    popoto.queryviewer.createQueryArea = function () {
        var id = "#" + popoto.queryviewer.containerId;

        popoto.queryviewer.queryConstraintSpanElements = d3.select(id).append("p").attr("class", "ppt-query-constraint-elements").selectAll(".queryConstraintSpan");
        popoto.queryviewer.querySpanElements = d3.select(id).append("p").attr("class", "ppt-query-elements").selectAll(".querySpan");
    };

    /**
     * Update all the elements displayed on the query viewer based on current graph.
     */
    popoto.queryviewer.updateQuery = function () {

        // Remove all query span elements
        popoto.queryviewer.queryConstraintSpanElements = popoto.queryviewer.queryConstraintSpanElements.data([]);
        popoto.queryviewer.querySpanElements = popoto.queryviewer.querySpanElements.data([]);

        popoto.queryviewer.queryConstraintSpanElements.exit().remove();
        popoto.queryviewer.querySpanElements.exit().remove();

        // Update data
        popoto.queryviewer.queryConstraintSpanElements = popoto.queryviewer.queryConstraintSpanElements.data(popoto.queryviewer.generateConstraintData(popoto.graph.force.links(), popoto.graph.force.nodes()));
        popoto.queryviewer.querySpanElements = popoto.queryviewer.querySpanElements.data(popoto.queryviewer.generateData(popoto.graph.force.links(), popoto.graph.force.nodes()));

        // Remove old span (not needed as all have been cleaned before)
        // popoto.queryviewer.querySpanElements.exit().remove();

        // Add new span
        popoto.queryviewer.queryConstraintSpanElements.enter().append("span")
            .on("contextmenu", popoto.queryviewer.rightClickSpan)
            .on("click", popoto.queryviewer.clickSpan)
            .on("mouseover", popoto.queryviewer.mouseOverSpan)
            .on("mouseout", popoto.queryviewer.mouseOutSpan);

        popoto.queryviewer.querySpanElements.enter().append("span")
            .on("contextmenu", popoto.queryviewer.rightClickSpan)
            .on("click", popoto.queryviewer.clickSpan)
            .on("mouseover", popoto.queryviewer.mouseOverSpan)
            .on("mouseout", popoto.queryviewer.mouseOutSpan);

        // Update all span
        popoto.queryviewer.queryConstraintSpanElements
            .attr("id", function (d) {
                return d.id
            })
            .attr("class", function (d) {
                if (d.isLink) {
                    return "ppt-span-link";
                } else {
                    if (d.type === popoto.graph.node.NodeTypes.ROOT) {
                        return "ppt-span-root";
                    } else if (d.type === popoto.graph.node.NodeTypes.CHOOSE) {
                        if (d.ref.value) {
                            return "ppt-span-value";
                        } else {
                            return "ppt-span-choose";
                        }
                    } else if (d.type === popoto.graph.node.NodeTypes.VALUE) {
                        return "ppt-span-value";
                    } else if (d.type === popoto.graph.node.NodeTypes.GROUP) {
                        return "ppt-span-group";
                    } else {
                        return "ppt-span";
                    }
                }
            })
            .text(function (d) {
                return d.term + " ";
            });

        popoto.queryviewer.querySpanElements
            .attr("id", function (d) {
                return d.id
            })
            .attr("class", function (d) {
                if (d.isLink) {
                    return "ppt-span-link";
                } else {
                    if (d.type === popoto.graph.node.NodeTypes.ROOT) {
                        return "ppt-span-root";
                    } else if (d.type === popoto.graph.node.NodeTypes.CHOOSE) {
                        if (d.ref.value) {
                            return "ppt-span-value";
                        } else {
                            return "ppt-span-choose";
                        }
                    } else if (d.type === popoto.graph.node.NodeTypes.VALUE) {
                        return "ppt-span-value";
                    } else if (d.type === popoto.graph.node.NodeTypes.GROUP) {
                        return "ppt-span-group";
                    } else {
                        return "ppt-span";
                    }
                }
            })
            .text(function (d) {
                return d.term + " ";
            });
    };

    popoto.queryviewer.generateConstraintData = function (links, nodes) {
        var elmts = [], id = 0;

        // Add
        elmts.push(
            {id: id++, term: popoto.queryviewer.QUERY_STARTER}
        );

        // Add the root node as query term
        if (nodes.length > 0) {
            elmts.push(
                {id: id++, type: nodes[0].type, term: popoto.provider.getSemanticValue(nodes[0]), ref: nodes[0]}
            );
        }

        // Add a span for each link and its target node
        links.forEach(function (l) {

            var sourceNode = l.source;
            var targetNode = l.target;
            if (l.type === popoto.graph.link.LinkTypes.RELATION && targetNode.type !== popoto.graph.node.NodeTypes.GROUP && targetNode.value) {
                if (sourceNode.type === popoto.graph.node.NodeTypes.GROUP) {
                    elmts.push(
                        {id: id++, type: sourceNode.type, term: popoto.provider.getSemanticValue(sourceNode), ref: sourceNode}
                    );
                }

                elmts.push({id: id++, isLink: true, term: popoto.provider.getLinkSemanticValue(l), ref: l});

                if (targetNode.type !== popoto.graph.node.NodeTypes.GROUP) {
                    if (targetNode.value) {
                        elmts.push(
                            {id: id++, type: targetNode.type, term: popoto.provider.getSemanticValue(targetNode), ref: targetNode}
                        );
                    } else {
                        elmts.push(
                            {id: id++, type: targetNode.type, term: "<" + popoto.queryviewer.CHOOSE_LABEL + " " + popoto.provider.getSemanticValue(targetNode) + ">", ref: targetNode}
                        );
                    }
                }
            }
        });

        return elmts;
    };

    // TODO add option nodes in generated query when no value is available
    popoto.queryviewer.generateData = function (links, nodes) {
        var elmts = [], options = [], id = 0;

        // Add a span for each link and its target node
        links.forEach(function (l) {

            var sourceNode = l.source;
            var targetNode = l.target;

            if (targetNode.type === popoto.graph.node.NodeTypes.GROUP) {
                options.push(
                    {id: id++, type: targetNode.type, term: popoto.provider.getSemanticValue(targetNode), ref: targetNode}
                );
            }

            if (l.type === popoto.graph.link.LinkTypes.RELATION && targetNode.type !== popoto.graph.node.NodeTypes.GROUP && !targetNode.value) {
                if (sourceNode.type === popoto.graph.node.NodeTypes.GROUP) {
                    elmts.push(
                        {id: id++, type: sourceNode.type, term: popoto.provider.getSemanticValue(sourceNode), ref: sourceNode}
                    );
                }

                elmts.push({id: id++, isLink: true, term: popoto.provider.getLinkSemanticValue(l), ref: l});

                if (targetNode.type !== popoto.graph.node.NodeTypes.GROUP) {
                    if (targetNode.value) {
                        elmts.push(
                            {id: id++, type: targetNode.type, term: popoto.provider.getSemanticValue(targetNode), ref: targetNode}
                        );
                    } else {
                        elmts.push(
                            {id: id++, type: targetNode.type, term: "<" + popoto.queryviewer.CHOOSE_LABEL + " " + popoto.provider.getSemanticValue(targetNode) + ">", ref: targetNode}
                        );
                    }
                }
            }
        });

        return elmts.concat(options);
    };

    /**
     *
     */
    popoto.queryviewer.mouseOverSpan = function () {
        d3.select(this).classed("hover", function (d) {
            return d.ref;
        });

        var hoveredSpan = d3.select(this).data()[0];

        if (hoveredSpan.ref) {
            var linkElmt = popoto.graph.svg.selectAll("#" + popoto.graph.link.gID + " > g").filter(function (d) {
                return d === hoveredSpan.ref;
            });
            linkElmt.select("path").classed("ppt-link-hover", true);
            linkElmt.select("text").classed("ppt-link-hover", true);

            var nodeElmt = popoto.graph.svg.selectAll("#" + popoto.graph.node.gID + " > g").filter(function (d) {
                return d === hoveredSpan.ref;
            });

            nodeElmt.select(".ppt-g-node-background").selectAll("circle").transition().style("fill-opacity", 0.5);
        }
    };

    popoto.queryviewer.rightClickSpan = function () {
        var hoveredSpan = d3.select(this).data()[0];

        if (!hoveredSpan.isLink && hoveredSpan.ref) {
            var nodeElmt = popoto.graph.svg.selectAll("#" + popoto.graph.node.gID + " > g").filter(function (d) {
                return d === hoveredSpan.ref;
            });

            nodeElmt.on("contextmenu").call(nodeElmt.node(), hoveredSpan.ref);
        }
    };

    popoto.queryviewer.clickSpan = function () {
        var hoveredSpan = d3.select(this).data()[0];

        if (!hoveredSpan.isLink && hoveredSpan.ref) {
            var nodeElmt = popoto.graph.svg.selectAll("#" + popoto.graph.node.gID + " > g").filter(function (d) {
                return d === hoveredSpan.ref;
            });

            nodeElmt.on("click").call(nodeElmt.node(), hoveredSpan.ref);
        }
    };

    /**
     *
     */
    popoto.queryviewer.mouseOutSpan = function () {
        d3.select(this).classed("hover", false);

        var hoveredSpan = d3.select(this).data()[0];

        if (hoveredSpan.ref) {
            var linkElmt = popoto.graph.svg.selectAll("#" + popoto.graph.link.gID + " > g").filter(function (d) {
                return d === hoveredSpan.ref;
            });
            linkElmt.select("path").classed("ppt-link-hover", false);
            linkElmt.select("text").classed("ppt-link-hover", false);

            var nodeElmt = popoto.graph.svg.selectAll("#" + popoto.graph.node.gID + " > g").filter(function (d) {
                return d === hoveredSpan.ref;
            });
            nodeElmt.select(".ppt-g-node-background").selectAll("circle").transition().style("fill-opacity", 0);
        }
    };

// CYPHER VIEWER -----------------------------------------------------------------------------------------------------

    // TODO not available yet
    popoto.cypherviewer = {};
    popoto.cypherviewer.containerId = "popoto-cypher";

// QUERY ------------------------------------------------------------------------------------------------------------
    popoto.query = {};
    /**
     * Define the number of results displayed in result list.
     */
    popoto.query.RESULTS_PAGE_SIZE = 100;
    popoto.query.VALUE_QUERY_LIMIT = 1000;
    popoto.query.USE_PARENT_RELATION = false;
    popoto.query.USE_RELATION_DIRECTION = true;

    /**
     * Immutable constant object to identify Neo4j internal ID
     */
    popoto.query.NEO4J_INTERNAL_ID = Object.freeze({queryInternalName: "NEO4JID"});

    /**
     * Function used to filter returned relations
     * return false if the result should be filtered out.
     *
     * @param d relation returned object
     * @returns {boolean}
     */
    popoto.query.filterRelation = function (d) {
        return true;
    };

    /**
     * Generate the query to count nodes of a label.
     * If the label is defined as distinct in configuration the query will count only distinct values on constraint attribute.
     */
    popoto.query.generateTaxonomyCountQuery = function (label) {
        var constraintAttr = popoto.provider.getConstraintAttribute(label);

        var whereElements = [];

        var predefinedConstraints = popoto.provider.getPredefinedConstraints(label);
        predefinedConstraints.forEach(function (predefinedConstraint) {
            whereElements.push(predefinedConstraint.replace(new RegExp("\\$identifier", 'g'), "n"));
        });

        if (constraintAttr === popoto.query.NEO4J_INTERNAL_ID) {
            return "MATCH (n:`" + label + "`)" + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN count(DISTINCT ID(n)) as count"
        } else {
            return "MATCH (n:`" + label + "`)" + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN count(DISTINCT n." + constraintAttr + ") as count"
        }
    };

    /**
     * Generate Cypher query match and where elements from root node, selected node and a set of the graph links.
     *
     * @param rootNode root node in the graph.
     * @param selectedNode graph target node.
     * @param links list of links subset of the graph.
     * @returns {{matchElements: Array, whereElements: Array}}  list of match and where elements.
     * @param isConstraintNeeded
     */
    popoto.query.generateQueryElements = function (rootNode, selectedNode, links, isConstraintNeeded) {
        var matchElements = [];
        var whereElements = [];
        var rel = popoto.query.USE_RELATION_DIRECTION ? "->" : "-";

        var rootPredefinedConstraints = popoto.provider.getPredefinedConstraints(rootNode.label);

        rootPredefinedConstraints.forEach(function (predefinedConstraint) {
            whereElements.push(predefinedConstraint.replace(new RegExp("\\$identifier", 'g'), rootNode.internalLabel));
        });

        // Generate root node match element
        if (rootNode.value && (isConstraintNeeded || rootNode.immutable)) {
            var rootConstraintAttr = popoto.provider.getConstraintAttribute(rootNode.label);
            if (rootConstraintAttr === popoto.query.NEO4J_INTERNAL_ID) {
                matchElements.push("(" + rootNode.internalLabel + ":`" + rootNode.label + "`)");
                whereElements.push("ID(" + rootNode.internalLabel + ") = " + rootNode.value.internalID);
            } else {
                var constraintValue = rootNode.value.attributes[rootConstraintAttr];

                if (typeof constraintValue === "boolean" || typeof constraintValue === "number") {
                    matchElements.push("(" + rootNode.internalLabel + ":`" + rootNode.label + "`{`" + rootConstraintAttr + "`:" + constraintValue + "})");
                } else {
                    matchElements.push("(" + rootNode.internalLabel + ":`" + rootNode.label + "`{`" + rootConstraintAttr + "`:\"" + constraintValue + "\"})");
                }
            }
        } else {
            matchElements.push("(" + rootNode.internalLabel + ":`" + rootNode.label + "`)");
        }

        // Generate match elements for each links
        links.forEach(function (l) {
            var sourceNode = l.source;
            var targetNode = l.target;

            var predefinedConstraints = popoto.provider.getPredefinedConstraints(targetNode.label);

            predefinedConstraints.forEach(function (predefinedConstraint) {
                whereElements.push(predefinedConstraint.replace(new RegExp("\\$identifier", 'g'), targetNode.internalLabel));
            });

            if (targetNode.value && targetNode !== selectedNode && (isConstraintNeeded || rootNode.immutable)) {
                var constraintAttr = popoto.provider.getConstraintAttribute(targetNode.label);
                var constraintValue = targetNode.value.attributes[constraintAttr];
                if (constraintAttr === popoto.query.NEO4J_INTERNAL_ID) {
                    matchElements.push("(" + sourceNode.internalLabel + ":`" + sourceNode.label + "`)-[:`" + l.label + "`]" + rel + "(" + targetNode.internalLabel + ":`" + targetNode.label + "`)");
                    whereElements.push("ID(" + targetNode.internalLabel + ") = " + targetNode.value.internalID);
                } else {
                    if (typeof constraintValue === "boolean" || typeof constraintValue === "number") {
                        matchElements.push("(" + sourceNode.internalLabel + ":`" + sourceNode.label + "`)-[:`" + l.label + "`]" + rel + "(" + targetNode.internalLabel + ":`" + targetNode.label + "`{`" + constraintAttr + "`:" + constraintValue + "})");
                    } else {
                        matchElements.push("(" + sourceNode.internalLabel + ":`" + sourceNode.label + "`)-[:`" + l.label + "`]" + rel + "(" + targetNode.internalLabel + ":`" + targetNode.label + "`{`" + constraintAttr + "`:\"" + constraintValue + "\"})");
                    }
                }
            } else {
                matchElements.push("(" + sourceNode.internalLabel + ":`" + sourceNode.label + "`)-[:`" + l.label + "`]" + rel + "(" + targetNode.internalLabel + ":`" + targetNode.label + "`)");
            }
        });

        return {"matchElements": matchElements, "whereElements": whereElements};
    };

    /**
     * Filter links to get only paths from root to leaf containing a value or being the selectedNode.
     * All other paths in the graph containing no value are ignored.
     *
     * @param rootNode root node of the graph.
     * @param targetNode node in the graph target of the query.
     * @param initialLinks list of links repreasenting the graph to filter.
     * @returns {Array} list of relevant links.
     */
    popoto.query.getRelevantLinks = function (rootNode, targetNode, initialLinks) {

        var links = initialLinks.slice();
        var filteredLinks = [];
        var finalLinks = [];

        // Filter all links to keep only those containing a value or being the selected node.
        links.forEach(function (l) {
            if (l.target.value || l.target === targetNode) {
                filteredLinks.push(l);
            }
        });

        // All the filtered links are removed from initial links list.
        filteredLinks.forEach(function (l) {
            links.splice(links.indexOf(l), 1);
        });

        // Then all the intermediate links up to the root node are added to get only the relevant links.
        filteredLinks.forEach(function (fl) {
            var sourceNode = fl.source;
            var search = true;

            while (search) {
                var intermediateLink = null;
                links.forEach(function (l) {
                    if (l.target === sourceNode) {
                        intermediateLink = l;
                    }
                });

                if (intermediateLink === null) { // no intermediate links needed
                    search = false
                } else {
                    if (intermediateLink.source === rootNode) {
                        finalLinks.push(intermediateLink);
                        links.splice(links.indexOf(intermediateLink), 1);
                        search = false;
                    } else {
                        finalLinks.push(intermediateLink);
                        links.splice(links.indexOf(intermediateLink), 1);
                        sourceNode = intermediateLink.source;
                    }
                }
            }
        });

        return filteredLinks.concat(finalLinks);
    };

    /**
     * Get the list of link defining the complete path from node to root.
     * All other links are ignored.
     *
     * @param node The node where to start in the graph.
     * @param links
     */
    popoto.query.getLinksToRoot = function (node, links) {
        var pathLinks = [];
        var targetNode = node;

        while (targetNode !== popoto.graph.getRootNode()) {
            var nodeLink;

            for (var i = 0; i < links.length; i++) {
                var link = links[i];
                if (link.target === targetNode) {
                    nodeLink = link;
                    break;
                }
            }

            if (nodeLink) {
                pathLinks.push(nodeLink);
                targetNode = nodeLink.source;
            }
        }

        return pathLinks;
    };

    /**
     * Generate a Cypher query to retrieve all the relation available for a given node.
     *
     * @param targetNode
     * @returns {string}
     */
    popoto.query.generateLinkQuery = function (targetNode) {

        var linksToRoot = popoto.query.getLinksToRoot(targetNode, popoto.graph.force.links());
        var queryElements = popoto.query.generateQueryElements(popoto.graph.getRootNode(), targetNode, linksToRoot, false);
        var matchElements = queryElements.matchElements,
            returnElements = [],
            whereElements = queryElements.whereElements,
            endElements = [];
        var rel = popoto.query.USE_RELATION_DIRECTION ? "->" : "-";

        matchElements.push("(" + targetNode.internalLabel + ":`" + targetNode.label + "`)-[r]" + rel + "(x)");
        returnElements.push("type(r) AS relationship");
        if (popoto.query.USE_PARENT_RELATION) {
            returnElements.push("head(labels(x)) AS label");
        } else {
            //returnElements.push("last(labels(x)) AS label");
            returnElements.push("labels(x) AS label");
        }
        returnElements.push("count(r) AS count");
        endElements.push("ORDER BY count(r) DESC");

        return "MATCH " + matchElements.join(", ") + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN " + returnElements.join(", ") + " " + endElements.join(" ");
    };

    /**
     * Generate a Cypher query
     * @returns {string}
     */
    popoto.query.generateResultCypherQuery = function () {

        var rootNode = popoto.graph.getRootNode();
        var queryElements = popoto.query.generateQueryElements(rootNode, rootNode, popoto.query.getRelevantLinks(rootNode, rootNode, popoto.graph.force.links()), true);
        var matchElements = queryElements.matchElements,
            returnElements = [],
            whereElements = queryElements.whereElements,
            endElements = [];

        // Sort results by specified attribute
        var resultOrderByAttribute = popoto.provider.getResultOrderByAttribute(rootNode.label);
        if (resultOrderByAttribute) {
            var order = popoto.provider.isResultOrderAscending(rootNode.label) ? "ASC" : "DESC";
            endElements.push("ORDER BY " + resultOrderByAttribute + " " + order);
        }

        endElements.push("LIMIT " + popoto.query.RESULTS_PAGE_SIZE);

        var resultAttributes = popoto.provider.getReturnAttributes(rootNode.label);
        var constraintAttribute = popoto.provider.getConstraintAttribute(rootNode.label);

        for (var i = 0; i < resultAttributes.length; i++) {
            var attribute = resultAttributes[i];
            if (attribute === popoto.query.NEO4J_INTERNAL_ID) {
                if (attribute == constraintAttribute) {
                    returnElements.push("ID(" + rootNode.internalLabel + ") AS " + popoto.query.NEO4J_INTERNAL_ID.queryInternalName);
                } else {
                    returnElements.push("COLLECT(DISTINCT ID(" + rootNode.internalLabel + ")) AS " + popoto.query.NEO4J_INTERNAL_ID.queryInternalName);
                }
            } else {
                if (attribute == constraintAttribute) {
                    returnElements.push(rootNode.internalLabel + "." + attribute + " AS " + attribute);
                } else {
                    returnElements.push("COLLECT(DISTINCT " + rootNode.internalLabel + "." + attribute + ") AS " + attribute);
                }
            }
        }

        return "MATCH " + matchElements.join(", ") + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN DISTINCT " + returnElements.join(", ") + " " + endElements.join(" ");
    };

    popoto.query.generateResultCypherQueryCount = function () {

        var rootNode = popoto.graph.getRootNode();
        var queryElements = popoto.query.generateQueryElements(rootNode, rootNode, popoto.query.getRelevantLinks(rootNode, rootNode, popoto.graph.force.links()), true);
        var constraintAttribute = popoto.provider.getConstraintAttribute(rootNode.label);
        var matchElements = queryElements.matchElements,
            returnElements = [],
            whereElements = queryElements.whereElements,
            endElements = [];

        if (constraintAttribute === popoto.query.NEO4J_INTERNAL_ID) {
            returnElements.push("count(DISTINCT ID(" + rootNode.internalLabel + ")) AS count");
        } else {
            returnElements.push("count(DISTINCT " + rootNode.internalLabel + "." + constraintAttribute + ") AS count");
        }

        return "MATCH " + matchElements.join(", ") + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN " + returnElements.join(", ") + (endElements.length > 0 ? " " + endElements.join(" ") : "");
    };

    /**
     * Generate the query to update node counts.
     *
     * @param countedNode the counted node
     * @returns {string} the node count cypher query;
     */
    popoto.query.generateNodeCountCypherQuery = function (countedNode) {

        var queryElements = popoto.query.generateQueryElements(popoto.graph.getRootNode(), countedNode, popoto.query.getRelevantLinks(popoto.graph.getRootNode(), countedNode, popoto.graph.force.links()), true);
        var matchElements = queryElements.matchElements,
            whereElements = queryElements.whereElements,
            returnElements = [];

        var countAttr = popoto.provider.getConstraintAttribute(countedNode.label);

        if (countAttr === popoto.query.NEO4J_INTERNAL_ID) {
            returnElements.push("count(DISTINCT ID(" + countedNode.internalLabel + ")) as count");
        } else {
            returnElements.push("count(DISTINCT " + countedNode.internalLabel + "." + countAttr + ") as count");
        }

        return "MATCH " + matchElements.join(", ") + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN " + returnElements.join(", ");
    };

    /**
     * Generate a Cypher query from the graph model to get all the possible values for the targetNode element.
     *
     * @param targetNode node in the graph to get the values.
     * @returns {string} the query to execute to get all the values of targetNode corresponding to the graph.
     */
    popoto.query.generateValueQuery = function (targetNode) {

        var rootNode = popoto.graph.getRootNode();
        var queryElements = popoto.query.generateQueryElements(rootNode, targetNode, popoto.query.getRelevantLinks(rootNode, targetNode, popoto.graph.force.links()), true);
        var matchElements = queryElements.matchElements,
            endElements = [],
            whereElements = queryElements.whereElements,
            returnElements = [];

        // Sort results by specified attribute
        var valueOrderByAttribute = popoto.provider.getValueOrderByAttribute(targetNode.label);
        if (valueOrderByAttribute) {
            var order = popoto.provider.isValueOrderAscending(targetNode.label) ? "ASC" : "DESC";
            endElements.push("ORDER BY " + valueOrderByAttribute + " " + order);
        }

        endElements.push("LIMIT " + popoto.query.VALUE_QUERY_LIMIT);

        var resultAttributes = popoto.provider.getReturnAttributes(targetNode.label);
        var constraintAttribute = popoto.provider.getConstraintAttribute(targetNode.label);

        for (var i = 0; i < resultAttributes.length; i++) {
            if (resultAttributes[i] === popoto.query.NEO4J_INTERNAL_ID) {
                if (resultAttributes[i] == constraintAttribute) {
                    returnElements.push("ID(" + targetNode.internalLabel + ") AS " + popoto.query.NEO4J_INTERNAL_ID.queryInternalName);
                } else {
                    returnElements.push("COLLECT (DISTINCT ID(" + targetNode.internalLabel + ")) AS " + popoto.query.NEO4J_INTERNAL_ID.queryInternalName);
                }
            } else {
                if (resultAttributes[i] == constraintAttribute) {
                    returnElements.push(targetNode.internalLabel + "." + resultAttributes[i] + " AS " + resultAttributes[i]);
                } else {
                    returnElements.push("COLLECT(DISTINCT " + targetNode.internalLabel + "." + resultAttributes[i] + ") AS " + resultAttributes[i]);
                }
            }
        }

        // Add count return attribute on root node
        var rootConstraintAttr = popoto.provider.getConstraintAttribute(rootNode.label);

        if (rootConstraintAttr === popoto.query.NEO4J_INTERNAL_ID) {
            returnElements.push("count(DISTINCT ID(" + rootNode.internalLabel + ")) AS count");
        } else {
            returnElements.push("count(DISTINCT " + rootNode.internalLabel + "." + rootConstraintAttr + ") AS count");
        }

        return "MATCH " + matchElements.join(", ") + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN DISTINCT " + returnElements.join(", ") + " " + endElements.join(" ");
    };

    ///////////////////////////////////////////////////////////////////
    // Results

    popoto.result = {};
    popoto.result.containerId = "popoto-results";
    popoto.result.hasChanged = true;
    popoto.result.resultCountListeners = [];
    popoto.result.resultListeners = [];

    /**
     * Register a listener to the result count event.
     * This listener will be called on evry result change with total result count.
     */
    popoto.result.onTotalResultCount = function (listener) {
        popoto.result.resultCountListeners.push(listener);
    };

    popoto.result.onResultReceived = function (listener) {
        popoto.result.resultListeners.push(listener);
    };

    /**
     * Parse REST returned data and generate a list of result objects.
     *
     * @param data
     * @returns {Array}
     */
    popoto.result.parseResultData = function (data) {

        var results = [];
        if (data.results && data.results.length > 0) {
            for (var x = 0; x < data.results[0].data.length; x++) {

                var obj = {
                    "resultIndex": x,
                    "label": popoto.graph.getRootNode().label,
                    "attributes": {}
                };

                for (var i = 0; i < data.results[0].columns.length; i++) {
                    // Some results can be an array as collect is used in query
                    // So all values are converted to string
                    obj.attributes[data.results[0].columns[i]] = "" + data.results[0].data[x].row[i];
                }

                results.push(obj);
            }
        }

        return results;
    };

    popoto.result.updateResults = function () {
        if (popoto.result.hasChanged) {
            var query = popoto.query.generateResultCypherQuery();

            // FIXME temporary cypher query update here. To be replaced by real interactive cypher viewer.
            if (popoto.cypherviewer.isActive) {
                d3.select("#" + popoto.cypherviewer.containerId)
                    // In this temporary version only the match part of the query is displayed to avoid huge query with lot of return attributes.
                    .text(query.split("RETURN")[0] + " RETURN " + popoto.graph.getRootNode().internalLabel);
            }

            popoto.logger.info("Results ==> ");
            popoto.rest.post(
                {
                    "statements": [
                        {
                            "statement": query
                        }]
                })
                .done(function (data) {

                    if (data.errors && data.errors.length > 0) {
                        popoto.logger.error("Cypher query error:" + JSON.stringify(data.errors));
                    }

                    // Parse data
                    var resultObjects = popoto.result.parseResultData(data);

                    // Notify listeners
                    popoto.result.resultListeners.forEach(function (listener) {
                        listener(resultObjects);
                    });

                    // Update displayed results only if needed ()
                    if (popoto.result.isActive) {
                        // Clear all results
                        var results = d3.select("#" + popoto.result.containerId).selectAll(".ppt-result").data([]);
                        results.exit().remove();

                        // Update data
                        results = d3.select("#" + popoto.result.containerId).selectAll(".ppt-result").data(resultObjects, function (d) {
                            return d.resultIndex;
                        });

                        // Add new elements
                        var pElmt = results.enter()
                            .append("p")
                            .attr("class", "ppt-result")
                            .attr("id", function (d) {
                                return "popoto-result-" + d.resultIndex;
                            });

                        // Generate results with providers
                        pElmt.each(function (d) {
                            popoto.provider.getDisplayResultFunction(d.label)(d3.select(this));
                        });
                    }

                    popoto.result.hasChanged = false;
                })
                .fail(function (xhr, textStatus, errorThrown) {
                    popoto.logger.error(textStatus + ": error while accessing Neo4j server on URL:\"" + popoto.rest.CYPHER_URL + "\" defined in \"popoto.rest.CYPHER_URL\" property: " + errorThrown);

                    // Notify listeners
                    popoto.result.resultListeners.forEach(function (listener) {
                        listener([]);
                    });

                });

            // Execute query to get total result count
            // But only if needed, if listeners have been added
            if (popoto.result.resultCountListeners.length > 0) {
                popoto.logger.info("Results count ==> ");
                popoto.rest.post(
                    {
                        "statements": [
                            {
                                "statement": popoto.query.generateResultCypherQueryCount()
                            }]
                    })
                    .done(function (data) {

                        if (data.errors && data.errors.length > 0) {
                            popoto.logger.error("Cypher query error:" + JSON.stringify(data.errors));
                        }

                        var count = 0;

                        if (data.results && data.results.length > 0) {
                            count = data.results[0].data[0].row[0];
                        }

                        popoto.result.resultCountListeners.forEach(function (listener) {
                            listener(count);
                        });

                    })
                    .fail(function (xhr, textStatus, errorThrown) {
                        popoto.logger.error(textStatus + ": error while accessing Neo4j server on URL:\"" + popoto.rest.CYPHER_URL + "\" defined in \"popoto.rest.CYPHER_URL\" property: " + errorThrown);

                        popoto.result.resultCountListeners.forEach(function (listener) {
                            listener(0);
                        });
                    });
            }
        }
    };

// NODE LABEL PROVIDERS -----------------------------------------------------------------------------------------------------

    popoto.provider = {};
    popoto.provider.linkProvider = {};
    popoto.provider.taxonomyProvider = {};
    popoto.provider.nodeProviders = {};

    /**
     *  Get the text representation of a link.
     *
     * @param link the link to get the text representation.
     * @returns {string} the text representation of the link.
     */
    popoto.provider.getLinkTextValue = function (link) {
        if (popoto.provider.linkProvider.hasOwnProperty("getLinkTextValue")) {
            return popoto.provider.linkProvider.getLinkTextValue(link);
        } else {
            if (popoto.provider.DEFAULT_LINK_PROVIDER.hasOwnProperty("getLinkTextValue")) {
                return popoto.provider.DEFAULT_LINK_PROVIDER.getLinkTextValue(link);
            } else {
                popoto.logger.error("No provider defined for getLinkTextValue");
            }
        }
    };

    /**
     *  Get the semantic text representation of a link.
     *
     * @param link the link to get the semantic text representation.
     * @returns {string} the semantic text representation of the link.
     */
    popoto.provider.getLinkSemanticValue = function (link) {
        if (popoto.provider.linkProvider.hasOwnProperty("getLinkSemanticValue")) {
            return popoto.provider.linkProvider.getLinkSemanticValue(link);
        } else {
            if (popoto.provider.DEFAULT_LINK_PROVIDER.hasOwnProperty("getLinkSemanticValue")) {
                return popoto.provider.DEFAULT_LINK_PROVIDER.getLinkSemanticValue(link);
            } else {
                popoto.logger.error("No provider defined for getLinkSemanticValue");
            }
        }
    };

    /**
     * Label provider used by default if none have been defined for a label.
     * This provider can be changed if needed to customize default behavior.
     * If some properties are not found in user customized providers, default values will be extracted from this provider.
     */
    popoto.provider.DEFAULT_LINK_PROVIDER = Object.freeze(
        {
            /**
             * Function used to return the text representation of a link.
             *
             * The default behavior is to return the internal relation name as text for relation links.
             * 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.
             *
             * @param link the link to represent as text.
             * @returns {string} the text representation of the link.
             */
            "getLinkTextValue": function (link) {
                if (link.type === popoto.graph.link.LinkTypes.VALUE) {
                    // Links between node and list of values.

                    if (popoto.provider.isTextDisplayed(link.target)) {
                        // Don't display text on link if text is displayed on target node.
                        return "";
                    } else {
                        // No text is displayed on target node then the text is displayed on link.
                        return popoto.provider.getTextValue(link.target);
                    }

                } else {

                    // Link
                    return link.label
                }
            },

            /**
             * Function used to return a descriptive text representation of a link.
             * This representation should be more complete than getLinkTextValue and can contain semantic data.
             * This function is used for example to generate the label in the query viewer.
             *
             * The default behavior is to return the getLinkTextValue.
             *
             * @param link the link to represent as text.
             * @returns {string} the text semantic representation of the link.
             */
            "getLinkSemanticValue": function (link) {
                return popoto.provider.getLinkTextValue(link);
            }
        });
    popoto.provider.linkProvider = popoto.provider.DEFAULT_LINK_PROVIDER;

    /**
     *  Get the text representation of a taxonomy.
     *
     * @param label the label used for the taxonomy.
     * @returns {string} the text representation of the taxonomy.
     */
    popoto.provider.getTaxonomyTextValue = function (label) {
        if (popoto.provider.taxonomyProvider.hasOwnProperty("getTextValue")) {
            return popoto.provider.taxonomyProvider.getTextValue(label);
        } else {
            if (popoto.provider.DEFAULT_TAXONOMY_PROVIDER.hasOwnProperty("getTextValue")) {
                return popoto.provider.DEFAULT_TAXONOMY_PROVIDER.getTextValue(label);
            } else {
                popoto.logger.error("No provider defined for taxonomy getTextValue");
            }
        }
    };

    /**
     * Label provider used by default if none have been defined for a label.
     * This provider can be changed if needed to customize default behavior.
     * If some properties are not found in user customized providers, default values will be extracted from this provider.
     */
    popoto.provider.DEFAULT_TAXONOMY_PROVIDER = Object.freeze(
        {
            /**
             * Function used to return the text representation of a taxonomy.
             *
             * The default behavior is to return the label without changes.
             *
             * @param label the label used to represent the taxonomy.
             * @returns {string} the text representation of the taxonomy.
             */
            "getTextValue": function (label) {
                return label;
            }
        });
    popoto.provider.taxonomyProvider = popoto.provider.DEFAULT_TAXONOMY_PROVIDER;

    /**
     * Define the different type of rendering of a node for a given label.
     * TEXT: default rendering type, the node will be displayed with an ellipse and a text in it.
     * IMAGE: the node is displayed as an image using the image tag in the svg graph.
     * In this case an image path is required.
     * SVG: the node is displayed using a list of svg path, each path can contain its own color.
     */
    popoto.provider.NodeDisplayTypes = Object.freeze({TEXT: 0, IMAGE: 1, SVG: 2});

    /**
     * Get the label provider for the given label.
     * If no provider is defined for the label:
     * First search in parent provider.
     * Then if not found will create one from default provider.
     *
     * @param label to retrieve the corresponding label provider.
     * @returns {object} corresponding label provider.
     */
    popoto.provider.getProvider = function (label) {
        if (label === undefined) {
            popoto.logger.error("Node label is undefined, no label provider can be found.");
        } else {
            if (popoto.provider.nodeProviders.hasOwnProperty(label)) {
                return popoto.provider.nodeProviders[label];
            } else {
                popoto.logger.debug("No direct provider found for label " + label);

                // Search in all children list definitions to find the parent provider.
                for (var p in popoto.provider.nodeProviders) {
                    if (popoto.provider.nodeProviders.hasOwnProperty(p)) {
                        var provider = popoto.provider.nodeProviders[p];
                        if (provider.hasOwnProperty("children")) {
                            if (provider["children"].indexOf(label) > -1) {
                                popoto.logger.debug("No provider is defined for label (" + label + "), parent (" + p + ") will be used");
                                // A provider containing the required label in its children definition has been found it will be cloned.

                                var newProvider = {"parent": p};
                                for (var pr in provider) {
                                    if (provider.hasOwnProperty(pr) && pr != "children" && pr != "parent") {
                                        newProvider[pr] = provider[pr];
                                    }
                                }

                                popoto.provider.nodeProviders[label] = newProvider;
                                return popoto.provider.nodeProviders[label];
                            }
                        }
                    }
                }

                popoto.logger.debug("No label provider defined for label (" + label + ") default one will be created from popoto.provider.DEFAULT_PROVIDER");

                popoto.provider.nodeProviders[label] = {};
                // Clone default provider properties in new provider.
                for (var prop in popoto.provider.DEFAULT_PROVIDER) {
                    if (popoto.provider.DEFAULT_PROVIDER.hasOwnProperty(prop)) {
                        popoto.provider.nodeProviders[label][prop] = popoto.provider.DEFAULT_PROVIDER[prop];
                    }
                }
                return popoto.provider.nodeProviders[label];
            }
        }
    };

    /**
     * Get the property or function defined in node label provider.
     * If the property is not found search is done in parents.
     * If not found in parent, property defined in popoto.provider.DEFAULT_PROVIDER is returned.
     * If not found in default provider, defaultValue is set and returned.
     *
     * @param label node label to get the property in its provider.
     * @param name name of the property to retrieve.
     * @returns {*} node property defined in its label provider.
     */
    popoto.provider.getProperty = function (label, name) {
        var provider = popoto.provider.getProvider(label);

        if (!provider.hasOwnProperty(name)) {
            var providerIterator = provider;

            // Check parents
            var isPropertyFound = false;
            while (providerIterator.hasOwnProperty("parent") && !isPropertyFound) {
                providerIterator = popoto.provider.getProvider(providerIterator.parent);
                if (providerIterator.hasOwnProperty(name)) {

                    // Set attribute in child to optimize next call.
                    provider[name] = providerIterator[name];
                    isPropertyFound = true;
                }
            }

            if (!isPropertyFound) {
                popoto.logger.debug("No \"" + name + "\" property found for node label provider (" + label + "), default value will be used");
                if (popoto.provider.DEFAULT_PROVIDER.hasOwnProperty(name)) {
                    provider[name] = popoto.provider.DEFAULT_PROVIDER[name];
                } else {
                    popoto.logger.error("No default value for \"" + name + "\" property found for label provider (" + label + ")");
                }
            }
        }
        return provider[name];
    };

    /**
     * Return the "isSearchable" property for the node label provider.
     * Is Searchable defined whether the label can be used as graph query builder root.
     * If true the label can be displayed in the taxonomy filter.
     *
     * @param label
     * @returns {*}
     */
    popoto.provider.getIsSearchable = function (label) {
        return popoto.provider.getProperty(label, "isSearchable");
    };

    /**
     * Return the list of attributes defined in node label provider.
     * Parents return attributes are also returned.
     *
     * @param label used to retrieve parent attributes.
     * @returns {Array} list of return attributes for a node.
     */
    popoto.provider.getReturnAttributes = function (label) {
        var provider = popoto.provider.getProvider(label);
        var attributes = {}; // Object is used as a Set to merge possible duplicate in parents

        if (provider.hasOwnProperty("returnAttributes")) {
            for (var i = 0; i < provider.returnAttributes.length; i++) {
                if (provider.returnAttributes[i] === popoto.query.NEO4J_INTERNAL_ID) {
                    attributes[popoto.query.NEO4J_INTERNAL_ID.queryInternalName] = true;
                } else {
                    attributes[provider.returnAttributes[i]] = true;
                }
            }
        }

        // Add parent attributes
        while (provider.hasOwnProperty("parent")) {
            provider = popoto.provider.getProvider(provider.parent);
            if (provider.hasOwnProperty("returnAttributes")) {
                for (var j = 0; j < provider.returnAttributes.length; j++) {
                    if (provider.returnAttributes[j] === popoto.query.NEO4J_INTERNAL_ID) {
                        attributes[popoto.query.NEO4J_INTERNAL_ID.queryInternalName] = true;
                    } else {
                        attributes[provider.returnAttributes[j]] = true;
                    }
                }
            }
        }

        // Add default provider attributes if any but not internal id as this id is added only if none has been found.
        if (popoto.provider.DEFAULT_PROVIDER.hasOwnProperty("returnAttributes")) {
            for (var k = 0; k < popoto.provider.DEFAULT_PROVIDER.returnAttributes.length; k++) {
                if (popoto.provider.DEFAULT_PROVIDER.returnAttributes[k] !== popoto.query.NEO4J_INTERNAL_ID) {
                    attributes[popoto.provider.DEFAULT_PROVIDER.returnAttributes[k]] = true;
                }
            }
        }

        // Add constraint attribute in the list
        var constraintAttribute = popoto.provider.getConstraintAttribute(label);
        if (constraintAttribute === popoto.query.NEO4J_INTERNAL_ID) {
            attributes[popoto.query.NEO4J_INTERNAL_ID.queryInternalName] = true;
        } else {
            attributes[constraintAttribute] = true;
        }


        // Add all in array
        var attrList = [];
        for (var attr in attributes) {
            if (attributes.hasOwnProperty(attr)) {
                if (attr == popoto.query.NEO4J_INTERNAL_ID.queryInternalName) {
                    attrList.push(popoto.query.NEO4J_INTERNAL_ID);
                } else {
                    attrList.push(attr);
                }
            }
        }

        // If no attributes have been found internal ID is used
        if (attrList.length <= 0) {
            attrList.push(popoto.query.NEO4J_INTERNAL_ID);
        }
        return attrList;
    };

    /**
     * Return the attribute to use as constraint attribute for a node defined in its label provider.
     *
     * @param label
     * @returns {*}
     */
    popoto.provider.getConstraintAttribute = function (label) {
        return popoto.provider.getProperty(label, "constraintAttribute");
    };

    /**
     * Return a list of predefined constraint defined in the node label configuration.
     *
     * @param label
     * @returns {*}
     */
    popoto.provider.getPredefinedConstraints = function (label) {
        return popoto.provider.getProperty(label, "getPredefinedConstraints")();
    };


    popoto.provider.getValueOrderByAttribute = function (label) {
        return popoto.provider.getProperty(label, "valueOrderByAttribute");
    };

    popoto.provider.isValueOrderAscending = function (label) {
        return popoto.provider.getProperty(label, "isValueOrderAscending");
    };

    popoto.provider.getResultOrderByAttribute = function (label) {
        return popoto.provider.getProperty(label, "resultOrderByAttribute");
    };

    popoto.provider.isResultOrderAscending = function (label) {
        return popoto.provider.getProperty(label, "isResultOrderAscending");
    };

    /**
     * Return the value of the getTextValue function defined in the label provider corresponding to the parameter node.
     * If no "getTextValue" function is defined in the provider, search is done in parents.
     * If none is found in parent default provider method is used.
     *
     * @param node
     */
    popoto.provider.getTextValue = function (node) {
        return popoto.provider.getProperty(node.label, "getTextValue")(node);
    };


    /**
     * Return the value of the getTextValue function defined in the label provider corresponding to the parameter node.
     * If no "getTextValue" function is defined in the provider, search is done in parents.
     * If none is found in parent default provider method is used.
     *
     * @param node
     */
    popoto.provider.getTextValue = function (node) {
        return popoto.provider.getProperty(node.label, "getTextValue")(node);
    };

    /**
     * Return the value of the getSemanticValue function defined in the label provider corresponding to the parameter node.
     * The semantic value is a more detailed description of the node used for example in the query viewer.
     * If no "getTextValue" function is defined in the provider, search is done in parents.
     * If none is found in parent default provider method is used.
     *
     * @param node
     * @returns {*}
     */
    popoto.provider.getSemanticValue = function (node) {
        return popoto.provider.getProperty(node.label, "getSemanticValue")(node);
    };

    /**
     * Return a list of SVG paths objects, each defined by a "d" property containing the path and "f" property for the color.
     *
     * @param node
     * @returns {*}
     */
    popoto.provider.getSVGPaths = function (node) {
        return popoto.provider.getProperty(node.label, "getSVGPaths")(node);
    };

    /**
     * Check in label provider if text must be displayed with images nodes.
     * @param node
     * @returns {*}
     */
    popoto.provider.isTextDisplayed = function (node) {
        return popoto.provider.getProperty(node.label, "getIsTextDisplayed")(node);
    };

    /**
     * Return the getIsGroup property.
     *
     * @param node
     * @returns {*}
     */
    popoto.provider.getIsGroup = function (node) {
        return popoto.provider.getProperty(node.label, "getIsGroup")(node);
    };

    /**
     * Return the node display type.
     * can be TEXT, IMAGE, SVG or GROUP.
     *
     * @param node
     * @returns {*}
     */
    popoto.provider.getNodeDisplayType = function (node) {
        return popoto.provider.getProperty(node.label, "getDisplayType")(node);
    };

    /**
     * Return the file path of the image defined in the provider.
     *
     * @param node the node to get the image path.
     * @returns {string} the path of the node image.
     */
    popoto.provider.getImagePath = function (node) {
        return popoto.provider.getProperty(node.label, "getImagePath")(node);
    };

    /**
     * Return the width size of the node image.
     *
     * @param node the node to get the image width.
     * @returns {int} the image width.
     */
    popoto.provider.getImageWidth = function (node) {
        return popoto.provider.getProperty(node.label, "getImageWidth")(node);
    };

    /**
     * Return the height size of the node image.
     *
     * @param node the node to get the image height.
     * @returns {int} the image height.
     */
    popoto.provider.getImageHeight = function (node) {
        return popoto.provider.getProperty(node.label, "getImageHeight")(node);
    };

    /**
     * Return the displayResults function defined in label parameter's provider.
     *
     * @param label
     * @returns {*}
     */
    popoto.provider.getDisplayResultFunction = function (label) {
        return popoto.provider.getProperty(label, "displayResults");
    };

    /**
     * Select the label if there is more than one.
     * 
     * Discards the label with an underscore.
     */
    popoto.provider.getLabelFilter = function (nodeLabel) {
        if (Array.isArray(nodeLabel)) {
            // use last label
            var label = nodeLabel[nodeLabel.length - 1];
            if (label.indexOf('_') != -1 && nodeLabel.length > 1) {
                // skip if wrong label
                label = nodeLabel[nodeLabel.length - 2];
            }
            // replace array with string
            nodeLabel = label;
        }
        return nodeLabel;
    }
    
    /**
     * Label provider used by default if none have been defined for a label.
     * This provider can be changed if needed to customize default behavior.
     * If some properties are not found in user customized providers, default values will be extracted from this provider.
     */
    popoto.provider.DEFAULT_PROVIDER = Object.freeze(
        {
            /**********************************************************************
             * Label specific parameters:
             *
             * These attributes are specific to a node label and will be used for every node having this label.
             **********************************************************************/

            /**
             * Defines whether this label can be used as root element of the graph query builder.
             * This property is also used to determine whether the label can be displayed in the taxonomy filter.
             *
             * The default value is true.
             */
            "isSearchable": true,

            /**
             * Defines the list of attribute to return for node of this label.
             * All the attributes listed here will be added in generated cypher queries and available in result list and in node provider's functions.
             *
             * The default value contains only the Neo4j internal id.
             * This id is used by default because it is a convenient way to identify a node when nothing is known about its attributes.
             * But you should really consider using your application attributes instead, it is a bad practice to rely on this attribute.
             */
            "returnAttributes": [popoto.query.NEO4J_INTERNAL_ID],

            /**
             * Defines the attribute used to order the value displayed on node.
             *
             * Default value is "count" attribute.
             */
            "valueOrderByAttribute": "count",

            /**
             * Defines whether the value query order by is ascending, if false order by will be descending.
             *
             * Default value is false (descending)
             */
            "isValueOrderAscending": false,

            /**
             * Defines the attribute used to order the results.
             *
             * Default value is "null" to disable order by.
             */
            "resultOrderByAttribute": null,

            /**
             * Defines whether the result query order by is ascending, if false order by will be descending.
             *
             * Default value is true (ascending)
             */
            "isResultOrderAscending": true,

            /**
             * Defines the attribute of the node to use in query constraint for nodes of this label.
             * This attribute is used in the generated cypher query to build the constraints with selected values.
             *
             * The default value is the Neo4j internal id.
             * This id is used by default because it is a convenient way to identify a node when nothing is known about its attributes.
             * But you should really consider using your application attributes instead, it is a bad practice to rely on this attribute.
             */
            "constraintAttribute": popoto.query.NEO4J_INTERNAL_ID,

            /**
             * Defines the attribute of the node to display as a text identifying the node.
             * 
             * The default value is the Neo4j internal id.
             */
            "displayAttribute": popoto.query.NEO4J_INTERNAL_ID,
            
            /**
             * Return the list of predefined constraints to add for the given label.
             * These constraints will be added in every generated Cypher query.
             *
             * For example if the returned list contain ["$identifier.born > 1976"] for "Person" nodes everywhere in popoto.js the generated Cypher query will add the constraint
             * "WHERE person.born > 1976"
             *
             * @returns {Array}
             */
            "getPredefinedConstraints": function () {
                return [];
            },

            /**********************************************************************
             * Node specific parameters:
             *
             * These attributes are specific to nodes (in graph or query viewer) for a given label.
             * But they can be customized for nodes of the same label.
             * The parameters are defined by a function that will be called with the node as parameter.
             * In this function the node internal attributes can be used to customize the value to return.
             **********************************************************************/

            /**
             * Function returning the display type of a node.
             * This type defines how the node will be drawn in the graph.
             *
             * The result must be one of the following values:
             *
             * popoto.provider.NodeDisplayTypes.IMAGE
             *  In this case the node will be drawn as an image and "getImagePath" function is required to return the node image path.
             *
             * popoto.provider.NodeDisplayTypes.SVG
             *  In this case the node will be drawn as SVG paths and "getSVGPaths"
             *
             * popoto.provider.NodeDisplayTypes.TEXT
             *  In this case the node will be drawn as a simple ellipse.
             *
             * Default value is TEXT.
             *
             * @param node the node to extract its type.
             * @returns {number} one value from popoto.provider.NodeDisplayTypes
             */
            "getDisplayType": function (node) {
                return popoto.provider.NodeDisplayTypes.TEXT;
            },

            /**
             * Function defining whether the node is a group node.
             * In this case no count are displayed and no value can be selected on the node.
             *
             * Default value is false.
             */
            "getIsGroup": function (node) {
                return false;
            },

            /**
             * Function defining whether the node text representation must be displayed on graph.
             * If true the value returned for getTextValue on node will be displayed on graph.
             *
             * This text will be added in addition to the getDisplayType representation.
             * It can be displayed on all type of node display, images, SVG or text.
             *
             * Default value is true
             *
             * @param node the node to display on graph.
             * @returns {boolean} true if text must be displayed on graph for the node.
             */
            "getIsTextDisplayed": function (node) {
                return true;
            },

            /**
             * Function used to return the text representation of a node.
             *
             * The default behavior is to return the label of the node
             * or the value of constraint attribute of the node if it contains value.
             *
             * The returned value is truncated using popoto.graph.node.NODE_MAX_CHARS property.
             *
             * @param node the node to represent as text.
             * @returns {string} the text representation of the node.
             */
            "getTextValue": function (node) {
                var text;
                var textAttr = popoto.provider.getProperty(node.label, "displayAttribute");
                if (node.type === popoto.graph.node.NodeTypes.VALUE) {
                    if (textAttr === popoto.query.NEO4J_INTERNAL_ID) {
                        text = "" + node.internalID;
                    } else {
                        text = "" + node.attributes[textAttr];
                    }
                } else {
                    if (node.value === undefined) {
                        text = node.label;
                    } else {
                        if (textAttr === popoto.query.NEO4J_INTERNAL_ID) {
                            text = "" + node.value.internalID;
                        } else {
                            text = "" + node.value.attributes[textAttr];
                        }
                    }
                }
                // Text is truncated to fill the ellipse
                return text.substring(0, popoto.graph.node.NODE_MAX_CHARS);
            },

            /**
             * Function used to return a descriptive text representation of a link.
             * This representation should be more complete than getTextValue and can contain semantic data.
             * This function is used for example to generate the label in the query viewer.
             *
             * The default behavior is to return the getTextValue not truncated.
             *
             * @param node the node to represent as text.
             * @returns {string} the text semantic representation of the node.
             */
            "getSemanticValue": function (node) {
                var text;
                var textAttr = popoto.provider.getProperty(node.label, "displayAttribute");
                if (node.type === popoto.graph.node.NodeTypes.VALUE) {
                    if (textAttr === popoto.query.NEO4J_INTERNAL_ID) {
                        text = "" + node.internalID;
                    } else {
                        text = "" + node.attributes[textAttr];
                    }
                } else {
                    if (node.value === undefined) {
                        text = node.label;
                    } else {
                        if (textAttr === popoto.query.NEO4J_INTERNAL_ID) {
                            text = "" + node.value.internalID;
                        } else {
                            text = "" + node.value.attributes[textAttr];
                        }
                    }
                }
                return text;
            },

            /**
             * Function returning the image file path to use for a node.
             * This function is only used for popoto.provider.NodeDisplayTypes.IMAGE type nodes.
             *
             * @param node
             * @returns {string}
             */
            "getImagePath": function (node) {
                if (node.type === popoto.graph.node.NodeTypes.VALUE) {
                    return "css/image/node-yellow.png";
                } else {
                    if (node.value === undefined) {
                        if (node.type === popoto.graph.node.NodeTypes.ROOT) {
                            return "css/image/node-blue.png";
                        }
                        if (node.type === popoto.graph.node.NodeTypes.CHOOSE) {
                            return "css/image/node-green.png";
                        }
                        if (node.type === popoto.graph.node.NodeTypes.GROUP) {
                            return "css/image/node-black.png";
                        }
                    } else {
                        return "css/image/node-orange.png";
                    }
                }
            },

            /**
             * Function returning the image width of the node.
             * This function is only used for popoto.provider.NodeDisplayTypes.IMAGE type nodes.
             *
             * @param node
             * @returns {number}
             */
            "getImageWidth": function (node) {
                return 125;
            },

            /**
             * Function returning the image height of the node.
             * This function is only used for popoto.provider.NodeDisplayTypes.IMAGE type nodes.
             *
             * @param node
             * @returns {number}
             */
            "getImageHeight": function (node) {
                return 125;
            },

            /**********************************************************************
             * Results specific parameters:
             *
             * These attributes are specific to result display.
             **********************************************************************/

            /**
             * Generate the result entry content using d3.js mechanisms.
             *
             * The parameter of the function is the &lt;p&gt; selected with d3.js
             *
             * The default behavior is to generate a &lt;table&gt; containing all the return attributes in a &lt;th&gt; and their value in a &lt;td&gt;.
             *
             * @param pElmt the &lt;p&gt; element generated in the result list.
             */
            "displayResults": function (pElmt) {
                var result = pElmt.data()[0];

                var returnAttributes = popoto.provider.getReturnAttributes(result.label);

                var table = pElmt.append("table").attr("class", "ppt-result-table");

                returnAttributes.forEach(function (attribute) {
                    var tr = table.append("tr");
                    tr.append("th").text(function () {
                        return attribute + ":";
                    });
                    if (result.attributes[attribute] !== undefined) {
                        tr.append("td").text(function (result) {
                            return result.attributes[attribute];
                        });
                    }
                });
            }

        });

    return popoto;
}();