You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@senssoft.apache.org by ar...@apache.org on 2017/09/06 12:53:17 UTC
[1/3] incubator-senssoft-tap git commit: changes in histograms for
new data structure
Repository: incubator-senssoft-tap
Updated Branches:
refs/heads/ryan-sankey 607675ba5 -> 855c20594
changes in histograms for new data structure
Project: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/commit/7ef50924
Tree: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/tree/7ef50924
Diff: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/diff/7ef50924
Branch: refs/heads/ryan-sankey
Commit: 7ef50924633167ad979f35f3081f88c104f98769
Parents: 607675b
Author: Arthi Vezhavendan <ar...@gmail.com>
Authored: Tue Sep 5 21:31:21 2017 -0400
Committer: Arthi Vezhavendan <ar...@gmail.com>
Committed: Tue Sep 5 21:31:21 2017 -0400
----------------------------------------------------------------------
public/components/visualizations/Counts.jsx | 134 ++++---------------
.../components/visualizations/VerticalBar.jsx | 59 ++++----
2 files changed, 48 insertions(+), 145 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/blob/7ef50924/public/components/visualizations/Counts.jsx
----------------------------------------------------------------------
diff --git a/public/components/visualizations/Counts.jsx b/public/components/visualizations/Counts.jsx
index 02f30f1..6d8f764 100644
--- a/public/components/visualizations/Counts.jsx
+++ b/public/components/visualizations/Counts.jsx
@@ -42,18 +42,6 @@ class Counts extends Component {
render() {
var dataSubset = subset(this.props.data, this.props.filters);
- var selectedGroup = this.state.selectedGroup;
- if (selectedGroup == '') {
- selectedGroup = Object.keys(dataSubset)[0];
- }
-
- var selectedActivity = this.state.selectedActivity;
- if (selectedActivity == '') {
- selectedActivity = Object.keys(dataSubset[selectedGroup].activities)[0];
- }
-
- var activity = dataSubset[selectedGroup].activities[selectedActivity];
-
return(
<div className='ui grid'>
<div className='sixteen wide column'>
@@ -63,50 +51,13 @@ class Counts extends Component {
$.each(dataSubset, (group, logs) => {
groupData.push({
id : group,
- selected : (selectedGroup == group),
- ot1 : logs.ot1count,
- ot2 : logs.ot2count,
+ ot1 : logs,
});
});
return groupData;
})()} />
</div>
-
- <div className='ten wide column'>
- <HorizontalBar grouped={this.props.filters.ab} select={this.selectActivity} data={(() => {
- var activityData = [];
-
- $.each(dataSubset[selectedGroup].activities, (id, log) => {
- activityData.push({
- id : id,
- name : log.ele,
- selected : (selectedActivity == id),
- ot1 : log.ot1count,
- ot2 : log.ot2count,
- });
- });
-
- return activityData;
- })()} />
- </div>
-
- <div className='six wide column'>
- <div id='counts-details' className='ui segment'>
- Activity Details
- <br></br>
- <br></br>
- Name: {activity.name}
- <br></br>
- Element: {activity.ele}
- <br></br>
- Group: {activity.group}
- <br></br>
- OT1: {activity.ot1count}
- <br></br>
- OT2: {activity.ot2count}
- </div>
- </div>
</div>
);
}
@@ -118,7 +69,7 @@ function subset(data, filters) {
// var dataSubset = data.filter((p) => {
// return (filters.gender === 0 || filters.gender == p.intake_data.demographics.Gender) && (filters.educationlevels.includes(+p.intake_data.education["Most school completed"]));
// });
- var dataSubset = data;
+ var dataSubset = data; //snarl apply filtered data here
var logs = {};
@@ -128,7 +79,6 @@ function subset(data, filters) {
logs[log.group] = {
group : log.group,
ot1count : 0,
- ot2count : 0,
activities : {},
};
}
@@ -137,7 +87,6 @@ function subset(data, filters) {
logs[log.group].activities[log.id] = {
id : id,
ot1count : 0,
- ot2count : 0,
name : log.name,
ele : log.ele,
group : log.group,
@@ -148,30 +97,6 @@ function subset(data, filters) {
logs[log.group].activities[log.id].ot1count += log.count;
});
- $.each(p.ot2logs, (id, log) => {
- if (!logs.hasOwnProperty(log.group)) {
- logs[log.group] = {
- group : log.group,
- ot1count : 0,
- ot2count : 0,
- activities : {},
- };
- }
-
- if (!logs[log.group].activities.hasOwnProperty(log.id)) {
- logs[log.group].activities[log.id] = {
- id : id,
- ot1count : 0,
- ot2count : 0,
- name : log.name,
- ele : log.ele,
- group : log.group,
- }
- }
-
- logs[log.group].ot2count += log.count;
- logs[log.group].activities[log.id].ot2count += log.count;
- });
});
return logs;
@@ -179,39 +104,28 @@ function subset(data, filters) {
function preprocessData(data) {
data.forEach(function (p) {
- var ot1logs = {};
- var ot2logs = {};
-
- p.log_data.OT1.logs.log_id.forEach(function (id, i) {
- if (ot1logs.hasOwnProperty(id)) {
- ot1logs[id].count += p.log_data.OT1.logs.count[i];
- } else {
- ot1logs[id] = {
- 'id' : id,
- 'count' : p.log_data.OT1.logs.count[i],
- 'name' : p.log_data.OT1.logs.log_strings[i],
- 'ele' : p.log_data.OT1.logs.elementId[i],
- 'group' : p.log_data.OT1.logs.elementGroup[i]
- };
- }
- });
-
- p.log_data.OT2.logs.log_id.forEach(function (id, i) {
- if (ot2logs.hasOwnProperty(id)) {
- ot2logs[id].count += p.log_data.OT2.logs.count[i];
- } else {
- ot2logs[id] = {
- 'id' : id,
- 'count' : p.log_data.OT2.logs.count[i],
- 'name' : p.log_data.OT2.logs.log_strings[i],
- 'ele' : p.log_data.OT2.logs.elementId[i],
- 'group' : p.log_data.OT2.logs.elementGroup[i]
- };
- }
- });
-
- p.ot1logs = ot1logs;
- p.ot2logs = ot2logs;
+ // var ot1logs = {};
+
+ // p.counts.forEach(function (cnt, i) {
+ // if;
+ // })
+
+ // p.log_data.OT1.logs.log_id.forEach(function (id, i) {
+ // if (ot1logs.hasOwnProperty(id)) {
+ // ot1logs[id].count += p.log_data.OT1.logs.count[i];
+ // } else {
+ // ot1logs[id] = {
+ // 'id' : id,
+ // 'count' : p.log_data.OT1.logs.count[i],
+ // 'name' : p.log_data.OT1.logs.log_strings[i],
+ // 'ele' : p.log_data.OT1.logs.elementId[i],
+ // 'group' : p.log_data.OT1.logs.elementGroup[i]
+ // };
+ // }
+ // });
+
+ //p.ot1logs = ot1logs;
+ //p.ot2logs = ot1logs; //set ot2 logs to be same as ot1 logs - snarl
});
}
http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/blob/7ef50924/public/components/visualizations/VerticalBar.jsx
----------------------------------------------------------------------
diff --git a/public/components/visualizations/VerticalBar.jsx b/public/components/visualizations/VerticalBar.jsx
index e491fcd..2623a35 100644
--- a/public/components/visualizations/VerticalBar.jsx
+++ b/public/components/visualizations/VerticalBar.jsx
@@ -33,8 +33,8 @@ class VerticalBar extends Component {
bottom : 30,
left : 40,
};
- this.fullWidth = 500;
- this.fullHeight = 400;
+ this.fullWidth = 600;
+ this.fullHeight = 300;
this.width = this.fullWidth - this.margin.left - this.margin.right;
this.height = this.fullHeight - this.margin.top - this.margin.bottom;
@@ -43,8 +43,9 @@ class VerticalBar extends Component {
.padding(0.1)
.align(0.1);
- this.x1 = d3.scaleBand()
- .domain(['ot1', 'ot2']);
+ //x1 is for ab testing
+ // this.x1 = d3.scaleBand()
+ // .domain(['ot1', 'ot2']);
this.y = d3.scaleLinear()
.rangeRound([this.height, 0]);
@@ -87,7 +88,12 @@ class VerticalBar extends Component {
this.svg.append('g')
.attr('class', 'x axis')
- .attr('transform', `translate(0,${this.height})`);
+ .attr('transform', `translate(0,${this.height})`)
+ .selectAll("text")
+ //.style("text-anchor", "end")
+ //.attr("dx", "-.8em")
+ //.attr("dy", "-.55em")
+ .attr("transform", "rotate(90)" ); //TODO: text on x axis not rotating
this.svg.append('g')
.attr('class', 'y axis');
@@ -97,22 +103,22 @@ class VerticalBar extends Component {
update() {
let data = this.props.data;
- let grouped = this.props.grouped;
+ //let grouped = this.props.grouped; //grouped is for ab testing
+ let grouped = false;
let t = d3.transition()
.duration(500);
- this.x.domain(data.map((d) => d.id));
- this.x1.rangeRound([0, this.x.bandwidth()]);
+ this.x.domain(data.map((d) => d.ot1.target));//ot1.target));
this.y.domain([0, d3.max(data, (d) => {
- return grouped ? Math.max(d.ot1, d.ot2) : (d.ot1 + d.ot2);
+ return d.ot1.counts.reduce((a, b) => a + b, 0);
})]);
this.svg.select('.x.axis').call(this.xAxis);
this.svg.select('.y.axis').call(this.yAxis);
this.groups = this.svg.selectAll('.group')
- .data(data, (d) => d.id);
+ .data(data, (d) => d.ot1.target);
this.groups.exit()
.attr('class', 'exit')
@@ -127,31 +133,14 @@ class VerticalBar extends Component {
this.groups
.transition(t)
- .attr('transform', (d) => `translate(${this.x(d.id)},0)`);
+ .attr('transform', (d) => `translate(${this.x(d.ot1.target)},0)`);
+
this.bars = this.groups.selectAll('.bar')
.data((d) => {
- if (grouped) {
- var ot1 = {
- id : d.id,
- selected : d.selected,
- count : d.ot1,
- type : 'ot1',
- };
-
- var ot2 = {
- id : d.id,
- selected : d.selected,
- count : d.ot2,
- type : 'ot2',
- };
-
- return [ot1, ot2];
- } else {
- d.count = d.ot1 + d.ot2;
+ d.count = d.ot1.counts.reduce((a, b) => a + b, 0);
return [d];
- }
- });
+ });
this.bars.exit()
.attr('class', 'exit')
@@ -167,14 +156,14 @@ class VerticalBar extends Component {
this.bars
.on('click', (d) => {
- this.props.select(d.id);
+ this.props.select(d.ot1.target);
})
.transition(t)
- .attr('x', (d) => grouped ? this.x1(d.type) : 0)
- .attr('width', (d) => grouped ? this.x1.bandwidth() : this.x.bandwidth())
+ .attr('x', (d) => 0)
+ .attr('width', (d) => this.x.bandwidth())
.attr('y', (d) => this.y(d.count))
.attr('height', (d) => this.height - this.y(d.count))
- .style('fill', (d) => grouped ? this.color(d.type) : this.color(d.id))
+ .style('fill', (d) => this.color(d.id))
.style('stroke', (d) => d.selected ? '#283F4E' : '')
.style('stroke-width', (d) => d.selected ? '3px' : '0px');
[2/3] incubator-senssoft-tap git commit: initial integration of
Ryan's extended d3 sankey library
Posted by ar...@apache.org.
initial integration of Ryan's extended d3 sankey library
Project: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/commit/1c1d7c09
Tree: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/tree/1c1d7c09
Diff: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/diff/1c1d7c09
Branch: refs/heads/ryan-sankey
Commit: 1c1d7c09fa611cfd65fa4d8b16de34128f1ecba2
Parents: 7ef5092
Author: Arthi Vezhavendan <ar...@gmail.com>
Authored: Tue Sep 5 22:01:21 2017 -0400
Committer: Arthi Vezhavendan <ar...@gmail.com>
Committed: Tue Sep 5 22:01:21 2017 -0400
----------------------------------------------------------------------
public/components/visualizations/SankeyPlot.jsx | 656 ++++++++++---------
1 file changed, 357 insertions(+), 299 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/blob/1c1d7c09/public/components/visualizations/SankeyPlot.jsx
----------------------------------------------------------------------
diff --git a/public/components/visualizations/SankeyPlot.jsx b/public/components/visualizations/SankeyPlot.jsx
index f49c22a..6d7e47b 100644
--- a/public/components/visualizations/SankeyPlot.jsx
+++ b/public/components/visualizations/SankeyPlot.jsx
@@ -15,6 +15,8 @@
import React, { Component, PropTypes } from 'react';
import * as d3 from 'd3';
+//import {'d3-interpolate'} from 'd3';
+//require('../../d3sankey.js').default;
const colors_old = ['#A7003C', '#00A76B', '#0090A7', '#003DA7', '#6B00A7'];
const colors_new = ['#d45d35', '#DBA915', '#BFD02C', '#38A6D8', '#852EB7'];
@@ -22,7 +24,9 @@ const colors_new = ['#d45d35', '#DBA915', '#BFD02C', '#38A6D8', '#852EB7'];
class SankeyPlot extends Component {
constructor(props) {
super(props);
- this.d3element = props.element;
+
+ //this.d3element = props.element;
+ this.d3element = 'sankey-plot';
}
componentDidMount() {
@@ -49,142 +53,113 @@ class SankeyPlot extends Component {
left : 20,
};
this.fullWidth = 600;
- this.fullHeight = 400;
+ this.fullHeight = 300;
this.width = this.fullWidth - this.margin.left - this.margin.right;
this.height = this.fullHeight - this.margin.top - this.margin.bottom;
- this.mainRadius = 280;
- // this.color = d3.scaleOrdinal()
- // .range([
- // '#A7003C', // Red
- // '#00A76B', // Green
- // '#0090A7', // Teal
- // '#003DA7', // Blue
- // '#6B00A7' // Purple
- // ]);
+ this.formatNumber = d3.format(',.0f');
+ this.format = d => `${this.formatNumber(d)} TWh`;
+
this.color = d3.scaleOrdinal()
.range(colors_old);
- this.arc = d3.arc()
- // .padAngle(0.002)
- .innerRadius(this.mainRadius - 50)
- .outerRadius(this.mainRadius);
-
- this.ribbon = d3.ribbon();
-
- this.graphFlow = graphFlow()
- .radius(this.mainRadius - 50)
- .innerRadius(this.mainRadius - 150);
-
this.svg = d3.select(`#${this.d3element}`).append('svg')
.attr('width', this.fullWidth)
.attr('height', this.fullHeight)
- .append('g')
- .attr('transform', `translate(${this.margin.left + this.width / 2},${this.margin.top + this.height / 2})`);
+ .append('g');
+
+ this.sankey = sankey()
+ .nodeWidth(15)
+ .nodePadding(10)
+ .size([this.width, this.height]);
+
+ this.path = this.sankey.link();
- this.tooltip = d3.select('body').append('div')
- .attr('class', 'tooltip')
- .style('opacity', 0);
+
+ // this.tooltip = d3.select('body').append('div')
+ // .attr('class', 'tooltip')
+ // .style('opacity', 0);
this.update();
}
// D3 render
update() {
- let data = this.props.data[this.props.metric];
- let layout = this.graphFlow(data);
-
- let t = d3.transition()
- .duration(500);
-
- this.arcs = this.svg.selectAll('.arc')
- .data(layout.inArcs.concat(layout.outArcs), (d) => d.type + d.index);
-
- this.arcs.exit()
- .attr('class', 'exit')
- .transition(t)
- .style('fill-opacity', 0)
- .remove();
-
- this.arcs = this.arcs.enter()
- .append('path')
- .attr('class', 'arc')
- .merge(this.arcs);
-
- this.arcs
- .on('mouseover', (d) => {
- this.highlight(d, 'arc');
- this.showTooltip(data.in[d.index], d3.event.pageX, d3.event.pageY);
- })
- .on('mouseout', (d) => {
- this.restore();
- this.hideTooltip();
- })
- .transition(t)
- // TODO : add arc tweens
- .attr('d', this.arc)
- .style('fill', (d) => this.color(data.in[d.index].elementGroup));
-
- this.chords = this.svg.selectAll('.chord')
- .data(layout.inChords.concat(layout.outChords), (d) => d.index + d.type + d.subindex);
-
- this.chords.exit()
- .attr('class', 'exit')
- .transition(t)
- .style('fill-opacity', 0)
- .remove();
-
- this.chords = this.chords.enter()
- .append('path')
- .attr('class', 'chord')
- .style('fill', '#B0B9BE')
- .merge(this.chords);
-
- this.chords
- .transition(t)
- .attr('d', this.ribbon)
- .style('fill-opacity', 0.5);
-
- this.circles = this.svg.selectAll('.node')
- .data($.map(layout.blt, (val, key) => val), (d) => d.index);
-
- this.circles.exit()
- .attr('class', 'exit')
- .transition(t)
- .attr('r', 0)
- .remove();
-
- this.circles = this.circles.enter()
- .append('circle')
+ //let data = this.props.data[this.props.metric];
+ let data = require('../../sankey_example.js').default;
+ console.log("data in update = " + data);
+ console.log("nodes: "+ data[0].nodes.length);
+ console.log("links: "+ data[1].links.length);
+
+ this.sankey
+ .nodes(data[0].nodes)
+ .links(data[1].links)
+ .layout(32);
+
+ const link = this.svg.append('g').selectAll('.link')
+ .data(data[1].links)
+ .enter().append('path')
+ .attr('class', 'linkSankey')
+ .attr('d', this.path)
+ .style('stroke-width', d => Math.max(100, d.dy))
+ .style('fill', 'none')
+ .style('stroke', "#000")
+ .style('stroke-opacity', .2)
+ .sort((a, b) => b.dy - a.dy);
+
+ link.append('title')
+ .text(d => `${d.source.name} → ${d.target.name}\n${this.format(d.value)}`);
+
+ const node = this.svg.append('g').selectAll('.node')
+ .data(data[0].nodes)
+ .enter().append('g')
.attr('class', 'node')
- .merge(this.circles);
-
- this.circles
- .on('mouseover', (d) => {
- this.highlight(d, 'circle');
- this.showTooltip(data.in[d.index], d3.event.pageX, d3.event.pageY);
- })
- .on('mouseout', (d) => {
- this.restore();
- this.hideTooltip();
- })
- .transition(t)
- .attr('r', (d) => d.r)
- .attr('cx', (d) => d.x)
- .attr('cy', (d) => d.y)
- .style('fill', (d) => this.color(data.in[d.index].elementGroup))
- .style('fill-opacity', 0.75);
+ .attr('transform', d => `translate(${d.x},${d.y})`)
+ .call(d3.drag()
+ .subject(d => d)
+ .on('start', function() {
+ this.parentNode.appendChild(this);
+ })
+ .on('drag', this.dragmove(d => d)));
+
+ node.append('rect')
+ .attr('height', d => d.dy)
+ .attr('width', this.sankey.nodeWidth())
+ .style('fill', d => d.color = this.color(d.name.replace(/ .*/, '')))
+ .style('stroke', d => d3.rgb(d.color).darker(2))
+ .append('title')
+ .text(d => `${d.name}\n${this.format(d.value)}`);
+
+ node.append('text')
+ .attr('x', -6)
+ .attr('y', d => d.dy / 2)
+ .attr('dy', '.35em')
+ .attr('text-anchor', 'end')
+ .attr('transform', null)
+ .text(d => d.name)
+ .filter(d => d.x < this.width / 2)
+ .attr('x', 6 + this.sankey.nodeWidth())
+ .attr('text-anchor', 'start');
}
+
+ dragmove(d) {
+ //TODO: fix dragmove: the function is called unnecessarily and doens't work
+ // d3.select(this).attr('transform', `translate(${d.x},${d.y = Math.max(0, Math.min(this.height - d.dy, d3.event.y))})`);
+ // sankey.relayout();
+ // link.attr('d', this.path);
+ }
hideTooltip() {
+ //console.log("HIDE TOOLTIP HAPPENED - todo: verify");
this.tooltip.transition()
.duration(350)
.style('opacity', 0);
}
showTooltip(activity, x, y) {
+ //console.log("SHOW TOOLTIP HAPPENED - todo: verify");
this.tooltip.transition()
.duration(350)
.style('opacity', 0.9);
@@ -195,231 +170,314 @@ class SankeyPlot extends Component {
.html(`Action: ${activity.action}<br>Id: ${activity.elementId}<br>Group: ${activity.elementGroup}`);
}
- highlight(d, type) {
- var indices = [];
-
- if (type === 'arc') {
- this.chords.style('fill-opacity', (c) => {
- if (c.index !== d.index || c.type !== d.type) {
- return 0.1;
- } else {
- indices.push(c.subindex);
- return 0.5;
- }
- });
-
- this.circles.style('fill-opacity', (c) => indices.includes(c.index) ? 0.75 : 0.1);
- this.arcs.style('fill-opacity', (c) => c === d ? 1 : 0.25);
- } else if (type === 'circle') {
- this.chords.style('fill-opacity', (c) => {
- if (c.subindex !== d.index) {
- return 0.1;
- } else {
- indices.push(c.index);
- return 0.5;
- }
- });
-
- this.circles.style('fill-opacity', (c) => c === d ? 0.75 : 0.25);
- this.arcs.style('fill-opacity', (c) => indices.includes(c.index) ? 1 : 0.1);
- }
- }
-
- restore() {
- this.chords.style('fill-opacity', 0.5);
- this.circles.style('fill-opacity', 0.75);
- this.arcs.style('fill-opacity', 1);
- }
-
}
-// Custom layout function for graph viz
-// Converts input data into return arrays of component svg elements
-function graphFlow() {
- const tau = Math.PI * 2;
-
- var padAngle = 0;
- var spaceAngle = tau / 4;
- var radius = 0;
- var innerRadius = 0;
-
- function layout(data) {
- var result = {};
- result.in = arrayToObj(data.in);
- result.out = arrayToObj(data.out);
- result.blt = arrayToObj(circleLayout(data.blt, innerRadius));
+// d3-sankey layout taken from: <<>> and modified by Ryan
+function sankey() {
+ var sankey = {},
+ nodeWidth = 24,
+ nodePadding = 8,
+ size = [1, 1],
+ nodes = [],
+ links = [];
- var arcAngle = (tau - (spaceAngle * 2)) / 2;
- var inStart = (tau + spaceAngle) / 2;
- var outStart = spaceAngle / 2;
-
- var inSide = sideLayout(data.inMatrix, result.blt, inStart, arcAngle, padAngle, radius, 'in');
- var outSide = sideLayout(data.outMatrix, result.blt, outStart, arcAngle, padAngle, radius, 'out');
-
- result.inArcs = inSide[0];
- result.inChords = inSide[1];
- result.outArcs = outSide[0];
- result.outChords = outSide[1];
+ sankey.nodeWidth = function(_) {
+ if (!arguments.length) return nodeWidth;
+ nodeWidth = +_;
+ return sankey;
+ };
- return result;
- }
+ sankey.nodePadding = function(_) {
+ if (!arguments.length) return nodePadding;
+ nodePadding = +_;
+ return sankey;
+ };
- layout.padAngle = (value) => {
- return value ? (padAngle = value, layout) : padAngle;
- };
+ sankey.nodes = function(_) {
+ if (!arguments.length) return nodes;
+ nodes = _;
+ return sankey;
+ };
- layout.spaceAngle = (value) => {
- return value ? (spaceAngle = value, layout) : spaceAngle;
- };
+ sankey.links = function(_) {
+ if (!arguments.length) return links;
+ links = _;
+ return sankey;
+ };
- layout.radius = (value) => {
- return value ? (radius = value, layout) : radius;
- };
+ sankey.size = function(_) {
+ if (!arguments.length) return size;
+ size = _;
+ return sankey;
+ };
- layout.innerRadius = (value) => {
- return value ? (innerRadius = value, layout) : innerRadius;
- };
+ sankey.layout = function(iterations) {
+ computeNodeLinks();
+ computeNodeValues();
+ computeNodeBreadths();
+ computeNodeDepths(iterations);
+ computeLinkDepths();
+ return sankey;
+ };
- return layout;
-}
+ sankey.relayout = function() {
+ computeLinkDepths();
+ return sankey;
+ };
-function sideLayout(matrix, circles, startAngle, angle, padAngle, radius, type) {
- var n = matrix.length;
- var m = matrix[0].length;
- var groupSums = [];
- var total = 0;
- var arcs = new Array(n);
- var chordTemp = new Array(n * m);
- var chords = [];
- var k;
- var dx;
- var x;
- var x0;
- var i;
- var j;
-
- matrix.forEach((group) => {
- groupSums.push(group.reduce( (prev, curr) => prev + curr ));
- });
-
- total = groupSums.reduce( (prev, curr) => prev + curr );
-
- k = Math.max(0, angle - padAngle * n) / total;
- dx = k ? padAngle : angle / n;
-
- x = startAngle;
- i = -1;
-
- while(++i < n) {
- x0 = x;
- j = -1;
-
- while(++j < n) {
- var v = matrix[i][j];
- var a0 = x;
- var a1 = x += v * k;
-
- chordTemp[j + (n * i)] = {
- index : i,
- subindex : j,
- startAngle : a0,
- endAngle : a1,
- value : v,
+ sankey.link = function() {
+ var curvature = .5;
+
+ function link(d) {
+ var x0 = d.source.x + d.source.dx,
+ x1 = d.target.x,
+ xi = d3.interpolateNumber(x0, x1),
+ x2 = xi(curvature),
+ x3 = xi(1 - curvature),
+ y0 = d.source.y + d.sy + d.dy / 2,
+ y1 = d.target.y + d.ty + d.dy / 2;
+ return "M" + x0 + "," + y0 + "C" + x2 + "," + y0 + " " + x3 + "," + y1 + " " + x1 + "," + y1;
+ }
+
+ link.curvature = function(_) {
+ if (!arguments.length) return curvature;
+ curvature = +_;
+ return link;
};
- }
- arcs[i] = {
- index : i,
- type : type,
- startAngle : x0,
- endAngle : x,
- value : groupSums[i],
+ return link;
};
- x += dx;
- }
+ // Populate the sourceLinks and targetLinks for each node.
+ // Also, if the source and target are not objects, assume they are indices.
+ function computeNodeLinks() {
+ nodes.forEach(function(node) {
+ node.sourceLinks = [];
+ node.targetLinks = [];
+ });
+ links.forEach(function(link) {
+ var source = link.source,
+ target = link.target;
+ if (typeof source === "number") source = link.source = nodes[link.source];
+ if (typeof target === "number") target = link.target = nodes[link.target];
+ source.sourceLinks.push(link);
+ target.targetLinks.push(link);
+ });
+ }
- chordTemp.forEach((chord) => {
- if (chord.value > 0) {
- let circle = circles[chord.subindex];
-
- chords.push({
- index : chord.index,
- subindex : chord.subindex,
- type : type,
- source : {
- startAngle : chord.startAngle,
- endAngle : chord.endAngle,
- radius : radius,
- },
- target : {
- startAngle : circle.theta - 0.001,
- endAngle : circle.theta + 0.001,
- radius : circle.radius,
- },
+ // Compute the value (size) of each node by summing the associated links.
+ function computeNodeValues() {
+ nodes.forEach(function(node) {
+ node.value = Math.max(
+ d3.sum(node.sourceLinks, value),
+ d3.sum(node.targetLinks, value)
+ );
});
}
- });
- return [arcs, chords];
+ // Iteratively assign the breadth (x-position) for each node.
+ // Nodes are assigned the maximum breadth of incoming neighbors plus one;
+ // nodes with no incoming links are assigned breadth zero, while
+ // nodes with no outgoing links are assigned the maximum breadth.
+ function computeNodeBreadths() {
+ var remainingNodes = nodes,
+ nextNodes,
+ x = 0;
+
+ while (remainingNodes.length) {
+ nextNodes = [];
+ remainingNodes.forEach(function(node) {
+ node.x = x;
+ node.dx = nodeWidth;
+ node.sourceLinks.forEach(function(link) {
+ if (nextNodes.indexOf(link.target) < 0) {
+ nextNodes.push(link.target);
+ }
+ });
+ });
+ remainingNodes = nextNodes;
+ ++x;
+ }
+
+ //
+ moveSinksRight(x);
+ scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
+ }
-}
+ function moveSourcesRight() {
+ nodes.forEach(function(node) {
+ if (!node.targetLinks.length) {
+ node.x = d3.min(node.sourceLinks, function(d) {
+ return d.target.x;
+ }) - 1;
+ }
+ });
+ }
-function circleLayout(circles, innerRadius) {
- circles.forEach((d) => {
- d.r = d.value;
- });
+ function moveSinksRight(x) {
+ nodes.forEach(function(node) {
+ if (!node.sourceLinks.length) {
+ node.x = x - 1;
+ }
+ });
+ }
- d3.packSiblings(circles);
- var enclose = d3.packEnclose(circles);
- var k = innerRadius / enclose.r;
+ function scaleNodeBreadths(kx) {
+ nodes.forEach(function(node) {
+ node.x *= kx;
+ });
+ }
+
+ function computeNodeDepths(iterations) {
+ var nodesByBreadth = d3.nest()
+ .key(function(d) {
+ return d.x;
+ })
+ .sortKeys(d3.ascending)
+ .entries(nodes)
+ .map(function(d) {
+ return d.values;
+ });
+
+ //
+ initializeNodeDepth();
+ resolveCollisions();
+ for (var alpha = 1; iterations > 0; --iterations) {
+ relaxRightToLeft(alpha *= .99);
+ resolveCollisions();
+ relaxLeftToRight(alpha);
+ resolveCollisions();
+ }
+
+ function initializeNodeDepth() {
+ var ky = d3.min(nodesByBreadth, function(nodes) {
+ return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
+ });
+
+ nodesByBreadth.forEach(function(nodes) {
+ nodes.forEach(function(node, i) {
+ node.y = i;
+ node.dy = node.value * ky;
+ });
+ });
+
+ links.forEach(function(link) {
+ link.dy = link.value * ky;
+ });
+ }
+
+ function relaxLeftToRight(alpha) {
+ nodesByBreadth.forEach(function(nodes, breadth) {
+ nodes.forEach(function(node) {
+ if (node.targetLinks.length) {
+ var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
+ node.y += (y - center(node)) * alpha;
+ }
+ });
+ });
+
+ function weightedSource(link) {
+ return center(link.source) * link.value;
+ }
+ }
+
+ function relaxRightToLeft(alpha) {
+ nodesByBreadth.slice().reverse().forEach(function(nodes) {
+ nodes.forEach(function(node) {
+ if (node.sourceLinks.length) {
+ var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
+ node.y += (y - center(node)) * alpha;
+ }
+ });
+ });
+
+ function weightedTarget(link) {
+ return center(link.target) * link.value;
+ }
+ }
+
+ function resolveCollisions() {
+ nodesByBreadth.forEach(function(nodes) {
+ var node,
+ dy,
+ y0 = 0,
+ n = nodes.length,
+ i;
+
+ // Push any overlapping nodes down.
+ nodes.sort(ascendingDepth);
+ for (i = 0; i < n; ++i) {
+ node = nodes[i];
+ dy = y0 - node.y;
+ if (dy > 0) node.y += dy;
+ y0 = node.y + node.dy + nodePadding;
+ }
+
+ // If the bottommost node goes outside the bounds, push it back up.
+ dy = y0 - nodePadding - size[1];
+ if (dy > 0) {
+ y0 = node.y -= dy;
+
+ // Push any overlapping nodes back up.
+ for (i = n - 2; i >= 0; --i) {
+ node = nodes[i];
+ dy = node.y + node.dy + nodePadding - y0;
+ if (dy > 0) node.y -= dy;
+ y0 = node.y;
+ }
+ }
+ });
+ }
+
+ function ascendingDepth(a, b) {
+ return a.y - b.y;
+ }
+ }
- circles.forEach((d) => {
- d.r = d.r * k;
- d.x = d.x * k;
- d.y = d.y * k;
+ function computeLinkDepths() {
+ nodes.forEach(function(node) {
+ node.sourceLinks.sort(ascendingTargetDepth);
+ node.targetLinks.sort(ascendingSourceDepth);
+ });
+ nodes.forEach(function(node) {
+ var sy = 0,
+ ty = 0;
+ node.sourceLinks.forEach(function(link) {
+ link.sy = sy;
+ sy += link.dy;
+ });
+ node.targetLinks.forEach(function(link) {
+ link.ty = ty;
+ ty += link.dy;
+ });
+ });
- let rSq = Math.pow(d.x, 2) + Math.pow(d.y, 2);
- d.radius = Math.sqrt(rSq);
- d.theta = Math.atan2(d.y, d.x) + (Math.PI / 2);
- });
+ function ascendingSourceDepth(a, b) {
+ return a.source.y - b.source.y;
+ }
- return circles;
-}
+ function ascendingTargetDepth(a, b) {
+ return a.target.y - b.target.y;
+ }
+ }
-function arrayToObj(a) {
- var o = {};
+ function center(node) {
+ return node.y + node.dy / 2;
+ }
- a.forEach((d) => {
- o[d.index] = d;
- });
+ function value(link) {
+ return link.value;
+ }
- return o;
+ return sankey;
}
SankeyPlot.propTypes = {
element : PropTypes.string.isRequired,
data : PropTypes.object,
metric : PropTypes.string.isRequired,
- // data : PropTypes.shape({
- // inMatrix : PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
- // outMatrix : PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
- // in : PropTypes.arrayOf(PropTypes.shape({
- // index : PropTypes.number,
- // name : PropTypes.string,
- // })),
- // out : PropTypes.arrayOf(PropTypes.shape({
- // index : PropTypes.number,
- // name : PropTypes.string,
- // })),
- // between : PropTypes.arrayOf(PropTypes.shape({
- // index : PropTypes.number,
- // name : PropTypes.string,
- // value : PropTypes.number,
- // })),
- // }).isRequired,
};
[3/3] incubator-senssoft-tap git commit: Modified vis layout to
include both sankey and bar graph
Posted by ar...@apache.org.
Modified vis layout to include both sankey and bar graph
Project: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/commit/855c2059
Tree: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/tree/855c2059
Diff: http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/diff/855c2059
Branch: refs/heads/ryan-sankey
Commit: 855c20594fbcdcb24f51226645ff0fb53b7c6829
Parents: 1c1d7c0
Author: Arthi Vezhavendan <ar...@gmail.com>
Authored: Tue Sep 5 22:29:46 2017 -0400
Committer: Arthi Vezhavendan <ar...@gmail.com>
Committed: Tue Sep 5 22:29:46 2017 -0400
----------------------------------------------------------------------
public/components/AppResults.jsx | 111 +++++++++++++++++++++-------------
1 file changed, 68 insertions(+), 43 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-senssoft-tap/blob/855c2059/public/components/AppResults.jsx
----------------------------------------------------------------------
diff --git a/public/components/AppResults.jsx b/public/components/AppResults.jsx
index a9e987c..163e753 100644
--- a/public/components/AppResults.jsx
+++ b/public/components/AppResults.jsx
@@ -73,11 +73,11 @@ class AppResults extends Component {
// },
// });
- $('#ab-toggle').checkbox({
- onChange : () => {
- this.setState({ ab : $('input[name=a-b]').is(':checked') });
- },
- });
+ // $('#ab-toggle').checkbox({
+ // onChange : () => {
+ // this.setState({ ab : $('input[name=a-b]').is(':checked') });
+ // },
+ // });
$('#graph-ab-toggle').checkbox({
onChange: () => {
@@ -87,27 +87,27 @@ class AppResults extends Component {
}
render() {
- console.log("is an app defined here?");
- console.log(this.props.app);
+ //console.log("is an app defined here?");
+ //console.log(this.props.app);
const { name, results } = this.props.app;
// get graph data from distill
- var url = 'http://localhost:8090/sankey/userale?from=now-15m&to=now&size=20';
- //var url = 'http://localhost:8090';
- axios.get(url)
- .then( (response) => {
- console.log("response", response);
- var sankeyhtml = response.data;
- // this.setState({
- // fetchUser: response.data
- // });
- //console.log("fetchUser", this.state.fetchUser);
- })
- .catch( (error) => {
- console.log(error);
- });
+ // var url = 'http://distill:8090/sankey/userale?from=now-15m&to=now&size=20'; //snarl-distill calls
+ // //var url = 'http://localhost:8090';
+ // axios.get(url)
+ // .then( (response) => {
+ // //console.log("response", response);
+ // var sankeyhtml = response.data;
+ // // this.setState({
+ // // fetchUser: response.data
+ // // });
+ // //console.log("fetchUser", this.state.fetchUser);
+ // })
+ // .catch( (error) => {
+ // console.log(error);
+ // });
var sankeyhtml = "";
// var url = 'http://distill:8090/sankey/userale?from=now-15m&to=now&size=20';
@@ -140,23 +140,56 @@ class AppResults extends Component {
<div className='item'>
<a id='counts' className='active main-controls title'>
<i className='dropdown icon'></i>
- Activity Counts
+ Sankey and Counts
</a>
<div className='active content'>
- <div className='field'>
- <div id='ab-toggle' className='ui toggle checkbox'>
- <input type='checkbox' name='a-b'></input>
- <label>A/B</label>
+ <div className='content'>
+ <div className='ui form'>
+ <div className='grouped fields'>
+ <div className='field'>
+ <div className='ui radio checkbox'>
+ <input type='radio' name='metric' value='out_degree' defaultChecked></input>
+ <label>Out Degree</label>
+ </div>
+ </div>
+ <div className='field'>
+ <div className='ui radio checkbox'>
+ <input type='radio' name='metric' value='in_degree'></input>
+ <label>In Degree</label>
+ </div>
+ </div>
+ <div className='field'>
+ <div className='ui radio checkbox'>
+ <input type='radio' name='metric' value='betweenness_cent_dir_weighted'></input>
+ <label>Weighted Betweenness</label>
+ </div>
+ </div>
+ <div className='field'>
+ <div className='ui radio checkbox'>
+ <input type='radio' name='metric' value='closeness_cent_dir_weighted'></input>
+ <label>Weighted Closeness</label>
+ </div>
+ </div>
+ <div className='field'>
+ <div className='ui radio checkbox'>
+ <input type='radio' name='metric' value='closeness_cent_dir_unweighted'></input>
+ <label>Unweighted Closeness</label>
+ </div>
+ </div>
</div>
+
</div>
+ </div>
</div>
</div>
+
+
<div className='item'>
<a id='graph' className='main-controls title'>
<i className='dropdown icon'></i>
- Graph Metrics
+ Graph Metrics Example
</a>
<div className='content'>
<div className='ui form'>
@@ -276,25 +309,17 @@ class AppResults extends Component {
case 'sankey':
return (
<div>
- <iframe srcDoc={sankeyhtml} height="300px" width="700px" style={{border: '0px'}}/>
+ <SankeyPlot filters={this.state} data={results.graph} element='sankey-plot-viz' metric={this.state.metric} />
</div>
);
case 'counts':
- default:
- return <Counts filters={this.state} data={results.counts} />;
- // case 'countsandbowie':
- // return (
- // <div>
- // <CountsAndBowie metric={this.state.metric} element='graph-metrics-viz' data={results.graph} />
- // {this.state.graphAb ?
- // <CountsAndBowie
- // metric='betweenness_cent_dir_weighted'
- // element='graph-metrics-viz-b'
- // data={results.graph}
- // /> : null
- // }
- // </div>
- // );
+ default:
+ return (
+ <div>
+ <SankeyPlot filters={this.state} data={results.graph} element='sankey-plot-viz' metric={this.state.metric} />
+ <Counts filters={this.state} data={results.counts} />
+ </div>
+ );
}
})()}
</div>