You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by fe...@apache.org on 2017/12/23 18:36:10 UTC
zeppelin git commit: [ZEPPELIN-3038] Network visualization not show
"source" and "target" node/edge properties
Repository: zeppelin
Updated Branches:
refs/heads/master 53e6f743d -> 851dcb1a3
[ZEPPELIN-3038] Network visualization not show "source" and "target" node/edge properties
### What is this PR for?
The Network visualization not show "source" and "target" node/edge properties when the graph is flattened to create the table representation.
### What type of PR is it?
[Bug Fix]
### Todos
* [x] - Fixed
### Screenshot
Before:
![zeppelin before](https://user-images.githubusercontent.com/1833335/32510305-6ee266c8-c3f0-11e7-9e28-7ed1f304ebcb.PNG)
After:
![zeppelin after](https://user-images.githubusercontent.com/1833335/32510324-8121cc3e-c3f0-11e7-9f1f-84bba563aebc.PNG)
### What is the Jira issue?
[ZEPPELIN-3038](https://issues.apache.org/jira/browse/ZEPPELIN-3038)
### How should this be tested?
Please use this [notebook](https://gist.github.com/conker84/9574127c2389d08164423894aa93b67f)
### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? no
Author: Andrea Santurbano <sa...@gmail.com>
Closes #2653 from conker84/network-fix and squashes the following commits:
d4f19b6 [Andrea Santurbano] removed unused property
44100c6 [Andrea Santurbano] Removed semicolons
273c88f [Andrea Santurbano] added test on edges prevent double rendering
5eeabc2 [Andrea Santurbano] networkdata class now shows fields "source" and "target" + added test case moved the logic related to the visualization from networkdata to visualization-d3network added some optimization
Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/851dcb1a
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/851dcb1a
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/851dcb1a
Branch: refs/heads/master
Commit: 851dcb1a392a3b720501982c6c2c4a3d30468a7e
Parents: 53e6f74
Author: Andrea Santurbano <sa...@gmail.com>
Authored: Tue Nov 7 18:39:58 2017 +0100
Committer: Felix Cheung <fe...@apache.org>
Committed: Sat Dec 23 10:34:39 2017 -0800
----------------------------------------------------------------------
zeppelin-web/src/app/tabledata/networkdata.js | 104 +++-----------
.../src/app/tabledata/networkdata.test.js | 27 +++-
.../builtins/visualization-d3network.js | 135 ++++++++++++++-----
3 files changed, 150 insertions(+), 116 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/851dcb1a/zeppelin-web/src/app/tabledata/networkdata.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/networkdata.js b/zeppelin-web/src/app/tabledata/networkdata.js
index 7983d82..70cd86b 100644
--- a/zeppelin-web/src/app/tabledata/networkdata.js
+++ b/zeppelin-web/src/app/tabledata/networkdata.js
@@ -40,42 +40,40 @@ export default class NetworkData extends TableData {
return
}
- this.setNodesDefaults()
- this.setEdgesDefaults()
-
+ this.graph.edges = this.graph.edges || []
this.networkNodes = angular.equals({}, this.graph.labels || {})
? null : {count: this.graph.nodes.length, labels: this.graph.labels}
this.networkRelationships = angular.equals([], this.graph.types || [])
? null : {count: this.graph.edges.length, types: this.graph.types}
- let rows = []
- let comment = ''
- let entities = this.graph.nodes.concat(this.graph.edges)
- let baseColumnNames = [{name: 'id', index: 0, aggr: 'sum'},
- {name: 'label', index: 1, aggr: 'sum'}]
- let internalFieldsToJump = ['count', 'size', 'totalCount',
- 'data', 'x', 'y', 'labels']
- let baseCols = _.map(baseColumnNames, function(col) { return col.name })
- let keys = _.map(entities, function(elem) { return Object.keys(elem.data || {}) })
+ const rows = []
+ const comment = ''
+ const entities = this.graph.nodes.concat(this.graph.edges)
+ const baseColumnNames = [{name: 'id', index: 0, aggr: 'sum'}]
+ const containsLabelField = _.find(entities, (entity) => 'label' in entity) != null
+ if (this.graph.labels || this.graph.types || containsLabelField) {
+ baseColumnNames.push({name: 'label', index: 1, aggr: 'sum'})
+ }
+ const internalFieldsToJump = ['count', 'size', 'totalCount',
+ 'data', 'x', 'y', 'labels', 'source', 'target']
+ const baseCols = _.map(baseColumnNames, (col) => col.name)
+ let keys = _.map(entities, (elem) => Object.keys(elem.data || {}))
keys = _.flatten(keys)
- keys = _.uniq(keys).filter(function(key) {
- return baseCols.indexOf(key) === -1
- })
- let columnNames = baseColumnNames.concat(_.map(keys, function(elem, i) {
+ keys = _.uniq(keys).filter((key) => baseCols.indexOf(key) === -1)
+ const entityColumnNames = _.map(keys, (elem, i) => {
return {name: elem, index: i + baseColumnNames.length, aggr: 'sum'}
- }))
+ })
+ const columnNames = baseColumnNames.concat(entityColumnNames)
for (let i = 0; i < entities.length; i++) {
- let entity = entities[i]
- let col = []
- let col2 = []
+ const entity = entities[i]
+ const col = []
entity.data = entity.data || {}
for (let j = 0; j < columnNames.length; j++) {
- let name = columnNames[j].name
- let value = name in entity && internalFieldsToJump.indexOf(name) === -1
+ const name = columnNames[j].name
+ const value = name in entity && internalFieldsToJump.indexOf(name) === -1
? entity[name] : entity.data[name]
- let parsedValue = value === null || value === undefined ? '' : value
+ const parsedValue = value === null || value === undefined ? '' : value
col.push(parsedValue)
- col2.push({key: name, value: parsedValue})
}
rows.push(col)
}
@@ -84,62 +82,4 @@ export default class NetworkData extends TableData {
this.columns = columnNames
this.rows = rows
}
-
- setNodesDefaults() {
- }
-
- setEdgesDefaults() {
- this.graph.edges
- .sort((a, b) => {
- if (a.source > b.source) {
- return 1
- } else if (a.source < b.source) {
- return -1
- } else if (a.target > b.target) {
- return 1
- } else if (a.target < b.target) {
- return -1
- } else {
- return 0
- }
- })
- this.graph.edges
- .forEach((edge, index) => {
- let prevEdge = this.graph.edges[index - 1]
- edge.count = (index > 0 && +edge.source === +prevEdge.source && +edge.target === +prevEdge.target
- ? prevEdge.count : 0) + 1
- edge.totalCount = this.graph.edges
- .filter((innerEdge) => +edge.source === +innerEdge.source && +edge.target === +innerEdge.target)
- .length
- })
- this.graph.edges
- .forEach((edge) => {
- if (typeof +edge.source === 'number') {
- edge.source = this.graph.nodes.filter((node) => +edge.source === +node.id)[0] || null
- }
- if (typeof +edge.target === 'number') {
- edge.target = this.graph.nodes.filter((node) => +edge.target === +node.id)[0] || null
- }
- })
- }
-
- getNetworkProperties() {
- let baseCols = ['id', 'label']
- let properties = {}
- this.graph.nodes.forEach(function(node) {
- let hasLabel = 'label' in node && node.label !== ''
- if (!hasLabel) {
- return
- }
- let label = node.label
- let hasKey = hasLabel && label in properties
- let keys = _.uniq(Object.keys(node.data || {})
- .concat(hasKey ? properties[label].keys : baseCols))
- if (!hasKey) {
- properties[label] = {selected: 'label'}
- }
- properties[label].keys = keys
- })
- return properties
- }
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/851dcb1a/zeppelin-web/src/app/tabledata/networkdata.test.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/networkdata.test.js b/zeppelin-web/src/app/tabledata/networkdata.test.js
index f8d98a8..739ac19 100644
--- a/zeppelin-web/src/app/tabledata/networkdata.test.js
+++ b/zeppelin-web/src/app/tabledata/networkdata.test.js
@@ -35,12 +35,33 @@ describe('NetworkData build', function() {
msg: JSON.stringify(jsonExpected)
})
- expect(nd.columns.length).toBe(2)
+ expect(nd.columns.length).toBe(1)
expect(nd.rows.length).toBe(3)
expect(nd.graph.nodes[0].id).toBe(jsonExpected.nodes[0].id)
expect(nd.graph.nodes[1].id).toBe(jsonExpected.nodes[1].id)
expect(nd.graph.edges[0].id).toBe(jsonExpected.edges[0].id)
- expect(nd.graph.edges[0].source.id).toBe(jsonExpected.nodes[1].id)
- expect(nd.graph.edges[0].target.id).toBe(jsonExpected.nodes[0].id)
+ expect(nd.graph.edges[0].source).toBe(jsonExpected.edges[0].source)
+ expect(nd.graph.edges[0].target).toBe(jsonExpected.edges[0].target)
+ })
+
+ it('should able to show data fields source and target', function() {
+ let jsonExpected = {nodes: [{id: 1, data: {source: 'Source'}}, {id: 2, data: {target: 'Target'}}],
+ edges: [{source: 2, target: 1, id: 1, data: {source: 'Source Edge Data', target: 'Target Edge Data'}}]}
+ nd.loadParagraphResult({
+ type: DatasetType.NETWORK,
+ msg: JSON.stringify(jsonExpected)
+ })
+
+ expect(nd.columns.length).toBe(3)
+ expect(nd.rows.length).toBe(3)
+ expect(nd.graph.nodes[0].id).toBe(jsonExpected.nodes[0].id)
+ expect(nd.graph.nodes[1].id).toBe(jsonExpected.nodes[1].id)
+ expect(nd.graph.edges[0].id).toBe(jsonExpected.edges[0].id)
+ expect(nd.graph.edges[0].source).toBe(jsonExpected.edges[0].source)
+ expect(nd.graph.edges[0].target).toBe(jsonExpected.edges[0].target)
+ expect(nd.graph.nodes[0].data.source).toBe(jsonExpected.nodes[0].data.source)
+ expect(nd.graph.nodes[1].data.target).toBe(jsonExpected.nodes[1].data.target)
+ expect(nd.graph.edges[0].data.source).toBe(jsonExpected.edges[0].data.source)
+ expect(nd.graph.edges[0].data.target).toBe(jsonExpected.edges[0].data.target)
})
})
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/851dcb1a/zeppelin-web/src/app/visualization/builtins/visualization-d3network.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-d3network.js b/zeppelin-web/src/app/visualization/builtins/visualization-d3network.js
index 506b1c5..46ee251 100644
--- a/zeppelin-web/src/app/visualization/builtins/visualization-d3network.js
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-d3network.js
@@ -55,25 +55,36 @@ export default class NetworkVisualization extends Visualization {
console.log('graph not found')
return
}
- console.log('Render Graph Visualization')
+ if (!networkData.isRendered) {
+ networkData.isRendered = true
+ } else {
+ return
+ }
+ console.log('Rendering the graph')
- let transformationConfig = this.transformation.getSetting().scope.config
+ if (networkData.graph.edges.length &&
+ !networkData.isDefaultSet) {
+ networkData.isDefaultSet = true
+ this._setEdgesDefaults(networkData.graph)
+ }
+
+ const transformationConfig = this.transformation.getSetting().scope.config
console.log('cfg', transformationConfig)
if (transformationConfig && angular.equals({}, transformationConfig.properties)) {
- transformationConfig.properties = networkData.getNetworkProperties()
+ transformationConfig.properties = this.getNetworkProperties(networkData.graph)
}
this.targetEl.empty().append('<svg></svg>')
- let width = this.targetEl.width()
- let height = this.targetEl.height()
- let self = this
- let defaultOpacity = 0
- let nodeSize = 10
- let textOffset = 3
- let linkSize = 10
+ const width = this.targetEl.width()
+ const height = this.targetEl.height()
+ const self = this
+ const defaultOpacity = 0
+ const nodeSize = 10
+ const textOffset = 3
+ const linkSize = 10
- let arcPath = (leftHand, d) => {
+ const arcPath = (leftHand, d) => {
let start = leftHand ? d.source : d.target
let end = leftHand ? d.target : d.source
let dx = end.x - start.x
@@ -84,7 +95,7 @@ export default class NetworkVisualization extends Visualization {
return `M${start.x},${start.y}A${dr},${dr} 0 0,${sweep} ${end.x},${end.y}`
}
// Use elliptical arc path segments to doubly-encode directionality.
- let tick = () => {
+ const tick = () => {
// Links
linkPath.attr('d', function(d) {
return arcPath(true, d)
@@ -97,7 +108,7 @@ export default class NetworkVisualization extends Visualization {
text.attr('transform', (d) => `translate(${d.x},${d.y})`)
}
- let setOpacity = (scale) => {
+ const setOpacity = (scale) => {
let opacity = scale >= +transformationConfig.d3Graph.zoom.minScale ? 1 : 0
this.svg.selectAll('.nodeLabel')
.style('opacity', opacity)
@@ -105,7 +116,7 @@ export default class NetworkVisualization extends Visualization {
.style('opacity', opacity)
}
- let zoom = d3.behavior.zoom()
+ const zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on('zoom', () => {
console.log('zoom')
@@ -135,13 +146,15 @@ export default class NetworkVisualization extends Visualization {
})
.start()
- let renderFooterOnClick = (entity, type) => {
- let footerId = this.containerId + '_footer'
- let obj = {id: entity.id, label: entity.defaultLabel || entity.label, type: type}
- let html = [this.$interpolate(['<li><b>{{type}}_id:</b> {{id}}</li>',
- '<li><b>{{type}}_type:</b> {{label}}</li>'].join(''))(obj)]
+ const renderFooterOnClick = (entity, type) => {
+ const footerId = this.containerId + '_footer'
+ const obj = {id: entity.id, label: entity.defaultLabel || entity.label, type: type}
+ let html = [`<li><b>${obj.type}_id:</b> ${obj.id}</li>`]
+ if (obj.label) {
+ html.push(`<li><b>${obj.type}_type:</b> ${obj.label}</li>`)
+ }
html = html.concat(_.map(entity.data, (v, k) => {
- return this.$interpolate('<li><b>{{field}}:</b> {{value}}</li>')({field: k, value: v})
+ return `<li><b>${k}:</b> ${v}</li>`
}))
angular.element('#' + footerId)
.find('.list-inline')
@@ -149,7 +162,7 @@ export default class NetworkVisualization extends Visualization {
.append(html.join(''))
}
- let drag = d3.behavior.drag()
+ const drag = d3.behavior.drag()
.origin((d) => d)
.on('dragstart', function(d) {
console.log('dragstart')
@@ -171,7 +184,7 @@ export default class NetworkVisualization extends Visualization {
self.force.resume()
})
- let container = this.svg.append('g')
+ const container = this.svg.append('g')
if (networkData.graph.directed) {
container.append('svg:defs').selectAll('marker')
.data(['arrowMarker-' + this.containerId])
@@ -188,7 +201,7 @@ export default class NetworkVisualization extends Visualization {
.attr('d', 'M0,-5L10,0L0,5')
}
// Links
- let link = container.append('svg:g')
+ const link = container.append('svg:g')
.on('click', () => {
renderFooterOnClick(d3.select(d3.event.target).datum(), 'edge')
})
@@ -196,13 +209,13 @@ export default class NetworkVisualization extends Visualization {
.data(self.force.links())
.enter()
.append('g')
- let getPathId = (d) => this.containerId + '_' + d.source.index + '_' + d.target.index + '_' + d.count
- let showLabel = (d) => this._showNodeLabel(d)
- let linkPath = link.append('svg:path')
+ const getPathId = (d) => this.containerId + '_' + d.source.index + '_' + d.target.index + '_' + d.count
+ const showLabel = (d) => this._showNodeLabel(d)
+ const linkPath = link.append('svg:path')
.attr('class', 'link')
.attr('size', linkSize)
.attr('marker-end', `url(#arrowMarker-${this.containerId})`)
- let textPath = link.append('svg:path')
+ const textPath = link.append('svg:path')
.attr('id', getPathId)
.attr('class', 'textpath')
container.append('svg:g')
@@ -218,7 +231,7 @@ export default class NetworkVisualization extends Visualization {
.text((d) => d.label)
.style('opacity', defaultOpacity)
// Nodes
- let circle = container.append('svg:g')
+ const circle = container.append('svg:g')
.on('click', () => {
renderFooterOnClick(d3.select(d3.event.target).datum(), 'node')
})
@@ -229,7 +242,7 @@ export default class NetworkVisualization extends Visualization {
.attr('fill', (d) => networkData.graph.labels && d.label in networkData.graph.labels
? networkData.graph.labels[d.label] : '#000000')
.call(drag)
- let text = container.append('svg:g').selectAll('g')
+ const text = container.append('svg:g').selectAll('g')
.data(self.force.nodes())
.enter().append('svg:g')
text.append('svg:text')
@@ -252,12 +265,72 @@ export default class NetworkVisualization extends Visualization {
}
_showNodeLabel(d) {
- let transformationConfig = this.transformation.getSetting().scope.config
- let selectedLabel = (transformationConfig.properties[d.label] || {selected: 'label'}).selected
+ const transformationConfig = this.transformation.getSetting().scope.config
+ const selectedLabel = (transformationConfig.properties[d.label] || {selected: 'label'}).selected
return d.data[selectedLabel] || d[selectedLabel]
}
getTransformation() {
return this.transformation
}
+
+ setNodesDefaults() {
+ }
+
+ _setEdgesDefaults(graph) {
+ graph.edges
+ .sort((a, b) => {
+ if (a.source > b.source) {
+ return 1
+ } else if (a.source < b.source) {
+ return -1
+ } else if (a.target > b.target) {
+ return 1
+ } else if (a.target < b.target) {
+ return -1
+ } else {
+ return 0
+ }
+ })
+ graph.edges
+ .forEach((edge, index) => {
+ let prevEdge = graph.edges[index - 1]
+ edge.count = (index > 0 && +edge.source === +prevEdge.source && +edge.target === +prevEdge.target
+ ? prevEdge.count : 0) + 1
+ edge.totalCount = graph.edges
+ .filter((innerEdge) => +edge.source === +innerEdge.source && +edge.target === +innerEdge.target)
+ .length
+ })
+ graph.edges
+ .forEach((edge) => {
+ if (typeof +edge.source === 'number') {
+ // edge.source = graph.nodes.filter((node) => +edge.source === +node.id)[0] || null
+ edge.source = _.find(graph.nodes, (node) => +edge.source === +node.id)
+ }
+ if (typeof +edge.target === 'number') {
+ // edge.target = graph.nodes.filter((node) => +edge.target === +node.id)[0] || null
+ edge.target = _.find(graph.nodes, (node) => +edge.target === +node.id)
+ }
+ })
+ }
+
+ getNetworkProperties(graph) {
+ const baseCols = ['id', 'label']
+ const properties = {}
+ graph.nodes.forEach(function(node) {
+ const hasLabel = 'label' in node && node.label !== ''
+ if (!hasLabel) {
+ return
+ }
+ const label = node.label
+ const hasKey = hasLabel && label in properties
+ const keys = _.uniq(Object.keys(node.data || {})
+ .concat(hasKey ? properties[label].keys : baseCols))
+ if (!hasKey) {
+ properties[label] = {selected: 'label'}
+ }
+ properties[label].keys = keys
+ })
+ return properties
+ }
}