mirror of
https://github.com/edcommonwealth/sqm-dashboards.git
synced 2026-03-07 21:48:16 -08:00
Create grouped bar chart on analyze page
This commit is contained in:
parent
d7b0fe0e36
commit
7a9830915b
20 changed files with 204 additions and 4 deletions
|
|
@ -5,5 +5,8 @@ class AnalyzeController < SqmApplicationController
|
||||||
|
|
||||||
@subcategory ||= Subcategory.find_by_subcategory_id(params[:subcategory_id])
|
@subcategory ||= Subcategory.find_by_subcategory_id(params[:subcategory_id])
|
||||||
@subcategory ||= Subcategory.find_by_subcategory_id('1A')
|
@subcategory ||= Subcategory.find_by_subcategory_id('1A')
|
||||||
|
|
||||||
|
@measure = @subcategory.measures.first
|
||||||
|
@academic_year ||= AcademicYear.order('range DESC').first
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
class CategoriesController < SqmApplicationController
|
class CategoriesController < SqmApplicationController
|
||||||
|
helper GaugeHelper
|
||||||
def show
|
def show
|
||||||
@categories = Category.sorted.map { |category| CategoryPresenter.new(category:) }
|
@categories = Category.sorted.map { |category| CategoryPresenter.new(category:) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
class HomeController < ApplicationController
|
class HomeController < ApplicationController
|
||||||
|
helper HeaderHelper
|
||||||
def index
|
def index
|
||||||
@districts = District.all.order(:name)
|
@districts = District.all.order(:name)
|
||||||
@schools = School.all.includes([:district]).order(:name)
|
@schools = School.all.includes([:district]).order(:name)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
class OverviewController < SqmApplicationController
|
class OverviewController < SqmApplicationController
|
||||||
before_action :check_empty_dataset, only: [:index]
|
before_action :check_empty_dataset, only: [:index]
|
||||||
|
helper VarianceHelper
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@variance_chart_row_presenters = Measure.all.map(&method(:presenter_for_measure))
|
@variance_chart_row_presenters = Measure.all.map(&method(:presenter_for_measure))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
class SqmApplicationController < ApplicationController
|
class SqmApplicationController < ApplicationController
|
||||||
protect_from_forgery with: :exception, prepend: true
|
protect_from_forgery with: :exception, prepend: true
|
||||||
before_action :set_schools_and_districts
|
before_action :set_schools_and_districts
|
||||||
|
helper HeaderHelper
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|
|
||||||
57
app/helpers/analyze_helper.rb
Normal file
57
app/helpers/analyze_helper.rb
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
module AnalyzeHelper
|
||||||
|
def zone_label_width
|
||||||
|
15
|
||||||
|
end
|
||||||
|
|
||||||
|
def zone_label_x
|
||||||
|
2
|
||||||
|
end
|
||||||
|
|
||||||
|
def graph_height
|
||||||
|
85
|
||||||
|
end
|
||||||
|
|
||||||
|
def svg_height
|
||||||
|
400
|
||||||
|
end
|
||||||
|
|
||||||
|
def graph_width
|
||||||
|
85
|
||||||
|
end
|
||||||
|
|
||||||
|
def benchmark_y
|
||||||
|
(zone_height * 2) - (benchmark_height / 2.0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def benchmark_height
|
||||||
|
1
|
||||||
|
end
|
||||||
|
|
||||||
|
def grouped_chart_width
|
||||||
|
graph_width / data_sources
|
||||||
|
end
|
||||||
|
|
||||||
|
def grouped_chart_divider_x(position)
|
||||||
|
zone_label_width + (grouped_chart_width * position)
|
||||||
|
end
|
||||||
|
|
||||||
|
def bar_label_height
|
||||||
|
(100 - ((100 - graph_height) / 2))
|
||||||
|
end
|
||||||
|
|
||||||
|
def bar_label_x(position)
|
||||||
|
zone_label_width + (grouped_chart_width * position) - (grouped_chart_width / 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def zone_height
|
||||||
|
graph_height / 5
|
||||||
|
end
|
||||||
|
|
||||||
|
def zone_label_y(position)
|
||||||
|
8.5 * (position + position - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def data_sources
|
||||||
|
3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -135,7 +135,6 @@ class Measure < ActiveRecord::Base
|
||||||
averages << student_survey_items.first.send(name) if includes_student_survey_items?
|
averages << student_survey_items.first.send(name) if includes_student_survey_items?
|
||||||
averages << teacher_survey_items.first.send(name) if includes_teacher_survey_items?
|
averages << teacher_survey_items.first.send(name) if includes_teacher_survey_items?
|
||||||
(averages << admin_data_items.map(&name)).flatten! if includes_admin_data_items?
|
(averages << admin_data_items.map(&name)).flatten! if includes_admin_data_items?
|
||||||
|
|
||||||
averages.average
|
averages.average
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ class SurveyItemResponse < ActiveRecord::Base
|
||||||
belongs_to :survey_item
|
belongs_to :survey_item
|
||||||
|
|
||||||
scope :exclude_boston, lambda {
|
scope :exclude_boston, lambda {
|
||||||
boston = District.where(name: 'Boston').first
|
boston = District.find_by_name('Boston')
|
||||||
where.not(school: boston.schools) if boston.present?
|
where.not(school: boston.schools) if boston.present?
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
59
app/presenters/grouped_bar_chart_presenter.rb
Normal file
59
app/presenters/grouped_bar_chart_presenter.rb
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
class GroupedBarChartPresenter
|
||||||
|
attr_reader :score
|
||||||
|
|
||||||
|
def initialize(measure:, score:)
|
||||||
|
@measure = measure
|
||||||
|
@score = score.average
|
||||||
|
@meets_teacher_threshold = score.meets_teacher_threshold?
|
||||||
|
@meets_student_threshold = score.meets_student_threshold?
|
||||||
|
@measure_name = @measure.name
|
||||||
|
@measure_id = @measure.measure_id
|
||||||
|
@category = @measure.subcategory.category
|
||||||
|
end
|
||||||
|
|
||||||
|
IDEAL_ZONE_WIDTH_PERCENTAGE = 0.17
|
||||||
|
APPROVAL_ZONE_WIDTH_PERCENTAGE = 0.17
|
||||||
|
GROWTH_ZONE_WIDTH_PERCENTAGE = 0.17
|
||||||
|
WATCH_ZONE_WIDTH_PERCENTAGE = 0.17
|
||||||
|
WARNING_ZONE_WIDTH_PERCENTAGE = 0.17
|
||||||
|
|
||||||
|
def y_offset
|
||||||
|
case zone.type
|
||||||
|
when :ideal, :approval
|
||||||
|
34 - bar_height_percentage * 100
|
||||||
|
else
|
||||||
|
34
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def bar_height_percentage
|
||||||
|
case zone.type
|
||||||
|
when :ideal
|
||||||
|
percentage * IDEAL_ZONE_WIDTH_PERCENTAGE + APPROVAL_ZONE_WIDTH_PERCENTAGE
|
||||||
|
when :approval
|
||||||
|
percentage * APPROVAL_ZONE_WIDTH_PERCENTAGE
|
||||||
|
when :growth
|
||||||
|
(1 - percentage) * GROWTH_ZONE_WIDTH_PERCENTAGE
|
||||||
|
when :watch
|
||||||
|
(1 - percentage) * WATCH_ZONE_WIDTH_PERCENTAGE + GROWTH_ZONE_WIDTH_PERCENTAGE
|
||||||
|
when :warning
|
||||||
|
(1 - percentage) * WARNING_ZONE_WIDTH_PERCENTAGE + WATCH_ZONE_WIDTH_PERCENTAGE + GROWTH_ZONE_WIDTH_PERCENTAGE
|
||||||
|
else
|
||||||
|
0.0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def percentage
|
||||||
|
(@score - zone.low_benchmark) / (zone.high_benchmark - zone.low_benchmark)
|
||||||
|
end
|
||||||
|
|
||||||
|
def zone
|
||||||
|
zones = Zones.new(
|
||||||
|
watch_low_benchmark: @measure.watch_low_benchmark,
|
||||||
|
growth_low_benchmark: @measure.growth_low_benchmark,
|
||||||
|
approval_low_benchmark: @measure.approval_low_benchmark,
|
||||||
|
ideal_low_benchmark: @measure.ideal_low_benchmark
|
||||||
|
)
|
||||||
|
zones.zone_for_score(@score)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,9 +1,74 @@
|
||||||
<% content_for :title do %>
|
<% content_for :title do %>
|
||||||
<div class="sub-header-2 color-white m-0"> Analysis of <%= @school.name %> </div>
|
<div class="sub-header-2 color-white m-0"> Analysis of <%= @school.name %> </div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% presenter = GroupedBarChartPresenter.new(measure: @measure, score: @measure.score(school: @school, academic_year: @academic_year)) %>
|
||||||
<div class="graph-content">
|
<div class="graph-content">
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs sub-header-4">
|
||||||
<%= @category.category_id %>:<%= @category.name %> > <%= @subcategory.subcategory_id %>:<%= @subcategory.name %>
|
<%= @category.category_id %>:<%= @category.name %> > <%= @subcategory.subcategory_id %>:<%= @subcategory.name %>
|
||||||
</div>
|
</div>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div class="mt-6" >
|
||||||
|
<p class="construct-id">Measure <%= @measure.measure_id %></p>
|
||||||
|
<span class="sub-header-2">
|
||||||
|
<%= @measure.name %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<svg width="100%" height="<%= svg_height %>" >
|
||||||
|
<g>
|
||||||
|
<rect x="0" y="0" width="100%" height="<%= zone_height * 2 %>%" fill="#edecf0"/>
|
||||||
|
<rect x="0" y="<%= zone_height * 2 %>%" width="100%" height="<%= zone_height * 3 %>%" fill="#fffaee"/>
|
||||||
|
<rect x="0" y="0" width="100%" height="<%= graph_height %>%" fill="none" stroke="grey"/>
|
||||||
|
<line x1="<%= grouped_chart_divider_x(1) %>%" y1="0" x2="<%= grouped_chart_divider_x(1) %>%" y2="85%" stroke="grey" stroke-width="1" stroke-dasharray="5,2"/>
|
||||||
|
<line x1="<%= grouped_chart_divider_x(2) %>%" y1="0" x2="<%= grouped_chart_divider_x(2) %>%" y2="85%" stroke="grey" stroke-width="1" stroke-dasharray="5,2"/>
|
||||||
|
<rect x="0" y="<%= benchmark_y %>%" width="100%" height="<%= benchmark_height %>%" fill="black"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g stroke-width="1" >
|
||||||
|
<line x1="0" y1="17%" x2="100%" y2="17%" stroke="white" />
|
||||||
|
<line x1="0" y1="51%" x2="100%" y2="51%" stroke="#edecf0" />
|
||||||
|
<line x1="0" y1="68%" x2="100%" y2="68%" stroke="#edecf0" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g >
|
||||||
|
<text class="zone-header" x="<%= zone_label_x %>%" y="<%= zone_label_y(1) %>%" text-anchor="start" dominant-baseline="middle">
|
||||||
|
Ideal
|
||||||
|
</text>
|
||||||
|
<text class="zone-header" x="<%= zone_label_x %>%" y="<%= zone_label_y(2) %>%" text-anchor="start" dominant-baseline="middle">
|
||||||
|
Approval
|
||||||
|
</text>
|
||||||
|
<text class="zone-header" x="<%= zone_label_x %>%" y="<%= zone_label_y(3) %>%" text-anchor="start" dominant-baseline="middle">
|
||||||
|
Growth
|
||||||
|
</text>
|
||||||
|
<text class="zone-header" x="<%= zone_label_x %>%" y="<%= zone_label_y(4) %>%" text-anchor="start" dominant-baseline="middle">
|
||||||
|
Watch
|
||||||
|
</text>
|
||||||
|
<text class="zone-header" x="<%= zone_label_x %>%" y="<%= zone_label_y(5) %>%" text-anchor="start" dominant-baseline="middle">
|
||||||
|
Warning
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<text class="graph-footer" x="<%= bar_label_x(1) %>%" y="<%= bar_label_height %>%" text-anchor="middle" dominant-baseline="middle">
|
||||||
|
All Students
|
||||||
|
</text>
|
||||||
|
<text class="graph-footer" x="<%= bar_label_x(2) %>%" y="<%= bar_label_height %>%" text-anchor="middle" dominant-baseline="middle">
|
||||||
|
All Teachers
|
||||||
|
</text>
|
||||||
|
<text class="graph-footer" x="<%= bar_label_x(3) %>%" y="<%= bar_label_height %>%" text-anchor="middle" dominant-baseline="middle">
|
||||||
|
All Survey Data
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<rect x="<%= bar_label_x(3) - 2.5 %>%" y="<%= presenter.y_offset %>%" width="5%" height="<%= presenter.bar_height_percentage * 100 %>%" fill="#3E3A38"/>
|
||||||
|
<text x="<%= bar_label_x(3) %>%" y="<%= 5 %>%" text-anchor="middle" dominant-baseline="middle" >
|
||||||
|
<%= presenter.score %>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -69,4 +69,6 @@ Rails.application.configure do
|
||||||
# Use an evented file watcher to asynchronously detect changes in source code,
|
# Use an evented file watcher to asynchronously detect changes in source code,
|
||||||
# routes, locales, etc. This feature depends on the listen gem.
|
# routes, locales, etc. This feature depends on the listen gem.
|
||||||
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
|
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
|
||||||
|
|
||||||
|
config.action_controller.include_all_helpers = false
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -109,4 +109,6 @@ Rails.application.configure do
|
||||||
# config.active_record.database_selector = { delay: 2.seconds }
|
# config.active_record.database_selector = { delay: 2.seconds }
|
||||||
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
|
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
|
||||||
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
|
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
|
||||||
|
|
||||||
|
config.action_controller.include_all_helpers = false
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -52,4 +52,6 @@ Rails.application.configure do
|
||||||
|
|
||||||
# Raises error for missing translations.
|
# Raises error for missing translations.
|
||||||
# config.action_view.raise_on_missing_translations = true
|
# config.action_view.raise_on_missing_translations = true
|
||||||
|
|
||||||
|
config.action_controller.include_all_helpers = false
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
include VarianceHelper
|
||||||
|
|
||||||
describe OverviewController, type: :controller do
|
describe OverviewController, type: :controller do
|
||||||
include BasicAuthHelper
|
include BasicAuthHelper
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
include GaugeHelper
|
||||||
|
|
||||||
describe 'categories/show' do
|
describe 'categories/show' do
|
||||||
before :each do
|
before :each do
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
include SchedulesHelper
|
||||||
|
|
||||||
module Legacy
|
module Legacy
|
||||||
RSpec.describe 'legacy/schedules/edit', type: :view do
|
RSpec.describe 'legacy/schedules/edit', type: :view do
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
include SchedulesHelper
|
||||||
|
|
||||||
module Legacy
|
module Legacy
|
||||||
RSpec.describe 'legacy/schedules/new', type: :view do
|
RSpec.describe 'legacy/schedules/new', type: :view do
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
include SchedulesHelper
|
||||||
|
|
||||||
module Legacy
|
module Legacy
|
||||||
RSpec.describe 'legacy/schedules/show', type: :view do
|
RSpec.describe 'legacy/schedules/show', type: :view do
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
include VarianceHelper
|
||||||
|
|
||||||
describe 'overview/index' do
|
describe 'overview/index' do
|
||||||
subject { Nokogiri::HTML(rendered) }
|
subject { Nokogiri::HTML(rendered) }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
include VarianceHelper
|
||||||
|
|
||||||
describe 'overview/_variance_chart.html.erb' do
|
describe 'overview/_variance_chart.html.erb' do
|
||||||
before do
|
before do
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue