LineageLayoutView.js 22.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

define(['require',
    'backbone',
    'hbs!tmpl/graph/LineageLayoutView_tmpl',
    'collection/VLineageList',
    'models/VEntity',
24
    'utils/Utils',
25
    'dagreD3',
26
    'd3-tip',
27
    'utils/Enums',
28 29 30
    'utils/UrlLinks',
    'platform'
], function(require, Backbone, LineageLayoutViewtmpl, VLineageList, VEntity, Utils, dagreD3, d3Tip, Enums, UrlLinks, platform) {
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
    'use strict';

    var LineageLayoutView = Backbone.Marionette.LayoutView.extend(
        /** @lends LineageLayoutView */
        {
            _viewName: 'LineageLayoutView',

            template: LineageLayoutViewtmpl,

            /** Layout sub regions */
            regions: {},

            /** ui selector cache */
            ui: {
                graph: ".graph"
            },

            /** ui events hash */
            events: function() {
                var events = {};
                return events;
            },

            /**
             * intialize a new LineageLayoutView Layout
             * @constructs
             */
            initialize: function(options) {
59
                _.extend(this, _.pick(options, 'guid', 'entityDefCollection', 'actionCallBack', 'fetchCollection'));
60
                this.collection = new VLineageList();
61
                this.lineageData = null;
62
                this.typeMap = {};
63
                this.apiGuid = {};
64
                this.asyncFetchCounter = 0;
65
                this.edgeCall;
66 67
            },
            onRender: function() {
68
                var that = this;
69
                this.$('.fontLoader').show();
70
                this.fetchGraphData();
71 72 73
                if (platform.name === "IE") {
                    this.$('svg').css('opacity', '0');
                }
74 75 76
                if (this.layoutRendered) {
                    this.layoutRendered();
                }
77
                this.g = new dagreD3.graphlib.Graph()
78 79 80 81 82 83 84 85 86 87
                    .setGraph({
                        nodesep: 50,
                        ranksep: 90,
                        rankdir: "LR",
                        marginx: 20,
                        marginy: 20,
                        transition: function transition(selection) {
                            return selection.transition().duration(500);
                        }
                    })
88 89 90 91 92
                    .setDefaultEdgeLabel(function() {
                        return {};
                    });
            },
            fetchGraphData: function() {
93 94 95
                var that = this;
                this.fromToObj = {};
                this.collection.getLineage(this.guid, {
96
                    skipDefaultError: true,
97 98
                    success: function(data) {
                        if (data.relations.length) {
99
                            that.lineageData = data;
100 101 102 103 104
                            that.generateData(data.relations, data.guidEntityMap);
                        } else {
                            that.noLineage();
                        }
                    },
105
                    cust_error: function(model, response) {
106
                        that.lineageData = [];
107
                        that.noLineage();
108
                    }
109
                })
110
            },
111
            noLineage: function() {
112
                this.$('.fontLoader').hide();
113
                //this.$('svg').height('100');
114
                this.$('svg').html('<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle">No lineage data found</text>');
115 116 117
                if (this.actionCallBack) {
                    this.actionCallBack();
                }
118
            },
119
            generateData: function(relations, guidEntityMap) {
120 121
                var that = this;

122 123 124 125 126
                function makeNodeObj(relationObj) {
                    var obj = {};
                    obj['shape'] = "img";
                    obj['typeName'] = relationObj.typeName
                    obj['label'] = relationObj.displayText.trunc(18);
127
                    obj['toolTipLabel'] = relationObj.displayText;
128
                    obj['id'] = relationObj.guid;
129
                    obj['isLineage'] = true;
130
                    obj['queryText'] = relationObj.queryText;
131 132 133
                    if (relationObj.status) {
                        obj['status'] = relationObj.status;
                    }
134 135 136
                    var entityDef = that.entityDefCollection.fullCollection.find({ name: relationObj.typeName });
                    if (entityDef && entityDef.get('superTypes')) {
                        obj['isProcess'] = _.contains(entityDef.get('superTypes'), "Process") ? true : false;
137
                    }
138

139
                    return obj;
140 141
                }

142 143 144 145 146 147 148 149
                _.each(relations, function(obj, index) {
                    if (!that.fromToObj[obj.fromEntityId]) {
                        that.fromToObj[obj.fromEntityId] = makeNodeObj(guidEntityMap[obj.fromEntityId]);
                        that.g.setNode(obj.fromEntityId, that.fromToObj[obj.fromEntityId]);
                    }
                    if (!that.fromToObj[obj.toEntityId]) {
                        that.fromToObj[obj.toEntityId] = makeNodeObj(guidEntityMap[obj.toEntityId]);
                        that.g.setNode(obj.toEntityId, that.fromToObj[obj.toEntityId]);
150
                    }
151 152
                    var styleObj = {
                        fill: 'none',
153 154
                        stroke: '#8bc152',
                        width: 2
155
                    }
156
                    that.g.setEdge(obj.fromEntityId, obj.toEntityId, { 'arrowhead': "arrowPoint", lineInterpolate: 'basis', "style": "fill:" + styleObj.fill + ";stroke:" + styleObj.stroke + ";stroke-width:" + styleObj.width + "", 'styleObj': styleObj });
157
                });
158 159 160 161 162

                if (this.fromToObj[this.guid]) {
                    this.fromToObj[this.guid]['isLineage'] = false;
                    this.checkForLineageOrImpactFlag(relations, this.guid);
                }
163 164
                if (this.asyncFetchCounter == 0) {
                    this.createGraph();
165
                }
166
            },
167 168 169 170 171
            checkForLineageOrImpactFlag: function(relations, guid) {
                var that = this,
                    nodeFound = _.where(relations, { 'fromEntityId': guid });
                if (nodeFound.length) {
                    _.each(nodeFound, function(node) {
172 173 174 175 176 177 178 179 180 181
                        if (!node["traversed"]) {
                            node["traversed"] = true;
                            that.fromToObj[node.toEntityId]['isLineage'] = false;
                            var styleObj = {
                                fill: 'none',
                                stroke: '#fb4200',
                                width: 2
                            }
                            that.g.setEdge(node.fromEntityId, node.toEntityId, { 'arrowhead': "arrowPoint", lineInterpolate: 'basis', "style": "fill:" + styleObj.fill + ";stroke:" + styleObj.stroke + ";stroke-width:" + styleObj.width + "", 'styleObj': styleObj });
                            that.checkForLineageOrImpactFlag(relations, node.toEntityId);
182 183 184 185
                        }
                    });
                }
            },
186 187 188 189 190 191 192 193
            toggleInformationSlider: function(options) {
                if (options.open && !this.$('.lineage-edge-details').hasClass("open")) {
                    this.$('.lineage-edge-details').addClass('open');
                } else if (options.close && this.$('.lineage-edge-details').hasClass("open")) {
                    d3.selectAll('circle').attr("stroke", "none");
                    this.$('.lineage-edge-details').removeClass('open');
                }
            },
194 195 196 197 198 199 200 201 202
            setGraphZoomPositionCal: function(argument) {
                var initialScale = 1.2,
                    svgEl = this.$('svg'),
                    scaleEl = this.$('svg').find('>g'),
                    translateValue = [(this.$('svg').width() - this.g.graph().width * initialScale) / 2, (this.$('svg').height() - this.g.graph().height * initialScale) / 2]
                if (_.keys(this.g._nodes).length > 15) {
                    initialScale = 0;
                    this.$('svg').addClass('noScale');
                }
203 204 205 206 207 208 209 210 211 212
                if (svgEl.parents('.panel.panel-fullscreen').length) {
                    translateValue = [20, 20];
                    if (svgEl.hasClass('noScale')) {
                        if (!scaleEl.hasClass('scaleLinage')) {
                            scaleEl.addClass('scaleLinage');
                            initialScale = 1.2;
                        } else {
                            scaleEl.removeClass('scaleLinage');
                            initialScale = 0;
                        }
213 214 215 216 217 218 219 220 221 222 223 224 225
                    }
                } else {
                    scaleEl.removeClass('scaleLinage');
                }
                this.zoom.translate(translateValue)
                    .scale(initialScale);
            },
            zoomed: function(that) {
                this.$('svg').find('>g').attr("transform",
                    "translate(" + this.zoom.translate() + ")" +
                    "scale(" + this.zoom.scale() + ")"
                );
            },
226
            createGraph: function() {
227 228 229
                var that = this,
                    width = this.$('svg').width(),
                    height = this.$('svg').height();
230 231 232 233 234 235 236
                this.g.nodes().forEach(function(v) {
                    var node = that.g.node(v);
                    // Round the corners of the nodes
                    if (node) {
                        node.rx = node.ry = 5;
                    }
                });
237 238 239 240 241 242 243 244 245 246 247 248 249
                // Create the renderer
                var render = new dagreD3.render();
                // Add our custom arrow (a hollow-point)
                render.arrows().arrowPoint = function normal(parent, id, edge, type) {
                    var marker = parent.append("marker")
                        .attr("id", id)
                        .attr("viewBox", "0 0 10 10")
                        .attr("refX", 9)
                        .attr("refY", 5)
                        .attr("markerUnits", "strokeWidth")
                        .attr("markerWidth", 10)
                        .attr("markerHeight", 8)
                        .attr("orient", "auto");
250

251 252 253 254
                    var path = marker.append("path")
                        .attr("d", "M 0 0 L 10 5 L 0 10 z")
                        .style("stroke-width", 1)
                        .style("stroke-dasharray", "1,0")
255 256
                        .style("fill", edge.styleObj.stroke)
                        .style("stroke", edge.styleObj.stroke);
257 258 259 260
                    dagreD3.util.applyStyle(path, edge[type + "Style"]);
                };
                render.shapes().img = function circle(parent, bbox, node) {
                    //var r = Math.max(bbox.width, bbox.height) / 2,
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
                    if (node.id == that.guid) {
                        var currentNode = true
                    }
                    var shapeSvg = parent.append('circle')
                        .attr('fill', 'url(#img_' + node.id + ')')
                        .attr('r', currentNode ? '15px' : '14px')
                        .attr("class", "nodeImage " + (currentNode ? "currentNode" : (node.isProcess ? "blue" : "green")));

                    parent.insert("defs")
                        .append("pattern")
                        .attr("x", "0%")
                        .attr("y", "0%")
                        .attr("patternUnits", "objectBoundingBox")
                        .attr("id", "img_" + node.id)
                        .attr("width", "100%")
                        .attr("height", "100%")
                        .append('image')
278 279 280 281
                        .attr("xlink:href", function(d) {
                            if (node) {
                                if (node.isProcess) {
                                    if (Enums.entityStateReadOnly[node.status]) {
282
                                        return UrlLinks.apiBaseUrl + '/img/icon-gear-delete.png';
283
                                    } else if (node.id == that.guid) {
284
                                        return UrlLinks.apiBaseUrl + '/img/icon-gear-active.png';
285
                                    } else {
286
                                        return UrlLinks.apiBaseUrl + '/img/icon-gear.png';
287 288 289
                                    }
                                } else {
                                    if (Enums.entityStateReadOnly[node.status]) {
290
                                        return UrlLinks.apiBaseUrl + '/img/icon-table-delete.png';
291
                                    } else if (node.id == that.guid) {
292
                                        return UrlLinks.apiBaseUrl + '/img/icon-table-active.png';
293
                                    } else {
294
                                        return UrlLinks.apiBaseUrl + '/img/icon-table.png';
295 296
                                    }
                                }
297
                            }
298 299 300 301 302 303
                        })
                        .attr("x", "2")
                        .attr("y", "2")
                        .attr("width", currentNode ? "26" : "24")
                        .attr("height", currentNode ? "26" : "24")

304 305
                    node.intersect = function(point) {
                        //return dagreD3.intersect.circle(node, points, point);
306
                        return dagreD3.intersect.circle(node, currentNode ? 16 : 13, point);
307
                    };
308 309 310
                    return shapeSvg;
                };
                // Set up an SVG group so that we can translate the final graph.
311 312 313
                var svg = this.svg = d3.select(this.$("svg")[0])
                    .attr("viewBox", "0 0 " + width + " " + height)
                    .attr("enable-background", "new 0 0 " + width + " " + height),
314
                    svgGroup = svg.append("g");
315
                var zoom = this.zoom = d3.behavior.zoom()
316
                    .scaleExtent([0.5, 6])
317
                    .on("zoom", that.zoomed.bind(this));
318 319


320 321 322 323 324 325 326 327 328
                function interpolateZoom(translate, scale) {
                    var self = this;
                    return d3.transition().duration(350).tween("zoom", function() {
                        var iTranslate = d3.interpolate(zoom.translate(), translate),
                            iScale = d3.interpolate(zoom.scale(), scale);
                        return function(t) {
                            zoom
                                .scale(iScale(t))
                                .translate(iTranslate(t));
329
                            that.zoomed();
330 331 332
                        };
                    });
                }
333

334 335 336 337 338 339 340 341 342 343 344
                function zoomClick() {
                    var clicked = d3.event.target,
                        direction = 1,
                        factor = 0.2,
                        target_zoom = 1,
                        center = [that.g.graph().width / 2, that.g.graph().height / 2],
                        extent = zoom.scaleExtent(),
                        translate = zoom.translate(),
                        translate0 = [],
                        l = [],
                        view = { x: translate[0], y: translate[1], k: zoom.scale() };
345

346 347 348
                    d3.event.preventDefault();
                    direction = (this.id === 'zoom_in') ? 1 : -1;
                    target_zoom = zoom.scale() * (1 + factor * direction);
349

350 351 352
                    if (target_zoom < extent[0] || target_zoom > extent[1]) {
                        return false;
                    }
353

354 355 356
                    translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
                    view.k = target_zoom;
                    l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
357

358 359
                    view.x += center[0] - l[0];
                    view.y += center[1] - l[1];
360

361 362
                    interpolateZoom([view.x, view.y], view.k);
                }
363
                d3.selectAll(this.$('span.lineageZoomButton')).on('click', zoomClick);
364 365
                var tooltip = d3Tip()
                    .attr('class', 'd3-tip')
366
                    .offset([10, 0])
367 368
                    .html(function(d) {
                        var value = that.g.node(d);
369 370 371 372 373
                        var htmlStr = "";
                        if (value.id !== that.guid) {
                            htmlStr = "<h5 style='text-align: center;'>" + (value.isLineage ? "Lineage" : "Impact") + "</h5>";
                        }
                        htmlStr += "<h5 class='text-center'><span style='color:#359f89'>" + value.toolTipLabel + "</span></h5> ";
374 375 376
                        if (value.typeName) {
                            htmlStr += "<h5 class='text-center'><span>(" + value.typeName + ")</span></h5> ";
                        }
377 378 379
                        if (value.queryText) {
                            htmlStr += "<h5>Query: <span style='color:#359f89'>" + value.queryText + "</span></h5> ";
                        }
380
                        return "<div class='tip-inner-scroll'>" + htmlStr + "</div>";
381
                    });
382

383 384
                svg.call(zoom)
                    .call(tooltip);
385 386 387
                if (platform.name !== "IE") {
                    this.$('.fontLoader').hide();
                }
388 389 390 391 392 393
                render(svgGroup, this.g);
                svg.on("dblclick.zoom", null)
                    .on("wheel.zoom", null);
                //change text postion 
                svgGroup.selectAll("g.nodes g.label")
                    .attr("transform", "translate(2,-30)");
394 395
                svgGroup.selectAll("g.nodes g.node")
                    .on('mouseenter', function(d) {
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
                        that.activeNode = true;
                        var matrix = this.getScreenCTM()
                            .translate(+this.getAttribute("cx"), +this.getAttribute("cy"));
                        that.$('svg').find('.node').removeClass('active');
                        $(this).addClass('active');

                        // Fix
                        var width = $('body').width();
                        var currentELWidth = $(this).offset();
                        var direction = 'e';
                        if (((width - currentELWidth.left) < 330)) {
                            direction = (((width - currentELWidth.left) < 330) && ((currentELWidth.top) < 400)) ? 'sw' : 'w';
                            if (((width - currentELWidth.left) < 330) && ((currentELWidth.top) > 600)) {
                                direction = 'nw';
                            }
                        } else if (((currentELWidth.top) > 600)) {
                            direction = (((width - currentELWidth.left) < 330) && ((currentELWidth.top) > 600)) ? 'nw' : 'n';
                            if ((currentELWidth.left) < 50) {
                                direction = 'ne'
                            }
                        } else if ((currentELWidth.top) < 400) {
                            direction = ((currentELWidth.left) < 50) ? 'se' : 's';
                        }
                        tooltip.direction(direction).show(d)
420 421 422 423
                    })
                    .on('dblclick', function(d) {
                        tooltip.hide(d);
                        Utils.setUrl({
424
                            url: '#!/detailPage/' + d + '?tabActive=lineage',
425 426
                            mergeBrowserUrl: false,
                            trigger: true
427
                        });
428 429 430 431 432 433 434 435 436
                    }).on('mouseleave', function(d) {
                        that.activeNode = false;
                        var nodeEL = this;
                        setTimeout(function(argument) {
                            if (!(that.activeTip || that.activeNode)) {
                                $(nodeEL).removeClass('active');
                                tooltip.hide(d);
                            }
                        }, 400)
437
                    });
438 439 440 441 442 443 444 445
                svgGroup.selectAll("g.edgePath path.path").on('click', function(d) {
                    var data = { obj: _.find(that.lineageData.relations, { "fromEntityId": d.v, "toEntityId": d.w }) },
                        relationshipId = data.obj.relationshipId;
                    require(['views/graph/PropagationPropertyModal'], function(PropagationPropertyModal) {
                        var view = new PropagationPropertyModal({
                            edgeInfo: data,
                            relationshipId: relationshipId,
                            lineageData: that.lineageData,
446 447
                            apiGuid: that.apiGuid,
                            detailPageFetchCollection: that.fetchCollection
448 449 450
                        });
                    });
                })
451 452 453 454 455 456 457 458 459
                $('body').on('mouseover', '.d3-tip', function(el) {
                    that.activeTip = true;
                });
                $('body').on('mouseleave', '.d3-tip', function(el) {
                    that.activeTip = false;
                    that.$('svg').find('.node').removeClass('active');
                    tooltip.hide();
                });

460
                // Center the graph
461 462
                this.setGraphZoomPositionCal();
                zoom.event(svg);
463
                //svg.attr('height', this.g.graph().height * initialScale + 40);
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
                if (platform.name === "IE") {
                    this.IEGraphRenderDone = 0;
                    this.$('svg .edgePath').each(function(argument) {
                        var childNode = $(this).find('marker');
                        $(this).find('marker').remove();
                        var eleRef = this;
                        ++that.IEGraphRenderDone;
                        setTimeout(function(argument) {
                            $(eleRef).find('defs').append(childNode);
                            --that.IEGraphRenderDone;
                            if (that.IEGraphRenderDone === 0) {
                                this.$('.fontLoader').hide();
                                this.$('svg').fadeTo(1000, 1)
                            }
                        }, 1000);
                    });
                }
481 482 483
            }
        });
    return LineageLayoutView;
484
});