Commit 83ecea7f authored by hugo's avatar hugo
Browse files

Merge branch 'graphui' of https://github.com/citricacid/counterstat into graphui

parents 595086aa be6db8a4
......@@ -181,27 +181,9 @@ get '/api/hourly_chart' do
# TODO refactor into builder
require_logged_in
query = {}
branch_id = params[:branch_id]
query[:branch] = branch_id == 'all' ? AccumulatedBranch.first : Branch.find(branch_id)
query[:start_date] = Date.parse(params[:period_start])
query[:end_date] = Date.parse(params[:period_end])
query[:start_hour] = params[:start_hour].to_i
query[:end_hour] = params[:end_hour].to_i
query[:included_days] = params[:included_days].split(',').map {|s| s.to_i}
query[:stacking] = params[:stacking]
query[:split_days] = params[:split_days].present?
query[:split_weeks] = params[:split_weeks].present?
query[:agg_type] = params[:aggregate_type]
query[:graph_type] = params[:type]
bg = BetaGraph.new(query)
bg.get_hourly.to_json
builder = GraphBuilder.new
builder.set_query(params)
builder.query.get_series.to_json
end
get '/api/periodical_chart' do
......
......@@ -8,7 +8,305 @@ require_relative 'graph_templates'
require_relative 'chart_utils'
# WIP
class GraphBuilder
attr_reader :graph
def initialize
@graph = HourlyGraph.new
end
def set_query(params)
set_branch(params[:branch_id])
set_period(params[:period_start], params[:period_end], params[:period_label])
set_included_hours(params[:start_hour], params[:end_hour])
set_included_days(params[:included_days])
set_stacking if params[:stacking].present?
set_split_by_days if params[:split_days].present?
set_split_by_weeks if params[:split_weeks].present?
set_aggregate_type(params[:aggregate_type])
set_graph_type(params[:type])
self
end
def set_stacking
@graph.stacking = 'normal'
end
def set_split_by_days
@graph.split_by_days = true
self
end
def set_split_by_weeks
@graph.split_by_weeks = true
self
end
def set_split_by_months
@graph.split_by_months = true
self
end
def set_aggregate_type(type)
@graph.aggregate_type = type.downcase
# error unless type == 'total' or type == "average"
self
end
def set_graph_type(type)
@graph.graph_type = type.downcase
# error unless type == 'column' or type == "spline"
self
end
def set_period(date_start, date_end, period_label = nil)
@graph.period_start = Date.strptime(date_start, '%d-%m-%Y')
@graph.period_end = Date.strptime(date_end, '%d-%m-%Y')
@graph.period_label = period_label || "periode: #{date_start.strftime('%d/%m/%Y')} - #{date_end.strftime('%d/%m/%Y')}"
# error add unless period_start >= period_end
rescue
# error add
ensure
self
end
def set_included_hours(hour_start, hour_end)
@graph.hour_start = hour_start.to_i
@graph.hour_end = hour_end.to_i
# error add if hour < 0 > 23, start < end
self
end
def set_branch(branch_id)
@graph.branch = branch_id == 'all' ? AccumulatedBranch.first : Branch.find(branch_id)
# if !is_a?(Branch) find(branch to )
#@graph.branch = branch
self
end
def set_included_days(list = [])
@graph.included_days = list.split(',').map {|s| s.to_i}
# error if list contains <0 or > 6
self
end
def query
@graph
end
end
class BG
attr_accessor :branch, :period_start, :period_end, :hour_start, :hour_end, :included_days,
:split_by_days, :split_by_weeks, :split_by_months, :aggregate_type, :graph_type, :stacking,
:total_no_of_visitors, :period_label
def initialize
@split_by_days, @split_by_weeks, @split_by_months = false
@stacking = nil
@total_no_of_visitors = 0
end
def validate
error = "Ugyldig format" unless @period_start.is_a?(Date)
error = "Ugyldig format" unless @period_end.is_a?(Date)
error = "Ugyldig format" unless @period_end.is_a?(Date) # errors.clear end >) start
# unless branch.is_a?(Branch)
#
end
end
class HourlyGraph < BG
def initialize
super
end
def get_series
@counts = @branch.get_aggregated_counts_per_hour(@period_start, @period_end, @included_days)
results = get_hourly_results
visitors_count = results.map {|serie| serie[:in_series]}.flatten.select {|i| i.is_a? (Fixnum)}
@total_no_of_visitors = visitors_count.inject(0) {|sum, elem| sum + elem}
@name = @branch.name
@title_string = "Besøkende inn for #{@name}"
@subtitle_string = "periode: #{@period_label} | antall: #{@total_no_of_visitors}"
@xcategories = (@hour_start..@hour_end).map {|i| "#{i}-#{i+1}"}
get_hourly_visitors_graph(results)
end
# ----------
def get_hourly_results
labels = ['søn','man', 'tir', 'ons', 'tor', 'fre', 'lør']
results = [] # each result has a name and data series, plus in_series, average_series, no_of_days
if @split_by_days && @split_by_weeks
tmp_date = @period_start
while tmp_date <= @period_end
if @included_days.include?(tmp_date.wday)
stats = create_hourly_visitors(tmp_date.strftime('%d-%m-%Y'), true, 1)
results << stats.merge({name: tmp_date.strftime('%d/%m/%y')})
end
tmp_date = tmp_date.next_day
end
elsif @split_by_days # and join weeks
@included_days.each do |day|
no_of_days = (@period_start...@period_end).select {|d| d.wday == day}.size
stats = create_hourly_visitors(day, false, no_of_days)
results << stats.merge({name: labels[day.to_i]})
end
elsif @split_by_weeks # and join_days
tmp_date = @period_start
while tmp_date <= @period_end
dates = (tmp_date...tmp_date.next_week).map {|d| d.strftime('%d-%m-%Y')}
no_of_days = (tmp_date...tmp_date.next_week).select {|d| @included_days.include?(d.wday)}.size
stats = create_hourly_visitors(dates, true, no_of_days)
results << stats.merge({name: tmp_date.strftime('Uke %V-%G')})
tmp_date = tmp_date.next_week
end
else # join days and join_weeks (normal state)
no_of_days = (@period_start..@period_end).select {|d| @included_days.include?(d.wday)}.size
stats = create_hourly_visitors(@included_days, false, no_of_days)
names = @included_days.map {|d| labels[d]}
results << stats.merge({name: names})
end
results.each do |result|
result[:data] = @aggregate_type == 'total' ? result[:in_series] : result[:average_series]
end
results
end
def create_hourly_visitors(day, split = false, no_of_days = nil)
days = [*day]
slots = {}
# the split parameter is actually used to discern between wdays and datetimes... do fix
@counts.each do |time, count|
next if !split and !days.include?(time.wday)
next if split and !days.include?(time.strftime('%d-%m-%Y'))
next if time.hour < @hour_start || time.hour > @hour_end
time_slot = count[:hour_slot]
slots[time_slot] = {visitors_in: 0, visitors_out: 0} if slots[time_slot].nil?
slots[time_slot][:visitors_in] += count[:visitors_in]
slots[time_slot][:visitors_out] += count[:visitors_out]
end
in_series = []
average_series = []
slots.each do |time_slot, values|
in_series << [time_slot, values[:visitors_in]]
average_series << [time_slot, values[:visitors_out]/no_of_days]
end
# unsorted keys can lead to some issues with Highcharts - fixing it here:
in_series = in_series.sort_by{|k,v| k.to_s.split('-')[0].to_i}
average_series = average_series.sort_by{|k,v| k.to_s.split('-')[0].to_i}
{in_series: in_series, no_of_days: no_of_days, average_series: average_series}
end
def get_hourly_visitors_graph(series)
options = {
series: series,
chart: {
type: @graph_type,
renderTo: 'container',
zoomType: 'x'
},
title: {
text: "#{@title_string}",
x: -20
},
subtitle: {
text: "#{@subtitle_string}",
x: -20
},
xAxis: {
categories: @xcategories
},
yAxis: {
allowDecimals: false,
title: {
text: 'Antall'
}
},
plotOptions: {
column: {
stacking: @stacking
}
}
}
# remind me: why did i do this again?
options.merge(categories: @xcategories)
end
# -----------
# end class HourlyGraph
end
class PeriodicalGraph < BG
def initialize
super
end
end
class BetaGraph
attr_reader :counts
......@@ -31,11 +329,6 @@ class BetaGraph
@counts = @branch.get_aggregated_counts(@start_date, @end_date, @included_days)
@xcategories = (@start_hour..@end_hour).map {|i| "#{i}-#{i+1}"}
puts @xcategories.inspect
puts @start_hour
puts @end_hour
end
def get_hourly
......
......@@ -25,7 +25,56 @@ end
# ----------------------------------------
module BranchUtils
# Sums all counts from all counters in the given period.
#
# Optional parameter included_days accepts an integer array where sunday = 0 and saturday = 6
# example: [2,3] means return hash will only contain counts for tuesdays and wednesdays
#
def get_aggregated_counts(start_date, end_date, included_days = [])
all_counts = counts.where('(date >= ? and date <= ?) or (date = ? and time = ?)', start_date, end_date, end_date + 1, '00:00:00' )
counts = Hash.new {|hsh, key| hsh[key] = {visitors_in: 0, visitors_out: 0}}
for count in all_counts
date = count[:date]
time = count[:time]
datetime = Time.new(date.year, date.month, date.day, time.hour, time.min, 0, 0) - 15.minutes # Last '0' means 0 time zone adjustment
next unless included_days.blank? or included_days.include?(datetime.wday)
counts[datetime].merge!({visitors_in: count[:visitors_in], visitors_out: count[:visitors_out]}) { |_key, a, b| a + b }
end
counts
end
def get_aggregated_counts_per_hour(start_date, end_date, included_days = [])
counts = get_aggregated_counts(start_date, end_date, included_days)
slots = {}
counts.each do |time, count|
datetime = Time.new(time.year, time.month, time.day, time.hour, 0, 0, 0)
slots[datetime] = {visitors_in: 0, visitors_out: 0, hour_slot: "#{time.hour}-#{time.hour + 1}"} if slots[datetime].nil?
slots[datetime][:visitors_in] += count[:visitors_in]
slots[datetime][:visitors_out] += count[:visitors_out]
end
slots
end
end
class Branch < ActiveRecord::Base
include BranchUtils
default_scope { order(:name) }
has_many :counters
......@@ -54,33 +103,6 @@ class Branch < ActiveRecord::Base
counts.where('(date = ? and time != ?) or (date = ? and time = ?) ', date, '00:00:00', date + 1, '00:00:00')
end
# Sums all counts from all counters in the given period.
#
# Optional parameter included_days accepts an integer array where sunday = 0 and saturday = 6
# example: [2,3] means return hash will only contain counts for tuesdays and wednesdays
#
def get_aggregated_counts(start_date, end_date, included_days = [])
all_counts = counts.where('(date >= ? and date <= ?) or (date = ? and time = ?)', start_date, end_date, end_date + 1, '00:00:00' )
counts = {}
for count in all_counts
date = count[:date]
time = count[:time]
datetime = Time.new(date.year, date.month, date.day, time.hour, time.min, 0, 0) # Last '0' means 0 time zone adjustment
is_midnight = datetime.hour == 0 and datetime.min == 0
next unless included_days.blank? or included_days.include?(datetime.wday) or (is_midnight && included_days.include?(datetime.prev_day.wday)) # sunday is zero
if counts.has_key?(datetime)
counts[datetime].merge!({:in => count[:visitors_in], :out => count[:visitors_out]}) { |_key, a, b| a + b }
else
counts[datetime] = {:in => count[:visitors_in], :out => count[:visitors_out]}
end
end
counts
end
def find_first_active_date
counts.minimum(:date)
end
......@@ -212,6 +234,8 @@ end
class AccumulatedBranch < ActiveRecord::Base
include BranchUtils
has_many :daily_statistics, as: :aggregate
has_many :weekly_statistics, as: :aggregate
has_many :monthly_statistics, as: :aggregate
......
$(function() {
function createHourlyParams() {
const includedDays = $('input[name="included_days"]:checked').
map(function() {return this.value}).
......@@ -54,6 +56,7 @@ function createRows(series) {
function ajaxSuccess(data) {
Highcharts.chart('container', data);
const $table = $('#multi_hourly_table')
$table.find('thead').html(createHeaders(data.categories))
$('#stats_body').html(createRows(data.series))
......@@ -75,17 +78,25 @@ function ajaxSuccess(data) {
})
}
function ajaxFail() {
function ajaxFail(xhr, textStatus, errorThrown) {
console.log(textStatus)
console.log(errorThrown)
alert("Beklager. Det har oppstått en feil.")
$('#table').hide()
}
function ajaxAlways() {
$('.loadbar').hide()
}
$('#get_hourly_bargraph_button').click(function() {
$.getJSON('/api/hourly_chart?' + createHourlyParams() + '&type=column').done(ajaxSuccess).fail(ajaxFail)
$('.loadbar').show()
$.getJSON('/api/hourly_chart?' + createHourlyParams() + '&type=column').done(ajaxSuccess).fail(ajaxFail).always(ajaxAlways)
})
$('#get_hourly_splinegraph_button').click(function() {
$.getJSON('/api/hourly_chart?' + createHourlyParams() + '&type=spline').done(ajaxSuccess).fail(ajaxFail)
$('.loadbar').show()
$.getJSON('/api/hourly_chart?' + createHourlyParams() + '&type=spline').done(ajaxSuccess).fail(ajaxFail).always(ajaxAlways)
})
......@@ -164,13 +175,4 @@ $("#select_quarter").val(currentQuarter).change();
})
......@@ -35,7 +35,12 @@ $(function() {
// set label
const periodTypeLabel = $('.period-select.active').text();
$('#branch_header').text(periodTypeLabel + ' ' + nameList);
if (reportType == 'report') {
$('#branch_header').text(periodTypeLabel + ' rapport');
} else {
$('#branch_header').text(periodTypeLabel + ' ' + nameList);
}
// Toggle views based on current state
const showAggregateName = activeButtons.length > 1 || activeButtons.text() === 'iterate_all' || reportType === 'report'
......@@ -55,7 +60,15 @@ $(function() {
const url = '/api/statistics/' + reportType + '/' + periodType + parameters;
populateTable(reportType, url)
// Show message if subcounter view is selected for the aggregated branch
if (reportType == 'subcounter' && $('button#all').hasClass('active')) {
$('.dataTables_wrapper').hide()
$('#empty_subcounter_message').show()
} else {
$('#empty_subcounter_message').hide()
populateTable(reportType, url)
}
}
// Reduce number of active branches to one for modes that do not support multiple branches
......
......@@ -84,15 +84,13 @@ Velg filial: <select id="branch_selector" name="branch_id" class="param">
</label>
<input type="checkbox" name="split_days" value="normal" class="param"/> Skill ut dager
<input type="checkbox" name="split_weeks" value="normal" class="param"/> Skill ut uker
<input type="checkbox" name="split_months" value="normal" class="param"/> Skill ut måneder
</div>
<button id="get_hourly_bargraph_button">Søylediagram</button>
<button id="get_hourly_splinegraph_button">Spline</button>
<button id="get_bargraph_button">Søylediagram</button>
<input type="checkbox" name="stacking" value="normal" class="param"/> Stakk
<img class="loadbar" src="/images/ajax-loader.gif" style="display: none;">
<div id="container"></div>
......
......@@ -58,16 +58,16 @@
<div class="btn-group" role="group" data-toggle="buttons" style="padding-bottom: 10px">
<label>
<input type="radio" name="aggregate_type" value="unpartitioned" checked="checked"/>Fulltid
<input type="radio" name="aggregate_type" value="unpartitioned" checked="checked"/><abbr title="Standard besøksoversikt">Fulltid</abbr>
</label>
<label>
<input type="radio" name="aggregate_type" value="partitioned"/>Meråpent
<input type="radio" name="aggregate_type" value="partitioned"/><abbr title="Detaljert oversikt for meråpent">Meråpent</abbr>
</label>
<label>
<input type="radio" name="aggregate_type" value="keyfigures"/>Nøkkeltall
<input type="radio" name="aggregate_type" value="keyfigures"/><abbr title="Total for meråpent og fulltid">Nøkkeltall</abbr>
</label>
<label>
<input type='checkbox' id='toggle_ratio'/>Vis ratio
<input type='checkbox' id='toggle_ratio'/><abbr title="Forholdstall inn/ut for besøkende">Vis ratio</abbr>
</label>
<img class="loadbar" src="/images/ajax-loader.gif"></img>
<div id='updated' style='float:right; padding-right: 5px;'>Siste oppdatering:
......
......@@ -47,7 +47,7 @@
<h3 id="bar"></h3>
</div>
<h4 id="empty_subcounter_message">Samleoversikten har ingen undertellere. Velg en avdeling.</h4>
<table id="static_table" class='table display'>
<thead>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment