Draw the teacher survey data bar. Finishes #182226823

pull/1/head
Nelson Jovel 4 years ago
parent a134de3fdd
commit 3d7e62f21f

@ -6,7 +6,7 @@ class AnalyzeController < SqmApplicationController
@subcategory ||= Subcategory.find_by_subcategory_id(params[:subcategory_id])
@subcategory ||= Subcategory.find_by_subcategory_id('1A')
@measure = @subcategory.measures.includes([:admin_data_items]).first
@measure = @subcategory.measures.includes(%i[admin_data_items category])[0]
@academic_year ||= AcademicYear.order('range DESC').first
end
end

@ -7,7 +7,7 @@ module AnalyzeHelper
2
end
def graph_height
def analyze_graph_height
85
end
@ -20,7 +20,7 @@ module AnalyzeHelper
end
def benchmark_y
(zone_height * 2) - (benchmark_height / 2.0)
(analyze_zone_height * 2) - (benchmark_height / 2.0)
end
def benchmark_height
@ -36,15 +36,19 @@ module AnalyzeHelper
end
def bar_label_height
(100 - ((100 - graph_height) / 2))
(100 - ((100 - analyze_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
def analyze_zone_height
analyze_graph_height / 5
end
def zone_height_percentage
analyze_zone_height / 100.0
end
def zone_label_y(position)

@ -57,8 +57,8 @@ class Measure < ActiveRecord::Base
next Score.new(nil, false, false, false) if incalculable_score
scores = []
scores << collect_survey_scale_average(teacher_scales, school, academic_year) if meets_teacher_threshold
scores << collect_survey_scale_average(student_scales, school, academic_year) if meets_student_threshold
scores << teacher_score(school:, academic_year:).average if meets_teacher_threshold
scores << student_score(school:, academic_year:).average if meets_student_threshold
scores << collect_admin_scale_average(admin_data_items, school, academic_year) if includes_admin_data_items?
average = scores.flatten.compact.remove_zeros.average
@ -72,6 +72,26 @@ class Measure < ActiveRecord::Base
@score[[school, academic_year]]
end
def student_score(school:, academic_year:)
@student_score ||= begin
meets_student_threshold = sufficient_student_data?(school:, academic_year:)
meets_teacher_threshold = sufficient_teacher_data?(school:, academic_year:)
meets_admin_data_threshold = all_admin_data_collected?(school:, academic_year:)
average = collect_survey_scale_average(student_scales, school, academic_year) if meets_student_threshold
Score.new(average, meets_teacher_threshold, meets_student_threshold, meets_admin_data_threshold)
end
end
def teacher_score(school:, academic_year:)
@teacher_score ||= begin
meets_student_threshold = sufficient_student_data?(school:, academic_year:)
meets_teacher_threshold = sufficient_teacher_data?(school:, academic_year:)
meets_admin_data_threshold = all_admin_data_collected?(school:, academic_year:)
average = collect_survey_scale_average(teacher_scales, school, academic_year) if meets_teacher_threshold
Score.new(average, meets_teacher_threshold, meets_student_threshold, meets_admin_data_threshold)
end
end
def warning_low_benchmark
1
end

@ -1,7 +1,8 @@
class GroupedBarChartPresenter
attr_reader :score
class GroupedBarColumnPresenter
include AnalyzeHelper
attr_reader :score, :measure_name, :measure_id, :category, :position, :type
def initialize(measure:, score:)
def initialize(measure:, score:, position:, type:)
@measure = measure
@score = score.average
@meets_teacher_threshold = score.meets_teacher_threshold?
@ -9,35 +10,35 @@ class GroupedBarChartPresenter
@measure_name = @measure.name
@measure_id = @measure.measure_id
@category = @measure.subcategory.category
@position = position
@type = type
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
(analyze_zone_height * 2) - bar_height_percentage
else
34
(analyze_zone_height * 2)
end
end
def bar_color
"fill-#{zone.type}"
end
def bar_height_percentage
case zone.type
when :ideal
percentage * IDEAL_ZONE_WIDTH_PERCENTAGE + APPROVAL_ZONE_WIDTH_PERCENTAGE
(percentage * zone_height_percentage + zone_height_percentage) * 100
when :approval
percentage * APPROVAL_ZONE_WIDTH_PERCENTAGE
(percentage * zone_height_percentage) * 100
when :growth
(1 - percentage) * GROWTH_ZONE_WIDTH_PERCENTAGE
((1 - percentage) * zone_height_percentage) * 100
when :watch
(1 - percentage) * WATCH_ZONE_WIDTH_PERCENTAGE + GROWTH_ZONE_WIDTH_PERCENTAGE
((1 - percentage) * zone_height_percentage + zone_height_percentage) * 100
when :warning
(1 - percentage) * WARNING_ZONE_WIDTH_PERCENTAGE + WATCH_ZONE_WIDTH_PERCENTAGE + GROWTH_ZONE_WIDTH_PERCENTAGE
((1 - percentage) * zone_height_percentage + zone_height_percentage + zone_height_percentage) * 100
else
0.0
end
@ -56,4 +57,15 @@ class GroupedBarChartPresenter
)
zones.zone_for_score(@score)
end
def label
case type
when :all
'All Survey Data'
when :student
'All Students'
when :teacher
'All Teachers'
end
end
end

@ -0,0 +1,41 @@
<svg width="100%" height="<%= svg_height %>" >
<g id="graph-background">
<rect x="0" y="0" width="100%" height="<%= analyze_zone_height * 2 %>%" fill="#edecf0"/>
<rect x="0" y="<%= analyze_zone_height * 2 %>%" width="100%" height="<%= analyze_zone_height * 3 %>%" fill="#fffaee"/>
<rect x="0" y="0" width="100%" height="<%= analyze_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 id="zone-dividers" 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 id="zone-labels">
<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>
<% presenter = GroupedBarColumnPresenter.new(measure: measure, score: measure.student_score(school: @school, academic_year: @academic_year), position: 1, type: :student) %>
<%= render partial: "grouped_bar_column", locals: {presenter: presenter} %>
<% presenter = GroupedBarColumnPresenter.new(measure: measure, score: measure.teacher_score(school: @school, academic_year: @academic_year), position: 2, type: :teacher) %>
<%= render partial: "grouped_bar_column", locals: {presenter: presenter} %>
<% presenter = GroupedBarColumnPresenter.new(measure: measure, score: measure.score(school: @school, academic_year: @academic_year), position: 3, type: :all) %>
<%= render partial: "grouped_bar_column", locals: {presenter: presenter} %>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -0,0 +1,11 @@
<g id="grouped-bar-column">
<rect x="<%= bar_label_x(presenter.position) - 2.5 %>%" y="<%= presenter.y_offset %>%" width="5%" height="<%= presenter.bar_height_percentage %>%" fill="#3E3A38" data-for-measure-id="<%= presenter.measure_id %>"/>
<text x="<%= bar_label_x(presenter.position) %>%" y="<%= 5 %>%" text-anchor="middle" dominant-baseline="middle" >
<%= presenter.score %>
</text>
<text class="graph-footer" x="<%= bar_label_x(presenter.position) %>%" y="<%= bar_label_height %>%" text-anchor="middle" dominant-baseline="middle" data-grouped-bar-label="<%= presenter.label %>">
<%= presenter.label %>
</text>
</g>

@ -1,74 +1,20 @@
<% content_for :title do %>
<div class="sub-header-2 color-white m-0"> Analysis of <%= @school.name %> </div>
<h1 class="sub-header-2 color-white m-0"> Analysis of <%= @school.name %> </h1>
<% end %>
<% presenter = GroupedBarChartPresenter.new(measure: @measure, score: @measure.score(school: @school, academic_year: @academic_year)) %>
<div class="graph-content">
<div class="breadcrumbs sub-header-4">
<%= @category.category_id %>:<%= @category.name %> > <%= @subcategory.subcategory_id %>:<%= @subcategory.name %>
</div>
<hr/>
<div class="mt-6" >
<section class="mt-6" >
<p class="construct-id">Measure <%= @measure.measure_id %></p>
<span class="sub-header-2">
<%= @measure.name %>
</span>
</div>
<h2> <%= @measure.name %> </h2>
<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>
<%= render partial: "grouped_bar_chart" , locals: { measure: @measure} %>
</div>
</section>
</div>

@ -0,0 +1,187 @@
require 'rails_helper'
describe GroupedBarColumnPresenter do
let(:watch_low_benchmark) { 2.9 }
let(:growth_low_benchmark) { 3.1 }
let(:approval_low_benchmark) { 3.6 }
let(:ideal_low_benchmark) { 3.8 }
let(:measure) do
measure = create(
:measure,
name: 'Some Title'
)
scale = create(:scale, measure:)
create(:student_survey_item, scale:,
watch_low_benchmark:,
growth_low_benchmark:,
approval_low_benchmark:,
ideal_low_benchmark:)
measure
end
let(:measure_without_admin_data_items) do
create(
:measure,
name: 'Some Title'
)
end
let(:presenter) do
GroupedBarColumnPresenter.new measure:, score:, position: 1, type: :all
end
shared_examples_for 'measure_name' do
it 'returns the measure name' do
expect(presenter.measure_name).to eq 'Some Title'
end
end
context 'when the score is in the Ideal zone' do
let(:score) { Score.new(4.4, true, true) }
it_behaves_like 'measure_name'
it 'returns the correct color' do
expect(presenter.bar_color).to eq 'fill-ideal'
end
it 'returns a bar width equal to the approval zone width plus the proportionate ideal zone width' do
expect(presenter.bar_height_percentage).to be_within(0.01).of(25.5)
end
it 'returns a y_offset equal to the ' do
expect(presenter.y_offset).to be_within(0.01).of(8.5)
end
end
context 'when the score is in the Approval zone' do
let(:score) { Score.new(3.7, true, true) }
it_behaves_like 'measure_name'
it 'returns the correct color' do
expect(presenter.bar_color).to eq 'fill-approval'
end
it 'returns a bar width equal to the proportionate approval zone width' do
expect(presenter.bar_height_percentage).to be_within(0.01).of(8.5)
end
it 'returns an x-offset of 60%' do
expect(presenter.y_offset).to be_within(0.01).of(25.5)
end
end
context 'when the score is in the Growth zone' do
let(:score) { Score.new(3.2, true, true) }
it_behaves_like 'measure_name'
it 'returns the correct color' do
expect(presenter.bar_color).to eq 'fill-growth'
end
it 'returns a bar width equal to the proportionate growth zone width' do
expect(presenter.bar_height_percentage).to be_within(0.01).of(13.59)
end
context 'in order to achieve the visual effect' do
it 'returns an x-offset equal to 60% minus the bar width' do
expect(presenter.y_offset).to eq 34
end
end
end
context 'when the score is in the Watch zone' do
let(:score) { Score.new(2.9, true, true) }
it_behaves_like 'measure_name'
it 'returns the correct color' do
expect(presenter.bar_color).to eq 'fill-watch'
end
it 'returns a bar width equal to the proportionate watch zone width plus the growth zone width' do
expect(presenter.bar_height_percentage).to eq 34
end
context 'in order to achieve the visual effect' do
it 'returns an x-offset equal to 60% minus the bar width' do
expect(presenter.y_offset).to eq 34
end
end
end
context 'when the score is in the Warning zone' do
let(:score) { Score.new(1.0, true, true) }
it_behaves_like 'measure_name'
it 'returns the correct color' do
expect(presenter.bar_color).to eq 'fill-warning'
end
it 'returns a bar width equal to the proportionate warning zone width plus the watch & growth zone widths' do
expect(presenter.bar_height_percentage).to eq 51
end
context 'in order to achieve the visual effect' do
it 'returns an y-offset equal to 60% minus the bar width' do
expect(presenter.y_offset).to eq 34
end
end
end
# context 'when a measure contains teacher survey items' do
# before :each do
# scale = create(:scale, measure:)
# create :teacher_survey_item, scale:
# end
# context 'when there are insufficient teacher survey item responses' do
# let(:score) { Score.new(nil, false, true) }
# it 'shows a message saying there are insufficient responses' do
# expect(presenter.insufficient_teacher_responses?).to be true
# end
# end
# context 'when there are sufficient teacher survey item responses' do
# let(:score) { Score.new(nil, true, true) }
# it 'does not show a partial data indicator' do
# expect(presenter.show_teacher_inapplicability_message?).to be true
# end
# end
# end
# context 'when a measure does not contain teacher survey items' do
# context 'when there are insufficient teacher survey item responses' do
# let(:score) { Score.new(nil, false, true) }
# it 'shows a message saying the measure is not based on teacher survey items' do
# expect(presenter.show_teacher_inapplicability_message?).to be false
# end
# end
# end
# context 'when a measure contains student survey items' do
# before :each do
# scale = create(:scale, measure:)
# create :student_survey_item, scale:
# end
# context 'when there are insufficient student survey item responses' do
# let(:score) { Score.new(nil, true, false) }
# it 'shows a partial data indicator' do
# expect(presenter.show_student_inapplicability_message?).to be true
# end
# end
# context 'when there are sufficient student survey item responses' do
# let(:score) { Score.new(nil, true, true) }
# it 'shows a partial data indicator' do
# expect(presenter.show_student_inapplicability_message?).to be true
# end
# end
# end
end

@ -1,4 +1,5 @@
require 'rails_helper'
include AnalyzeHelper
describe 'District Admin', js: true do
let(:district) { District.find_by_slug 'winchester' }

@ -0,0 +1,87 @@
require 'rails_helper'
include AnalyzeHelper
describe 'analyze/index' do
subject { Nokogiri::HTML(rendered) }
let(:category) { create(:category) }
let(:subcategory) { create(:subcategory, category:) }
let(:support_for_teaching) do
measure = create(:measure, name: 'Support For Teaching Development & Growth', measure_id: '1', subcategory:)
scale = create(:scale, measure:)
create(:student_survey_item,
scale:,
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
ideal_low_benchmark: 4.5)
measure
end
let(:effective_leadership) do
measure = create(:measure, name: 'Effective Leadership', measure_id: '2', subcategory:)
scale = create(:scale, measure:)
create(:teacher_survey_item,
scale:,
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
ideal_low_benchmark: 4.5)
measure
end
let(:professional_qualifications) do
measure = create(:measure, name: 'Professional Qualifications', measure_id: '3', subcategory:)
scale = create(:scale, measure:)
create(:admin_data_item,
scale:,
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
ideal_low_benchmark: 4.5)
measure
end
let(:academic_year) { create(:academic_year) }
before :each do
# assign :category_presenters, []
# assign :grouped_bar_column_presenters, grouped_bar_column_presenters
assign :academic_year, academic_year
# assign :academic_years, [academic_year]
assign :district, create(:district)
assign :school, create(:school)
assign :category, category
assign :subcategory, subcategory
assign :measure, support_for_teaching
render
end
context 'when all the presenters have a non-nil score' do
# let(:grouped_bar_column_presenters) do
# measure = create(:measure, name: 'Display Me', measure_id: 'display-me')
# scale = create(:scale, measure:)
# create(:student_survey_item,
# scale:,
# watch_low_benchmark: 1.5,
# growth_low_benchmark: 2.5,
# approval_low_benchmark: 3.5,
# ideal_low_benchmark: 4.5)
# [
# GroupedBarColumnPresenter.new(measure:,
# score: Score.new(rand))
# ]
# end
it 'displays a set of grouped bars for each presenter' do
displayed_variance_rows = subject.css('[data-for-measure-id]')
expect(displayed_variance_rows.count).to eq 3
expect(displayed_variance_rows.first.attribute('data-for-measure-id').value).to eq '1'
displayed_variance_labels = subject.css('[data-grouped-bar-label]')
expect(displayed_variance_labels.count).to eq 3
expect(displayed_variance_labels.first.inner_text).to include 'All Students'
expect(displayed_variance_labels.last.inner_text).to include 'All Survey Data'
end
end
end
Loading…
Cancel
Save