Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

added visuals for circle mode of follower network

  • Loading branch information...
commit 90244d7d2cb6a48ed2045dabc5c6f56f6d029e54 1 parent 3cbdd08
Juli authored
9 lib/githubviz.rb
@@ -66,7 +66,7 @@ def process_data
66 66
67 67 if @user
68 68
69   - @api = GitHubV3API.new('')
  69 + @api = GitHubV3API.new('c1616feca6aa3e63655dd92766a475c2227ed6a0')
70 70
71 71 @data[@user] = @api.get("/users/#{@user}")
72 72 @data[@user]['level'] = 0
@@ -80,7 +80,7 @@ def process_data
80 80
81 81 get '/repo_viz' do
82 82 @user = params[:user]
83   - @api = GitHubV3API.new('')
  83 + @api = GitHubV3API.new('c1616feca6aa3e63655dd92766a475c2227ed6a0')
84 84 user_data = @api.users.get(@user)
85 85 my_repos = @api.repos.list
86 86 page = 1
@@ -175,7 +175,12 @@ def process_data
175 175 end
176 176
177 177 get '/circle_viz' do
  178 + @api = GitHubV3API.new('c1616feca6aa3e63655dd92766a475c2227ed6a0')
178 179 @user = params[:user]
  180 + user_data = @api.users.get(@user)
  181 + #@data[@user] = @api.get("/users/#{@user}")
  182 + #@data[@user]['follower_count'] = @data[@user]['followers']
  183 + #@data[@user]['followers'] = @api.get("/users/#{@user}/followers")
