8000 add score frequency chart by robguthrie · Pull Request #8559 · loomio/loomio · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

add score frequency chart #8559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions app/assets/stylesheets/vtfy/components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,18 @@
height: 36px;
}

.v-table {
table.v-table {
border-radius: 4px;
border-spacing: 0;
line-height: 1.5;
max-width: 800px;
margin-bottom: 16px;
td, th {
font-size: .8rem;
// height: 32px;
padding: 0 4px;
border-bottom: thin solid rgba(0,0,0,.12);
}
td.no-border {
border-bottom: none;
border: 0;
}
}
td.v-table, th.v-table {
font-size: .8rem;
// height: 32px;
padding: 0 4px;
border-bottom: thin solid rgba(0,0,0,.12);
}

.v-layout-table {
Expand Down
11 changes: 3 additions & 8 deletions app/helpers/dev/fake_data_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ def fake_poll(args = {})
when 'ranked_choice'
options[:custom_fields][:minimum_stance_choices] = 3
when 'score'
options[:custom_fields][:max_score] = 9
options[:custom_fields][:min_score] = -9
options[:custom_fields][:max_score] = 5
options[:custom_fields][:min_score] = -5
end

Poll.new(options)
Expand All @@ -218,12 +218,7 @@ def fake_score(poll)

def fake_stance(args = {})
poll = args[:poll] || saved(fake_poll)

choice = if poll.minimum_stance_choices > 1
poll.poll_options.sample(poll.minimum_stance_choices).map do |option|
[option.name, fake_score(poll)]
end.to_h
elsif poll.require_all_choices
choice = if poll.has_variable_score
poll.poll_options.map do |option|
[option.name, fake_score(poll)]
end.to_h
Expand Down
2 changes: 2 additions & 0 deletions app/models/poll.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ def chart_type
case poll_type
when 'proposal' then 'pie'
when 'meeting' then 'grid'
when 'score' then 'score_counts'
else
'bar'
end
Expand All @@ -159,6 +160,7 @@ def icon_type
when 'proposal' then 'pie'
when 'meeting' then 'grid'
when 'count' then 'count'
when 'score' then 'score_counts'
else
'bar'
end
Expand Down
9 changes: 9 additions & 0 deletions app/models/poll_option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,23 @@ class PollOption < ApplicationRecord

scope :dangling, -> { joins('left join polls on polls.id = poll_id').where('polls.id is null') }


def update_counts!
update_columns(
score_counts: calculate_score_counts,
voter_scores: poll.anonymous ? {} : stance_choices.latest.where('stances.participant_id is not null').includes(:stance).map { |c| [c.stance.participant_id, c.score] }.to_h,
total_score: stance_choices.latest.sum(:score),
voter_count: stances.latest.count
)
end

# what is the frequency of each of the scores chosen for this option?
def calculate_score_counts
h = {}
stance_choices.each { |c| h[c.score] = h.fetch(c.score, 0) + 1 }
h
end

def color
if poll.poll_type == 'proposal'
{
Expand Down
4 changes: 3 additions & 1 deletion app/serializers/poll_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ class PollSerializer < ApplicationSerializer
:undecided_voters_count,
:voter_can_add_options,
:voters_count,
:versions_count
:versions_count,
:min_score,
:max_score

has_one :discussion, serializer: DiscussionSerializer, root: :discussions
has_one :created_event, serializer: EventSerializer, root: :events
Expand Down
2 changes: 2 additions & 0 deletions app/services/poll_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ def self.calculate_results(poll, poll_options)
max_score_percent: poll.total_score > 0 ? ((option.total_score.to_f / poll.stance_counts.max.to_f) * 100) : 0,
voter_percent: poll.voters_count > 0 ? ((option.voter_count.to_f / poll.voters_count.to_f) * 100) : 0,
average: option.average_score,
score_counts: option.score_counts,
voter_scores: option.voter_scores,
voter_ids: option.voter_ids.take(500),
voter_count: option.voter_count,
Expand All @@ -294,6 +295,7 @@ def self.calculate_results(poll, poll_options)
voter_percent: poll.voters_count > 0 ? (poll.undecided_voters_count.to_f / poll.voters_count.to_f * 100) : 0,
average: 0,
voter_scores: {},
score_counts: {},
voter_ids: poll.undecided_voters.map(&:id).take(500),
voter_count: poll.undecided_voters_count,
color: '#BBBBBB'
Expand Down
20 changes: 15 additions & 5 deletions app/views/event_mailer/poll/results/_simple.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
- when 'voters'
%th.text-left.text-subtitle-2
%tbody
- max_count = (poll.results.map {|o| o[:score_counts].values.max}.compact).max
- results.each_with_index do |option, index|
%tr
- poll.result_columns.each do |col|
Expand All @@ -36,13 +37,22 @@
.poll-mailer-proposal__chart.poll-mailer__results-chart.d-flex.align-center.justify-center
%img.poll-mailer-proposal__chart-image{style: "height: 128px; width: 128px", src: google_pie_chart_url(poll), width: 128, height: 128}
- when 'bar'
%td.pr-2.py-2{style: 'width: 128px'}
- if option[poll.chart_column] > 0

- if poll.chart_type == 'score_counts'
%td.pr-2.py-2{style: 'width: 128px'}
%table{cellspacing: 0, cellpadding: 0, width: '100%', height: '100%'}
%tr
%td.no-border.rounded{style: "background-color: #{option[:color]}; height: 24px", height: 24, width: "#{option[poll.chart_column]}%"}
%td.no-border
- (poll.min_score..poll.max_score).to_a.each do |score|
- count = option[:score_counts].fetch(score.to_s, 0)
- pct = count.to_f / max_count.to_f
%td.nostyles{style: "border:0; height: 24px; background-color: rgba(0,90,250,#{pct})"}=raw "&nbsp;"
- else
%td.pr-2.py-2{style: 'width: 128px'}
- if option[poll.chart_column] > 0

%table{cellspacing: 0, cellpadding: 0, width: '100%', height: '100%'}
%tr
%td.no-border.rounded{style: "background-color: #{option[:color]}; height: 24px", height: 24, width: "#{option[poll.chart_column]}%"}
%td.no-border
- when 'grid'
/ - if (index == 0)
/ %td.pr-2.py-2{rowspan: results.size}
Expand Down
1 change: 1 addition & 0 deletions config/initializers/premailer_rails.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Premailer::Rails.config.merge!(css_to_attributes: false)
28 changes: 0 additions & 28 deletions vue/src/components/poll/common/chart/bar.vue

This file was deleted.

59 changes: 0 additions & 59 deletions vue/src/components/poll/common/chart/count.vue
52 changes: 39 additions & 13 deletions vue/src/components/poll/common/chart/table.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
<script lang="coffee">
import Records from '@/shared/services/records'
import { fieldFromTemplate, myLastStanceFor } from '@/shared/helpers/poll'
import { max, values, orderBy, compact } from 'lodash'
import BarIcon from '@/components/poll/common/icon/bar.vue'
import CountIcon from '@/components/poll/common/icon/count.vue'
import { max, values, orderBy, compact, range } from 'lodash'
import PieIcon from '@/components/poll/common/icon/pie.vue'
import GridIcon from '@/components/poll/common/icon/grid.vue'
import Vue from 'vue'

export default
components: {BarIcon, CountIcon, PieIcon, GridIcon}
components: {PieIcon}
props:
poll: Object

data: ->
users: {}

scoreStrings: range(@poll.minScore, @poll.maxScore+1, 1).map (s) -> s.toString()
maxCount: max(@poll.results.map((o) -> max(Object.values(o.score_counts))))
methods:
pct: (option, score) ->
num = parseFloat(option.score_counts[score.toString()] || 0) / parseFloat(@maxCount)
Math.round(num * 100) / 100

created: ->
@watchRecords
collections: ['users']
Expand All @@ -28,7 +30,6 @@ export default

<template lang="pug">
.poll-common-chart-table
//- p {{poll.results}}
v-simple-table(dense)
thead
tr
Expand All @@ -43,15 +44,40 @@ export default
th.text-right(v-if="col == 'voter_count'" v-t='"membership_card.voters"')
th(v-if="col == 'voters'")
tbody
tr(v-for="option, index in poll.results" :key="option.id")
tr(
v-for="option, index in poll.results"
:key="option.id"
)
template(v-for="col in poll.resultColumns")
td.pa-0(style="vertical-align: top" v-if="col == 'pie' && index == 0" :rowspan="poll.results.length")
pie-icon.ma-2(:poll="poll" :size='128')
td.pr-2.py-2(v-if="col == 'bar'" style="width: 128px; padding: 0 8px 0 0")
td.pa-0(
v-if="col == 'pie' && index == 0"
style="vertical-align: top"
:rowspan="poll.results.length"
)
pie-icon.ma-2(
:poll="poll"
:size='128')
td.pr-2.py-2(
v-if="col == 'bar' && poll.chartType=='bar'"
style="width: 128px; padding: 0 8px 0 0"
)
div.rounded(:style="{width: option[poll.chartColumn]+'%', height: '24px', 'background-color': option.color}")
td.pr-2.py-2(
v-if="col == 'bar' && poll.chartType== 'score_counts'"
style="width: 128px; padding: 0 8px 0 0"
)
//- div.rounded(:style="{width: option[poll.chartColumn]+'%', height: '24px', 'background-color': option.color}")
table
tr
td(
:style="{'background-color': 'rgba(0,90,250,'+pct(option, score)+')'}"
:key="score"
v-for="score in scoreStrings") &nbsp;
td(v-if="col == 'name' && option.name_format == 'iso8601'")
// poll-meeting-time(:name='option.name')
td(v-if="col == 'name' && option.name_format == 'i18n' && poll.chartType == 'pie'" v-t="option.name" :style="{'border-left': '4px solid ' + option.color}")
td(v-if="col == 'name' && option.name_format == 'i18n' && poll.chartType == 'pie'"
v-t="option.name"
:style="{'border-left': '4px solid ' + option.color}")
td(v-if="col == 'name' && option.name_format == 'i18n' && poll.chartType != 'pie'" v-t="option.name")
td(v-if="col == 'name' && option.name_format == 'none'") {{option.name}}
td.text-right(v-if="col == 'rank'") {{option.rank}}
Expand Down
4 changes: 3 additions & 1 deletion vue/src/components/poll/common/icon/panel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import BarIcon from '@/components/poll/common/icon/bar.vue'
import CountIcon from '@/components/poll/common/icon/count.vue'
import PieIcon from '@/components/poll/common/icon/pie.vue'
import GridIcon from '@/components/poll/common/icon/grid.vue'
import ScoreCountsIcon from '@/components/poll/common/icon/score_counts.vue'

export default
components: {BarIcon, CountIcon, PieIcon, GridIcon}
components: {BarIcon, CountIcon, PieIcon, GridIcon, ScoreCountsIcon}
props:
poll: Object
showMyStance: Boolean
Expand All @@ -27,6 +28,7 @@ export default
count-icon(v-if="poll.iconType == 'count'" :poll="poll" :size='size')
pie-icon(v-if="poll.iconType == 'pie'" :poll="poll" :size='size')
grid-icon(v-if="poll.iconType == 'grid'" :poll="poll" :size='size')
score-counts-icon(v-if="poll.iconType == 'score_counts'" :poll="poll" :size='size')
.poll-common-chart-preview__stance-container(v-if='showMyStance && (myStance || poll.iCanVote())')
poll-common-stance-icon(:poll="poll" :stance="myStance" :size="stanceSize")

Expand Down
42 changes: 42 additions & 0 deletions vue/src/components/poll/common/icon/score_counts.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script lang="coffee">
import {range, max, compact} from 'lodash'

export default
props:
poll: Object
size: Number

data: ->
scoreStrings: range(@poll.minScore, @poll.maxScore+1, 1).map (s) -> s.toString()
maxCount: max(@poll.results.map((o) -> max(Object.values(o.score_counts))))

methods:
pct: (option, score) ->
num = parseFloat(option.score_counts[score.toString()] || 0) / parseFloat(@maxCount)
Math.round(num * 100) / 100

computed:
cellHeight: -> @size / @poll.results.length
cellWidth: -> @size / @scoreStrings.length

</script>

<template lang="pug">
div.poll-common-icon-grid.d-flex.align-center.justify-center
table
tbody
tr(:key="option.id" v-for="option in poll.results")
td(
v-for="score in scoreStrings"
:key="score"
:style="{'backgroundColor': 'rgba(0,90,250,'+pct(option, score)+')'}"
)
.poll-meeting-icon__cell(:style="{height: cellHeight+'px', width: cellWidth +'px'}")
| &nbsp;
</template>

<style lang="sass">
table.poll-common-chart-score-counts
max-width: 100%
max-height: 100%
</style>
0