<commit_before># frozen_string_literal: true

#
# Here's how to set up a Gruff::BoxPlot.
#
#   g = Gruff::BoxPlot.new
#   g.data "A", [2, 3, 5, 6, 8, 10, 11, 15, 17, 20, 28, 29, 33, 34, 45, 46, 49, 61]
#   g.data "B", [3, 4, 34, 35, 38, 39, 45, 60, 61, 69, 80, 130]
#   g.data "C", [4, 40, 41, 46, 57, 64, 77, 76, 79, 78, 99, 153]
#   g.write("box_plot.png")
#
class Gruff::BoxPlot < Gruff::Base
  # Specifies the filling opacity in area graph. Default is +0.2+.
  attr_writer :fill_opacity

  # Specifies the stroke width in line. Default is +3.0+.
  attr_writer :stroke_width

  # Can be used to adjust the spaces between the bars.
  # Accepts values between 0.00 and 1.00 where 0.00 means no spacing at all
  # and 1 means that each bars' width is nearly 0 (so each bar is a simple
  # line with no x dimension).
  #
  # Default value is +0.8+.
  def spacing_factor=(space_percent)
    raise ArgumentError, 'spacing_factor must be between 0.00 and 1.00' unless (space_percent >= 0) && (space_percent <= 1)

    @spacing_factor = (1 - space_percent)
  end

private

  def initialize_attributes
    super
    @fill_opacity = 0.2
    @stroke_width = 3.0
    @spacing_factor = 0.8
  end

  def setup_data
    self.maximum_value = (boxes.map(&:upper_outliers) + boxes.map(&:upper_whisker)).flatten.max unless @maximum_value
    self.minimum_value = (boxes.map(&:lower_outliers) + boxes.map(&:lower_whisker)).flatten.min unless @minimum_value

    super
  end

  def draw_graph
    # Setup the BarConversion Object
    conversion = Gruff::BarConversion.new(
      top: @graph_top, bottom: @graph_bottom,
      minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
    )

    width = (@graph_width - calculate_spacing) / normalized_boxes.size
    bar_width = width * @spacing_factor
    padding = width - bar_width

    normalized_boxes.each_with_index do |box, index|
      left_x = @graph_left + (width * index) + (padding / 2)
      right_x = left_x + bar_width
      center_x = (left_x + right_x) / 2

      first_y, = conversion.get_top_bottom_scaled(box.first_quartile)
      third_y, = conversion.get_top_bottom_scaled(box.third_quartile)
      Gruff::Renderer::Rectangle.new(renderer, color: box.color, width: @stroke_width, opacity: @fill_opacity)
                                .render(left_x, first_y, right_x, third_y)

      median_y, = conversion.get_top_bottom_scaled(box.median)
      Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width * 2).render(left_x, median_y, right_x, median_y)

      minmax_left_x  = left_x + (bar_width / 4)
      minmax_right_x = right_x - (bar_width / 4)
      min_y, = conversion.get_top_bottom_scaled(box.lower_whisker)
      Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, min_y, minmax_right_x, min_y)
      Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
                               .render(center_x, min_y, center_x, first_y)

      max_y, = conversion.get_top_bottom_scaled(box.upper_whisker)
      Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, max_y, minmax_right_x, max_y)
      Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
                               .render(center_x, max_y, center_x, third_y)

      box.lower_outliers.each do |outlier|
        outlier_y, = conversion.get_top_bottom_scaled(outlier)
        Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
      end

      box.upper_outliers.each do |outlier|
        outlier_y, = conversion.get_top_bottom_scaled(outlier)
        Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
      end

      draw_label(center_x, index)
    end
  end

  def boxes
    @boxes ||= store.data.map { |data| Gruff::BoxPlot::BoxData.new(data.label, data.points, data.color) }
  end

  def normalized_boxes
    @normalized_boxes ||= store.norm_data.map { |data| Gruff::BoxPlot::BoxData.new(data.label, data.points, data.color) }
  end

  def calculate_spacing
    @scale * (column_count - 1)
  end

  # @private
  class BoxData < Struct.new(:label, :points, :color)
    def initialize(label, points, color)
      super(label, points.sort, color)
    end

    def min
      points.first || 0
    end

    def max
      points.last || 0
    end

    def min_whisker
      [min, first_quartile - (1.5 * interquartile_range)].max
    end

    def max_whisker
      [max, third_quartile + (1.5 * interquartile_range)].min
    end

    def upper_whisker
      max = max_whisker
      points.select { |point| point <= max }.max
    end

    def lower_whisker
      min = min_whisker
      points.select { |point| point >= min }.min
    end

    def median
      if points.size.zero?
        0
      elsif points.size.odd?
        points[points.size / 2]
      else
        (points[points.size / 2] + points[(points.size / 2) - 1]) / 2.0
      end
    end

    def first_quartile
      if points.size.zero?
        0
      elsif points.size.odd?
        points[points.size / 4]
      else
        (points[points.size / 4] + points[(points.size / 4) - 1]) / 2.0
      end
    end

    def third_quartile
      if points.size.zero?
        0
      elsif points.size.odd?
        points[(points.size * 3) / 4]
      else
        (points[(points.size * 3) / 4] + points[((points.size * 3) / 4) - 1]) / 2.0
      end
    end

    def lower_outliers
      min = lower_whisker
      points.select { |point| point < min }
    end

    def upper_outliers
      max = upper_whisker
      points.select { |point| point > max }
    end

    def interquartile_range
      third_quartile - first_quartile
    end
  end