179 184 erb :circle
180 185 end
181 186
125 lib/public/d3.layout.js
@@ -69,48 +69,44 @@ d3.layout.chord = function() {
69 69
70 70 function relayout() {
71 71 var subgroups = {},
72   - groupSums = [],
73 72 groupIndex = d3.range(n),
74 73 subgroupIndex = [],
  74 + sum_cell = [],
75 75 k,
76 76 x,
77 77 x0,
78 78 i,
79 79 j;
80   -
  80 + a = 0;
  81 + b = 0;
  82 + c = 0;
81 83 chords = [];
82 84 groups = [];
83 85
84 86 // Compute the sum.
85 87 k = 0, i = -1; while (++i < n) {
86 88 x = 0, j = -1; while (++j < n) {
87   - x += matrix[i][j];
  89 + x += matrix[i][j];// Summe der Zellinhalte
88 90 }
89   - groupSums.push(x);
90 91 subgroupIndex.push(d3.range(n));
91   - k += x;
92   - }
93   -
94   - // Sort groups…
95   - if (sortGroups) {
96   - groupIndex.sort(function(a, b) {
97   - return sortGroups(groupSums[a], groupSums[b]);
98   - });
  92 + sum_cell.push({index: i, value: x});//Zum Zählen der "Null-Nodes"
  93 + k += x; //Addition und Zuweisung der Summe ausgehender Verlinkungen
99 94 }
100   -
101   - // Sort subgroups…
102   - if (sortSubgroups) {
103   - subgroupIndex.forEach(function(d, i) {
104   - d.sort(function(a, b) {
105   - return sortSubgroups(matrix[i][a], matrix[i][b]);
106   - });
107   - });
  95 +
  96 + a = 0; while (++a < sum_cell.length){
  97 + if (sum_cell[a].value == 0){
  98 + b += 1;
  99 + }else{
  100 + c += 1;
  101 + }
108 102 }
109   -
  103 + //k += b;
  104 + //alert("Länge: " + sum_cell.length + ", b:" + b + ", k: " + k);
110 105 // Convert the sum to scaling factor for [0, 2pi].
111 106 // TODO Allow start and end angle to be specified.
112 107 // TODO Allow padding to be specified as percentage?
113   - k = (2 * Math.PI - padding * n) / k;
  108 + k = (2 * Math.PI - padding * n - 0.025 * b) / k;
  109 +
114 110
115 111 // Compute the start and end angle for each group and subgroup.
116 112 // Note: Opera has a bug reordering object literal properties!
@@ -119,23 +115,32 @@ d3.layout.chord = function() {
119 115 var di = groupIndex[i],
120 116 dj = subgroupIndex[di][j],
121 117 v = matrix[di][dj],
122   - a0 = x,
123   - a1 = x += v * k;
  118 +
  119 + a0 = x += (v * k)/3,
  120 + a1 = x += (v * k)/3;//Neuzuweisung x (Startposition)
  121 + //if (v==0){a0 = a1 = x += 0.025/2}
124 122 subgroups[di + "-" + dj] = {
125   - index: di,
  123 + index: di, //jeder Index 23Mal vorhanden --> min = 0, max = 22
126 124 subindex: dj,
127 125 startAngle: a0,
128 126 endAngle: a1,
129   - value: v
  127 + value: v //binäre Werte für ausgehende Verbindungen aus der quadratischen Beziehungs-Matrix --> je Index 23 Werte --> gesamt 23²
130 128 };
  129 + x += (v * k)/3 //Padding für Graphen des Nodes
  130 + //alert("subgroups: " + subgroups[di + "-" + dj].index + ", start: " + subgroups[di + "-" + dj].startAngle + ", end: " + subgroups[di + "-" + dj].endAngle + ", value: " + subgroups[di + "-" + dj].value);
  131 + }
  132 + if (x0 == x){
  133 + x += 0.025 //zeige auch die Nodes, die keine ausgehenden Verbindungen haben
131 134 }
132   - groups.push({
133   - index: di,
  135 + groups.push({ // Groups bildet RückgabeArray an View
  136 + index: di, //Node
134 137 startAngle: x0,
135 138 endAngle: x,
136   - value: (x - x0) / k
  139 + value: (x - x0) / k //Anzahl ausgehender Verbindungen je Node
137 140 });
138 141 x += padding;
  142 + //alert("groups: " + groups[di].index + ", value: " + groups[di].value + ", start: " + groups[di].startAngle + ", end: " + groups[di].endAngle);
  143 +
139 144 }
140 145
141 146 // Generate chords for each (non-empty) subgroup-subgroup link.
@@ -143,24 +148,38 @@ d3.layout.chord = function() {
143 148 j = i - 1; while (++j < n) {
144 149 var source = subgroups[i + "-" + j],
145 150 target = subgroups[j + "-" + i];
146   - if (source.value || target.value) {
147   - chords.push(source.value < target.value
148   - ? {source: target, target: source}
149   - : {source: source, target: target});
  151 + if (source.value || target.value){
  152 + if (source.value < target.value) {
  153 + var group = groups[source.index];
  154 + if (group.startAngle == source.startAngle){
  155 + source.startAngle += (group.endAngle - group.startAngle)/2;
  156 + source.endAngle += (group.endAngle - group.startAngle)/2;}
  157 + else if (group.endAngle == source.endAngle){
  158 + source.startAngle -= (group.endAngle - group.startAngle)/2;
  159 + source.endAngle -= (group.endAngle - group.startAngle)/2;}
  160 + chords.push({source: target, target: source});}
  161 + //groups.push({source: target, target: source});}
  162 +
  163 + else {
  164 + var group = groups[target.index];
  165 + if (group.startAngle == target.startAngle){
  166 + target.startAngle += (group.endAngle - group.startAngle)/2;
  167 + target.endAngle += (group.endAngle - group.startAngle)/2;}
  168 + else if (group.endAngle == target.endAngle){
  169 + target.startAngle -= (group.endAngle - group.startAngle)/2;
  170 + target.endAngle -= (group.endAngle - group.startAngle)/2;
  171 + }
  172 + chords.push({source: source, target: target});}
  173 + //groups.push({source: target, target: source});}
  174 +
  175 +
  176 + //alert("Index: " + chords[i].target.index + "TargetSTART: " + chords[i].target.startAngle + "TargetEND: " + chords[i].target.endAngle + "Value: " + chords[i].target.value);
  177 +
150 178 }
151 179 }
152 180 }
153   -
154   - if (sortChords) resort();
155   - }
156   -
157   - function resort() {
158   - chords.sort(function(a, b) {
159   - return sortChords(
160   - (a.source.value + a.target.value) / 2,
161   - (b.source.value + b.target.value) / 2);
162   - });
163 181 }
  182 +
164 183
165 184 chord.matrix = function(x) {
166 185 if (!arguments.length) return matrix;
@@ -176,26 +195,6 @@ d3.layout.chord = function() {
176 195 return chord;
177 196 };
178 197
179   - chord.sortGroups = function(x) {
180   - if (!arguments.length) return sortGroups;
181   - sortGroups = x;
182   - chords = groups = null;
183   - return chord;
184   - };
185   -
186   - chord.sortSubgroups = function(x) {
187   - if (!arguments.length) return sortSubgroups;
188   - sortSubgroups = x;
189   - chords = null;
190   - return chord;
191   - };
192   -
193   - chord.sortChords = function(x) {
194   - if (!arguments.length) return sortChords;
195   - sortChords = x;
196   - if (chords) resort();
197   - return chord;
198   - };
199 198
200 199 chord.chords = function() {
201 200 if (!chords) relayout();
29 lib/public/flare-imports.json
... ... @@ -0,0 +1,29 @@
  1 +[
  2 +{"name":"flare.analytics.cluster","size":3938,"imports":["flare.animate"]},
  3 +{"name":"flare.analytics.graph","size":3534,"imports":["flare.animate","flare.vis.data","flare.util","flare.vis.operator"]},
  4 +{"name":"flare.animate","size":17010,"imports":[]},
  5 +{"name":"flare.data.converters","size":721,"imports":[]},
  6 +{"name":"flare.data.DataField","size":1759,"imports":["flare.data.DataUtil"]},
  7 +{"name":"flare.data.DataSchema","size":2165,"imports":["flare.data.DataField","flare.util"]},
  8 +{"name":"flare.data.DataSet","size":586,"imports":["flare.data.DataTable"]},
  9 +{"name":"flare.data.DataSource","size":3331,"imports":["flare.data.converters","flare.data.DataSchema"]},
  10 +{"name":"flare.data.DataTable","size":772,"imports":["flare.data.DataSchema"]},
  11 +{"name":"flare.data.DataUtil","size":3322,"imports":["flare.data.DataField","flare.data.DataSchema"]},
  12 +{"name":"flare.display","size":8833,"imports":[]},
  13 +{"name":"flare.flex","size":4116,"imports":["flare.display","flare.data.DataSet"]},
  14 +{"name":"flare.physics","size":1082,"imports":[]},
  15 +{"name":"flare.query","size":1616,"imports":[]},
  16 +{"name":"flare.scale","size":2105,"imports":[]},
  17 +{"name":"flare.util","size":8258,"imports":[]},
  18 +{"name":"flare.vis.axis","size":1302,"imports":["flare.animate","flare.vis.Visualization"]},
  19 +{"name":"flare.vis.controls","size":2138,"imports":["flare.vis.Visualization","flare.vis.operator"]},
  20 +{"name":"flare.vis.data","size":20544,"imports":["flare.util","flare.vis.events","flare.data.DataSet","flare.data.DataSchema","flare.data.DataField"]},
  21 +{"name":"flare.vis.events","size":2313,"imports":["flare.vis.data"]},
  22 +{"name":"flare.vis.legend","size":4614,"imports":["flare.util","flare.display"]},
  23 +{"name":"flare.vis.operator","size":4461,"imports":[]},
  24 +{"name":"flare.vis.pix","size":4461,"imports":[]},
  25 +{"name":"flare.vis.pic","size":4461,"imports":[]},
  26 +{"name":"flare.vis.piz","size":4461,"imports":[]},
  27 +{"name":"flare.vis.pit","size":4461,"imports":[]},
  28 +{"name":"flare.vis.Visualization","size":16540,"imports":["flare.animate","flare.vis.operator","flare.vis.events","flare.vis.data","flare.vis.axis","flare.util","flare.vis.controls"]}
  29 +]
244 lib/views/circle.erb
... ... @@ -1,5 +1,245 @@
1   -<h1>Circle Mode</h1>
2   -<div class="gallery"><%= @user %></div>
  1 +<h1><%= @user %>'s CIRCLE Mode</h1>
  2 +<div class="gallery" id ="chord"></div>
  3 +<script type="text/javascript" src="d3.js?2.7.2"></script>
  4 +<script src='d3.layout.js?2.7.2' type='text/javascript'></script>
  5 +<script src='d3.geom.js?2.7.2' type='text/javascript'> </script>
  6 +<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
  7 +<script type="text/javascript">
  8 +$(function (){
  9 +var fader = false;
  10 +var index = [];
  11 +var index2 = [];
  12 +var a = 0;
  13 +var b = 0;
  14 +var r1 = 700 / 2,
  15 + r0 = r1 - 140;
  16 +
  17 +var fill = d3.scale.category20();
  18 +
  19 +var chord = d3.layout.chord()
  20 + .padding(.02)
  21 +
  22 +var arc = d3.svg.arc()
  23 + .innerRadius(r0)
  24 + .outerRadius(r0 + 20);
  25 +
  26 +var clickarea = d3.select("#chord").append("div")
  27 + .attr("class", "clickarea")
  28 + .style("opacity", 0);
  29 +
  30 +var svg = d3.select("#chord").append("svg")
  31 + .attr("width", r1 * 2)
  32 + .attr("height", r1 * 2)
  33 + //.attr("transform", "translate(" + r1 + "," + r1 + ")")
  34 + //.attr("height", 10)
  35 + .append("g")
  36 + .attr("transform", "translate(" + r1 + "," + r1 + ")");
  37 +
  38 +d3.select("div.clickarea").on("click", fade2(1));
  39 +
  40 +
  41 +d3.json("flare-imports.json", function(imports) {
  42 + var indexByName = {},
  43 + nameByIndex = {},
  44 + matrix = [],
  45 + n = 0;
  46 +
  47 + // Returns the Flare package name for the given class name.
  48 + function name(name) {
  49 + return name;
  50 + }
  51 +
  52 + // Compute a unique index for each package name.
  53 + imports.forEach(function(d) {
  54 + d = name(d.name);
  55 + if (!(d in indexByName)) {
  56 + nameByIndex[n] = d;
  57 + indexByName[d] = n++;
  58 + }
  59 + });
  60 +
  61 + // Construct a square matrix counting package imports.
  62 + imports.forEach(function(d) {
  63 + var source = indexByName[name(d.name)],
  64 + row = matrix[source];
  65 + if (!row) {
  66 + row = matrix[source] = [];
  67 + for (var i = -1; ++i < n;) row[i] = 0;
  68 + }
  69 + d.imports.forEach(function(d) { row[indexByName[name(d)]]++; });
  70 + });
  71 +
  72 + chord.matrix(matrix);
  73 +
  74 + var node = svg.selectAll("g.group")
  75 + .data(chord.groups)
  76 + .enter().append("g")
  77 +
  78 + node.append("path")
  79 + .style("fill", "#666666")
  80 + .style("stroke","#666666")
  81 + .attr("d", arc)
  82 + .on("mouseover", fade_mover(0))
  83 + .on("mouseout", fade_mout(1));
  84 +
  85 + node.append("text")
  86 + .each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
  87 + .attr("dy", ".35em")
  88 + .attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
  89 + .attr("transform", function(d) {
  90 + return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
  91 + + "translate(" + (r0 + 26) + ")"
  92 + + (d.angle > Math.PI ? "rotate(180)" : "");
  93 + })
  94 + .text(function(d) { return nameByIndex[d.index]; });
  95 +
  96 + svg.selectAll("path.chord")
  97 + .data(chord.chords)
  98 + .enter().append("path")
  99 + .attr("class", "chord")
  100 + .style("stroke", "#999999")
  101 + .style("fill", "#999999" )
  102 + .attr("d", d3.svg.chord().radius(r0))
  103 + .style("opacity", 0);
  104 +
  105 +});
  106 +//show or hide chords of selected node by mouseover (depends on switching opacity on/ off in function fade2)
  107 +function fade_mover(opacity) {
  108 + return function(g, i) {
  109 + a = 0;
  110 + b = 0;
  111 + index = [];
  112 + index2 = [];
  113 + svg.selectAll("path.chord")
  114 + .filter(function(d) {
  115 + if (d.source.index == i){
  116 + index[a] = [d.target.index];
  117 + a += 1;}
  118 + if (d.target.index == i){
  119 + index2[b] = [d.source.index];
  120 + b += 1;}
  121 + if (fader == false){
  122 + opacity = 1;
  123 + return d.source.index == i || d.target.index == i;
  124 + }
  125 + else if (fader == true) {
  126 + opacity = 0;
  127 + return d.source.index != i && d.target.index != i;}
  128 + })
  129 + .transition()
  130 + .style("opacity", opacity)
  131 + .duration(400)
  132 +
  133 + index.forEach(function(ind){
  134 + svg.selectAll("path")
  135 + .filter(function(d) {
  136 + return d.index == ind;
  137 + index = [];
  138 + a = 0;
  139 + })
  140 + .transition()
  141 + .style("stroke", "#0d50f6")
  142 + .style("fill", "#0d50f6");
  143 + });
  144 + index2.forEach(function(ind2){
  145 + svg.selectAll("path")
  146 + .filter(function(d) {
  147 + return d.index == ind2;
  148 + index2 = [];
  149 + b = 0;
  150 + })
  151 + .transition()
  152 + .style("stroke", "#0cb0f6")
  153 + .style("fill", "#0cb0f6");
  154 + });
  155 + svg.selectAll("path")
  156 + .filter(function(d) {
  157 + return d.index == i;
  158 + })
  159 + .transition()
  160 + .style("stroke", "#ff0442")
  161 + .style("fill", "#ff0442");
  162 + };
  163 +};
  164 +//show or hide chords of selected node by mouseout (depends on switching opacity on/ off in function fade2)
  165 +function fade_mout(opacity) {
  166 + return function(g, i) {
  167 + a = 0;
  168 + b = 0;
  169 + index = [];
  170 + index2 = [];
  171 + svg.selectAll("path.chord")
  172 + .filter(function(d) {
  173 + if (d.source.index == i){
  174 + index[a] = [d.target.index];
  175 + a += 1;}
  176 + if (d.target.index == i){
  177 + index2[b] = [d.source.index];
  178 + b += 1;}
  179 + if (fader == false){
  180 + opacity = 0;
  181 + return d.source.index == i || d.target.index == i;}
  182 + else if (fader == true) {
  183 + opacity = 1;
  184 + return d.source.index != i && d.target.index != i;}
  185 + })
  186 + .transition()
  187 + .style("opacity", opacity)
  188 + .duration(400)
  189 +
  190 + index.forEach(function(ind){
  191 + svg.selectAll("path")
  192 + .filter(function(d) {
  193 + return d.index == ind;
  194 + index = [];
  195 + a = 0;
  196 + })
  197 + .transition()
  198 + .style("stroke", "#666666")
  199 + .style("fill", "#666666");
  200 + });
  201 +
  202 + index2.forEach(function(ind2){
  203 + svg.selectAll("path")
  204 + .filter(function(d) {
  205 + return d.index == ind2;
  206 + index2 = [];
  207 + b = 0;
  208 + })
  209 + .transition()
  210 + .style("stroke", "#666666")
  211 + .style("fill", "#666666");
  212 + });
  213 + svg.selectAll("path")
  214 + .filter(function(d) {
  215 + return d.index == i;
  216 + })
  217 + .transition()
  218 + .style("stroke", "#666666")
  219 + .style("fill", "#666666");
  220 + };
  221 +};
  222 +//show or hide all chords by clicking the center of the circle
  223 +function fade2(opacity) {
  224 + return function(g, i) {
  225 + if (fader == true){
  226 + opacity = 0;
  227 + fader = false;}
  228 + else{
  229 + opacity = 1;
  230 + fader= true;}
  231 + svg.selectAll("path.chord")
  232 + .filter(function(d) {
  233 + return d.source.index != i || d.target.index != i;
  234 + })
  235 + .transition()
  236 + .style("opacity", opacity)
  237 + .duration(400);
  238 + };
  239 +};
  240 +});
  241 +</script>
  242 +
3 243
4 244
5 245
8 lib/views/follower.erb
@@ -23,6 +23,9 @@
23 23 $('#chart').html(data);
24 24 }
25 25 });
  26 + $('#desc_text').empty();
  27 + $('#desc_text').append($("<p>network of followers</p>"));
  28 + $('#description').show();
26 29 });
27 30 $('#network_mode').click(function(ev){
28 31 ev.preventDefault();
@@ -40,6 +43,7 @@
40 43 });
41 44 </script>
42 45 <div id="viz">
  46 + <!--<%= @result%>-->
43 47 <div class="description">
44 48 <div class="level_buttons">
45 49 <form class="level_form" action="/follower_viz" method="post">
@@ -57,7 +61,7 @@
57 61 <a href="#" id ="network_mode" class="mode">Network Mode</a>
58 62 </div>
59 63 </div>
60   - <div style="float:right;padding-right:20%">
  64 + <div id ="desc_text" style="float:right;padding-right:18%">
61 65 <h3><%= @user%>'s network of followers</h3>
62 66 <p>This visualization shows the user's network of followers in 1 level or 2 levels.<br/>
63 67 In the 1st level all gitusers which are directly following <%=@user%> are displayed. <br/>
@@ -75,7 +79,7 @@
75 79 $(function (){
76 80
77 81 var w = $(window).width(),
78   - h = $(window).height() - $("#header").height() -20,
  82 + h = $(window).height() - $("#header").height() -80,
79 83 fill = d3.scale.category20();
80 84
81 85 var vis = d3.select("#chart").append("svg")
42 lib/views/index.erb
@@ -112,6 +112,14 @@
112 112 font-size: 10pt;
113 113 }
114 114
  115 + .gallery{
  116 + text-align: center;
  117 + }
  118 +
  119 + #desc_text{
  120 + width:450px;
  121 + }
  122 +
115 123 #header{
116 124 padding:10px;
117 125 }
@@ -119,6 +127,7 @@
119 127 .description {
120 128 text-align:center;
121 129 clear:both;
  130 + height: 85px;
122 131 }
123 132
124 133 .formlabel {
@@ -152,6 +161,39 @@
152 161 font-size: 10pt;
153 162 padding: 5px 5px 0 0;
154 163 }
  164 +
  165 + #chord {
  166 + font-size: 8pt;
  167 + font-family: sans-serif;
  168 + height: 700px;
  169 + width: 700px;
  170 + padding-left: 0px;
  171 + }
  172 +
  173 + path.chord {
  174 + fill-opacity: .85;
  175 + stroke-opacity: 1.0;
  176 + }
  177 +
  178 + path{
  179 + fill-opacity: 1.0;
  180 + stroke-opacity: 1.0;
  181 + }
  182 +
  183 + .clickarea{
  184 + width: 280px;
  185 + height: 280px;
  186 + margin: 210px 0 0 16%;
  187 + z-index: 1;
  188 + position: absolute;
  189 + cursor: pointer;
  190 + }
  191 +
  192 + svg {
  193 + clear: both;
  194 + position: absolute;
  195 + z-index: 0;
  196 + }
155 197
156 198 </style>
157 199 </head>

0 comments on commit 90244d7

Please sign in to comment.
Something went wrong with that request. Please try again.