mirror of https://github.com/apache/superset.git
Refactor force-directed graph (#5691)
* Refactor and add props to Force Directed Graph * update label and enable renderTrigger
This commit is contained in:
parent
ebe585df3d
commit
3929f0f79d
|
@ -838,6 +838,7 @@ export const controls = {
|
||||||
|
|
||||||
link_length: {
|
link_length: {
|
||||||
type: 'SelectControl',
|
type: 'SelectControl',
|
||||||
|
renderTrigger: true,
|
||||||
freeForm: true,
|
freeForm: true,
|
||||||
label: t('Link Length'),
|
label: t('Link Length'),
|
||||||
default: '200',
|
default: '200',
|
||||||
|
@ -847,6 +848,7 @@ export const controls = {
|
||||||
|
|
||||||
charge: {
|
charge: {
|
||||||
type: 'SelectControl',
|
type: 'SelectControl',
|
||||||
|
renderTrigger: true,
|
||||||
freeForm: true,
|
freeForm: true,
|
||||||
label: t('Charge'),
|
label: t('Charge'),
|
||||||
default: '-500',
|
default: '-500',
|
||||||
|
|
|
@ -1398,7 +1398,7 @@ export const visTypes = {
|
||||||
},
|
},
|
||||||
|
|
||||||
directed_force: {
|
directed_force: {
|
||||||
label: t('Directed Force Layout'),
|
label: t('Force-directed Graph'),
|
||||||
controlPanelSections: [
|
controlPanelSections: [
|
||||||
{
|
{
|
||||||
label: t('Query'),
|
label: t('Query'),
|
||||||
|
|
|
@ -1,18 +1,34 @@
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import d3 from 'd3';
|
import d3 from 'd3';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import './directed_force.css';
|
||||||
|
|
||||||
require('./directed_force.css');
|
const propTypes = {
|
||||||
|
data: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
source: PropTypes.string,
|
||||||
|
target: PropTypes.string,
|
||||||
|
value: PropTypes.number,
|
||||||
|
})),
|
||||||
|
width: PropTypes.number,
|
||||||
|
height: PropTypes.number,
|
||||||
|
linkLength: PropTypes.number,
|
||||||
|
charge: PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
/* Modified from http://bl.ocks.org/d3noob/5141278 */
|
/* Modified from http://bl.ocks.org/d3noob/5141278 */
|
||||||
const directedForceVis = function (slice, json) {
|
function ForceDirectedGraph(element, props) {
|
||||||
const div = d3.select(slice.selector);
|
PropTypes.checkPropTypes(propTypes, props, 'prop', 'ForceDirectedGraph');
|
||||||
const width = slice.width();
|
|
||||||
const height = slice.height();
|
|
||||||
const fd = slice.formData;
|
|
||||||
const linkLength = fd.link_length || 200;
|
|
||||||
const charge = fd.charge || -500;
|
|
||||||
|
|
||||||
const links = json.data;
|
const {
|
||||||
|
data,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
linkLength = 200,
|
||||||
|
charge = -500,
|
||||||
|
} = props;
|
||||||
|
const div = d3.select(element);
|
||||||
|
|
||||||
|
const links = data;
|
||||||
const nodes = {};
|
const nodes = {};
|
||||||
// Compute the distinct nodes from the links.
|
// Compute the distinct nodes from the links.
|
||||||
links.forEach(function (link) {
|
links.forEach(function (link) {
|
||||||
|
@ -73,73 +89,73 @@ const directedForceVis = function (slice, json) {
|
||||||
/* eslint-enable no-use-before-define */
|
/* eslint-enable no-use-before-define */
|
||||||
|
|
||||||
const force = d3.layout.force()
|
const force = d3.layout.force()
|
||||||
.nodes(d3.values(nodes))
|
.nodes(d3.values(nodes))
|
||||||
.links(links)
|
.links(links)
|
||||||
.size([width, height])
|
.size([width, height])
|
||||||
.linkDistance(linkLength)
|
.linkDistance(linkLength)
|
||||||
.charge(charge)
|
.charge(charge)
|
||||||
.on('tick', tick)
|
.on('tick', tick)
|
||||||
.start();
|
.start();
|
||||||
|
|
||||||
div.selectAll('*').remove();
|
div.selectAll('*').remove();
|
||||||
const svg = div.append('svg')
|
const svg = div.append('svg')
|
||||||
.attr('width', width)
|
.attr('width', width)
|
||||||
.attr('height', height);
|
.attr('height', height);
|
||||||
|
|
||||||
// build the arrow.
|
// build the arrow.
|
||||||
svg.append('svg:defs').selectAll('marker')
|
svg.append('svg:defs').selectAll('marker')
|
||||||
.data(['end']) // Different link/path types can be defined here
|
.data(['end']) // Different link/path types can be defined here
|
||||||
.enter()
|
.enter()
|
||||||
.append('svg:marker') // This section adds in the arrows
|
.append('svg:marker') // This section adds in the arrows
|
||||||
.attr('id', String)
|
.attr('id', String)
|
||||||
.attr('viewBox', '0 -5 10 10')
|
.attr('viewBox', '0 -5 10 10')
|
||||||
.attr('refX', 15)
|
.attr('refX', 15)
|
||||||
.attr('refY', -1.5)
|
.attr('refY', -1.5)
|
||||||
.attr('markerWidth', 6)
|
.attr('markerWidth', 6)
|
||||||
.attr('markerHeight', 6)
|
.attr('markerHeight', 6)
|
||||||
.attr('orient', 'auto')
|
.attr('orient', 'auto')
|
||||||
.append('svg:path')
|
.append('svg:path')
|
||||||
.attr('d', 'M0,-5L10,0L0,5');
|
.attr('d', 'M0,-5L10,0L0,5');
|
||||||
|
|
||||||
const edgeScale = d3.scale.linear()
|
const edgeScale = d3.scale.linear()
|
||||||
.range([0.1, 0.5]);
|
.range([0.1, 0.5]);
|
||||||
// add the links and the arrows
|
// add the links and the arrows
|
||||||
const path = svg.append('svg:g').selectAll('path')
|
const path = svg.append('svg:g').selectAll('path')
|
||||||
.data(force.links())
|
.data(force.links())
|
||||||
.enter()
|
.enter()
|
||||||
.append('svg:path')
|
.append('svg:path')
|
||||||
.attr('class', 'link')
|
.attr('class', 'link')
|
||||||
.style('opacity', function (d) {
|
.style('opacity', function (d) {
|
||||||
return edgeScale(d.value / d.target.max);
|
return edgeScale(d.value / d.target.max);
|
||||||
})
|
})
|
||||||
.attr('marker-end', 'url(#end)');
|
.attr('marker-end', 'url(#end)');
|
||||||
|
|
||||||
// define the nodes
|
// define the nodes
|
||||||
const node = svg.selectAll('.node')
|
const node = svg.selectAll('.node')
|
||||||
.data(force.nodes())
|
.data(force.nodes())
|
||||||
.enter()
|
.enter()
|
||||||
.append('g')
|
.append('g')
|
||||||
.attr('class', 'node')
|
.attr('class', 'node')
|
||||||
.on('mouseenter', function () {
|
.on('mouseenter', function () {
|
||||||
d3.select(this)
|
d3.select(this)
|
||||||
.select('circle')
|
.select('circle')
|
||||||
.transition()
|
.transition()
|
||||||
.style('stroke-width', 5);
|
.style('stroke-width', 5);
|
||||||
|
|
||||||
d3.select(this)
|
d3.select(this)
|
||||||
.select('text')
|
.select('text')
|
||||||
.transition()
|
.transition()
|
||||||
.style('font-size', 25);
|
.style('font-size', 25);
|
||||||
})
|
})
|
||||||
.on('mouseleave', function () {
|
.on('mouseleave', function () {
|
||||||
d3.select(this)
|
d3.select(this)
|
||||||
.select('circle')
|
.select('circle')
|
||||||
.transition()
|
.transition()
|
||||||
.style('stroke-width', 1.5);
|
.style('stroke-width', 1.5);
|
||||||
d3.select(this)
|
d3.select(this)
|
||||||
.select('text')
|
.select('text')
|
||||||
.transition()
|
.transition()
|
||||||
.style('font-size', 12);
|
.style('font-size', 12);
|
||||||
})
|
})
|
||||||
.call(force.drag);
|
.call(force.drag);
|
||||||
|
|
||||||
|
@ -148,21 +164,33 @@ const directedForceVis = function (slice, json) {
|
||||||
return Math.sqrt(d.total);
|
return Math.sqrt(d.total);
|
||||||
});
|
});
|
||||||
const circleScale = d3.scale.linear()
|
const circleScale = d3.scale.linear()
|
||||||
.domain(ext)
|
.domain(ext)
|
||||||
.range([3, 30]);
|
.range([3, 30]);
|
||||||
|
|
||||||
node.append('circle')
|
node.append('circle')
|
||||||
.attr('r', function (d) {
|
.attr('r', function (d) {
|
||||||
return circleScale(Math.sqrt(d.total));
|
return circleScale(Math.sqrt(d.total));
|
||||||
});
|
});
|
||||||
|
|
||||||
// add the text
|
// add the text
|
||||||
node.append('text')
|
node.append('text')
|
||||||
.attr('x', 6)
|
.attr('x', 6)
|
||||||
.attr('dy', '.35em')
|
.attr('dy', '.35em')
|
||||||
.text(function (d) {
|
.text(d => d.name);
|
||||||
return d.name;
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = directedForceVis;
|
function adaptor(slice, payload) {
|
||||||
|
const { selector, formData } = slice;
|
||||||
|
const { link_length: linkLength, charge } = formData;
|
||||||
|
const element = document.querySelector(selector);
|
||||||
|
|
||||||
|
return ForceDirectedGraph(element, {
|
||||||
|
data: payload.data,
|
||||||
|
width: slice.width(),
|
||||||
|
height: slice.height(),
|
||||||
|
linkLength,
|
||||||
|
charge,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default adaptor;
|
||||||
|
|
Loading…
Reference in New Issue