class Palette

Constants

VERSION

Attributes

default[R]
colors[R]
desaturate_factor[R]
name[R]

Public Class Methods

blend_colors(colors, n_colors=6, as_cmap: false) click to toggle source
# File lib/palette/colors.rb, line 110
def self.blend_colors(colors, n_colors=6, as_cmap: false)
  name = "blend"
  cmap = Colors::LinearSegmentedColormap.new_from_list(name, colors)
  if as_cmap
    return cmap
  else
    bins = Helper.linspace(0r, 1r, Integer(n_colors))
    colors = cmap[bins]
    return colors
  end
end
cubehelix_colors(n_colors=6, start: 0, rot: 0.4r, gamma: 1.0r, hue: 0.8r, light: 0.85r, dark: 0.15r, reverse: false) click to toggle source
# File lib/palette/colors.rb, line 56
def self.cubehelix_colors(n_colors=6, start: 0, rot: 0.4r, gamma: 1.0r, hue: 0.8r,
                           light: 0.85r, dark: 0.15r, reverse: false)
  get_color_function = ->(p0, p1) do
    color = -> (x) do
      xg = x ** gamma
      a = hue * xg * (1 - xg) / 2
      phi = 2 * Math::PI * (start / 3 + rot * x)
      return xg + a * (p0 * Math.cos(phi) + p1 * Math.sin(phi))
    end
    return color
  end

  segmented_data = {
    red:   get_color_function.(-0.14861, 1.78277),
    green: get_color_function.(-0.29227, -0.90649),
    blue:  get_color_function.(1.97294, 0.0),
  }

  cmap = Colors::LinearSegmentedColormap.new("cubehelix", segmented_data)

  x = Helper.linspace(light, dark, n_colors)
  pal = cmap[x]
  pal.reverse! if reverse
  pal
end
default=(args) click to toggle source
# File lib/palette.rb, line 171
def default=(args)
  @default = case args
             when Palette
               args
             when Array
               case args[0]
               when Array
                 Palette.new(*args)
               else
                 Palette.new(args)
               end
             else
               Palette.new(args)
             end
end
hsl_colors(n_colors=6, h: 3.6r, s: 0.65r, l: 0.6r) click to toggle source

Get a set of evenly spaced colors in HSL hue space.

@param n_colors [Integer]

The number of colors in the palette

@param h [Numeric]

The hue value of the first color in degree

@param s [Numeric]

The saturation value of the first color (between 0 and 1)

@param l [Numeric]

The lightness value of the first color (between 0 and 1)

@return [Array<Colors::HSL>]

The array of colors
# File lib/palette/colors.rb, line 23
def self.hsl_colors(n_colors=6, h: 3.6r, s: 0.65r, l: 0.6r)
  hues = Helper.linspace(0, 1, n_colors + 1)[0...-1]
  hues.each_index do |i|
    hues[i] += (h/360r).to_f
    hues[i] %= 1
    hues[i] -= hues[i].to_i
  end
  (0...n_colors).map {|i| Colors::HSL.new(hues[i]*360r, s, l) }
end
husl_colors(n_colors=6, h: 3.6r, s: 0.9r, l: 0.65r) click to toggle source

Get a set of evenly spaced colors in HUSL hue space.

@param n_colors [Integer]

The number of colors in the palette

@param h [Numeric]

The hue value of the first color in degree

@param s [Numeric]

The saturation value of the first color (between 0 and 1)

@param l [Numeric]

The lightness value of the first color (between 0 and 1)

@return [Array<Colors::HSL>]

The array of colors
# File lib/palette/colors.rb, line 46
def self.husl_colors(n_colors=6, h: 3.6r, s: 0.9r, l: 0.65r)
  hues = Helper.linspace(0, 1, n_colors + 1)[0...-1]
  hues.each_index do |i|
    hues[i] += (h/360r).to_f
    hues[i] %= 1
    hues[i] *= 359
  end
  (0...n_colors).map {|i| Colors::HUSL.new(hues[i], s, l) }
end
matplotlib_colors(name, n_colors=6, as_cmap: false) click to toggle source
# File lib/palette/colors.rb, line 82
def self.matplotlib_colors(name, n_colors=6, as_cmap: false)
  if name.end_with?("_d")
    sub_name = name[0..-2]
    if sub_name.end_with?("_r")
      reverse = true
      sub_name = sub_name[0..-2]
    else
      reverse = false
    end
    colors = Palette.new(sub_name, 2).colors
    colors << Colors::RGB.parse("#333333")
    colors.reverse! if reverse
    cmap = blend_cmap(colors, n_colors, as_cmap: true)
  else
    cmap = Colors::ColormapRegistry[name]
  end

  return cmap if as_cmap

  bins = if MPL_QUAL_PALS.include?(name)
           Helper.linspace(0r, 1r, MPL_QUAL_PALS[name])[0, n_colors]
         else
           Helper.linspace(0r, 1r, Integer(n_colors) + 2)[1...-1]
         end
  colors = cmap[bins]
  return colors
end
new(palette=nil, n_colors=nil, desaturate_factor: nil) click to toggle source

Return a list of colors defining a color palette

