Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
ng-click="setGraphMode('map', true)"
tooltip="Map" tooltip-placement="bottom"><i class="fa fa-map-marker"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-if="paragraph.result.type == 'TABLE'"
ng-class="{'active': isGraphMode('forceLayout')}"
ng-click="setGraphMode('forceLayout', true)"><i class="fa fa-share-alt"></i>
</button>

<button type="button"
ng-if="paragraph.result.type != 'TABLE'"
Expand Down
7 changes: 7 additions & 0 deletions zeppelin-web/src/app/notebook/paragraph/paragraph-graph.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,11 @@
<span>Maps require internet connectivity.</span>
</span>
</div>

<div ng-if="getGraphMode()=='forceLayout'"
id="p{{paragraph.id}}_forceLayout"
class="forceLayout">
<svg></svg>
</div>

</div>
69 changes: 68 additions & 1 deletion zeppelin-web/src/app/notebook/paragraph/paragraph-pivot.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</ul>
</div>

<div class="row" ng-if="getGraphMode()!='scatterChart' && getGraphMode()!='map'">
<div class="row" ng-if="getGraphMode()!='scatterChart' && getGraphMode()!='map' && getGraphMode()!='forceLayout'">
<div class="col-md-4">
<span class="columns lightBold">
Keys
Expand Down Expand Up @@ -213,4 +213,71 @@
</span>
</div>
</div>

<div class="row" ng-if="getGraphMode()=='forceLayout'">
<div class="col-md-3">
<span class="columns lightBold">
Source
<ul data-drop="true"
ng-model="paragraph.config.graph.forceLayout.source"
jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
class="list-unstyled"
style="height:36px">
<li ng-if="paragraph.config.graph.forceLayout.source">
<div class="btn btn-primary btn-xs">
{{paragraph.config.graph.forceLayout.source.name}} <span class="fa fa-close" ng-click="removeForceLayoutOptionSource($index)"></span>
</div>
</li>
</ul>
</span>
</div>
<div class="col-md-3">
<span class="columns lightBold">
Source Group
<ul data-drop="true"
ng-model="paragraph.config.graph.forceLayout.sourceGroup"
jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
class="list-unstyled"
style="height:36px">
<li ng-if="paragraph.config.graph.forceLayout.sourceGroup">
<div class="btn btn-success btn-xs">
{{paragraph.config.graph.forceLayout.sourceGroup.name}} <span class="fa fa-close" ng-click="removeForceLayoutOptionSourceGroup($index)"></span>
</div>
</li>
</ul>
</span>
</div>
<div class="col-md-3">
<span class="columns lightBold">
Destination
<ul data-drop="true"
ng-model="paragraph.config.graph.forceLayout.dest"
jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
class="list-unstyled"
style="height:36px">
<li ng-if="paragraph.config.graph.forceLayout.dest">
<div class="btn btn-info btn-xs">
{{paragraph.config.graph.forceLayout.dest.name}} <span class="fa fa-close" ng-click="removeForceLayoutOptionDest($index)"></span>
</div>
</li>
</ul>
</span>
</div>
<div class="col-md-3">
<span class="columns lightBold">
Destination Group
<ul data-drop="true"
ng-model="paragraph.config.graph.forceLayout.destGroup"
jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
class="list-unstyled"
style="height:36px">
<li ng-if="paragraph.config.graph.forceLayout.destGroup">
<div class="btn btn-warning btn-xs">
{{paragraph.config.graph.forceLayout.destGroup.name}} <span class="fa fa-close" ng-click="removeForceLayoutOptionDestGroup($index)"></span>
</div>
</li>
</ul>
</span>
</div>
</div>
</div>
202 changes: 200 additions & 2 deletions zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
};

