New chart type : Chord Diagrams (#3013)

This commit is contained in:
Maxime Beauchemin 2017-06-26 16:44:47 -07:00 committed by GitHub
parent a55f963e52
commit 7045018d86
8 changed files with 194 additions and 4 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

View File

@ -334,11 +334,14 @@ export const controls = {
type: 'SelectControl',
multi: true,
label: 'Columns',
mapStateToProps: state => ({
choices: (state.datasource) ? state.datasource.gb_cols : [],
}),
default: [],
description: 'One or many controls to pivot as columns',
optionRenderer: c => <ColumnOption column={c} />,
valueRenderer: c => <ColumnOption column={c} />,
valueKey: 'column_name',
mapStateToProps: state => ({
options: (state.datasource) ? state.datasource.columns : [],
}),
},
all_columns: {

View File

@ -1,5 +1,7 @@
import { D3_TIME_FORMAT_OPTIONS } from './controls';
import * as v from '../validators';
export const sections = {
druidTimeSeries: {
label: 'Time',
@ -635,6 +637,37 @@ const visTypes = {
},
},
},
chord: {
label: 'Chord Diagram',
controlPanelSections: [
{
label: null,
controlSetRows: [
['groupby', 'columns'],
['metric'],
['row_limit', 'y_axis_format'],
],
},
],
controlOverrides: {
y_axis_format: {
label: 'Number format',
description: 'Choose a number format',
},
groupby: {
label: 'Source',
multi: false,
validators: [v.nonEmpty],
description: 'Choose a source',
},
columns: {
label: 'Target',
multi: false,
validators: [v.nonEmpty],
description: 'Choose a target',
},
},
},
country_map: {
label: 'Country Map',
controlPanelSections: [

View File

@ -65,7 +65,7 @@
"react-ace": "^5.0.1",
"react-addons-css-transition-group": "^15.6.0",
"react-addons-shallow-compare": "^15.4.2",
"react-alert": "^2.0.1",
"react-alert": "^1.0.14",
"react-bootstrap": "^0.31.0",
"react-bootstrap-table": "^3.1.7",
"react-dom": "^15.5.1",

View File

@ -0,0 +1,17 @@
.chord svg #circle circle {
fill: none;
pointer-events: all;
}
.chord svg .group path {
fill-opacity: .6;
}
.chord svg path.chord {
stroke: #000;
stroke-width: .25px;
}
.chord svg #circle:hover path.fade {
opacity: 0.2;
}

View File

@ -0,0 +1,101 @@
/* eslint-disable no-param-reassign */
import d3 from 'd3';
import { category21 } from '../javascripts/modules/colors';
import './chord.css';
function chordViz(slice, json) {
slice.container.html('');
const div = d3.select(slice.selector);
const nodes = json.data.nodes;
const fd = slice.formData;
const f = d3.format(fd.y_axis_format);
const width = slice.width();
const height = slice.height();
const outerRadius = Math.min(width, height) / 2 - 10;
const innerRadius = outerRadius - 24;
let chord;
const arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
const layout = d3.layout.chord()
.padding(0.04)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
const path = d3.svg.chord()
.radius(innerRadius);
const svg = div.append('svg')
.attr('width', width)
.attr('height', height)
.on('mouseout', () => chord.classed('fade', false))
.append('g')
.attr('id', 'circle')
.attr('transform', `translate(${width / 2}, ${height / 2})`);
svg.append('circle')
.attr('r', outerRadius);
// Compute the chord layout.
layout.matrix(json.data.matrix);
const group = svg.selectAll('.group')
.data(layout.groups)
.enter().append('g')
.attr('class', 'group')
.on('mouseover', (d, i) => {
chord.classed('fade', p => p.source.index !== i && p.target.index !== i);
});
// Add a mouseover title.
group.append('title').text((d, i) => `${nodes[i]}: ${f(d.value)}`);
// Add the group arc.
const groupPath = group.append('path')
.attr('id', (d, i) => 'group' + i)
.attr('d', arc)
.style('fill', (d, i) => category21(nodes[i]));
// Add a text label.
const groupText = group.append('text')
.attr('x', 6)
.attr('dy', 15);
groupText.append('textPath')
.attr('xlink:href', (d, i) => `#group${i}`)
.text((d, i) => nodes[i]);
// Remove the labels that don't fit. :(
groupText.filter(function (d, i) {
return groupPath[0][i].getTotalLength() / 2 - 16 < this.getComputedTextLength();
})
.remove();
// Add the chords.
chord = svg.selectAll('.chord')
.data(layout.chords)
.enter().append('path')
.attr('class', 'chord')
.on('mouseover', (d) => {
chord.classed('fade', p => p !== d);
})
.style('fill', d => category21(nodes[d.source.index]))
.attr('d', path);
// Add an elaborate mouseover title for each chord.
chord.append('title').text(function (d) {
return nodes[d.source.index]
+ ' → ' + nodes[d.target.index]
+ ': ' + f(d.source.value)
+ '\n' + nodes[d.target.index]
+ ' → ' + nodes[d.source.index]
+ ': ' + f(d.target.value);
});
}
module.exports = chordViz;

View File

@ -10,6 +10,7 @@ const vizMap = {
cal_heatmap: require('./cal_heatmap.js'),
compare: require('./nvd3_vis.js'),
directed_force: require('./directed_force.js'),
chord: require('./chord.jsx'),
dist_bar: require('./nvd3_vis.js'),
filter_box: require('./filter_box.jsx'),
heatmap: require('./heatmap.js'),

View File

@ -16,6 +16,7 @@ import uuid
import zlib
from collections import OrderedDict, defaultdict
from itertools import product
from datetime import datetime, timedelta
import pandas as pd
@ -1231,6 +1232,39 @@ class DirectedForceViz(BaseViz):
return df.to_dict(orient='records')
class ChordViz(BaseViz):
"""A Chord diagram"""
viz_type = "chord"
verbose_name = _("Directed Force Layout")
credits = '<a href="https://github.com/d3/d3-chord">Bostock</a>'
is_timeseries = False
def query_obj(self):
qry = super(ChordViz, self).query_obj()
fd = self.form_data
qry['groupby'] = [fd.get('groupby'), fd.get('columns')]
qry['metrics'] = [fd.get('metric')]
return qry
def get_data(self, df):
df.columns = ['source', 'target', 'value']
# Preparing a symetrical matrix like d3.chords calls for
nodes = list(set(df['source']) | set(df['target']))
matrix = {}
for source, target in product(nodes, nodes):
matrix[(source, target)] = 0
for source, target, value in df.to_records(index=False):
matrix[(source, target)] = value
m = [[matrix[(n1, n2)] for n1 in nodes] for n2 in nodes]
return {
'nodes': list(nodes),
'matrix': m,
}
class CountryMapViz(BaseViz):
"""A country centric"""
@ -1574,6 +1608,7 @@ viz_types_list = [
DirectedForceViz,
SankeyViz,
CountryMapViz,
ChordViz,
WorldMapViz,
FilterBoxViz,
IFrameViz,