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
<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