var setD3Chart = function(type, data, refresh) {
if (!$scope.chart[type]) {
if (!$scope.chart[type] && type !== 'forceLayout') {
var chart = nv.models[type]();
$scope.chart[type] = chart;
}
Expand Down Expand Up @@ -1087,6 +1087,8 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
$scope.chart[type].showDistX(true)
.showDistY(true);
//handle the problem of tooltip not showing when muliple points have same value.
} else if (type === 'forceLayout') {
var forceLayoutData = setForceLayoutData(data, 1000);
} else {
var p = pivot(data);
if (type === 'pieChart') {
Expand Down Expand Up @@ -1158,10 +1160,150 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
nv.utils.windowResize($scope.chart[type].update);
};

var renderForceLayout = function() {
var color = d3.scale.category10();
var r = 5;
var margin = {top: -5, right: -5, bottom: -5, left: -5};
var height = $scope.paragraph.config.graph.height;
var width = angular.element('#p' + $scope.paragraph.id + '_' + 'resize').width();

function zoomed() {
container.attr('transform', 'translate(' + d3.event.translate + ')scale(' + d3.event.scale + ')');
}

function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
/*jshint validthis:true */
d3.select(this).classed('dragging', true);
force.start();
}

function dragged(d) {
/*jshint validthis:true */
d3.select(this).attr('cx', d.x = d3.event.x).attr('cy', d.y = d3.event.y);
}

function dragended(d) {
/*jshint validthis:true */
d3.select(this).classed('dragging', false);
}

d3.select('#p' + $scope.paragraph.id + '_' + type + ' svg g').remove();
d3.select('#p' + $scope.paragraph.id + '_' + type + ' svg text').remove();
d3.select('#p' + $scope.paragraph.id + '_' + type + ' div.tooltip').remove();

if (forceLayoutData) {
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);

var zoom = d3.behavior.zoom()
.scaleExtent([0, 10])
.on('zoom', zoomed);

var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on('dragstart', dragstarted)
.on('drag', dragged)
.on('dragend', dragended);

var svg = d3.select('#p' + $scope.paragraph.id + '_' + type + ' svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.right + ')')
.call(zoom);

var tooltip = d3.select('#p' + $scope.paragraph.id + '_' + type).append('div')
.attr('class', 'tooltip')
.style('opacity', 0);

svg.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', 'none')
.style('pointer-events', 'all');

var container = svg.append('g')
.attr('class', 'container');

force.nodes(forceLayoutData.nodes)
.links(forceLayoutData.links)
.start();

var link = container.append('g')
.attr('class', 'links')
.selectAll('.link')
.data(forceLayoutData.links)
.enter().append('line')
.attr('class', 'link')
.style('stroke-width', function(d) { return Math.sqrt(d.value); });

var node = container.append('g')
.attr('class', 'nodes')
.selectAll('.node')
.data(forceLayoutData.nodes)
.enter().append('g')
.attr('class', 'node')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.call(drag);

node.append('circle')
.attr('r', r)
.style('fill', function(d) { return color(d.group); });

node.on('mouseover', function(d) {
var offsetX = d3.transform(container.attr('transform')).translate[0];
var offsetY = d3.transform(container.attr('transform')).translate[1];
var scale = d3.transform(container.attr('transform')).scale[0];
tooltip.transition()
.duration(200)
.style('opacity', 0.9);
tooltip.html('<strong>name: <span class=\'tooltip-text\'>' + d.name + '</span><br>' +
'group: <span class=\'tooltip-text\'>' + d.group + '</span></strong>')
.style('left', (d.px * scale + offsetX + 15) + 'px')
.style('top', (d.py * scale + offsetY - 18) + 'px');
})
.on('mouseout', function(d) {
tooltip.transition()
.duration(200)
.style('opacity', 0);
});

force.on('tick', function() {
link.attr('x1', function(d) { return d.source.x; })
.attr('y1', function(d) { return d.source.y; })
.attr('x2', function(d) { return d.target.x; })
.attr('y2', function(d) { return d.target.y; });

node.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });
});
} else {
// create 'No available data' svg message instead of graph when no data is available
var svg = d3.select('#p' + $scope.paragraph.id + '_' + type + ' svg')
.attr('width', width)
.attr('height', height)
.append('text')
.attr('x', width / 2)
.attr('y', height / 2)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.style('font-size', '18px')
.style('font-weight', 'bold')
.text('No available data (choose fields in settings)');
}
};