@param palette [nil, String, Palette]

Name of palette or nil to return current palette.
If a Palette is given, input colors are used but
possibly cycled and desaturated.

@param n_colors [Integer, nil]

Number of colors in the palette.
If `nil`, the default will depend on how `palette` is specified.
Named palettes default to 6 colors, but grabbing the current palette
or passing in a list of colors will not change the number of colors
unless this is specified.  Asking for more colors than exist in the
palette cause it to cycle.

@param desaturate_factor [Float, nil]

Propotion to desaturate each color by.

@return [Palette]

Color palette.  Behaves like a list.
# File lib/palette.rb, line 28
def initialize(palette=nil, n_colors=nil, desaturate_factor: nil)
  case
  when palette.nil?
    @name = nil
    palette = Colors::ColorData::DEFAULT_COLOR_CYCLE
    n_colors ||= palette.length
  else
    palette = normalize_palette_name(palette)
    case palette
    when String
      @name = palette
      # Use all colors in a qualitative palette or 6 of another kind
      n_colors ||= QUAL_PALETTE_SIZES.fetch(palette, 6)
      case @name
      when SEABORN_PALETTES.method(:has_key?).to_proc # NOTE: to_proc needs for Ruby 2.4
        palette = self.class.seaborn_colors(@name)
      when "hls", "HLS", "hsl", "HSL"
        palette = self.class.hsl_colors(n_colors)
      when "husl", "HUSL"
        palette = self.class.husl_colors(n_colors)
      when /\Ach:/
        # Cubehelix palette with params specified in string
        args, kwargs = parse_cubehelix_args(palette)
        palette = self.class.cubehelix_colors(n_colors, *args, **kwargs)
      else
        begin
          palette = self.class.matplotlib_colors(palette, n_colors)
        rescue ArgumentError
          raise ArgumentError,
                "#{palette} is not a valid palette name"
        end
      end
    else
      n_colors ||= palette.length
    end
  end
  if desaturate_factor
    palette = palette.map {|c| Colors.desaturate(c, desaturate_factor) }
  end

  # Always return as many colors as we asked for
  @colors = palette.cycle.take(n_colors).freeze
  @desaturate_factor = desaturate_factor
end
seaborn_colors(name) click to toggle source
# File lib/palette/colors.rb, line 4
def self.seaborn_colors(name)
  SEABORN_PALETTES[name].map do |hex_string|
    Colors::RGB.parse(hex_string)
  end
end

Public Instance Methods

==(other) click to toggle source

Two palettes are equal if they have the same colors, even if they have the different names and different desaturate factors.

Calls superclass method
# File lib/palette.rb, line 117
def ==(other)
  case other
  when Palette
    colors == other.colors
  else
    super
  end
end
[](i) click to toggle source
# File lib/palette.rb, line 126
def [](i)
  @colors[i % n_colors]
end
n_colors() click to toggle source
# File lib/palette.rb, line 111
def n_colors
  @colors.length
end
to_ary() click to toggle source
# File lib/palette.rb, line 134
def to_ary
  @colors.dup
end
to_colormap(n=self.n_colors) click to toggle source
# File lib/palette.rb, line 130
def to_colormap(n=self.n_colors)
  Colors::ListedColormap.new(colors, n_colors: n)
end
to_iruby() click to toggle source
# File lib/palette.rb, line 138
def to_iruby
  ["image/svg", to_svg]
end
to_svg() click to toggle source
# File lib/palette.rb, line 142
def to_svg
  w = 55
  n = n_colors
  svg = %Q[<svg width="#{n*w}" height="#{w}">]
  @colors.each_with_index do |color, i|
    hex = color.to_rgb.to_hex_string
    svg << %Q[<rect x="#{i*w}" y="0" width="#{w}" height="#{w}" style="fill:#{hex};]
    svg << %Q[stroke-width:2;stroke:rgb(255,255,255)"/>]
  end
  svg << %Q[</svg>]
  svg
end

Private Instance Methods

normalize_palette_name(palette) click to toggle source
# File lib/palette.rb, line 155
        def normalize_palette_name(palette)
  case palette
  when String
    palette
  when Symbol
    palette.to_s
  else
    palette.to_str
  end
rescue NoMethodError, TypeError
  palette
end
parse_cubehelix_args(str) click to toggle source
# File lib/palette.rb, line 73
        def parse_cubehelix_args(str)
  if str.start_with?("ch:")
    str = str[3..-1]
  end
  if str.end_with?("_r")
    reverse = true
    str = str[0..-3]
  else
    reverse = false
  end
  if str.empty?
    return [], {reverse: reverse}
  end

  short_key_map = {
    "s" => :start, "r" => :rot,   "g" => :gamma,
    "h" => :hue,   "l" => :light, "d" => :dark
  }

  all_args = str.split(",")
  args, kwargs = [], {}
  all_args.each do |a|
    if a.include? "="
      key, value = a.split("=")
      key = short_key_map.fetch(key.strip, key).to_sym
      kwargs[key] = Float(value.strip)
    else
      args << Float(a.strip)
    end
  end

  kwargs[:reverse] = true if reverse

  return args, kwargs
end