summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--web/i18n/en.yml3
-rw-r--r--web/lib/ui/relation.rb21
-rw-r--r--web/views/relation.erb10
-rw-r--r--web/viewsjs/relation.js.erb145
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;