module OrganisationsHelper # Функции для раскраски def group_key(code) return nil if code.nil? || code.size < 2 prefix = code[0, 2] case prefix when '21', '22', '23', '24', '25' '21-25' else prefix end end def keep_code?(code, type_data: 'min_max') return false if code.nil? || code.size < 2 prefix = code[0, 2] return false unless prefix.match?(/\A\d{2}\z/) if type_data == 'min_max' return false if %w[16 17 29].include?(prefix) || prefix.start_with?("3") return true if %w[21 22 23 24 25].include?(prefix) return !code.end_with?('00') elsif type_data == 'comparsion' return code.end_with?('00') end end def parse_number(value) return 0 if value.nil? || value.empty? || ['-', '(-)', 'X'].include?(value) negative = value.start_with?('(') && value.end_with?(')') value = value.tr('()', '') if negative value = value.delete(' ') return 0 unless value.match?(/\A\d+\z/) number = value.to_i negative ? -number : number end def prepare_data(data, type_data: 'min_max') return {} if data.nil? filtered_data = data.reject do |(_, _), values| values.all? { |value| ['-', '(-)', 'X'].include?(value) } end if type_data == 'min_max' prepared_data = filtered_data.each_with_object({}) do |((_, code), values), result| next if code.nil? || !keep_code?(code) group = group_key(code) result[group] ||= [] result[group] += values.map { |value| parse_number(value)} end min_max_data = prepared_data.transform_values do |values| sorted = values.sort size = sorted.size p50 = size.odd? ? sorted[size / 2] : (sorted[size / 2 - 1] + sorted[size / 2]) / 2.0 { min: sorted.first, p50: p50, max: sorted.last } end elsif type_data == 'comparsion' prepared_data = filtered_data.each_with_object({}) do |((_, code), values), result| next if code.nil? || !keep_code?(code, type_data: 'comparsion') result[code] ||= [] (0...values.size - 1).each do |index| if parse_number(values[index]) == parse_number(values[index + 1]) result[code] << nil elsif result[code] << (parse_number(values[index]) > parse_number(values[index + 1])) end end result[code] << nil end end end def get_color_for_cell(value, min:, median:, max:) value = parse_number(value) return rgb_to_hex(WHITE) if value.nil? || min.nil? || max.nil? || median.nil? return rgb_to_hex(GREEN) if min == max if value <= median ratio = median == min ? 0.0 : (value - min).to_f / (median - min) rgb = interpolate_color(RED, YELLOW, clamp(ratio)) else ratio = max == median ? 0.0 : (value - median).to_f / (max - median) rgb = interpolate_color(YELLOW, GREEN, clamp(ratio)) end rgb_to_hex(rgb) end def get_arrow_for_cell(value, condition) return content_tag(:td, value) if condition.nil? css_class = condition ? 'up': 'down' content_tag(:td, value, class: css_class) end private def interpolate_color(from, to, ratio) from.zip(to).map { |a, b| (a + (b - a) * ratio).round } end def rgb_to_hex(rgb) format("#%02X%02X%02X", *rgb) end def clamp(value) [[value, 0.0].max, 1.0].min end RED = [248, 105, 107].freeze YELLOW = [255, 235, 132].freeze GREEN = [99, 190, 123].freeze WHITE = [255, 255, 255].freeze end