end
<commit_msg>chore: Remove unnecessary method<commit_after># frozen_string_literal: true

#
# Here's how to set up a Gruff::BoxPlot.
#
#   g = Gruff::BoxPlot.new
#   g.data "A", [2, 3, 5, 6, 8, 10, 11, 15, 17, 20, 28, 29, 33, 34, 45, 46, 49, 61]
#   g.data "B", [3, 4, 34, 35, 38, 39, 45, 60, 61, 69, 80, 130]
#   g.data "C", [4, 40, 41, 46, 57, 64, 77, 76, 79, 78, 99, 153]
#   g.write("box_plot.png")
#
class Gruff::BoxPlot < Gruff::Base
  # Specifies the filling opacity in area graph. Default is +0.2+.
  attr_writer :fill_opacity

  # Specifies the stroke width in line. Default is +3.0+.
  attr_writer :stroke_width

  # Can be used to adjust the spaces between the bars.
  # Accepts values between 0.00 and 1.00 where 0.00 means no spacing at all
  # and 1 means that each bars' width is nearly 0 (so each bar is a simple
  # line with no x dimension).
  #
  # Default value is +0.8+.
  def spacing_factor=(space_percent)
    raise ArgumentError, 'spacing_factor must be between 0.00 and 1.00' unless (space_percent >= 0) && (space_percent <= 1)

    @spacing_factor = (1 - space_percent)
  end

private

  def initialize_attributes
    super
    @fill_opacity = 0.2
    @stroke_width = 3.0
    @spacing_factor = 0.8
  end

  def draw_graph
    # Setup the BarConversion Object
    conversion = Gruff::BarConversion.new(
      top: @graph_top, bottom: @graph_bottom,
      minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
    )

    width = (@graph_width - calculate_spacing) / normalized_boxes.size
    bar_width = width * @spacing_factor
    padding = width - bar_width

    normalized_boxes.each_with_index do |box, index|
      left_x = @graph_left + (width * index) + (padding / 2)
      right_x = left_x + bar_width
      center_x = (left_x + right_x) / 2

      first_y, = conversion.get_top_bottom_scaled(box.first_quartile)
      third_y, = conversion.get_top_bottom_scaled(box.third_quartile)
      Gruff::Renderer::Rectangle.new(renderer, color: box.color, width: @stroke_width, opacity: @fill_opacity)
                                .render(left_x, first_y, right_x, third_y)

      median_y, = conversion.get_top_bottom_scaled(box.median)
      Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width * 2).render(left_x, median_y, right_x, median_y)

      minmax_left_x  = left_x + (bar_width / 4)
      minmax_right_x = right_x - (bar_width / 4)
      min_y, = conversion.get_top_bottom_scaled(box.lower_whisker)
      Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, min_y, minmax_right_x, min_y)
      Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
                               .render(center_x, min_y, center_x, first_y)

      max_y, = conversion.get_top_bottom_scaled(box.upper_whisker)
      Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, max_y, minmax_right_x, max_y)
      Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
                               .render(center_x, max_y, center_x, third_y)

      box.lower_outliers.each do |outlier|
        outlier_y, = conversion.get_top_bottom_scaled(outlier)
        Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
      end

      box.upper_outliers.each do |outlier|
        outlier_y, = conversion.get_top_bottom_scaled(outlier)
        Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
      end

      draw_label(center_x, index)
    end
  end

  def normalized_boxes
    @normalized_boxes ||= store.norm_data.map { |data| Gruff::BoxPlot::BoxData.new(data.label, data.points, data.color) }
  end

  def calculate_spacing
    @scale * (column_count - 1)
  end

  # @private
  class BoxData < Struct.new(:label, :points, :color)
    def initialize(label, points, color)
      super(label, points.sort, color)
    end

    def min
      points.first || 0
    end

    def max
      points.last || 0
    end

    def min_whisker
      [min, first_quartile - (1.5 * interquartile_range)].max
    end

    def max_whisker
      [max, third_quartile + (1.5 * interquartile_range)].min
    end

    def upper_whisker
      max = max_whisker
      points.select { |point| point <= max }.max
    end

    def lower_whisker
      min = min_whisker
      points.select { |point| point >= min }.min
    end

    def median
      if points.size.zero?
        0
      elsif points.size.odd?
        points[points.size / 2]
      else
        (points[points.size / 2] + points[(points.size / 2) - 1]) / 2.0
      end
    end

    def first_quartile
      if points.size.zero?
        0
      elsif points.size.odd?
        points[points.size / 4]
      else
        (points[points.size / 4] + points[(points.size / 4) - 1]) / 2.0
      end
    end

    def third_quartile
      if points.size.zero?
        0
      elsif points.size.odd?
        points[(points.size * 3) / 4]
      else
        (points[(points.size * 3) / 4] + points[((points.size * 3) / 4) - 1]) / 2.0
      end
    end

    def lower_outliers
      min = lower_whisker
      points.select { |point| point < min }
    end

    def upper_outliers
      max = upper_whisker
      points.select { |point| point > max }
    end

    def interquartile_range
      third_quartile - first_quartile
    end
  end
end

{'fraction_non_alphanumeric': 0.11488994389296504, 'fraction_numerical': 0.02589555459646094, 'mean_word_length': 2.8124383020730503, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4853734', 'n_tokens_mistral': 4374, 'n_tokens_neox': 3965, 'n_words': 986}