Class: TimeBasedHistogram
- Inherits:
-
TimeBasedChart
- Object
- ChartBase
- TimeBasedChart
- TimeBasedHistogram
- Includes:
- GroupableIssueChart, PercentileValidation
- Defined in:
- lib/jirametrics/time_based_histogram.rb
Direct Known Subclasses
Constant Summary
Constants inherited from TimeBasedChart
TimeBasedChart::VALUE_AXIS_LABELS
Constants inherited from ChartBase
ChartBase::LABEL_POSITIONS, ChartBase::OKABE_ITO_PALETTE
Instance Attribute Summary collapse
-
#show_stats ⇒ Object
readonly
Returns the value of attribute show_stats.
Attributes included from GroupableIssueChart
#group_by_block, #issue_hints, #issue_periods
Attributes inherited from ChartBase
#aggregated_project, #all_boards, #atlassian_document_format, #board_id, #canvas_height, #canvas_width, #color_palette, #data_quality, #date_range, #file_system, #fix_versions, #holiday_dates, #issues, #settings, #time_range, #timezone_offset, #x_axis_title, #y_axis_title
Instance Method Summary collapse
- #any_empty_stats?(the_stats) ⇒ Boolean
- #average_for(histogram_data, total_values) ⇒ Object
- #data_set_for(histogram_data:, label:, color:) ⇒ Object
- #disable_stats ⇒ Object
- #histogram_data_for(items:) ⇒ Object
-
#initialize ⇒ TimeBasedHistogram
constructor
A new instance of TimeBasedHistogram.
- #label_for_item(item, hint:) ⇒ Object
-
#modes_for(histogram_data) ⇒ Object
Every value that ties for the highest frequency, so a flat or multi-modal distribution returns them all.
-
#percentile_explanation(percentile) ⇒ Object
What a given percentile is actually good for.
-
#percentiles(list = nil) ⇒ Object
Which percentiles to show as columns in the statistics table.
-
#percentiles_for(histogram_data, percentiles, _total_values) ⇒ Object
The data arrives as value => count.
- #run ⇒ Object
- #sort_items(items) ⇒ Object
-
#stats_cell(value) ⇒ Object
One cell of the statistics table.
- #stats_for(histogram_data:, percentiles:) ⇒ Object
-
#value_axis_title=(title) ⇒ Object
On a histogram the cycle time is plotted along the x-axis (count is on the y-axis).
Methods included from PercentileValidation
validate_percentile, validate_percentiles
Methods included from GroupableIssueChart
#accumulate_issue_for_group, #group_issues, #grouping_rules, #init_configuration_block, #reconcile_percentiles
Methods inherited from TimeBasedChart
#cycletime_unit, #duration_in_unit, #label_cycletime
Methods inherited from ChartBase
#aggregated_project?, #before_run, #call_before_run, #canvas, #canvas_responsive?, #chart_format, #collapsible_issues_panel, #color_block, #color_for, #comma_and, #completed_issues_in_range, #current_board, #cycletime, #cycletime_for_issue, #daily_chart_dataset, #date_annotation, #describe_non_working_days, #description_text, #format_integer, #format_status, #header_text, #holidays, #html_directory, #icon_span, #link_to_issue, #next_id, #next_palette_color, #non_working_day?, #normalize_annotation_datetime, #not_visible_icon, #not_visible_text, #ordinal, #percentile_of, #render, #render_axis_title, #render_top_text, #resolve_status, #stagger_label_positions, #status_category_color, #to_human_readable, #working_days_annotation, #wrap_and_render
Constructor Details
#initialize ⇒ TimeBasedHistogram
Returns a new instance of TimeBasedHistogram.
13 14 15 16 17 18 |
# File 'lib/jirametrics/time_based_histogram.rb', line 13 def initialize super percentiles [50, 85, 98] @show_stats = true end |
Instance Attribute Details
#show_stats ⇒ Object (readonly)
Returns the value of attribute show_stats.
11 12 13 |
# File 'lib/jirametrics/time_based_histogram.rb', line 11 def show_stats @show_stats end |
Instance Method Details
#any_empty_stats?(the_stats) ⇒ Boolean
107 108 109 |
# File 'lib/jirametrics/time_based_histogram.rb', line 107 def any_empty_stats? the_stats the_stats.any? { |_label, stats| stats.empty? } end |
#average_for(histogram_data, total_values) ⇒ Object
125 126 127 128 129 130 |
# File 'lib/jirametrics/time_based_histogram.rb', line 125 def average_for histogram_data, total_values return 0 if total_values.zero? weighted_sum = histogram_data.reduce(0) { |sum, (value, frequency)| sum + (value * frequency) } weighted_sum.to_f / total_values end |
#data_set_for(histogram_data:, label:, color:) ⇒ Object
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/jirametrics/time_based_histogram.rb', line 156 def data_set_for histogram_data:, label:, color: { type: 'bar', label: label, data: histogram_data.keys.sort.filter_map do |days| items = histogram_data[days] next if items.empty? { x: days, y: items.size, title: [title_for_item(count: items.size, value: days)] + sort_items(items).collect do |item| hint = @issue_hints&.fetch(item, nil) label_for_item(item, hint: hint) end } end, backgroundColor: color, borderRadius: 0 } end |
#disable_stats ⇒ Object
33 34 35 |
# File 'lib/jirametrics/time_based_histogram.rb', line 33 def disable_stats @show_stats = false end |
#histogram_data_for(items:) ⇒ Object
88 89 90 91 92 93 94 95 |
# File 'lib/jirametrics/time_based_histogram.rb', line 88 def histogram_data_for items: items_hash = {} items.each do |item| days = value_for_item item (items_hash[days] ||= []) << item if days.positive? end items_hash end |
#label_for_item(item, hint:) ⇒ Object
152 153 154 |
# File 'lib/jirametrics/time_based_histogram.rb', line 152 def label_for_item item, hint: raise NotImplementedError, "#{self.class} must implement label_for_item" end |
#modes_for(histogram_data) ⇒ Object
Every value that ties for the highest frequency, so a flat or multi-modal distribution returns them all.
133 134 135 136 137 |
# File 'lib/jirametrics/time_based_histogram.rb', line 133 def modes_for histogram_data sorted_by_frequency = histogram_data.sort_by { |_value, frequency| frequency } max_frequency = sorted_by_frequency[-1][1] sorted_by_frequency.select { |_value, frequency| frequency == max_frequency }.collect(&:first).sort end |
#percentile_explanation(percentile) ⇒ Object
What a given percentile is actually good for. These used to be a hardcoded list describing the 50th, 85th and 98th, sitting underneath a table whose columns follow the configuration, so the two drifted apart the moment anyone changed the setting. Bands rather than exact values, because 90 deserves an answer just as much as 85 does.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/jirametrics/time_based_histogram.rb', line 41 def percentile_explanation percentile case percentile when 0..49 'below the median, so half or more of your work takes longer than this. Useful for ' \ 'understanding your faster cases, but not a number to plan around.' when 50 'also known as the <b>Median</b>. Useful to establish short feedback loops, to monitor ' \ "that it's not drifting to the right." when 51..94 'useful to establish service level expectations, accounting for rare events.' else 'useful to gauge worst case expectations.' end end |
#percentiles(list = nil) ⇒ Object
Which percentiles to show as columns in the statistics table. An empty list drops the columns entirely. Values are validated here rather than at use, because they feed the percentile arithmetic and the table headers, where a bad one produces a wrong chart instead of an error.
28 29 30 31 |
# File 'lib/jirametrics/time_based_histogram.rb', line 28 def percentiles list = nil @percentiles = validate_percentiles(list) unless list.nil? @percentiles end |
#percentiles_for(histogram_data, percentiles, _total_values) ⇒ Object
The data arrives as value => count. Expanded back to a flat list so that the one shared percentile implementation is used here too: this chart and the scatterplot must not report different answers for the same percentile of the same data. Chart sized data makes the expansion cheap.
143 144 145 146 |
# File 'lib/jirametrics/time_based_histogram.rb', line 143 def percentiles_for histogram_data, percentiles, _total_values values = histogram_data.flat_map { |value, count| Array.new(count, value) } percentiles.to_h { |percentile| [percentile, percentile_of(values, percentile)] } end |
#run ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/jirametrics/time_based_histogram.rb', line 56 def run histogram_items = all_items rules_to_items = group_issues histogram_items the_stats = {} overall_histogram = histogram_data_for(items: histogram_items).transform_values(&:size) the_stats[:all] = stats_for histogram_data: overall_histogram, percentiles: @percentiles data_sets = rules_to_items.keys.collect do |rules| the_label = rules.label the_histogram = histogram_data_for(items: rules_to_items[rules]) if @show_stats the_stats[the_label] = stats_for( histogram_data: the_histogram.transform_values(&:size), percentiles: @percentiles ) end data_set_for( histogram_data: the_histogram, label: the_label, color: rules.color ) end if data_sets.empty? return "<h1 class='foldable'>#{@header_text}</h1>" \ '<div>No data matched the selected criteria. Nothing to show.</div>' end wrap_and_render(binding, __FILE__) end |
#sort_items(items) ⇒ Object
148 149 150 |
# File 'lib/jirametrics/time_based_histogram.rb', line 148 def sort_items items items end |
#stats_cell(value) ⇒ Object
One cell of the statistics table. A group can survive grouping and still have nothing to plot, when every item in it was excluded for having no measurable cycle time, and then there are no statistics to show for it at all. Dashing the cells keeps the group visible: dropping the row would make it look like the group had never existed.
101 102 103 104 105 |
# File 'lib/jirametrics/time_based_histogram.rb', line 101 def stats_cell value return '–' if value.nil? block_given? ? yield(value) : value end |
#stats_for(histogram_data:, percentiles:) ⇒ Object
111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/jirametrics/time_based_histogram.rb', line 111 def stats_for histogram_data:, percentiles: return {} if histogram_data.empty? total_values = histogram_data.values.sum min, max = histogram_data.keys.minmax { average: average_for(histogram_data, total_values), mode: modes_for(histogram_data), min: min, max: max, percentiles: percentiles_for(histogram_data, percentiles, total_values) } end |
#value_axis_title=(title) ⇒ Object
On a histogram the cycle time is plotted along the x-axis (count is on the y-axis).
21 22 23 |
# File 'lib/jirametrics/time_based_histogram.rb', line 21 def value_axis_title= title @x_axis_title = title end |