UNPKG

domo-ds

Version:

Domo dataset reviewer allowing you to see upstream and downstream dependencies for all datasets and dataflows in an instance of Domo.

1,490 lines (1,349 loc) 81 kB
<html ng-app="ds_lineage"> <head> <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" /> <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/1.0.4/angular-material.min.css" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic" /> <style type="text/css"> /* down{ display: block; margin: 4px 0 0 20px; } */ /* dataset:hover div.dataset{ background-color: #aaf; } dataflow:hover > div.dataflow{ background-color: #aaf; } */ *:focus { outline: none; } body { padding: 25px; font-family: Roboto, Helvetica Neue, sans-serif; } .container { padding: 15px; } .highlight { background-color: #0cc; color: #000 !important; font-weight: bold; border-radius: 4px; padding: 2px; } .item-title, .item-metadata { display: block; line-height: 2; } .item-title { font-weight: bold; } .autocomplete-custom-template li { border-bottom: 1px solid #ccc; height: auto; padding-top: 8px; padding-bottom: 8px; white-space: normal; } md-tooltip .md-content { height: auto; /* opacity: 1;*/ background-color: #333; line-height: 1em; padding-top: 8px; padding-bottom: 8px; font-size: 1.3em; } img.imgtiny { width: 15px; height: 15px; vertical-align: middle; } .pageHead { background-color: #444; height: 100px; color: #eee; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .pageHead img { float: left; margin-right: 20px; height: 100px; } .clear { clear: both; } .orderControl { cursor: pointer; padding: 5px; } .node circle { fill: #fff; stroke: steelblue; stroke-width: 1px; } .node circle.focus { fill: #aaf; } .node circle.picker { fill: rgba(0, 0, 0, 0); cursor: zoom-in; } .node circle.picker:hover { fill: #2a5e84; } .node text { font: 1.2em sans-serif; } .node { cursor: pointer; } /*.node:hover rect{ stroke: #000; fill: #91C3E8 !important; }*/ .node rect.hoverBox { fill: rgba(0, 0, 0, 0); } .node rect.titleBox { fill: rgba(255, 255, 255, 0.65); } .focalnode rect { display: none; } .link { fill: none; stroke: #ccc; stroke-width: 2px; } .link.pinpoint { stroke-width: 4px; stroke: #9e2216; } .node.duplicate circle.descendent, .node.duplicate circle.ancestor { fill: #fc9927 !important; } .node--internal.duplicate .titleBox { fill: #69bea8 !important; } .node--leaf.duplicate .titleBox { fill: #fc9927 !important; } .detailSection { padding: 8px; border: 1px solid rgba(0, 0, 0, 0.4); border-radius: 4px; display: inline-block; margin: 0 8px; } .detailSection .head { font-size: 1.4em; font-weight: bold; color: #444; text-align: center; border-bottom: solid thin #999; margin-bottom: 6px; } #svgholder { /* overflow: hidden; */ /* text-align: center; */ /* position: relative; */ width: calc(100%); height: calc(100% - 50px); /* border: solid thin green; */ } svg { border: solid 3px #eee; } div#details { /* background-color: rgba(255,255,255,0.95); */ /* border: 3px #ccc solid; */ font-family: inherit; } div#details .info { padding: 15px; } #details .name { font-size: 1.3em; font-weight: 400; margin-bottom: 1em; text-align: center; min-height: 2.6em; } #details table { border-collapse: collapse; } #details td.labels { text-align: right; text-transform: uppercase; font-size: 0.8em; font-weight: 700; color: #666; } #details td.field { text-align: left; font-weight: 700; padding: 0 3em 0 1em; } .href { text-decoration: none; border-bottom: dotted #aaa 1px; color: #4b87b0; cursor: pointer; font-weight: 500; margin-bottom: 3px; } .href:hover { border-bottom: none; color: #43799e; } </style> <style type="text/css"> #menu { width: 300px; height: 100%; position: fixed; top: 0px; left: 0px; } #content { width: calc(100% - 300px); height: 100%; position: fixed; top: 0px; left: 300px; } .stat { max-height: calc(100% - 200px); overflow-y: scroll; } .stat table { border-collapse: collapse; /* border: solid thin black; */ } .stat table th { color: #555; font-weight: 500; text-transform: uppercase; cursor: pointer; padding: 3px 5px; } .stat table th:hover { text-decoration: underline; background-color: #c8e2f4; } .stat table td { text-align: center; padding: 2px 4px; } .stat table td.left { text-align: left; } .stat table tr.datarow { cursor: pointer; } .stat table tr.datarow:hover { background: #bbb; } .statSearch { min-width: 40%; } </style> <!-- Libraries --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular-animate.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular-route.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular-sanitize.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-aria.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-messages.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angular_material/1.0.4/angular-material.min.js"></script> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script src="https://d3js.org/d3.v4.min.js"></script> <script src="https://d3js.org/d3-selection-multi.v1.min.js"></script> <!-- QQQQAAAAQQQQAAAAQQQQAAAAQQQQAAAAQQQQAAAAQQQQAAAAQQQQAAAA --> <script src="lib/sampleAEnetworks.js" type="text/javascript"></script> <!-- //QQQQAAAAQQQQAAAAQQQQAAAAQQQQAAAAQQQQAAAAQQQQAAAAQQQQAAAA --> <title>Dataset Analyzer</title> </head> <body ng-controller="main" ng-cloak> <div class="page"> <div id="menu"> <div class="pageHead"> <img ng-src="{{dl}}" /> <h2>Datawiz</h2> <h5>{{ i }}</h5> </div> <div style="text-align: center;"> <h4>Static Data as of<br />{{ static }}</h4> </div> <div> <md-content layout="row" layout-align="space-around start"> <md-button ng-click="$root.page='stats';" class="md-raised" ng-class="{'md-primary': $root.page=='stats'}" > Stats </md-button> <md-button ng-click="$root.page='svg';" class="md-raised" ng-class="{'md-primary': $root.page=='svg'}" > Datawiz </md-button> </md-content> </div> <div id="details" layout="column" layout-align="start stretch" ng-show="details.name"> <div> <div class="name" layout="column" layout-align="end stretch">{{ details.name }}</div> <table ng-show="details.cardInfo"> <tr> <td class="labels">Cards</td> <td class="field">{{ details.cardInfo.cardCount }}</td> </tr> <tr> <td class="labels">Card Views</td> <td class="field">{{ details.cardInfo.cardViewCount }}</td> </tr> <tr> <td class="labels">Columns</td> <td class="field">{{ details.columnCount }}</td> </tr> <tr> <td class="labels">Rows</td> <td class="field">{{ details.rowCount }}</td> </tr> <tr> <td class="labels">Type</td> <td class="field">{{ details.displayType }}</td> </tr> <tr> <td class="labels">Created</td> <td class="field">{{ details.created | date }}</td> </tr> <tr> <td class="labels">Last Touched</td> <td class="field">{{ details.lastTouched | date }}</td> </tr> <tr> <td class="labels">Last Updated</td> <td class="field">{{ details.lastUpdated | date }}</td> </tr> <tr> <td class="labels">Next Update</td> <td class="field">{{ details.nextUpdate | date }}</td> </tr> </table> <table ng-show="!details.cardInfo"> <tr> <td class="labels">Type</td> <td class="field">{{ details.databaseType }}</td> </tr> <tr> <td class="labels">Run Count</td> <td class="field">{{ details.executionCount }}</td> </tr> <tr> <td class="labels">Successful Runs</td> <td class="field">{{ details.executionSuccessCount }}</td> </tr> <tr> <td class="labels">Inputs</td> <td class="field">{{ details.inputs.length }}</td> </tr> <tr> <td class="labels">Outputs</td> <td class="field">{{ details.outputs.length }}</td> </tr> <tr> <td class="labels">Enabled</td> <td class="field">{{ details.enabled }}</td> </tr> <tr> <td class="labels">Created</td> <td class="field">{{ details.created | date }}</td> </tr> <tr> <td class="labels">Modified</td> <td class="field">{{ details.modified | date }}</td> </tr> <tr> <td class="labels">Last Run</td> <td class="field">{{ details.lastExecution.lastUpdated | date }}</td> </tr> <tr> <td class="labels">Last Duration</td> <td class="field">{{ details.duration }}</td> </tr> </table> </div> </div> </div> <div id="content" ng-init="$root.page = 'stats'; show = 'dataflows';"> <div ng-show="$root.page == 'stats'" class="statspage"> <div layout="row" layout-align="center start" class="statstopper"> <div layout="column"> <md-button ng-click="show='dataflows';" class="md-raised" ng-class="{'md-primary': show=='dataflows'}" > Dataflows </md-button> <md-button ng-click="csv.flows()"> Export </md-button> </div> <div layout="column"> <md-button ng-click="show='datasets';" class="md-raised" ng-class="{'md-primary': show=='datasets'}" > Datasets </md-button> <md-button ng-click="csv.datasets()"> Export </md-button> </div> <div layout="column" ng-show="useCards"> <md-button ng-click="show='cards';" class="md-raised" ng-class="{'md-primary': show=='cards'}" > Cards </md-button> <md-button ng-click="csv.cards()" ng-show="useCards"> Export </md-button> </div> </div> <div layout="column" layout-align="start center"> <md-input-container class="statSearch"> <label>Search</label> <input ng-model="statSearch" /> </md-input-container> </div> <div class="stat"> <div layout="column" layout-align="start center"> <table ng-init="dsOrderby='seconds'; dsOrderDir=true" ng-show="show=='datasets'"> <tr> <th ng-click="dsOrderby='name'; dsOrderDir= !(dsOrderDir && (dsOrderby=='name'));" class="orderControl" > Name </th> <th ng-click="dsOrderby='cardInfo.cardCount'; dsOrderDir= !(dsOrderDir && (dsOrderby=='cardInfo.cardCount'));" class="orderControl" > Card Count </th> <th ng-click="dsOrderby='columnCount'; dsOrderDir= !(dsOrderDir && (dsOrderby=='columnCount'));" class="orderControl" > Columns </th> <th ng-click="dsOrderby='rowCount'; dsOrderDir= !(dsOrderDir && (dsOrderby=='rowCount'));" class="orderControl" > Rows </th> <th ng-click="dsOrderby='lastUpdated'; dsOrderDir= !(dsOrderDir && (dsOrderby=='lastUpdated'))" class="orderControl" > Last Update </th> </tr> <tr ng-repeat="set in d | filter:statSearch | orderBy:dsOrderby:dsOrderDir" ng-click="$root.pick = set" class="datarow" > <td class="left">{{ set.name }}</td> <td>{{ set.cardInfo.cardCount }}</td> <td>{{ set.columnCount | number }}</td> <td>{{ set.rowCount | number }}</td> <td>{{ set.lastUpdated | date }}</td> </tr> </table> <table ng-init="dfOrderBy='seconds'; dfOrderDir=true" ng-show="show=='dataflows'"> <tr> <th ng-click="dfOrderBy='seconds'; dfOrderDir= !(dfOrderDir && (dfOrderBy=='seconds'));" class="orderControl" > Duration </th> <th ng-click="dfOrderBy='executionSuccessCount'; dfOrderDir= !(dfOrderDir && (dfOrderBy=='executionSuccessCount'));" class="orderControl" > Runs </th> <th ng-click="dfOrderBy='numInputs'; dfOrderDir= !(dfOrderDir && (dfOrderBy=='numInputs'));" class="orderControl" > Inputs </th> <th ng-click="dfOrderBy='numOutputs'; dfOrderDir= !(dfOrderDir && (dfOrderBy=='numOutputs'))" class="orderControl" > Outputs </th> <th ng-click="dfOrderBy='name'; dfOrderDir= !(dfOrderDir && (dfOrderBy=='name'));" class="orderControl" > Name </th> </tr> <tr ng-repeat="flow in f | filter:statSearch | orderBy:dfOrderBy:dfOrderDir" ng-click="$root.pick = flow" class="datarow" > <td class="left">{{ flow.duration }}</td> <td>{{ flow.executionSuccessCount }}</td> <td>{{ flow.numInputs }}</td> <td>{{ flow.numOutputs }}</td> <td class="left">{{ flow.name }}</td> </tr> </table> </div> </div> </div> <div ng-show="$root.page == 'svg'"> <div ng-show="$root.back.length>0"> <md-content> <div> <md-button ng-click="$root.goBack()" ng-disabled="$root.back.length<2"> <i class="fa fa-step-backward"></i> Back </md-button> <md-button ng-click="$root.goFwd()" ng-disabled="$root.fwd.length<1"> Forward <i class="fa fa-step-forward"></i> </md-button> </div> </md-content> </div> <div id="svgholder"></div> </div> </div> </div> <!-- END OF PAGE --> <script type="text/ng-template" id="/dataflowDetails.html"> <md-dialog style="min-width= 80%;"> <md-dialog-content> <div style="padding: 20px;"> <h1>{{me.name}}</h1> <div layout="row" > <div class="detailSection"> <div class="head">Links</div> <div layout="column" layout-align="start space-around"> <span class="href" ng-click="openLink('https://'+i+'/datacenter/dataflows/'+me.id+'/details')">Open in Domo</span> <span class="href" ng-click="closeDialog();$root.pick = me;">Re-center Chart</span> </div> </div> <div class="detailSection"> <div class="head">Info</div> </div> </div> </div> </md-dialog-content> <md-dialog-actions> <md-button ng-click="closeDialog()" class="md-primary"> Close </md-button> </md-dialog-actions> </md-dialog> </script> <script type="text/ng-template" id="/datasetDetails.html"> <md-dialog style="min-width= 80%;"> <md-dialog-content> <div style="padding: 20px;"> <h1>{{me.name}}</h1> <div layout="row" > <div class="detailSection"> <div class="head">Links</div> <div layout="column" layout-align="start space-around"> <span class="href" ng-click="openLink('https://'+i+'/datasources/'+me.id+'/details/overview')">Open in Domo</span> <span class="href" ng-click="closeDialog();$root.pick = me;">Re-center Chart</span> </div> </div> <div class="detailSection stat"> <div class="head">Cards</div> <table> <tr> <td> <label>Count:</label> </td> <td> {{me.cardInfo.cardCount}} </td> </tr> </table> <table> <tr ng-init="cardSort='kpiTitle';cardSortasc=true"> <th ng-click="cardSort='kpiTitle'; cardSortasc=!cardSortasc" >Title</th> <th ng-click="cardSort='views'; cardSortasc=!cardSortasc">Views</th> <th ng-click="cardSort='beastmodes'; cardSortasc=!cardSortasc">BeastModes</th> </tr> <tr ng-repeat="card in me.cards | orderBy:cardSort:cardSortasc"> <td style="text-align: left;"> <a ng-href="https://{{i}}/kpis/details/{{card.kpiId}}" target="_blank">{{card.kpiTitle}}</a> </td> <td> {{card.views}} </td> <td> {{card.beastmodes}} </td> </tr> </table> </div> </div> </div> </md-dialog-content> <md-dialog-actions> <md-button ng-click="closeDialog()" class="md-primary"> Close </md-button> </md-dialog-actions> </md-dialog> </script> <script type="text/javascript"> var app = angular.module("ds_lineage", ["ngSanitize", "ngMaterial", "ngAnimate"]); app.controller("main", function($scope, myData, $rootScope, $filter, $mdDialog, $timeout) { $scope.f = myData.dataFlows; $scope.d = myData.dataSets; $scope.i = myData.instance; $scope.csv = myData.csv; $scope.static = myData.staticDate; $scope.hoveredElement = 0; $scope.sl = myData.spinnerLarge; $scope.sm = myData.spinnerMed; $scope.ss = myData.spinnerSmall; $scope.dfi = myData.dataFlowImg; $scope.dsi = myData.datasetImg; $scope.dl = myData.domologo; $rootScope.showCards = false; $scope.useCards = myData.cards.length > 0; $scope.details = {}; $rootScope.page = "stats"; $scope.getMatches = function(str) { let a = []; if (str && str != "") { a = $filter("filter")($scope.d, { name: str }).concat( $filter("filter")($scope.f, { name: str }) ); } else { a = $scope.d.concat($scope.f); } return $filter("orderBy")(a, "name"); }; function buildTree(options) { var o = options; let ids = o.ids || {}; if (!o.root || !o.dir) { return []; } var me = o.root; if (ids[me.id]) { //&& me.type != 'datafusion' console.log("already found", me.id, ids[me.id], me); ids[me.id]++; if (ids[me.id] > 1) return []; //Agent - Leads by List Full } else { ids[me.id] = 1; } if ( (me.cardInfo && me.type != "datafusion") || (me.type == "datafusion" && o.dir != "up") ) { //is a dataset if (o.dir == "up") { var children = myData.getUpStream(me.id); } else { var children = myData.getDownStream(me.id); } var tree = []; children.forEach(function(child) { var branch = { name: myData.getDataflow(child.id).name, data: child }; branch[o.dir] = buildTree({ dir: o.dir, root: child, ids: ids }); tree.push(branch); }); return tree; } else if (me.inputs) { //is a dataflow if (o.dir == "up") { var children = me.inputs; } else { var children = me.outputs; } var tree = []; children.forEach(function(child) { var set = myData.getDataset(child.id); var branch = { name: set.name, data: set }; branch[o.dir] = buildTree({ dir: o.dir, root: set, ids: ids }); tree.push(branch); }); return tree; } } $rootScope.back = []; $rootScope.fwd = []; $rootScope.goBack = function() { if (!$rootScope.back.length > 2) return; $rootScope.fwd.unshift($rootScope.back.pop()); $rootScope.go($rootScope.back.pop(), true); }; $rootScope.goFwd = function() { if (!$rootScope.fwd.length > 0) return; $rootScope.go($rootScope.fwd.shift(), true); }; $rootScope.go = function(pick, shift) { if (!pick) { return; } if (!shift) { $rootScope.fwd.length = 0; } // console.log('pick', pick); $rootScope.picked = pick; $rootScope.back.push(pick); var tree = { name: pick.name, data: pick, up: buildTree({ dir: "up", root: pick }), down: buildTree({ dir: "down", root: pick }) }; if ($rootScope.page != "svg") { $rootScope.page = "svg"; } buildSVG(tree); }; $rootScope.$watch("pick", function(v) { if (!v) { return; } $rootScope.go(v); }); console.log("flows array", $scope.f); console.log("datasets array", $scope.d); function buildSVG(data) { // return; d3.select("svg.dsTree").remove(); var treeData = JSON.parse(JSON.stringify(data).replace(/"up"/g, '"children"')); var treeDataDown = JSON.parse(JSON.stringify(data).replace(/"down"/g, '"children"')); //ET+SF - Newsletter Subscriptions Engagement March 2016 // declares a tree layout and assigns the size var treemap = d3 .tree() .nodeSize([100, 300]) .separation(function(a, b) { return a.parent == b.parent ? 0.4 : 0.4; }); // assigns the data to a hierarchy using parent-child relationships var nodes = d3.hierarchy(treeData, function(d) { return d.children; }); // maps the node data to the tree layout nodes = treemap(nodes); function maxXY(node) { var xy = { y: node.x, x: node.y, ym: node.x, xm: node.y }; if (node.children) { node.children.forEach(function(me) { var myXY = maxXY(me); xy.x = myXY.x > xy.x ? myXY.x : xy.x; xy.xm = myXY.xm < xy.xm ? myXY.xm : xy.xm; xy.y = myXY.y > xy.y ? myXY.y : xy.y; xy.ym = myXY.ym < xy.ym ? myXY.ym : xy.ym; }); } return xy; } // declares a tree layout and assigns the size var treemapDown = d3 .tree() .nodeSize([100, 100]) .separation(function(a, b) { if (a.parent == b.parent) { if (b.children) { return 0.4; } else { return 0.4; } } else { return 0.4; } }); // assigns the data to a hierarchy using parent-child relationships var nodesDown = d3.hierarchy(treeDataDown, function(d) { return d.children; }); // maps the node data to the tree layout nodesDown = treemap(nodesDown); var margin = { top: 20, right: 90, bottom: 30, left: 90 }; var maxUP = maxXY(nodes); var maxDN = maxXY(nodesDown); console.log("max", maxUP, maxDN); var maxes = { width: maxUP.x + maxDN.x, height: maxUP.y - maxUP.ym > maxDN.y - maxDN.ym ? maxUP.y - maxUP.ym : maxDN.y - maxDN.ym, minX: (maxUP.x + 100 + margin.left) * -1, minY: (maxUP.ym < maxDN.ym ? maxUP.ym : maxDN.ym) - margin.top }; // maxes.width = (maxes.width < 1024) ? 1024 : maxes.width; // maxes.height = (maxes.height < 1024) ? 1024 : maxes.height; // console.log('maxes', maxes); // set the dimensions and margins of the diagram var width = maxes.width - margin.left - margin.right, height = maxes.height - margin.top - margin.bottom; var zoom = d3 .zoom() .scaleExtent([0.2, 10]) .on("zoom", redraw); //if you are sure that your zoom function is working just replace redraw with your zoom function // append the svg object to the body of the page // appends a 'group' element to 'svg' // moves the 'group' element to the top left margin var svgholder = d3 .select("#svgholder") .append("svg") .attr("width", "98%") //width + margin.left + margin.right) .attr("height", "98%") //height + margin.top + margin.bottom) .attr("class", "dsTree") .attr( "viewBox", maxes.minX + " " + maxes.minY + " " + (maxes.width + margin.left + margin.right + 350) + " " + (maxes.height + margin.top + margin.bottom) ) .call(zoom); var svg = svgholder.append("g"); var g2 = svg.append("g"); // .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var g = svg.append("g"); // .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); function redraw() { // d3.event.transform.x = Math.min(0, Math.max(d3.event.transform.x, width - width * d3.event.transform.k)); // d3.event.transform.y = Math.min(0, Math.max(d3.event.transform.y, height - height * d3.event.transform.k)); return svg.attr("transform", d3.event.transform); } var defs = svg.append("defs"); var fusionIcon = defs.append("g").attr("id", "fusionIcon"); fusionIcon.append("polygon").attrs({ points: "1.22 74.33 130 0 260 74.33 260 177.33 197.09 177.33 197 111.67 132.33 74.33 1.22 74.33", fill: "#9bcded" }); fusionIcon.append("polygon").attrs({ points: "-20 -20 -21 -20 -20 -20" }); fusionIcon.append("polygon").attrs({ points: "0 122 67.24 122 67.33 186.67 132.33 224 260 224 130 300 0 225 0 122", fill: "#9bcded" }); var dsIcon = defs.append("g").attr("id", "dsIcon"); dsIcon.append("rect").attrs({ x: "30", y: "90", width: "240", height: "45", stroke: "none", fill: "#888" }); dsIcon.append("rect").attrs({ x: "30", y: "150", width: "240", height: "45", stroke: "none", fill: "#888" }); dsIcon.append("rect").attrs({ x: "30", y: "210", width: "240", height: "45", stroke: "none", fill: "#888" }); dsIcon.append("rect").attrs({ x: "39", y: "97.5", width: "39", height: "30", stroke: "none", fill: "#fff" }); dsIcon.append("rect").attrs({ x: "39", y: "157.5", width: "39", height: "30", stroke: "none", fill: "#fff" }); dsIcon.append("rect").attrs({ x: "39", y: "217.5", width: "39", height: "30", stroke: "none", fill: "#fff" }); var dfIcon = defs.append("g"); dfIcon.attrs({ id: "dfIcon", stroke: "none", fill: "#888" }); dfIcon.append("polygon").attrs({ points: "125 30 175 30 174.67766953 160.6776695 125 150" }); dfIcon.append("polygon").attrs({ points: "30 270 30 190 110 270" }); dfIcon.append("polygon").attrs({ points: "270 270 270 190 190 270" }); dfIcon.append("polygon").attrs({ points: "87.47766953 247.8776695 52.02233047 212.6223305 132.32233047 132.3223305 174.67766953 160.6776695" }); dfIcon.append("polygon").attrs({ points: "212.6223305 247.9776695 247.9776695 212.6223305 197.6776695 162.3223305 162.3223305 197.6776695" }); var getlinks = function(d, arr) { arr.push("a" + d.data.data.id); if (!d.children) return arr; d.children.forEach(function(c) { arr = getlinks(c, arr); }); return arr; }; // adds the links between the nodes var linkDown = g2 .selectAll(".link") .data(nodesDown.descendants().slice(1)) .enter() .append("path") // .attr("class", "link") .attr("class", function(d) { var c = ["link"]; // console.log('c',c); c = getlinks(d, c); // console.log('c',c); return c.join(" "); }) .attr("d", function(d) { return ( "M" + d.y + "," + d.x + "C" + (d.y + d.parent.y) / 2 + "," + d.x + " " + (d.y + d.parent.y) / 2 + "," + d.parent.x + " " + d.parent.y + "," + d.parent.x ); }); // adds each node as a group var nodeDown = g2 .selectAll(".node") .data(nodesDown.descendants()) .enter() .append("g") .attr("class", function(d) { var focus = d.depth > 0 ? "" : " focalnode"; return ( "node" + (d.children ? " node--internal" : " node--leaf") + " c" + d.data.data.id + focus ); }) .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }); nodeDown.on("mouseover", function(d, i) { // console.log('hovered over ',d); d3.selectAll(".pinpoint").classed("pinpoint", false); d3.selectAll(`.a${d.data.data.id}`).classed("pinpoint", true); d3.selectAll(".duplicate").classed("duplicate", false); d3.selectAll(`.c${d.data.data.id}`).classed("duplicate", true); $scope.details = d.data.data; $scope.$apply(); d3.select(this).style("fill", "#000"); }); nodeDown.on("mouseout", function(d, i) { // $scope.details.forEach((data,idx,arr)=>{ // if(d.data.data=data){ // arr.splice(idx,1); // } // }) }); nodeDown.append("rect").attrs({ width: "240", height: "25", y: -12.5, x: 13, class: "titleBox" }); // adds the circle to the node nodeDown .append("circle") .attr("r", 15) .attr("class", function(d) { return "descendant"; }); nodeDown .append("use") .attr("xlink:href", function(d) { if (d.data.data.type == "datafusion") { return "#fusionIcon"; } if (d.data.data.inputs) { return "#dfIcon"; } else { return "#dsIcon"; } }) .attr("transform", function(d) { if (d.data.data.type == "datafusion") { return "scale(" + 25 / 300 + ") translate(-130,-150)"; } else { return "scale(" + 25 / 300 + ") translate(-150,-170)"; } }); // adds the text to the node nodeDown .append("text") .attr("dy", function(d) { return d.childrens ? "-1.5em" : ".35em"; }) .attr("x", function(d) { return d.childrens ? 0 : 20; }) .attr("class", "title") .style("text-anchor", function(d) { return d.childrens ? "middle" : "start"; }) .text(function(d) { if (d.data.up) { return ""; } else { return d.data.name.length > 20 ? d.data.name.substring(0, 19) + "..." : d.data.name; } }); nodeDown .append("rect") .attrs({ width: "150", height: "25", y: -12.5, x: 13, class: "hoverBox" }) .on("mouseover", function(d) { // d3.select(this).style("fill", "#91C3E8"); }) .on("mouseout", function(d) {}) .on("click", function(d) { var clickedElement = this; $scope.details = {}; $mdDialog.show({ templateUrl: d.data.data.cardInfo ? "/datasetDetails.html" : "/dataflowDetails.html", closeTo: clickedElement, openFrom: clickedElement, controller: function(scope, data, myData, $mdDialog) { scope.i = myData.instance; console.log("alert data", data); scope.datum = data; scope.me = data.data.data; scope.openLink = function(link) { $('<a href="' + link + '" target="_blank">hi</a>')[0].click(); }; scope.closeDialog = function() { $mdDialog.hide(); }; }, locals: { data: d }, clickOutsideToClose: true, fullscreen: true }); }); nodeDown .append("circle") .attr("r", function(d) { return d.data.down ? 20 : 15; }) .attr("class", function(d) { return "picker c" + d.data.data.id; }) .on("click", function(d) { $rootScope.go(d.data.data); }); // adds the links between the nodes var link = g .selectAll(".link") .data( (function() { var x = nodes.descendants().slice(1); x.forEach(function(me) { me.y = me.y * -1; }); return x; })() ) .enter() .append("path") // .attr("class", "link") .attr("class", function(d) { var c = ["link", "a" + d.data.data.id]; // console.log('c',c); c = getlinks(d, c); // console.log('c',c); return c.join(" "); }) .attr("d", function(d) { return ( "M" + d.y + "," + d.x + "C" + (d.y + d.parent.y) / 2 + "," + d.x + " " + (d.y + d.parent.y) / 2 + "," + d.parent.x + " " + d.parent.y + "," + d.parent.x ); }); // adds each node as a group var node = g .selectAll(".node") .data(nodes.descendants()) .enter() .append("g") .attr("class", function(d) { var focus = d.depth > 0 ? "" : " focalnode"; return ( "node" + (d.children ? " node--internal" : " node--leaf") + " c" + d.data.data.id + focus ); }) .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }); node.on("mouseover", function(d, i) { // console.log('hovered over ',d); d3.selectAll(".pinpoint").classed("pinpoint", false); d3.selectAll(`.a${d.data.data.id}`).classed("pinpoint", true); d3.selectAll(".duplicate").classed("duplicate", false); d3.selectAll(`.c${d.data.data.id}`).classed("duplicate", true); $scope.details = d.data.data; $scope.$apply(); }); nodeDown.on("mouseout", function(d, i) { // $scope.details.forEach((data,idx,arr)=>{ // if(d.data.data=data){ // arr.splice(idx,1); // } // }) }); node.append("rect").attrs({ width: "240", height: "25", y: -12.5, x: -250, class: "titleBox" }); // adds the circle to the node node .append("circle") .attr("r", function(d) { if (d.data.down) { return 20; } else { return 15; } }) .attr("class", function(d) { if (d.data.down) { return "focus"; } else { return "ancestor"; } }); node .append("use") .attr("xlink:href", function(d) { if (d.data.data.type == "datafusion") { return "#fusionIcon"; } if (d.data.data.inputs) { return "#dfIcon"; } else { return "#dsIcon"; } }) .attr("transform", function(d) { if (d.data.data.type == "datafusion") { return "scale(" + 25 / 300 + ") translate(-130,-150)"; } else { return "scale(" + 25 / 300 + ") translate(-150,-170)"; } }); // adds the text to the node node .append("text") .attr("dy", function(d) { return !d.data.down ? ".35em" : "-2.5em"; }) .attr("x", function(d) { return !d.data.down ? -20 : 0; }) .attr("class", "title") .style("text-anchor", function(d) { return !d.data.down ? "end" : "middle"; }) .text(function(d) { if (d.data.down) { return d.data.name; } else { return d.data.name.length > 20 ? d.data.name.substring(0, 19) + "..." : d.data.name; } }); node .append("rect") .attrs({ width: "150", height: "25", y: -12.5, x: -160, class: "hoverBox" }) .on("mouseover", function(d) { // d3.select(this).style("fill", "#91C3E8"); }) .on("mouseout", function(d) {}) .on("click", function(d) { var clickedElement = this; $mdDialog.show({ templateUrl: d.data.data.cardInfo ? "/datasetDetails.html" : "/dataflowDetails.html", closeTo: clickedElement, openFrom: clickedElement, controller: function(scope, data, myData, $mdDialog) { scope.i = myData.instance; console.log("alert data", data); scope.datum = data; scope.me = data.data.data; scope.openLink = function(link) { $('<a href="' + link + '" target="_blank">hi</a>')[0].click(); }; scope.closeDialog = function() { $mdDialog.hide(); }; }, locals: { data: d }, clickOutsideToClose: true, fullscreen: true }); }); node .append("circle") .attr("r", function(d) { return d.data.down ? 20 : 15; }) .attr("class", function(d) { return "picker c" + d.data.data.id; }) .on("click", function(d) { $rootScope.go(d.data.data); }); } }); app.filter("grab", function() { return function(input, start, end) { if (!input) { return []; } start = +start; //parse to int end = +end; return input.slice(start, end); }; }); app.value( "fusion", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAC00lEQVR42u2dS2vbUBCF5///g0KjRWjdJpuQRR6Ubrotxs6Thpokpo1oC6WWbCdObMtTyyXFGJJIiXRn5t4zcFbeGH/os5E159Kr5h9G5EL4EAAAABAAAAAEAAAAAYDCeX+S8rd0whe9Mb89SgDAVdZaPW79GPF0xv9nks34c3zDr+evAUCN+XA+5MFdxg9NMsp45+sAAOrSTdGxpCWyppuis9DSlX4tkVXdFB3tWiLrurGuJfJBN5a1RD7pxqKWyEfdWNIS+aobK1oi33VTWEu3Mloil7r5LqgbrVqikHRTSkuO7i1RiLrRpCUKWTcatESh60ZaSwTdlNfSboVaIujm+VpqVKClZwOIPNaNSy0RdCOrpVIAonaPD36O+PfNVE2ymS4trR8m/v0l+Vg2T1OO+3q+g3I7BPlc0MeLIQ/HGQBIJlfk4VyRkloKGoAGLQGAsJYA4IFfbq60BACPaOnKgZYAQFhLACCsJQAQ1lKtAN4cJYt/iF6arS99dVoaV3Q51ArgU/e6kjf5azhVdzXkN9QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACqBXA7mfFeR9f2+vZZv5Jb7bU+GVcVgPvpJmN+d5yisEkKwL8njJmb8Wix3AEAAgDuJ507WJuWggIQqpbUAQhNSyoBhKQl1QBC0JIJAD5ryQyAZS3te6QlcwB801IpAPm6vqad4FxLmhYGc0WWXVnFmmoFs1hT7ThYU8Wi9uoVKLSovZyNQKsKLvOqgmPBqoKgyzo6iso6VrXURl2N/ILGhoeFTQ0LhU0+VpbtWqwss/5ryZvSPotaqlM3KG4V1o2aLclIW3VxHFB1sSYtudYN6uuFdaN6UTtydYBDjAMcRLSkQTdhHuKjSDfmuiKilx5jFeMYK5EycK26Md+W8uRRhsp140VdTYTDPHVpyZJuUNgEAAgAAAACAACAAAAAIHn+AorhmEufxmTzAAAAAElFTkSuQmCC" ); app.value( "spinnerSmall", "data:image/gif;base64,R0lGODlhDwAPAKIAAP////D3/OLw+cXh85vL6////wAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFBQAFACwAAAAABQAFAAADBUi63M4JACH5BAUFAAUALAAABQAFAAUAAAMFSLrczgkAIfkEBQUABQAsAAAKAAUABQAAAwVIutzOCQAh+QQFBQAFACwFAAoABQAFAAADBUi63M4JACH5BAUFAAUALAUABQAFAAUAAAMFSLrczgkAIfkEBQUABQAsAAAAAAoABQAAAww4qtS9zBE45ISW2gQAIfkEBQUABQAsAAAAAA8ACgAAAx0oqtT+i70ZxaTxwqzb+F8hjiQYkqg5oKnJlu4rJgAh+QQJBQAFACwAAAAADwAPAAADKxiq1f6LvRnDpPHCrJv430SMIxg+ZGmKqSmwpAuPQ113jX3j+oAXvV8QlwAAIfkECQUABQAsAAAAAA8ADwAAAyxYqtT+i70Zy6Txwqxb+F/ngYFIkKWImqsovO8gyxMcz0Nt4/ljCzwdLPhIAAAh+QQJBQAFACwAAAAADwAPAAADLliq1P6LvRnLpPHCrFsdIIgtobiR5aiUg1qwUyDLQl3Hc2DfT67vuNlOEKQBHwkAIfkECQUABQAsBQAAAAoADwAAAyA4qtS9zBE45ISWWsH57sLXiZ4EhlKgqtbKpm4bw6uVAAAh+QQJBQAFACwFAAAACgAPAAADIyiq073MDSjkhJbawDn5X+eBhBiQpYiaaOG67VvELw2T8kwmACH5BAkFAAUALAUAAAAKAA8AAAMjGKrSvcwJGOSEltrC+fhf54GDWJCliJqrSLyvCcfuLNs1nAAAIfkECQUABQAsCgAAAAUADwAAAw4YutzOIspJ6xw46811AgAh+QQJBQAFACwKAAUABQAKAAADCRi63M4iyknrTAAh+QQJBQAFACwKAAoABQAFAAADBRi63M4JACH5BAUFAAUALAAAAAABAAEAAAMCWAkAIfkEBQUABQAsCgAKAAUABQAAAwVIutzOCQAh+QQFBQAFACwFAAoABQAFAAADBUi63M4JACH5BAUFAAUALAAACgAFAAUAAAMFSLrczgkAIfkEBQUABQAsAAAFAAUABQAAAwVIutzOCQAh+QQFBQAFACwFAAUABQAFAAADBUi63M4JACH5BAUFAAUALAoABQAFAAoAAAMJSLrcziPKSetMACH5BAUFAAUALAUAAAAKAA8AAAMdWKrUvcwRWOSElmbNu//eIIpCWY6kKaCDuqIu6yYAIfkECQUABQAsAAAAAA8ADwAAAydYqtS+kLkXJa321qmx/2AojtBgmkKaBix7oqrQum8sz+9gz0G+zwkAIfkECQUABQAsAAAAAA8ADwAAAyVIutz+MMoZh7Xy4qpz55ckjGNgmkWakuUZqCvrvjArzHBh43ACACH5BAkFAAUALAAAAAAPAA8AAAMoSLrc/jDKGYW1I+d2sR5c94FMJ4zhhTJB2xZwLLuvbNOBfdP6zPewBAAh+QQJBQAFACwAAAAADwAKAAADG0i63P4wyhmDtSLnwfnFmtB5XyiOX2COQ7qOCQAh+QQJBQAFACwAAAAADwAKAAADI0i6PO4skgflosNeqhUuIBiMo2CaoUgGJ5qubJsWcCvMdZsAACH5BAkFAAUALAAAAAAPAAoAAAMjSKrTLRAu5kaUs9o7ib5C911FaZ5Bmp5soa6t+Qax/NblnAAAIfkECQUABQAsAAAAAA8ABQAAAxI4qtIdEC7mRJSz2juHvkH3XQkAIfkECQUABQAsAAAAAAoABQAAAwwoqtG9zAUo5ISW2gQAIfkECQUABQAsAAAAAAUABQAAAwUYutzOCQAh+QQFBQAFACwAAAAAAQABAAADAlgJACH5BAkFAAUALAAAAAABAAEAAAMCWAkAOw==" ); app.value( "spinnerMed", "data:image/gif;base64,R0lGODlhGAAYAKIAAP////D3/OLw+cXh85vL6////wAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFBQAFACwAAAAACAAIAAADCEi63P4wypgAACH5BAUFAAUALAAACAAIAAgAAAMISLrc/jDKmAAAIfkEBQUABQAsAAAQAAgACAAAAwhIutz+MMqYAAAh+QQFBQAFACwIABAACAAIAAADCEi63P4wypgAACH5BAUFAAUALAgACAAIAAgAAAMISLrc/jDKmAAAIfkEBQUABQAsAAAAABAACAAAAxQ4ukr+jjEI5aLPNkz04J0GeqPIJQAh+QQFBQAFACwAAAAAGAAQAAADNii6Sv4wEsakfXTdm9uWnfCB3RiFJoSmzuC+biHPdF3AsK3P+LvvvdjPFhwMicFjraikMZuyBAAh+QQJBQAFACwAAAAAGAAYAAADTRi6Wv4wFsakfXTdm9uWXfCB3RiFJoSmjuC+7kXM9AzDck3fb64TvJjlZwv6dEHBsZZc7oxD4mBKnbIc1eq1kKVuu9YrePAFl7vn7DYBACH5BAkFAAUALAAAAAAYABgAAANOWLpK/jASxqR9dN2b25Zd8YHdGIUmhKZO4L4u28KvTNC1jMf6bu+Bn6+HswmOyONgyVxekslm8wlVSp2WqvVKrV6xEq3gO+hCyebo95IAACH5BAkFAAUALAAAAAAYABgAAANTWLpK/jASxqR9dN2b25Zd8YHdGIUmhKZOOLzwy3VxPGc1fFO5bLm9H