var retryRenderer = function() {
if (angular.element('#p' + $scope.paragraph.id + '_' + type + ' svg').length !== 0) {
try {
renderChart();
if (type !== 'forceLayout') {
renderChart();
} else {
renderForceLayout();
}
} catch (err) {
console.log('Chart drawing error %o', err);
}
Expand Down Expand Up @@ -1482,6 +1624,30 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
$scope.setGraphMode($scope.paragraph.config.graph.mode, true, false);
};

$scope.removeForceLayoutOptionSource = function(idx) {
$scope.paragraph.config.graph.forceLayout.source = null;
clearUnknownColsFromGraphOption();
$scope.setGraphMode($scope.paragraph.config.graph.mode, true, false);
};

$scope.removeForceLayoutOptionSourceGroup = function(idx) {
$scope.paragraph.config.graph.forceLayout.sourceGroup = null;
clearUnknownColsFromGraphOption();
$scope.setGraphMode($scope.paragraph.config.graph.mode, true, false);
};

$scope.removeForceLayoutOptionDest = function(idx) {
$scope.paragraph.config.graph.forceLayout.dest = null;
clearUnknownColsFromGraphOption();
$scope.setGraphMode($scope.paragraph.config.graph.mode, true, false);
};

$scope.removeForceLayoutOptionDestGroup = function(idx) {
$scope.paragraph.config.graph.forceLayout.destGroup = null;
clearUnknownColsFromGraphOption();
$scope.setGraphMode($scope.paragraph.config.graph.mode, true, false);
};

/* Clear unknown columns from graph option */
var clearUnknownColsFromGraphOption = function() {
var unique = function(list) {
Expand Down Expand Up @@ -2034,6 +2200,38 @@ angular.module('zeppelinWebApp').controller('ParagraphCtrl', function($scope, $r
};
};

var setForceLayoutData = function(data, limit) {
try {
var source = $scope.paragraph.config.graph.forceLayout.source;
var dest = $scope.paragraph.config.graph.forceLayout.dest;
var sourceGroup = $scope.paragraph.config.graph.forceLayout.sourceGroup;
var destGroup = $scope.paragraph.config.graph.forceLayout.destGroup;
var nodes = [];
var links = [];
var nodesMap = {};
angular.forEach(data.rows, function(row, index) {
if (index < limit) {
if (!(row[source.index] in nodesMap)) {
nodesMap[row[source.index]] = nodes.length;
nodes.push({name: row[source.index], group: row[sourceGroup.index]});
}
if (!(row[dest.index] in nodesMap)) {
nodesMap[row[dest.index]] = nodes.length;
nodes.push({name: row[dest.index], group: row[destGroup.index]});
}
}
});
angular.forEach(data.rows, function(row, index) {
if (index < limit) {
links.push({source: nodesMap[row[source.index]], target: nodesMap[row[dest.index]], value: 1});
}
});
return {nodes: nodes, links: links};
} catch (e) {
console.log(e.name + ': ' + e.message);
}
};

var isDiscrete = function(field) {
var getUnique = function(f) {
var uniqObj = {};
Expand Down
42 changes: 42 additions & 0 deletions zeppelin-web/src/app/notebook/paragraph/paragraph.css
Original file line number Diff line number Diff line change
Expand Up @@ -528,3 +528,45 @@ table.table-striped {
padding-left: 4px !important;
width: 20px;
}

/*
Force Layout CSS
*/

.node {
stroke: #999;
stroke-width: 1.5px;
}

.link {
stroke: #999;
stroke-opacity: .6;
}

.forceLayout div.tooltip {
position: absolute;
text-align: center;
width: auto;
padding: 4px 6px 4px 6px;
font: 12px sans-serif;
background: rgba(0, 0, 0, 0.75);
color: white;
border: 0px;
border-radius: 4px;
pointer-events: none;
}

.forceLayout div.tooltip:before {
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-right: 6px solid rgba(0, 0, 0, 0.75);
content: "";
position: absolute;
z-index: 99;
left: -6px;
top: 11px;
}

.forceLayout .tooltip-text {
color: orangered;
}