diff options
author | Jochen Topf <jochen@topf.org> | 2013-01-19 11:18:27 +0100 |
---|---|---|
committer | Jochen Topf <jochen@topf.org> | 2013-01-19 11:18:27 +0100 |
commit | 253b55a53212ea59cd188418ef138dcbbbaba718 (patch) | |
tree | a1e21869f4f471e59bab7c585acc486d4e65e075 | |
parent | 34bdd4c02b55047e2101219e58ef5131220ecb16 (diff) | |
download | taginfo-253b55a53212ea59cd188418ef138dcbbbaba718.tar taginfo-253b55a53212ea59cd188418ef138dcbbbaba718.tar.gz |
Add cool member role chart
-rw-r--r-- | web/i18n/en.yml | 3 | ||||
-rw-r--r-- | web/lib/ui/relation.rb | 21 | ||||
-rw-r--r-- | web/views/relation.erb | 10 | ||||
-rw-r--r-- | web/viewsjs/relation.js.erb | 145 |
4 files changed, 178 insertions, 1 deletions
diff --git a/web/i18n/en.yml b/web/i18n/en.yml index c9020ec..062786c 100644 --- a/web/i18n/en.yml +++ b/web/i18n/en.yml @@ -285,6 +285,9 @@ pages: ways_tooltip: Relation members of type way with this role (and as percentage of all members of type way) for this relation type. relations_tooltip: Relation members of type relation with this role (and as percentage of all members of type relation) for this relation type. no_roles_info: There is no information about roles for this relation type (most likely because there are only very few of those relations). + graph: + tab: Roles Graph + title: Roles Graph flexigrid: pagetext: Page diff --git a/web/lib/ui/relation.rb b/web/lib/ui/relation.rb index 5801ae2..27a0ea1 100644 --- a/web/lib/ui/relation.rb +++ b/web/lib/ui/relation.rb @@ -30,6 +30,27 @@ class Taginfo < Sinatra::Base condition("rtype=?", rtype). get_first_value().to_i + sum_count_all = @db.select("SELECT members_all FROM db.relation_types WHERE rtype=?", @rtype).get_first_value().to_i + + @roles = [] + sum = { 'nodes' => 0, 'ways' => 0, 'relations' => 0 } + @db.select("SELECT * FROM db.relation_roles WHERE rtype=? ORDER BY count_all DESC", @rtype).execute() do |row| + %w( nodes ways relations ).each do |type| + count = row["count_#{ type }"] + if row['count_all'] < sum_count_all * 0.01 + sum[type] += count + else + @roles << { :role => row['role'], :type => type, :value => count } + end + end + end + if sum['nodes'] > 0 || sum['ways'] > 0 || sum['relations'] > 0 + %w( nodes ways relations ).each do |type| + @roles << { :role => '...', :type => type, :value => sum[type] } + end + end + + javascript 'd3/d3.v3.min' javascript "#{ r18n.locale.code }/relation" erb :relation end diff --git a/web/views/relation.erb b/web/views/relation.erb index 240ed6c..845ab65 100644 --- a/web/views/relation.erb +++ b/web/views/relation.erb @@ -14,6 +14,7 @@ <ul> <li><a href="#overview"><%= t.pages.relation.overview.tab %></a></li> <li><a href="#roles"><%= t.pages.relation.roles.tab %></a></li> + <li><a href="#graph"><%= t.pages.relation.graph.tab %></a></li> </ul> <div id="overview"> <% if @image_url %> @@ -35,15 +36,22 @@ <p class="empty"><%= t.pages.relation.roles.no_roles_info %></p> <% end %> </div> + <div id="graph"> + <h2><%= t.pages.relation.graph.title %></h2> + <div class="canvas"> + </div> + </div> </div> <iframe id="josmiframe" name="josmiframe"></iframe> <% javascript do JS.raw(<<"JAVASCRIPT") function page_init2() { - var rtype = #{ @rtype.to_json }; + var rtype = #{ @rtype.to_json }, + roles = #{ @roles.to_json.gsub(/\},/, "},\n") }; jQuery('h1').html("#{ t.pages.relation.name } '" + fmt_rtype(rtype) + "'"); jQuery('span#taglink').html(link_to_value('type', rtype)); init_tabs([rtype]); + create_role_chart(roles); } JAVASCRIPT end diff --git a/web/viewsjs/relation.js.erb b/web/viewsjs/relation.js.erb index eb4ff9d..5d38ffb 100644 --- a/web/viewsjs/relation.js.erb +++ b/web/viewsjs/relation.js.erb @@ -57,6 +57,151 @@ var create_flexigrid_for = { } }; +// Marimekko chart based on http://bl.ocks.org/1005090 +function create_role_chart(data) { + var width = 800, + height = 360, + margin = 20; + + var x = d3.scale.linear() .range([0, width - 3 * margin]); + var y = d3.scale.linear() .range([0, height - 2 * margin]); + + var z = d3.scale.category10(); + + var n = d3.format(",d"), + p = d3.format("%"); + + var svg = d3.select("div.canvas").append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("transform", "translate(" + 2 * margin + "," + margin + ")"); + + var offset = 0; + + // Nest values by member type. We assume each member type+role is unique. + var member_types = d3.nest() + .key(function(d) { return d.type; }) + .entries(data); + + // Compute the total sum, the per-type sum, and the per-role offset. + // You can use reduce rather than reduceRight to reverse the ordering. + // We also record a reference to the parent type for each role. + var sum = member_types.reduce(function(v, p) { + return (p.offset = v) + (p.sum = p.values.reduceRight(function(v, d) { + d.parent = p; + return (d.offset = v) + d.value; + }, 0)); + }, 0); + + // Add x-axis ticks. + var xtick = svg.selectAll(".x") + .data(x.ticks(10)) + .enter().append("g") + .attr("class", "x") + .attr("transform", function(d) { return "translate(" + x(d) + "," + y(1) + ")"; }); + + xtick.append("line") + .attr("y2", 6) + .style("stroke", "#000"); + + xtick.append("text") + .attr("y", 8) + .attr("text-anchor", "middle") + .attr("dy", ".71em") + .text(p); + + // Add y-axis ticks. + var ytick = svg.selectAll(".y") + .data(y.ticks(10)) + .enter().append("g") + .attr("class", "y") + .attr("transform", function(d) { return "translate(0," + y(1 - d) + ")"; }); + + ytick.append("line") + .attr("x1", -6) + .style("stroke", "#000"); + + ytick.append("text") + .attr("x", -8) + .attr("text-anchor", "end") + .attr("dy", ".35em") + .text(p); + + // Add a group for each member type. + var member_types_svg = svg.selectAll(".member_types") + .data(member_types) + .enter().append("g") + .attr("class", "member_types") + .attr("xlink:title", function(d) { return d.key; }) + .attr("transform", function(d) { return "translate(" + x(d.offset / sum) + ")"; }) + .call(function(c) { + c.append("line") + .attr("x1", 0) + .attr("y1", -9.5) + .attr("x2", function(d) { return x(d.sum/sum); } ) + .attr("y2", -9.5) + .style("stroke", "#000"); + c.append("text") + .attr("x", 0) + .attr("y", -6) + .attr("text-anchor", "start") + .text(function(d) { return x(d.sum/sum/2) > 6 ? '<' : ''; }); + c.append("text") + .attr("x", function(d) { return x(d.sum/sum); } ) + .attr("y", -6) + .attr("text-anchor", "end") + .text(function(d) { return x(d.sum/sum/2) > 6 ? '>' : ''; }); + c.append("text") + .attr("x", function(d) { return x(d.sum/sum/2); } ) + .attr("y", -6) + .attr("text-anchor", "middle") + .style("stroke", "#ddddd4") + .style("stroke-width", 4) + .style("fill", "#000") + .text(function(d) { return x(d.sum/sum/2) > 20 ? texts.osm[d.key] : ''; }); + c.append("text") + .attr("x", function(d) { return x(d.sum/sum/2); } ) + .attr("y", -6) + .attr("text-anchor", "middle") + .style("fill", "#000") + .text(function(d) { return x(d.sum/sum/2) > 20 ? texts.osm[d.key] : ''; }); + }) + + // Add a rect for each role. + var roles = member_types_svg.selectAll(".role") + .data(function(d) { return d.values; }) + .enter().append("a") + .attr("class", "role") + .attr("xlink:title", function(d) { + if (d.role == '...') { + return "" + d.value + " member " + d.parent.key + " with other roles"; + } else { + return "" + d.value + " member " + d.parent.key + " with role '" + d.role + "'"; + } + }) + .call(function(c) { + c.append("rect") + .attr("y", function(d) { return y(d.offset / d.parent.sum); }) + .attr("height", function(d) { return y(d.value / d.parent.sum); }) + .attr("width", function(d) { return x(d.parent.sum / sum); }) + .style("fill", function(d) { return z(d.role); }); + c.append("text") + .attr("x", function(d) { return x(d.parent.sum / sum / 2); } ) + .attr("y", function(d) { return y(d.value / d.parent.sum / 2); } ) + .attr("dy", function(d) { return y(d.offset / d.parent.sum) + 4; }) + .attr("text-anchor", "middle") + .style("fill", "#fff") + .text(function(d) { + if (x(d.parent.sum / sum) > 40 && y(d.value / d.parent.sum) > 12) { + return d.role == '' ? '(empty)' : d.role; + } else { + return ''; + } + }); + }); +} + function page_init() { jQuery('#josm_button').bind('click', function() { jQuery('#josmiframe')[0].src = jQuery('#josm_button')[0].href; |