mirror of
https://github.com/openSUSE/osem.git
synced 2026-08-13 19:54:02 +00:00
Merge pull request #55 from differentreality/stats_charts
Stats charts for registrations (Admin)
This commit is contained in:
commit
5c719afbdf
13 changed files with 9349 additions and 110 deletions
|
|
@ -19,3 +19,5 @@
|
||||||
//= require cocoon
|
//= require cocoon
|
||||||
//= require bootstrap
|
//= require bootstrap
|
||||||
//= require osem
|
//= require osem
|
||||||
|
//= require d3.v3
|
||||||
|
//= require d3.v3.min
|
||||||
8774
app/assets/javascripts/d3.v3.js
vendored
Normal file
8774
app/assets/javascripts/d3.v3.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
5
app/assets/javascripts/d3.v3.min.js
vendored
Normal file
5
app/assets/javascripts/d3.v3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -11,8 +11,10 @@ class Admin::StatsController < ApplicationController
|
||||||
|
|
||||||
@registered_with_partner = @conference.registrations.where("attending_with_partner = ?", true).count
|
@registered_with_partner = @conference.registrations.where("attending_with_partner = ?", true).count
|
||||||
@attended_with_partner = @conference.registrations.where("attending_with_partner = ? AND attended = ?", true, true).count
|
@attended_with_partner = @conference.registrations.where("attending_with_partner = ? AND attended = ?", true, true).count
|
||||||
|
|
||||||
@handicapped_access = @conference.registrations.where("handicapped_access_required = ?", true).count
|
@handicapped_access = @conference.registrations.where(handicapped_access_required: true).count
|
||||||
|
@handicapped_access_attended = @conference.registrations.where(handicapped_access_required: true)
|
||||||
|
@handicapped_access_attended = @handicapped_access_attended.where(attended: true).count
|
||||||
@suggested_hotel_stay = @conference.registrations.where("using_affiliated_lodging = ?", true).count
|
@suggested_hotel_stay = @conference.registrations.where("using_affiliated_lodging = ?", true).count
|
||||||
|
|
||||||
@speakers = Person.joins(:events).where("events.conference_id = ? AND events.state LIKE ?", @conference.id, 'confirmed').uniq
|
@speakers = Person.joins(:events).where("events.conference_id = ? AND events.state LIKE ?", @conference.id, 'confirmed').uniq
|
||||||
|
|
@ -21,27 +23,53 @@ class Admin::StatsController < ApplicationController
|
||||||
|
|
||||||
@supporter_levels = @conference.supporter_levels
|
@supporter_levels = @conference.supporter_levels
|
||||||
@tickets = @conference.registrations.joins(:supporter_registration => :supporter_level)
|
@tickets = @conference.registrations.joins(:supporter_registration => :supporter_level)
|
||||||
@tickets = @tickets.where("registrations.conference_id = ? AND supporter_levels.title NOT LIKE ? ", @conference, "%Free%").count
|
@tickets = @tickets.where("supporter_levels.title NOT LIKE ? ", "%Free%")
|
||||||
|
|
||||||
|
@mydays = []
|
||||||
|
@mytickets = []
|
||||||
|
|
||||||
|
(@conference.registration_start_date..@conference.end_date).each do |day|
|
||||||
|
day_reg_count = @registrations.where("created_at LIKE '%#{day}%' ").count
|
||||||
|
day_ticket_count = @tickets.where("supporter_registrations.created_at LIKE '%#{day}%' ").count
|
||||||
|
if day_reg_count != 0
|
||||||
|
@mydays << {"status" => "#{day}", "value" => day_reg_count}
|
||||||
|
end
|
||||||
|
@conference.supporter_levels.each do |level|
|
||||||
|
day_ticket_count = @tickets.where("supporter_registrations.created_at LIKE ? AND supporter_levels.title LIKE ?", "%#{day}%", "%#{level.title}%").count
|
||||||
|
if day_ticket_count !=0
|
||||||
|
@mytickets << {"status" => "#{day}", "ticket" => "#{level}", "value" => day_ticket_count}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@other_info = [
|
||||||
|
{"status" => "with partner (registered)", "value" => @registered_with_partner},
|
||||||
|
{"status" => "with partner (attended)", "value" => @attended_with_partner},
|
||||||
|
{"status" => "handicapped (registered)", "value" => @handicapped_access},
|
||||||
|
{"status" => "handicapped (attended)", "value" => @handicapped_access_attended},
|
||||||
|
{"status" => "stay at suggested hotel", "value" => @suggested_hotel_stay}
|
||||||
|
]
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def speaker_reg(speaker)
|
def speaker_reg(speaker)
|
||||||
speaker.registrations.where("conference_id = ? AND person_id = ?", @conference.id, speaker.id).first
|
speaker.registrations.where("conference_id = ? AND person_id = ?", @conference.id, speaker.id).first
|
||||||
end
|
end
|
||||||
|
|
||||||
def speaker_diet(reg)
|
def speaker_diet(reg)
|
||||||
@conference.dietary_choices.find(reg.dietary_choice_id)
|
@conference.dietary_choices.find(reg.dietary_choice_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def diet_count(diet)
|
def diet_count(diet)
|
||||||
@conference.registrations.where("dietary_choice_id = ?", diet).count
|
@conference.registrations.where("dietary_choice_id = ?", diet)
|
||||||
end
|
end
|
||||||
|
|
||||||
def social_event_count(event)
|
def social_event_count(event)
|
||||||
@conference.registrations.joins(:social_events).where("registrations_social_events.social_event_id = ?", event).count
|
@conference.registrations.joins(:social_events).where("registrations_social_events.social_event_id = ?", event).count
|
||||||
end
|
end
|
||||||
|
|
||||||
helper_method :speaker_reg
|
helper_method :speaker_reg
|
||||||
helper_method :speaker_diet
|
helper_method :speaker_diet
|
||||||
helper_method :diet_count
|
helper_method :diet_count
|
||||||
helper_method :social_event_count
|
helper_method :social_event_count
|
||||||
end
|
end
|
||||||
70
app/views/admin/stats/_other_info.html
Normal file
70
app/views/admin/stats/_other_info.html
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var registered = '<%= h @registrations.count%>';
|
||||||
|
var data = <%=raw @other_info.to_json %>;
|
||||||
|
|
||||||
|
var valueLabelWidth = 70; // space reserved for value labels (right)
|
||||||
|
var barHeight = 24; // height of one bar
|
||||||
|
var barLabelWidth = 210; // space reserved for bar labels
|
||||||
|
var barLabelPadding = 8; // padding between bar and bar labels (left)
|
||||||
|
var gridLabelHeight = 18; // space reserved for gridline labels
|
||||||
|
var gridChartOffset = 3; // space between start of grid and first bar
|
||||||
|
var maxBarWidth = 350; // width of the bar with the max value
|
||||||
|
var color = d3.scale.category20();
|
||||||
|
|
||||||
|
// accessor functions
|
||||||
|
var barLabel = function(d) { return d['status']; };
|
||||||
|
var barValue = function(d) { return d['value'] / registered * 100; };
|
||||||
|
var barValues = function(d) { return d['value']; };
|
||||||
|
|
||||||
|
// scales
|
||||||
|
var yScale = d3.scale.ordinal().domain(d3.range(0, data.length)).rangeBands([0, data.length * barHeight]);
|
||||||
|
var y = function(d, i) { return yScale(i); };
|
||||||
|
var yText = function(d, i) { return y(d, i) + yScale.rangeBand() / 2; };
|
||||||
|
var yMax = d3.max(data, function(d){ return barValue(d) + 15; });
|
||||||
|
var x = d3.scale.linear().domain([0, yMax]).range([0, maxBarWidth]);
|
||||||
|
|
||||||
|
// svg container element
|
||||||
|
var chart = d3.select('#other_info').append("svg")
|
||||||
|
.attr('width', maxBarWidth + barLabelWidth + valueLabelWidth)
|
||||||
|
.attr('height', gridLabelHeight + gridChartOffset + data.length * barHeight);
|
||||||
|
|
||||||
|
// bar labels
|
||||||
|
var labelsContainer = chart.append('g')
|
||||||
|
.attr('transform', 'translate(' + (barLabelWidth - barLabelPadding) + ',' + (gridLabelHeight + gridChartOffset) + ')');
|
||||||
|
labelsContainer.selectAll('text').data(data).enter().append('text')
|
||||||
|
.attr('y', yText)
|
||||||
|
.attr('stroke', 'none')
|
||||||
|
.attr('fill', 'black')
|
||||||
|
.attr("dy", ".35em") // vertical-align: middle
|
||||||
|
.attr('text-anchor', 'end')
|
||||||
|
.text(barLabel);
|
||||||
|
|
||||||
|
// bars
|
||||||
|
var barsContainer = chart.append('g')
|
||||||
|
.attr('transform', 'translate(' + barLabelWidth + ',' + (gridLabelHeight + gridChartOffset) + ')');
|
||||||
|
|
||||||
|
barsContainer.selectAll("rect").data(data).enter().append("rect")
|
||||||
|
.attr('y', y)
|
||||||
|
.attr('height', yScale.rangeBand())
|
||||||
|
.attr('width', function(d) { return x(barValue(d)); })
|
||||||
|
.attr('stroke', 'white')
|
||||||
|
.attr('fill', function(d) { return color(barLabel(d)); });
|
||||||
|
|
||||||
|
// bar value labels
|
||||||
|
barsContainer.selectAll("text").data(data).enter().append("text")
|
||||||
|
.attr("x", 5)
|
||||||
|
.attr("y", yText)
|
||||||
|
.attr("dx", 3) // padding-left
|
||||||
|
.attr("dy", ".35em") // vertical-align: middle
|
||||||
|
.attr("text-anchor", "start") // text-align: right
|
||||||
|
.attr("fill", "black")
|
||||||
|
.attr("stroke", "none")
|
||||||
|
.text(function(d) { return d3.round(barValues(d), 2) + " (" + d3.round(barValue(d), 0) + "%)"; });
|
||||||
|
// start line
|
||||||
|
barsContainer.append("line")
|
||||||
|
.attr("y1", -gridChartOffset)
|
||||||
|
.attr("y2", yScale.rangeExtent()[1] + gridChartOffset)
|
||||||
|
.style("stroke", "#000");
|
||||||
|
|
||||||
|
</script>
|
||||||
85
app/views/admin/stats/_preregistered_onsite.html
Normal file
85
app/views/admin/stats/_preregistered_onsite.html
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.arc path {
|
||||||
|
stroke: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var width = 250,
|
||||||
|
height = 250,
|
||||||
|
radius = Math.min(width, height) / 2;
|
||||||
|
|
||||||
|
var color = d3.scale.ordinal()
|
||||||
|
.range(["#9ECAE1", "#6baed6", "#DEEBF7"]);
|
||||||
|
|
||||||
|
var arc = d3.svg.arc()
|
||||||
|
.outerRadius(radius - 10)
|
||||||
|
.innerRadius(0);
|
||||||
|
|
||||||
|
var pie = d3.layout.pie()
|
||||||
|
.sort(null)
|
||||||
|
.value(function(d) { return d.value; });
|
||||||
|
|
||||||
|
// Where to put the chart
|
||||||
|
var svg = d3.select("#preregistered_onsite").append("svg")
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height)
|
||||||
|
.append("g")
|
||||||
|
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
|
||||||
|
|
||||||
|
var total = '<%= h @registrations.count %>';
|
||||||
|
var pre_registered = '<%= h @pre_registered %>';
|
||||||
|
var onsite = '<%= h @registrations.count - @pre_registered %>';
|
||||||
|
var data = [
|
||||||
|
{"status":"onsite","value": onsite, "percent": d3.round(onsite / total * 100)},
|
||||||
|
{"status":"pre_registered","value":pre_registered, "percent": d3.round(pre_registered / total * 100)}
|
||||||
|
];
|
||||||
|
|
||||||
|
var g = svg.selectAll(".arc")
|
||||||
|
.data(pie(data))
|
||||||
|
.enter().append("g")
|
||||||
|
.attr("class", "arc");
|
||||||
|
|
||||||
|
g.append("path")
|
||||||
|
.attr("d", arc)
|
||||||
|
.style("fill", function(d) { return color(d.data.status); });
|
||||||
|
|
||||||
|
// Put text inside every piece of the pie-chart
|
||||||
|
g.append("text")
|
||||||
|
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
|
||||||
|
.attr("dy", ".35em")
|
||||||
|
.style("text-anchor", "middle")
|
||||||
|
.text(function(d) { return d.data.percent +"%"; });
|
||||||
|
|
||||||
|
// Title of chart
|
||||||
|
svg.append("text")
|
||||||
|
.attr("class", "title")
|
||||||
|
.attr("x", radius)
|
||||||
|
.attr("y", -radius+10)
|
||||||
|
.text("% of pre-registered");
|
||||||
|
|
||||||
|
// Create & position legend
|
||||||
|
var legend = d3.select("#preregistered_onsite").append("svg")
|
||||||
|
.attr("class", "legend")
|
||||||
|
.attr("width", radius * 2)
|
||||||
|
.attr("height", radius)
|
||||||
|
.selectAll("g")
|
||||||
|
.data(color.domain().slice().reverse())
|
||||||
|
.enter().append("g")
|
||||||
|
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
|
||||||
|
|
||||||
|
legend.append("rect")
|
||||||
|
.attr("width", 18)
|
||||||
|
.attr("height", 18)
|
||||||
|
.style("fill", color);
|
||||||
|
|
||||||
|
legend.append("text")
|
||||||
|
.attr("x", 24)
|
||||||
|
.attr("y", 9)
|
||||||
|
.attr("dy", ".15em")
|
||||||
|
.text(function(d) { return d + " (" + data.filter(function(v){return v.status === d;})[0].value + " or " + data.filter(function(v){return v.status === d;})[0].percent + "%)" ; });
|
||||||
|
|
||||||
|
</script>
|
||||||
85
app/views/admin/stats/_registered_attended.html
Normal file
85
app/views/admin/stats/_registered_attended.html
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.arc path {
|
||||||
|
stroke: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var width = 250,
|
||||||
|
height = 250,
|
||||||
|
radius = Math.min(width, height) / 2;
|
||||||
|
|
||||||
|
var color = d3.scale.ordinal()
|
||||||
|
.range(["#9ECAE1", "#6baed6", "#DEEBF7"]);
|
||||||
|
|
||||||
|
var arc = d3.svg.arc()
|
||||||
|
.outerRadius(radius - 10)
|
||||||
|
.innerRadius(0);
|
||||||
|
|
||||||
|
var pie = d3.layout.pie()
|
||||||
|
.sort(null)
|
||||||
|
.value(function(d) { return d.value; });
|
||||||
|
|
||||||
|
// Where to put the chart
|
||||||
|
var svg = d3.select("#registered_attended").append("svg")
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height)
|
||||||
|
.append("g")
|
||||||
|
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
|
||||||
|
|
||||||
|
var registered = '<%= h @pre_registered%>';
|
||||||
|
var attended = '<%= h @pre_registered_attended %>';
|
||||||
|
var not_attended = '<%= h @pre_registered - @pre_registered_attended %>';
|
||||||
|
var data = [
|
||||||
|
{"status":"did not attend","value":not_attended, "percent": d3.round(not_attended / registered * 100)},
|
||||||
|
{"status":"attended","value": attended, "percent": d3.round(attended / registered * 100)}
|
||||||
|
];
|
||||||
|
|
||||||
|
var g = svg.selectAll(".arc")
|
||||||
|
.data(pie(data))
|
||||||
|
.enter().append("g")
|
||||||
|
.attr("class", "arc");
|
||||||
|
|
||||||
|
g.append("path")
|
||||||
|
.attr("d", arc)
|
||||||
|
.style("fill", function(d) { return color(d.data.status); });
|
||||||
|
|
||||||
|
// Put text inside every piece of the pie-chart
|
||||||
|
g.append("text")
|
||||||
|
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
|
||||||
|
.attr("dy", ".35em")
|
||||||
|
.style("text-anchor", "middle")
|
||||||
|
.text(function(d) { return d.data.percent +"%"; });
|
||||||
|
|
||||||
|
// Title of chart
|
||||||
|
svg.append("text")
|
||||||
|
.attr("class", "title")
|
||||||
|
.attr("x", radius)
|
||||||
|
.attr("y", -radius+10)
|
||||||
|
.text("% of pre-registered");
|
||||||
|
|
||||||
|
// Create & position legend
|
||||||
|
var legend = d3.select("#registered_attended").append("svg")
|
||||||
|
.attr("class", "legend")
|
||||||
|
.attr("width", radius*2)
|
||||||
|
.attr("height", radius)
|
||||||
|
.selectAll("g")
|
||||||
|
.data(color.domain().slice().reverse())
|
||||||
|
.enter().append("g")
|
||||||
|
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
|
||||||
|
|
||||||
|
legend.append("rect")
|
||||||
|
.attr("width", 18)
|
||||||
|
.attr("height", 18)
|
||||||
|
.style("fill", color);
|
||||||
|
|
||||||
|
legend.append("text")
|
||||||
|
.attr("x", 24)
|
||||||
|
.attr("y", 9)
|
||||||
|
.attr("dy", ".15em")
|
||||||
|
.text(function(d) { return d + " (" + data.filter(function(v){return v.status === d;})[0].value + " or " + data.filter(function(v){return v.status === d;})[0].percent + "%)" ; });
|
||||||
|
|
||||||
|
</script>
|
||||||
71
app/views/admin/stats/_registered_time.html
Normal file
71
app/views/admin/stats/_registered_time.html
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var registered = '<%= h @registrations.count%>';
|
||||||
|
var data = <%=raw @mydays.to_json %>;
|
||||||
|
|
||||||
|
var valueLabelWidth = 10; // space reserved for value labels (right)
|
||||||
|
var barHeight = 24; // height of one bar
|
||||||
|
var barLabelWidth = 100; // space reserved for bar labels
|
||||||
|
var barLabelPadding = 8; // padding between bar and bar labels (left)
|
||||||
|
var gridLabelHeight = 18; // space reserved for gridline labels
|
||||||
|
var gridChartOffset = 3; // space between start of grid and first bar
|
||||||
|
var maxBarWidth = 420; // width of the bar with the max value
|
||||||
|
var color = d3.scale.category20();
|
||||||
|
|
||||||
|
// accessor functions
|
||||||
|
var barLabel = function(d) { return d['status']; };
|
||||||
|
var barValue = function(d) { return d['value'] / registered * 100; };
|
||||||
|
var barValues = function(d) { return d['value']; };
|
||||||
|
|
||||||
|
// scales
|
||||||
|
var yScale = d3.scale.ordinal().domain(d3.range(0, data.length)).rangeBands([0, data.length * barHeight]);
|
||||||
|
var y = function(d, i) { return yScale(i); };
|
||||||
|
var yText = function(d, i) { return y(d, i) + yScale.rangeBand() / 2; };
|
||||||
|
var yMax = d3.max(data, function(d){ return barValue(d) + 15; });
|
||||||
|
var x = d3.scale.linear().domain([0, yMax]).range([0, maxBarWidth]);
|
||||||
|
var width = maxBarWidth + barLabelWidth + valueLabelWidth;
|
||||||
|
// svg container element
|
||||||
|
var chart = d3.select('#registered_time').append("svg")
|
||||||
|
.attr('width', maxBarWidth + barLabelWidth + valueLabelWidth)
|
||||||
|
.attr('height', gridLabelHeight + gridChartOffset + data.length * barHeight);
|
||||||
|
|
||||||
|
// bar labels
|
||||||
|
var labelsContainer = chart.append('g')
|
||||||
|
.attr('transform', 'translate(' + (barLabelWidth - barLabelPadding) + ',' + (gridLabelHeight + gridChartOffset) + ')');
|
||||||
|
labelsContainer.selectAll('text').data(data).enter().append('text')
|
||||||
|
.attr('y', yText)
|
||||||
|
.attr('stroke', 'none')
|
||||||
|
.attr('fill', 'black')
|
||||||
|
.attr("dy", ".35em") // vertical-align: middle
|
||||||
|
.attr('text-anchor', 'end')
|
||||||
|
.text(barLabel);
|
||||||
|
|
||||||
|
// bars
|
||||||
|
var barsContainer = chart.append('g')
|
||||||
|
.attr('transform', 'translate(' + barLabelWidth + ',' + (gridLabelHeight + gridChartOffset) + ')');
|
||||||
|
|
||||||
|
barsContainer.selectAll("rect").data(data).enter().append("rect")
|
||||||
|
.attr('y', y)
|
||||||
|
.attr('height', yScale.rangeBand())
|
||||||
|
.attr('width', function(d) { return x(barValue(d)); })
|
||||||
|
.attr('stroke', 'white')
|
||||||
|
.attr('fill', function(d) { return color(barValue(d)); });
|
||||||
|
|
||||||
|
// bar value labels
|
||||||
|
barsContainer.selectAll("text").data(data).enter().append("text")
|
||||||
|
.attr("x", 5)
|
||||||
|
.attr("y", yText)
|
||||||
|
.attr("dx", 3) // padding-left
|
||||||
|
.attr("dy", ".35em") // vertical-align: middle
|
||||||
|
.attr("text-anchor", "start") // text-align: right
|
||||||
|
.attr("fill", "black")
|
||||||
|
.attr("stroke", "none")
|
||||||
|
.text(function(d) { return d3.round(barValues(d), 2) + " (" + d3.round(barValue(d), 0) + "%)"; });
|
||||||
|
// start line
|
||||||
|
barsContainer.append("line")
|
||||||
|
.attr("y1", -gridChartOffset)
|
||||||
|
.attr("y2", yScale.rangeExtent()[1] + gridChartOffset)
|
||||||
|
.style("stroke", "#000");
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
@ -1,72 +1,32 @@
|
||||||
.row-fluid
|
.row-fluid
|
||||||
.span12
|
.span4
|
||||||
%h3
|
%h4{:style => "text-indent: 25px"}= "Registrations - Attendees (#{(@attendees.to_f / @registered * 100).round}%)"
|
||||||
Total Registrations
|
.row-fluid
|
||||||
- if @registered
|
.span12#registrations_attendees
|
||||||
= "(#{@registered})"
|
|
||||||
- if @attendees
|
|
||||||
= " - Attendees (#{@attendees})"
|
|
||||||
.row-fluid
|
|
||||||
.span3
|
|
||||||
%table.table.table-bordered.table-striped
|
|
||||||
%thead
|
|
||||||
%th General Stats
|
|
||||||
%th #
|
|
||||||
%tr
|
|
||||||
%td Registered attended
|
|
||||||
%td= @pre_registered_attended
|
|
||||||
%tr
|
|
||||||
%td Registered not attended
|
|
||||||
%td= @pre_registered - @pre_registered_attended
|
|
||||||
%tr
|
|
||||||
%td Attended with partner
|
|
||||||
%td= @attended_with_partner
|
|
||||||
%tr
|
|
||||||
%td Registred to attend with partner
|
|
||||||
%td= @registered_with_partner
|
|
||||||
%tr
|
|
||||||
%td Handicapped Access for
|
|
||||||
%td= @handicapped_access
|
|
||||||
%tr
|
|
||||||
%td Staying at suggested hotel
|
|
||||||
%td= @suggested_hotel_stay
|
|
||||||
|
|
||||||
.span3
|
.span4
|
||||||
- if @conference.use_dietary_choices == true
|
%h4{:style => "text-indent: 10px"}= "Attendance of pre-registered"
|
||||||
- diet_choices = []
|
.row-fluid
|
||||||
- @conference.dietary_choices.each do |d|
|
.span12#registered_attended
|
||||||
- count = diet_count(d.id)
|
|
||||||
- if count > 0
|
.span4
|
||||||
- diet_choices << [d.title, count]
|
%h4{:style => "text-indent: 25px"}= "% of pre-registered"
|
||||||
- if diet_choices.any?
|
.row-fluid
|
||||||
%table.table.table-bordered.table-striped
|
.span12#preregistered_onsite
|
||||||
%thead
|
|
||||||
%th Dietary Choice
|
.row-fluid
|
||||||
%th #
|
.span6
|
||||||
%th Percentage
|
%h4= "Other Info"
|
||||||
- diet_choices.each do |d|
|
.row-fluid
|
||||||
%tr
|
.span12#other_info
|
||||||
%td= d[0]
|
|
||||||
%td= d[1]
|
.span6
|
||||||
%td
|
%h4{:style => "text-indent: 98px"}= "Registrations over time"
|
||||||
- if @registered and @registered > 0
|
.row-fluid
|
||||||
= "#{d[1] * 100 / @registered}% of registered"
|
.span12#registered_time
|
||||||
- if @attendees and @attendees > 0
|
|
||||||
%br
|
= render :partial => "registered_attended"
|
||||||
= "#{d[1] * 100 / @attendees}% of attendees"
|
= render :partial => "registered_time"
|
||||||
.span3
|
= render :partial => "preregistered_onsite"
|
||||||
- if @conference.social_events.count > 0
|
= render :partial => "registrations_attendees"
|
||||||
- social_events = []
|
= render :partial => "other_info"
|
||||||
- @conference.social_events.each do |social_event|
|
|
||||||
- count = social_event_count(social_event)
|
|
||||||
- if count > 0
|
|
||||||
- social_events << [social_event.title, count]
|
|
||||||
- if social_events.any?
|
|
||||||
%table.table.table-bordered.table-striped
|
|
||||||
%thead
|
|
||||||
%th Social Event
|
|
||||||
%th #
|
|
||||||
- social_events.each do |s|
|
|
||||||
%tr
|
|
||||||
%td= s[0]
|
|
||||||
%td= s[1]
|
|
||||||
96
app/views/admin/stats/_registrations_attendees.html
Normal file
96
app/views/admin/stats/_registrations_attendees.html
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
<script>
|
||||||
|
var w = 250,
|
||||||
|
h = 250,
|
||||||
|
r = Math.min(w, h) / 2 - 10,
|
||||||
|
inner = 80,
|
||||||
|
color = d3.scale.category20c();
|
||||||
|
|
||||||
|
var registrations = '<%= h @registered %>';
|
||||||
|
var attendees = '<%= h @attendees %>';
|
||||||
|
|
||||||
|
var data = [
|
||||||
|
{"status":"Registrations","value": registrations, "percent": ""},
|
||||||
|
{"status":"Attendees","value": attendees, "percent": d3.round(attendees/registrations * 100)+"%"}
|
||||||
|
];
|
||||||
|
|
||||||
|
var vis = d3.select("#registrations_attendees")
|
||||||
|
.append("svg:svg")
|
||||||
|
.data([data])
|
||||||
|
.attr("width", w)
|
||||||
|
.attr("height", h)
|
||||||
|
.append("svg:g")
|
||||||
|
.attr("transform", "translate(" + r * 1.1 + "," + r * 1.1 + ")")
|
||||||
|
|
||||||
|
var textTop = vis.append("text")
|
||||||
|
.attr("dy", ".35em")
|
||||||
|
.style("text-anchor", "middle")
|
||||||
|
.attr("class", "textTop")
|
||||||
|
.text( "Registered: " + registrations)
|
||||||
|
.attr("y", -10),
|
||||||
|
textBottom = vis.append("text")
|
||||||
|
.attr("dy", ".35em")
|
||||||
|
.style("text-anchor", "middle")
|
||||||
|
.attr("class", "textBottom")
|
||||||
|
.text("Attended: " + attendees)
|
||||||
|
.attr("y", 10);
|
||||||
|
|
||||||
|
var arc = d3.svg.arc()
|
||||||
|
.innerRadius(inner)
|
||||||
|
.outerRadius(r);
|
||||||
|
|
||||||
|
var arcOver = d3.svg.arc()
|
||||||
|
.innerRadius(inner + 5)
|
||||||
|
.outerRadius(r + 5);
|
||||||
|
|
||||||
|
var pie = d3.layout.pie()
|
||||||
|
.value(function(d) { return d.value; });
|
||||||
|
|
||||||
|
var arcs = vis.selectAll("g.slice")
|
||||||
|
.data(pie)
|
||||||
|
.enter()
|
||||||
|
.append("svg:g")
|
||||||
|
.attr("class", "slice")
|
||||||
|
.on("mouseover", function(d) {
|
||||||
|
d3.select(this).select("path").transition()
|
||||||
|
.duration(200)
|
||||||
|
.attr("d", arcOver)
|
||||||
|
|
||||||
|
textTop.text(d3.select(this).datum().data.status)
|
||||||
|
.attr("y", -10);
|
||||||
|
textBottom.text( d3.select(this).datum().data.value)
|
||||||
|
.attr("y", 10);
|
||||||
|
})
|
||||||
|
.on("mouseout", function(d) {
|
||||||
|
d3.select(this).select("path").transition()
|
||||||
|
.duration(100)
|
||||||
|
.attr("d", arc);
|
||||||
|
|
||||||
|
textTop.text("Registered: " + registrations)
|
||||||
|
.attr("y", -10);
|
||||||
|
textBottom.text("Attended: " + attendees);
|
||||||
|
});
|
||||||
|
|
||||||
|
arcs.append("svg:path")
|
||||||
|
.attr("fill", function(d, i) { return color(i); } )
|
||||||
|
.attr("d", arc);
|
||||||
|
|
||||||
|
var legend = d3.select("#registrations_attendees").append("svg")
|
||||||
|
.attr("class", "legend")
|
||||||
|
.attr("width", r * 2)
|
||||||
|
.attr("height", r)
|
||||||
|
.selectAll("g")
|
||||||
|
.data(data)
|
||||||
|
.enter().append("g")
|
||||||
|
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
|
||||||
|
|
||||||
|
legend.append("rect")
|
||||||
|
.attr("width", 18)
|
||||||
|
.attr("height", 18)
|
||||||
|
.style("fill", function(d, i) { return color(i); });
|
||||||
|
|
||||||
|
legend.append("text")
|
||||||
|
.attr("x", 24)
|
||||||
|
.attr("y", 9)
|
||||||
|
.attr("dy", ".35em")
|
||||||
|
.text(function(d) { return "Total " + d.status; });
|
||||||
|
</script>
|
||||||
|
|
@ -1,28 +1,5 @@
|
||||||
.row-fluid
|
- if @tickets
|
||||||
.span12
|
%h2
|
||||||
%h3
|
= "Non-Free Tickets(#{@tickets.count})"
|
||||||
Non-free Tickets (#{@tickets})
|
.span4#tickets_time
|
||||||
.row-fluid
|
= render :partial => "tickets_time"
|
||||||
.span5
|
|
||||||
- if @supporter_levels.count > 0
|
|
||||||
%table.table.table-bordered.table-striped
|
|
||||||
%thead
|
|
||||||
%th Support Level
|
|
||||||
%th #
|
|
||||||
%th %
|
|
||||||
- @supporter_levels.each do |level|
|
|
||||||
- reg_count = level.supporter_registrations.where(conference_id: @conference).count
|
|
||||||
- if reg_count > 0
|
|
||||||
%tr
|
|
||||||
%td= level.title
|
|
||||||
%td
|
|
||||||
= reg_count
|
|
||||||
%td
|
|
||||||
- if @registered and @registered > 0
|
|
||||||
= "#{reg_count * 100 / @registered}% of registered"
|
|
||||||
- if @attendees and @attendees > 0
|
|
||||||
%br
|
|
||||||
= "#{reg_count * 100 / @attendees}% of attendees"
|
|
||||||
- else
|
|
||||||
%p
|
|
||||||
No tickets sold yet
|
|
||||||
86
app/views/admin/stats/_tickets_time.html
Normal file
86
app/views/admin/stats/_tickets_time.html
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var registered = '<%= h @registrations.count%>';
|
||||||
|
var data = <%=raw @mytickets.to_json %>;
|
||||||
|
|
||||||
|
var valueLabelWidth = 40; // space reserved for value labels (right)
|
||||||
|
var barHeight = 24; // height of one bar
|
||||||
|
var barLabelWidth = 100; // space reserved for bar labels
|
||||||
|
var barLabelPadding = 8; // padding between bar and bar labels (left)
|
||||||
|
var gridLabelHeight = 18; // space reserved for gridline labels
|
||||||
|
var gridChartOffset = 3; // space between start of grid and first bar
|
||||||
|
var maxBarWidth = 420; // width of the bar with the max value
|
||||||
|
|
||||||
|
// accessor functions
|
||||||
|
var barLabel = function(d) { return d['status']; };
|
||||||
|
var barValue = function(d) { return d['value'] / registered * 100; };
|
||||||
|
var barValues = function(d) { return d['value']; };
|
||||||
|
|
||||||
|
// scales
|
||||||
|
var yScale = d3.scale.ordinal().domain(d3.range(0, data.length)).rangeBands([0, data.length * barHeight]);
|
||||||
|
var y = function(d, i) { return yScale(i); };
|
||||||
|
var yText = function(d, i) { return y(d, i) + yScale.rangeBand() / 2; };
|
||||||
|
var x = d3.scale.linear().domain([0, 100]).range([0, maxBarWidth]);
|
||||||
|
|
||||||
|
// svg container element
|
||||||
|
var chart = d3.select('#tickets_time').append("svg")
|
||||||
|
.attr('width', maxBarWidth + barLabelWidth + valueLabelWidth)
|
||||||
|
.attr('height', gridLabelHeight + gridChartOffset + data.length * barHeight);
|
||||||
|
/*
|
||||||
|
// grid line labels
|
||||||
|
var gridContainer = chart.append('g')
|
||||||
|
.attr('transform', 'translate(' + barLabelWidth + ',' + gridLabelHeight + ')');
|
||||||
|
gridContainer.selectAll("text").data(x.ticks(10)).enter().append("text")
|
||||||
|
.attr("x", x)
|
||||||
|
.attr("dy", -3)
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.text(String);
|
||||||
|
|
||||||
|
// vertical grid lines
|
||||||
|
gridContainer.selectAll("line").data(x.ticks(10)).enter().append("line")
|
||||||
|
.attr("x1", x)
|
||||||
|
.attr("x2", x)
|
||||||
|
.attr("y1", 0)
|
||||||
|
.attr("y2", yScale.rangeExtent()[1] + gridChartOffset)
|
||||||
|
.style("stroke", "#ccc");*/
|
||||||
|
|
||||||
|
// bar labels
|
||||||
|
var labelsContainer = chart.append('g')
|
||||||
|
.attr('transform', 'translate(' + (barLabelWidth - barLabelPadding) + ',' + (gridLabelHeight + gridChartOffset) + ')');
|
||||||
|
labelsContainer.selectAll('text').data(data).enter().append('text')
|
||||||
|
.attr('y', yText)
|
||||||
|
.attr('stroke', 'none')
|
||||||
|
.attr('fill', 'black')
|
||||||
|
.attr("dy", ".35em") // vertical-align: middle
|
||||||
|
.attr('text-anchor', 'end')
|
||||||
|
.text(barLabel);
|
||||||
|
|
||||||
|
// bars
|
||||||
|
var barsContainer = chart.append('g')
|
||||||
|
.attr('transform', 'translate(' + barLabelWidth + ',' + (gridLabelHeight + gridChartOffset) + ')');
|
||||||
|
|
||||||
|
barsContainer.selectAll("rect").data(data).enter().append("rect")
|
||||||
|
.attr('y', y)
|
||||||
|
.attr('height', yScale.rangeBand())
|
||||||
|
.attr('width', function(d) { return x(barValue(d)); })
|
||||||
|
.attr('stroke', 'white')
|
||||||
|
.attr('fill', 'steelblue');
|
||||||
|
|
||||||
|
// bar value labels
|
||||||
|
barsContainer.selectAll("text").data(data).enter().append("text")
|
||||||
|
.attr("x", function(d) { return x(barValue(d)); })
|
||||||
|
.attr("y", yText)
|
||||||
|
.attr("dx", 3) // padding-left
|
||||||
|
.attr("dy", ".35em") // vertical-align: middle
|
||||||
|
.attr("text-anchor", "start") // text-align: right
|
||||||
|
.attr("fill", "black")
|
||||||
|
.attr("stroke", "none")
|
||||||
|
.text(function(d) { return d3.round(barValues(d), 2) + " registrations (" + d3.round(barValue(d), 0) + "%)"; });
|
||||||
|
// start line
|
||||||
|
barsContainer.append("line")
|
||||||
|
.attr("y1", -gridChartOffset)
|
||||||
|
.attr("y2", yScale.rangeExtent()[1] + gridChartOffset)
|
||||||
|
.style("stroke", "#000");
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
@ -15,8 +15,8 @@
|
||||||
= render :partial => 'speakers'
|
= render :partial => 'speakers'
|
||||||
.tab-pane#tickets
|
.tab-pane#tickets
|
||||||
= render :partial => 'tickets'
|
= render :partial => 'tickets'
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
$('#myTab a').click(function (e) {
|
$('#myTab a').click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$(this).tab('show');
|
$(this).tab('show');
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue