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>