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
- #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_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
#average_for(histogram_data, total_values) ⇒ Object
111 112 113 114 115 116 |
# File 'lib/jirametrics/time_based_histogram.rb', line 111 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
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/jirametrics/time_based_histogram.rb', line 142 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
138 139 140 |
# File 'lib/jirametrics/time_based_histogram.rb', line 138 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.
119 120 121 122 123 |
# File 'lib/jirametrics/time_based_histogram.rb', line 119 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.
129 130 131 132 |
# File 'lib/jirametrics/time_based_histogram.rb', line 129 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
134 135 136 |
# File 'lib/jirametrics/time_based_histogram.rb', line 134 def sort_items items items end |
#stats_for(histogram_data:, percentiles:) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/jirametrics/time_based_histogram.rb', line 97 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 |