diff --git a/Gemfile b/Gemfile
index 07483b2..8ba4877 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,3 +1,6 @@
 source "https://rubygems.org/"
 
 gemspec
+
+# Temporary use this for module_function decorator support
+gem "yard", github: "mrkn/yard", branch: "module_function_decorator"
diff --git a/README.md b/README.md
index 8976cbe..e28ad4f 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,10 @@
 
 UnicodePlot provides the feature to make charts with Unicode characters.
 
+## Documentation
+
+https://red-data-tools.github.io/unicode_plot.rb/
+
 ## Install
 
 ```console
@@ -18,8 +22,7 @@ y_sin = x.map {|xi| Math.sin(xi) }
 y_cos = x.map {|xi| Math.cos(xi) }
 plot = UnicodePlot.lineplot(x, y_sin, name: "sin(x)", width: 40, height: 10)
 UnicodePlot.lineplot!(plot, x, y_cos, name: "cos(x)")
-plot.render($stdout)
-puts
+plot.render
 ```
 
 You can get the results below by running the above script:
@@ -31,8 +34,7 @@ You can get the results below by running the above script:
 ### barplot
 
 ```ruby
-plot = UnicodePlot.barplot(data: {'foo': 20, 'bar': 50}, title: "Bar")
-plot.render($stdout)
+UnicodePlot.barplot(data: {'foo': 20, 'bar': 50}, title: "Bar").render
 ```
 
 <img src="img/barplot.png" width="50%" />
@@ -40,8 +42,7 @@ plot.render($stdout)
 ### boxplot
 
 ```ruby
-plot = UnicodePlot.boxplot(data: {foo: [1, 3, 5], bar: [3, 5, 7]}, title: "Box")
-plot.render($stdout)
+UnicodePlot.boxplot(data: {foo: [1, 3, 5], bar: [3, 5, 7]}, title: "Box").render
 ```
 
 <img src="img/boxplot.png" width="50%" />
@@ -51,8 +52,7 @@ plot.render($stdout)
 ```ruby
 x = Array.new(500) { 20*rand - 10 } + Array.new(500) { 6*rand - 3 }
 y = Array.new(1000) { 30*rand - 10 }
-plot = UnicodePlot.densityplot(x, y, title: "Density")
-plot.render($stdout)
+UnicodePlot.densityplot(x, y, title: "Density").render
 ```
 
 <img src="img/densityplot.png" width="50%" />
@@ -61,8 +61,7 @@ plot.render($stdout)
 
 ```ruby
 x = Array.new(100) { rand(10) } + Array.new(100) { rand(30) + 10 }
-plot = UnicodePlot.histogram(x, title: "Histogram")
-plot.render($stdout)
+UnicodePlot.histogram(x, title: "Histogram").render
 ```
 
 <img src="img/histogram.png" width="50%" />
@@ -76,8 +75,7 @@ See [Usage](#usage) section above.
 ```ruby
 x = Array.new(50) { rand(20) - 10 }
 y = x.map {|xx| xx*rand(30) - 10 }
-plot = UnicodePlot.scatterplot(x, y, title: "Scatter")
-plot.render($stdout)
+UnicodePlot.scatterplot(x, y, title: "Scatter").render
 ```
 
 <img src="img/scatterplot.png" width="50%" />
diff --git a/Rakefile b/Rakefile
index 6de7fbb..53c7e96 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,9 +1,9 @@
 require "bundler/gem_helper"
+require "yard"
 
 base_dir = File.expand_path("..", __FILE__)
 helper = Bundler::GemHelper.new(base_dir)
 helper.install
-spec = helper.gemspec
 
 desc "Run test"
 task :test do
@@ -11,3 +11,6 @@ task :test do
 end
 
 task default: :test
+
+YARD::Rake::YardocTask.new do |task|
+end
diff --git a/lib/unicode_plot.rb b/lib/unicode_plot.rb
index 77887e2..8a0ba57 100644
--- a/lib/unicode_plot.rb
+++ b/lib/unicode_plot.rb
@@ -2,17 +2,13 @@ require 'stringio'
 
 require 'unicode_plot/version'
 
+require 'unicode_plot/io_context'
 require 'unicode_plot/utils'
 require 'unicode_plot/styled_printer'
 require 'unicode_plot/value_transformer'
 require 'unicode_plot/renderer'
 
 require 'unicode_plot/canvas'
-require 'unicode_plot/braille_canvas'
-require 'unicode_plot/density_canvas'
-require 'unicode_plot/lookup_canvas'
-require 'unicode_plot/ascii_canvas'
-require 'unicode_plot/dot_canvas'
 
 require 'unicode_plot/plot'
 require 'unicode_plot/grid_plot'
@@ -23,3 +19,5 @@ require 'unicode_plot/densityplot'
 require 'unicode_plot/lineplot'
 require 'unicode_plot/histogram'
 require 'unicode_plot/scatterplot'
+require 'unicode_plot/stairs'
+require 'unicode_plot/stemplot'
diff --git a/lib/unicode_plot/barplot.rb b/lib/unicode_plot/barplot.rb
index f2b561e..2a25762 100644
--- a/lib/unicode_plot/barplot.rb
+++ b/lib/unicode_plot/barplot.rb
@@ -69,6 +69,57 @@ module UnicodePlot
     end
   end
 
+  # @overload barplot(text, heights, xscale: nil, title: nil, xlabel: nil, ylabel: nil, labels: true, border: :barplot, margin: Plot::DEFAULT_MARGIN, padding: Plot::DEFAULT_PADDING, color: Barplot::DEFAULT_COLOR, width: Plot::DEFAULT_WIDTH, symbol: Barplot::DEFAULT_SYMBOL)
+  #
+  #   Draws a horizontal barplot.
+  #
+  #   @param text [Array<String>] The lables / captions of the bars.
+  #   @param heights [Array<Numeric>] The values / heights of the bars.
+  #   @param xscale [nil,:log,:ln,:log10,:lg,:log2,:lb,callable]
+  #          A function name symbol or callable object to transform the bar
+  #          length before plotting.  This effectively scales the x-axis
+  #          without influencing the captions of the individual bars.
+  #          e.g. use `xscale: :log10` for logscale.
+  #   @param title
+  #   @param xlabel
+  #   @param ylabel
+  #   @param labels
+  #   @param border
+  #   @param margin
+  #   @param padding
+  #   @param color
+  #   @param width
+  #   @param symbol [String] Specifies the character that should be used
+  #          to render the bars.
+  #
+  #   @return [Barplot] A plot object.
+  #
+  #   @example Example usage of barplot on IRB:
+  #
+  #       >> UnicodePlot.barplot(["Paris", "New York", "Moskau", "Madrid"],
+  #                              [2.244, 8.406, 11.92, 3.165],
+  #                              xlabel: "population [in mil]").render
+  #                   ┌                                        ┐
+  #             Paris ┤■■■■■■ 2.244
+  #          New York ┤■■■■■■■■■■■■■■■■■■■■■■■ 8.406
+  #            Moskau ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 11.92
+  #            Madrid ┤■■■■■■■■■ 3.165
+  #                   └                                        ┘
+  #                              population [in mil]
+  #       => nil
+  #
+  #   @see Plot
+  #   @see histogram
+  #   @see Barplot
+  #
+  # @overload barplot(data, **kwargs)
+  #
+  #   The different variation of barplot described above.
+  #
+  #   @param data [Hash] A hash in which the keys will be used as `text` and
+  #          the values will be utilized as `heights`.
+  #   @param kwargs Optional keyword arguments same as ones described above.
+  #   @return [Barplot] A plot object.
   module_function def barplot(*args,
                               width: Plot::DEFAULT_WIDTH,
                               color: Barplot::DEFAULT_COLOR,
@@ -108,10 +159,24 @@ module UnicodePlot
     plot
   end
 
+  # @overload barplot!(plot, text, heights)
+  #
+  #   Draw additional bars on the given existing plot.
+  #
+  #   @param plot [Barplot] the existing plot.
+  #   @return [Barplot] A plot object.
+  #
+  #   @see barplot
+  #
+  # @overload barplot!(plot, data)
+  #
+  #   The different variation of `barplot!` that takes the plotting data in a hash.
+  #
+  #   @param plot [Barplot] the existing plot.
+  #   @return [Barplot] A plot object.
   module_function def barplot!(plot,
                                *args,
-                               data: nil,
-                               **kw)
+                               data: nil)
     case args.length
     when 0
       data = Hash(data)
diff --git a/lib/unicode_plot/canvas.rb b/lib/unicode_plot/canvas.rb
index 3905cb7..324b063 100644
--- a/lib/unicode_plot/canvas.rb
+++ b/lib/unicode_plot/canvas.rb
@@ -2,16 +2,13 @@ module UnicodePlot
   class Canvas
     include BorderPrinter
 
+    CANVAS_CLASS_MAP = {}
+
     def self.create(canvas_type, width, height, **kw)
-      case canvas_type
-      when :ascii
-        AsciiCanvas.new(width, height, **kw)
-      when :braille
-        BrailleCanvas.new(width, height, **kw)
-      when :density
-        DensityCanvas.new(width, height, **kw)
-      when :dot
-        DotCanvas.new(width, height, **kw)
+      canvas_class = CANVAS_CLASS_MAP[canvas_type]
+      case canvas_class
+      when Class
+        canvas_class.new(width, height, **kw)
       else
         raise ArgumentError, "unknown canvas type: #{canvas_type}"
       end
@@ -166,4 +163,16 @@ module UnicodePlot
       raise ArgumentError, "#{name} has to be positive"
     end
   end
+
+  def self.canvas_types
+    Canvas::CANVAS_CLASS_MAP.keys
+  end
 end
+
+require_relative 'canvas/ascii_canvas'
+require_relative 'canvas/block_canvas'
+require_relative 'canvas/braille_canvas'
+require_relative 'canvas/density_canvas'
+require_relative 'canvas/dot_canvas'
+
+UnicodePlot::Canvas::CANVAS_CLASS_MAP.freeze
diff --git a/lib/unicode_plot/ascii_canvas.rb b/lib/unicode_plot/canvas/ascii_canvas.rb
similarity index 97%
rename from lib/unicode_plot/ascii_canvas.rb
rename to lib/unicode_plot/canvas/ascii_canvas.rb
index c625b22..1b8c81e 100644
--- a/lib/unicode_plot/ascii_canvas.rb
+++ b/lib/unicode_plot/canvas/ascii_canvas.rb
@@ -1,5 +1,9 @@
+require_relative 'lookup_canvas'
+
 module UnicodePlot
   class AsciiCanvas < LookupCanvas
+    Canvas::CANVAS_CLASS_MAP[:ascii] = self
+
     ASCII_SIGNS = [
       [ 0b100_000_000, 0b000_100_000, 0b000_000_100 ].freeze,
       [ 0b010_000_000, 0b000_010_000, 0b000_000_010 ].freeze,
diff --git a/lib/unicode_plot/canvas/block_canvas.rb b/lib/unicode_plot/canvas/block_canvas.rb
new file mode 100644
index 0000000..942b995
--- /dev/null
+++ b/lib/unicode_plot/canvas/block_canvas.rb
@@ -0,0 +1,38 @@
+module UnicodePlot
+  # The `BlockCanvas` is also Unicode-based.
+  # It has half the resolution of the `BrailleCanvas`.
+  # In contrast to BrailleCanvas, the pixels don't
+  # have visible spacing between them.
+  # This canvas effectively turns every character
+  # into 4 pixels that can individually be manipulated
+  # using binary operations.
+  class BlockCanvas < LookupCanvas
+    Canvas::CANVAS_CLASS_MAP[:block] = self
+
+    X_PIXEL_PER_CHAR = 2
+    Y_PIXEL_PER_CHAR = 2
+
+    def initialize(width, height, fill_char=0, **kw)
+      super(width, height,
+            X_PIXEL_PER_CHAR,
+            Y_PIXEL_PER_CHAR,
+            fill_char,
+            **kw)
+    end
+    
+    BLOCK_SIGNS = [
+      [0b1000, 0b0010].freeze,
+      [0b0100, 0b0001].freeze
+    ].freeze
+
+    BLOCK_DECODE = [
+      -' ', -'▗', -'▖', -'▄',
+      -'▝', -'▐', -'▞', -'▟',
+      -'▘', -'▚', -'▌', -'▙',
+      -'▀', -'▜', -'▛', -'█'
+    ].freeze
+
+    def lookup_encode(x,y) ; BLOCK_SIGNS[x][y] ; end
+    def lookup_decode(x) ; BLOCK_DECODE[x] ; end
+  end
+end
diff --git a/lib/unicode_plot/braille_canvas.rb b/lib/unicode_plot/canvas/braille_canvas.rb
similarity index 93%
rename from lib/unicode_plot/braille_canvas.rb
rename to lib/unicode_plot/canvas/braille_canvas.rb
index b509745..58c88b6 100644
--- a/lib/unicode_plot/braille_canvas.rb
+++ b/lib/unicode_plot/canvas/braille_canvas.rb
@@ -1,5 +1,7 @@
 module UnicodePlot
   class BrailleCanvas < Canvas
+    Canvas::CANVAS_CLASS_MAP[:braille] = self
+
     X_PIXEL_PER_CHAR = 2
     Y_PIXEL_PER_CHAR = 4
 
@@ -40,8 +42,8 @@ module UnicodePlot
       char_x_off = pixel_x % X_PIXEL_PER_CHAR + 1
       char_x += 1 if char_x < tx.round + 1 && char_x_off == 1
 
-      char_y = (pixel_y.fdiv(pixel_height) * height).floor + 1
       char_y_off = pixel_y % Y_PIXEL_PER_CHAR + 1
+      char_y = ((pixel_y - (char_y_off - 1)) / Y_PIXEL_PER_CHAR) + 1
 
       index = index_at(char_x - 1, char_y - 1)
       if index
diff --git a/lib/unicode_plot/density_canvas.rb b/lib/unicode_plot/canvas/density_canvas.rb
similarity index 97%
rename from lib/unicode_plot/density_canvas.rb
rename to lib/unicode_plot/canvas/density_canvas.rb
index 6d6a212..4d9c5b3 100644
--- a/lib/unicode_plot/density_canvas.rb
+++ b/lib/unicode_plot/canvas/density_canvas.rb
@@ -1,5 +1,7 @@
 module UnicodePlot
   class DensityCanvas < Canvas
+    Canvas::CANVAS_CLASS_MAP[:density] = self
+
     DENSITY_SIGNS = [" ", "░", "▒", "▓", "█"].freeze
 
     MIN_WIDTH = 5
diff --git a/lib/unicode_plot/dot_canvas.rb b/lib/unicode_plot/canvas/dot_canvas.rb
similarity index 93%
rename from lib/unicode_plot/dot_canvas.rb
rename to lib/unicode_plot/canvas/dot_canvas.rb
index a54ef82..8d6b39f 100644
--- a/lib/unicode_plot/dot_canvas.rb
+++ b/lib/unicode_plot/canvas/dot_canvas.rb
@@ -1,5 +1,7 @@
 module UnicodePlot
   class DotCanvas < LookupCanvas
+    Canvas::CANVAS_CLASS_MAP[:dot] = self
+
     DOT_SIGNS = [
                   [
                     0b10,
diff --git a/lib/unicode_plot/lookup_canvas.rb b/lib/unicode_plot/canvas/lookup_canvas.rb
similarity index 95%
rename from lib/unicode_plot/lookup_canvas.rb
rename to lib/unicode_plot/canvas/lookup_canvas.rb
index 01eb768..99a25d5 100644
--- a/lib/unicode_plot/lookup_canvas.rb
+++ b/lib/unicode_plot/canvas/lookup_canvas.rb
@@ -23,8 +23,8 @@ module UnicodePlot
       char_x_off = pixel_x % x_pixel_per_char + 1
       char_x += 1 if char_x < tx.round + 1 && char_x_off == 1
 
-      char_y = (pixel_y.fdiv(pixel_height) * height).floor + 1
       char_y_off = pixel_y % y_pixel_per_char + 1
+      char_y = ((pixel_y - (char_y_off - 1)) / y_pixel_per_char) + 1
 
       index = index_at(char_x - 1, char_y - 1)
       if index
diff --git a/lib/unicode_plot/io_context.rb b/lib/unicode_plot/io_context.rb
new file mode 100644
index 0000000..4070739
--- /dev/null
+++ b/lib/unicode_plot/io_context.rb
@@ -0,0 +1,32 @@
+require "forwardable"
+
+module UnicodePlot
+  class IOContext
+    extend Forwardable
+
+    def initialize(io, color: :auto)
+      @io = io
+      @color = check_color(color)
+    end
+
+    def_delegators :@io, :print, :puts
+
+    def color?
+      case @color
+      when :auto
+        @io.respond_to?(:tty?) ? @io.tty? : false
+      else
+        @color
+      end
+    end
+
+    private def check_color(color)
+      case color
+      when true, false, :auto
+        color
+      else
+        raise ArgumentError, "color must be either true, false, :auto"
+      end
+    end
+  end
+end
diff --git a/lib/unicode_plot/layout.rb b/lib/unicode_plot/layout.rb
deleted file mode 100644
index 9fb1b0f..0000000
--- a/lib/unicode_plot/layout.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-module UnicodePlot
-  class GridLayout
-    DEFAULT_WIDTH = 80
-
-    def initialize(n_rows, n_columns, width: Layout::DEFAULT_WIDTH)
-      @n_rows = n_rows
-      @n_columns = n_columns
-      @width = width
-    end
-
-    def [](i, j)
-      @plots[i * n_cols + j]
-    end
-
-    def []=(i, j, plot)
-      @plots[i * n_cols + j] = plot
-    end
-
-    def <<(plot)
-      @plots << plot
-    end
-
-    def render(out)
-      buffers = []
-      (0 ... n_rows).each do |i|
-        (0 ... n_columns).each do |j|
-          StringIO.open do |sio|
-            def sio.tty?; true; end
-            render_cell(sio, i, j)
-            sio.close
-            buffers << sio.string
-          end
-        end
-      end
-    end
-
-    def render_cell(out, i, j)
-      plot = self[i, j]
-      return unless plot
-      plot.width = cell_width
-    end
-  end
-
-  module_function def grid_layout(n_rows, n_cols, *plots, **kw)
-    grid = GridLayout.new(n_rows, n_cols, **kw)
-    plots.each do |plot|
-      grid << plot
-    end
-    grid
-  end
-end
diff --git a/lib/unicode_plot/lineplot.rb b/lib/unicode_plot/lineplot.rb
index 7ec7b0f..acc687b 100644
--- a/lib/unicode_plot/lineplot.rb
+++ b/lib/unicode_plot/lineplot.rb
@@ -4,6 +4,32 @@ module UnicodePlot
   class Lineplot < GridPlot
   end
 
+  # @overload lineplot([x], y, name: "", canvas: :braille, title: "", xlabel: "", ylabel: "", labels: true, border: :solid, margin: Plot::DEFAULT_MARGIN, padding: Plot::DEFAULT_PADDING, color: :auto, width: Plot::DEFAULT_WIDTH, height: GridPlot::DEFAULT_HEIGHT, xlim: [0, 0], ylim: [0, 0], canvas: :braille, grid: true)
+  #
+  #   Draws a path through the given points on a new canvas.
+  #
+  #   The first (optional) array `x` should contain the horizontal positions for all the points along the path.
+  #   The second array `y` should then contain the corresponding vertical positions respectively.
+  #   This means that the two vectors must be of the same length and ordering.
+  #
+  #   @param x [Array<Numeric>] Optional. The horizontal position for each point. If omitted, the axes of `y` will be used as `x`.
+  #   @param y [Array<Numeric>] The vertical position for each point.
+  #   @param name [String] Annotation of the current drawing to be displayed on the right.
+  #   @param title
+  #   @param xlabel
+  #   @param ylabel
+  #   @param labels
+  #   @param border
+  #   @param margin
+  #   @param padding
+  #   @param color
+  #   @param width
+  #   @param height
+  #   @param xlim
+  #   @param ylim
+  #   @param canvas [Symbol] The type of canvas that should be used for drawing.
+  #   @param grid [true,false] If `true`, draws grid-lines at the origin.
+  #   @return [Lineplot] A plot object.
   module_function def lineplot(*args,
                                canvas: :braille,
                                color: :auto,
@@ -41,6 +67,16 @@ module UnicodePlot
     end
   end
 
+  # @overload lineplot!(plot, [x], y, name: "", color: :auto)
+  #
+  #   Draws a path through the given points on the given canvas.
+  #
+  #   @param plot [Lineplot] The plot object.
+  #   @param x [Array<Numeric>] Optional. The horizontal position for each point. If omitted, the axes of `y` will be used as `x`.
+  #   @param y [Array<Numeric>] The vertical position for each point.
+  #   @param name [String] Annotation of the current drawing to be displayed on the right.
+  #   @param color
+  #   @return [Lineplot] The plot object same as the `plot` parameter.
   module_function def lineplot!(plot,
                                 *args,
                                 color: :auto,
diff --git a/lib/unicode_plot/plot.rb b/lib/unicode_plot/plot.rb
index e931144..728ce20 100644
--- a/lib/unicode_plot/plot.rb
+++ b/lib/unicode_plot/plot.rb
@@ -17,7 +17,7 @@ module UnicodePlot
       @title = title
       @xlabel = xlabel
       @ylabel = ylabel
-      @border = border
+      @border = check_border(border)
       @margin = check_margin(margin)
       @padding = padding
       @labels_left = {}
@@ -103,8 +103,8 @@ module UnicodePlot
       end
     end
 
-    def render(out=$stdout, newline: true)
-      Renderer.render(out, self, newline)
+    def render(out=$stdout, newline: true, color: :auto)
+      Renderer.render(IOContext.new(out, color: color), self, newline)
     end
 
     COLOR_CYCLE = [
@@ -142,5 +142,10 @@ module UnicodePlot
         raise ArgumentError, "row_index out of bounds"
       end
     end
+
+    private def check_border(border)
+      return border if BORDER_MAP.key?(border)
+      raise ArgumentError, "unknown border type: #{border}"
+    end
   end
 end
diff --git a/lib/unicode_plot/renderer.rb b/lib/unicode_plot/renderer.rb
index 7ec4aa4..7d3d417 100644
--- a/lib/unicode_plot/renderer.rb
+++ b/lib/unicode_plot/renderer.rb
@@ -40,6 +40,10 @@ module UnicodePlot
     barplot: BorderMaps::BORDER_BARPLOT,
   }.freeze
 
+  def self.border_types
+    BORDER_MAP.keys
+  end
+
   module BorderPrinter
     include StyledPrinter
 
@@ -128,7 +132,7 @@ module UnicodePlot
       left_len  = nocolor_string(left_str).length
       right_len = nocolor_string(right_str).length
 
-      unless color?(out)
+      unless out.color?
         left_str  = nocolor_string(left_str)
         right_str = nocolor_string(right_str)
       end
diff --git a/lib/unicode_plot/stairs.rb b/lib/unicode_plot/stairs.rb
new file mode 100644
index 0000000..8161167
--- /dev/null
+++ b/lib/unicode_plot/stairs.rb
@@ -0,0 +1,88 @@
+# coding: utf-8
+module UnicodePlot
+  # @overload stairs(x, y, style: :post, name: "", title: "", xlabel: "", ylabel: "", labels: true, border: :solid, margin: 3, padding: 1, color: :auto, width: 40, height: 15, xlim: [0, 0], ylim: [0, 0], canvas: :braille, grid: true)
+  #
+  #   Draws a staircase plot on a new canvas.
+  #
+  #   The first vector `x` should contain the horizontal
+  #   positions for all the points. The second vector `y` should then
+  #   contain the corresponding vertical positions respectively. This
+  #   means that the two vectors must be of the same length and
+  #   ordering.
+  #
+  #   @param x [Array<Numeric>] The horizontal position for each point.
+  #   @param y [Array<Numeric>] The vertical position for each point.
+  #   @param style [Symbol] Specifies where the transition of the stair takes place. Can be either `:pre` or `:post`.
+  #   @param name [String] Annotation of the current drawing to be displayed on the right.
+  #   @param height [Integer] Number of character rows that should be used for plotting.
+  #   @param xlim [Array<Numeric>] Plotting range for the x axis. `[0, 0]` stands for automatic.
+  #   @param ylim [Array<Numeric>] Plotting range for the y axis. `[0, 0]` stands for automatic.
+  #   @param canvas [Symbol] The type of canvas that should be used for drawing.
+  #   @param grid [Boolean] If `true`, draws grid-lines at the origin.
+  #
+  #   @return [Plot] A plot object.
+  #
+  #   @example Example usage of stairs on IRB:
+  #
+  #       >> UnicodePlot.stairs([1, 2, 4, 7, 8], [1, 3, 4, 2, 7], style: :post, title: "My Staircase Plot").render
+  #                        My Staircase Plot
+  #            ┌────────────────────────────────────────┐
+  #          7 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⡄⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⢸⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⢸│
+  #            │⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠧⠤⠤⠤⠤⠼│
+  #            │⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
+  #          1 │⣀⣀⣀⣀⣀⣸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
+  #            └────────────────────────────────────────┘
+  #            1                                        8
+  #       => nil
+  #
+  #   @see Plot
+  #   @see scatterplot
+  #   @see lineplot
+  module_function def stairs(xvec, yvec, style: :post, **kw)
+    x_vex, y_vex = compute_stair_lines(xvec, yvec, style: style)
+    lineplot(x_vex, y_vex, **kw)
+  end
+
+  # Similar to stairs, but takes an existing plot object as a first argument.
+  module_function def stairs!(plot, xvec, yvec, style: :post, **kw)
+    x_vex, y_vex = compute_stair_lines(xvec, yvec, style: style)
+    lineplot!(plot, x_vex, y_vex, **kw)
+  end
+
+  module_function def compute_stair_lines(x, y, style: :post)
+    x_vex = Array.new(x.length * 2 - 1, 0)
+    y_vex = Array.new(x.length * 2 - 1, 0)
+    x_vex[0] = x[0]
+    y_vex[0] = y[0]
+    o = 0
+    if style == :post
+      (1 ... x.length).each do |i|
+        x_vex[i + o] = x[i]
+        x_vex[i + o + 1] = x[i]
+        y_vex[i + o] = y[i-1]
+        y_vex[i + o + 1] = y[i]
+        o += 1
+      end
+    elsif style == :pre
+      (1 ... x.length).each do |i|
+        x_vex[i + o] = x[i-1]
+        x_vex[i + o + 1] = x[i]
+        y_vex[i + o] = y[i]
+        y_vex[i + o + 1] = y[i]
+        o += 1
+      end
+    end
+    return [x_vex, y_vex]
+  end
+end
diff --git a/lib/unicode_plot/stemplot.rb b/lib/unicode_plot/stemplot.rb
new file mode 100644
index 0000000..9a37779
--- /dev/null
+++ b/lib/unicode_plot/stemplot.rb
@@ -0,0 +1,344 @@
+# coding: utf-8
+
+module UnicodePlot
+
+  # ## Description
+  # 
+  # Draw a stem-leaf plot of the given vector +vec+.
+  #
+  # ```  
+  # stemplot(vec, **kwargs)
+  # ```
+  # 
+  # Draw a back-to-back stem-leaf plot of the given vectors +vec1+ and +vec2+.
+  #
+  # ```  
+  # stemplot(vec1, vec2, **kwargs)
+  # ```
+  #
+  # The vectors can be any object that converts to an Array, e.g. an Array, Range, etc.
+  # If all elements of the vector are Numeric, the stem-leaf plot is classified as a
+  # {NumericStemplot}, otherwise it is classified as a {StringStemplot}.  Back-to-back 
+  # stem-leaf plots must be the same type, i.e. String and Numeric stem-leaf plots cannot
+  # be mixed in a back-to-back plot.
+  # 
+  # ## Usage
+  # 
+  #     stemplot(vec, [vec2], scale:, divider:, padchar:, trim: )
+  # 
+  # ## Arguments
+  # 
+  # - +vec+: Vector for which the stem leaf plot should be computed.
+  # - +vec2+: Optional secondary vector, will be used to create a back-to-back stem-leaf plot.
+  # - +scale+: Set scale of plot. Default = 10. Scale is changed via orders of magnitude. Common values are 0.1, 1, and 10.  For String stems, the default value of 10 is a one character stem, 100 is a two character stem.
+  # - +divider+: Character for break between stem and leaf. Default = "|"
+  # - +padchar+: Character(s) to separate stems, leaves and dividers. Default = " "
+  # - +trim+: Trims the stem labels when there are no leaves.  This can be useful if your data is sparse. Default = +false+
+  # - +string_padchar+: Character used to replace missing position for input strings shorter than the stem-size.  Default = "_"
+  # 
+  # ## Result
+  # A plot of object type is sent to <tt>$stdout</tt>
+  # 
+  # @example Examples using Numbers
+  #     # Generate some numbers
+  #     fifty_floats = 50.times.map { rand(-1000..1000)/350.0 }
+  #     eighty_ints = 80.times.map { rand(1..100) }
+  #     another_eighty_ints = 80.times.map { rand(1..100) }
+  #     three_hundred_ints = 300.times.map { rand(-100..100) }
+  #     
+  #     # Single sided stem-plot 
+  #     UnicodePlot.stemplot(eighty_ints)
+  #     
+  #     # Single sided stem-plot with positive and negative values
+  #     UnicodePlot.stemplot(three_hundred_ints)
+  #     
+  #     # Single sided stem-plot using floating point values, scaled
+  #     UnicodePlot.stemplot(fifty_floats, scale: 1)
+  #     
+  #     # Single sided stem-plot using floating point values, scaled with new divider
+  #     UnicodePlot.stemplot(fifty_floats, scale: 1, divider: "😄")
+  #     
+  #     # Back to back stem-plot 
+  #     UnicodePlot.stemplot(eighty_ints, another_eighty_ints)
+  #
+  # @example Examples using Strings
+  #     # Generate some strings
+  #     words_1 = %w[apple junk ant age bee bar baz dog egg a]
+  #     words_2 = %w[ape flan can cat juice elf gnome child fruit]
+  #     
+  #     # Single sided stem-plot 
+  #     UnicodePlot.stemplot(words_1)
+  #     
+  #     # Back to back stem-plot 
+  #     UnicodePlot.stemplot(words_1, words_2)
+  #     
+  #     # Scaled stem plot using scale=100 (two letters for the stem) and trimmed stems
+  #     UnicodePlot.stemplot(words_1, scale: 100, trim: true)
+  #     
+  #     # Above, but changing the string_padchar
+  #     UnicodePlot.stemplot(words_1, scale: 100, trim: true, string_padchar: '?')
+
+  class Stemplot
+
+    # Use {factory} method -- should not be directly called. 
+    def initialize(*_args, **_kw)
+      @stemleafs = {}
+    end
+
+    # Factory method to create a Stemplot, creates either a NumericStemplot
+    # or StringStemplot depending on input.
+    #
+    # @param vector [Array] An array of elements to stem-leaf plot
+    # @return [NumericStemplot] If all elements are Numeric
+    # @return [StringStemplot] If any elements are not Numeric
+    def self.factory(vector, **kw)
+      vec = Array(vector)
+      if vec.all? { |item| item.is_a?(Numeric) }
+        NumericStemplot.new(vec, **kw)
+      else
+        StringStemplot.new(vec, **kw)
+      end
+    end
+
+    # Insert a stem and leaf
+    def insert(stem, leaf)
+      @stemleafs[stem] ||= []
+      @stemleafs[stem] << leaf
+    end
+
+    # Returns an unsorted list of stems
+    # @return [Array] Unsorted list of stems
+    def raw_stems
+      @stemleafs.keys
+    end
+    
+    # Returns a list of leaves for a given stem
+    # @param stem [Object] The stem
+    # @return [Array] Unsorted list of leaves
+    def leaves(stem)
+      @stemleafs[stem] || []
+    end
+
+    # Determines largest length of any stem
+    # @return [Integer] Length value
+    def max_stem_length
+      @stemleafs.values.map(&:length).max
+    end
+
+    # Returns a sorted list of stems
+    # @param all [Boolean] Return all stems if true, otherwise only return stems if a leaf exists for a stem
+    # @return [Array] Sorted list of stems
+    def stems(all: true)
+      self.class.sorted_stem_list(raw_stems, all: all)
+    end
+    
+  end
+  
+  class NumericStemplot < Stemplot
+    def initialize(vector, scale: 10, **kw)
+      super
+      Array(vector).each do |value|
+        fvalue = value.to_f.fdiv(scale/10.0)
+        stemnum = (fvalue/10).to_i
+        leafnum = (fvalue - (stemnum*10)).to_i
+        stemsign = value.negative? ? "-" : ''
+        stem = stemsign + stemnum.abs.to_s
+        leaf = leafnum.abs.to_s
+        self.insert(stem, leaf)
+      end
+    end
+
+    # Print key to STDOUT
+    # @param scale [Integer] Scale, should be a power of 10
+    # @param divider [String] Divider character between stem and leaf
+    def print_key(scale, divider)
+      # First print the key
+      puts "Key: 1#{divider}0 = #{scale}"
+      # Description of where the decimal is
+      trunclog = Math.log10(scale).truncate
+      ndigits = trunclog.abs
+      right_or_left = (trunclog < 0) ? "left" : "right"
+      puts "The decimal is #{ndigits} digit(s) to the #{right_or_left} of #{divider}"
+    end
+    
+    # Used when we have stems from a back-to-back stemplot and a combined list of stems is given
+    # @param stems [Array] Concatenated list of stems from two plots
+    # @param all [Boolean] Return all stems if true, otherwise only return stems if a leaf exists for a stem
+    # @return [Array] Sorted list of stems
+    def self.sorted_stem_list(stems, all: true)
+      negkeys, poskeys = stems.partition { |str| str[0] == '-'}
+      if all
+        negmin, negmax = negkeys.map(&:to_i).map(&:abs).minmax
+        posmin, posmax = poskeys.map(&:to_i).minmax
+        negrange = negmin ? (negmin..negmax).to_a.reverse.map { |s| "-"+s.to_s } : []
+        posrange = posmin ? (posmin..posmax).to_a.map(&:to_s) : []
+        return negrange + posrange
+      else
+        negkeys.sort! { |a,b| a.to_i <=> b.to_i }
+        poskeys.sort! { |a,b| a.to_i <=> b.to_i }
+        return negkeys + poskeys
+      end
+    end
+  end
+
+  class StringStemplot < Stemplot
+
+    def initialize(vector, scale: 10, string_padchar: '_', **_kw)
+      super
+      stem_places = Math.log10(scale).floor
+      raise ArgumentError, "Cannot take fewer than 1 place from stem.  Scale parameter should be greater than or equal to 10." if stem_places < 1
+      vector.each do |value|
+        # Strings may be shorter than the number of places we desire,
+        # so we will pad them with a string-pad-character.
+        padded_value = value.ljust(stem_places+1, string_padchar)
+        stem = padded_value[0...stem_places]
+        leaf = padded_value[stem_places]
+        self.insert(stem, leaf)
+      end
+    end
+    
+    # Function prototype to provide same interface as {NumericStemplot}.
+    # This function does not do anything.
+    # @return [false]
+    def print_key(_scale, _divider)
+      # intentionally empty
+      return false
+    end
+
+    # Used when we have stems from a back-to-back stemplot and a combined list of stems is given
+    # @param stems [Array] Concatenated list of stems from two plots
+    # @param all [Boolean] Return all stems if true, otherwise only return stems if a leaf exists for a stem
+    # @return [Array] Sorted list of stems
+    def self.sorted_stem_list(stems, all: true)
+      if all
+        rmin, rmax = stems.minmax
+        return (rmin .. rmax).to_a
+      else
+        stems.sort
+      end
+    end
+    
+  end
+  
+  # Print a Single-Vector stemplot to STDOUT.
+  #
+  # - Stem data is printed on the left.
+  # - Leaf data is printed on the right.
+  # - Key is printed at the bottom.
+  # @param plt [Stemplot] Stemplot object
+  # @param scale [Integer] Scale, should be a power of 10
+  # @param divider [String] Divider character between stem and leaf
+  # @param padchar [String] Padding character
+  # @param trim [Boolean] Trim missing stems from the plot
+  def stemplot1!(plt, 
+                 scale: 10,
+                 divider: "|",
+                 padchar: " ",
+                 trim: false,
+                 **_kw
+                )
+
+    stem_labels = plt.stems(all: !trim)
+    label_len = stem_labels.map(&:length).max
+    column_len = label_len + 1
+    
+    stem_labels.each do |stem|
+      leaves = plt.leaves(stem).sort
+      stemlbl = stem.rjust(label_len, padchar).ljust(column_len, padchar)
+      puts stemlbl + divider + padchar + leaves.join
+    end
+    plt.print_key(scale, divider)
+  end
+
+  # Print a Back-to-Back Stemplot to STDOUT
+  #
+  # - +plt1+ Leaf data is printed on the left.
+  # - Common stem data is printed in the center.
+  # - +plt2+ Leaf data is printed on the right.
+  # - Key is printed at the bottom.
+  # @param plt1 [Stemplot] Stemplot object for the left side
+  # @param plt2 [Stemplot] Stemplot object for the right side
+  # @param scale [Integer] Scale, should be a power of 10
+  # @param divider [String] Divider character between stem and leaf
+  # @param padchar [String] Padding character
+  # @param trim [Boolean] Trim missing stems from the plot
+  def stemplot2!(plt1, plt2,
+                 scale: 10,
+                 divider: "|",
+                 padchar: " ",
+                 trim: false,
+                 **_kw
+                )
+    stem_labels = plt1.class.sorted_stem_list( (plt1.raw_stems + plt2.raw_stems).uniq, all: !trim )
+    label_len = stem_labels.map(&:length).max
+    column_len = label_len + 1
+
+    leftleaf_len = plt1.max_stem_length
+
+    stem_labels.each do |stem|
+      left_leaves = plt1.leaves(stem).sort.join('')
+      right_leaves = plt2.leaves(stem).sort.join('')
+      left_leaves_just = left_leaves.reverse.rjust(leftleaf_len, padchar)
+      stem = stem.rjust(column_len, padchar).ljust(column_len+1, padchar)
+      puts left_leaves_just + padchar + divider + stem + divider + padchar + right_leaves
+    end
+
+    plt1.print_key(scale, divider)
+
+  end
+
+  # Generates one or more {Stemplot} objects from the input data
+  # and prints a Single or Double stemplot using {stemplot1!} or {stemplot2!}
+  # @see Stemplot
+  # @example Single sided stemplot
+  #     >> UnicodePlot.stemplot(eighty_ints)
+  #     0 | 257
+  #     1 | 00335679
+  #     2 | 034455899
+  #     3 | 145588
+  #     4 | 0022223
+  #     5 | 0223399
+  #     6 | 012345568889
+  #     7 | 01133334466777888
+  #     8 | 013689
+  #     9 | 22667
+  #     Key: 1|0 = 10
+  #     The decimal is 1 digit(s) to the right of |
+  #
+  # @example Back-to-back stemplot
+  #     >> UnicodePlot.stemplot(eighty_ints, another_eighty_ints)
+  #                   752 | 0 | 1244457899
+  #              97653300 | 1 | 4799
+  #             998554430 | 2 | 015668
+  #                885541 | 3 | 0144557888899
+  #               3222200 | 4 | 00268
+  #               9933220 | 5 | 0234778
+  #          988865543210 | 6 | 122222357889
+  #     88877766443333110 | 7 | 134556689
+  #                986310 | 8 | 24589
+  #                 76622 | 9 | 022234468
+  #     Key: 1|0 = 10
+  #     The decimal is 1 digit(s) to the right of |
+  #
+  def stemplot(*args, scale: 10, **kw)
+    case args.length
+    when 1
+      # Stemplot object
+      plt = Stemplot.factory(args[0], scale: scale, **kw)
+      # Dispatch to plot routine
+      stemplot1!(plt, scale: scale, **kw)
+    when 2
+      # Stemplot object
+      plt1 = Stemplot.factory(args[0], scale: scale)
+      plt2 = Stemplot.factory(args[1], scale: scale)
+      raise ArgumentError, "Plot types must be the same for back-to-back stemplot " +
+            "#{plt1.class} != #{plt2.class}" unless plt1.class == plt2.class
+      # Dispatch to plot routine
+      stemplot2!(plt1, plt2, scale: scale, **kw)
+    else
+      raise ArgumentError, "Expecting one or two arguments"
+    end
+  end
+
+  module_function :stemplot, :stemplot1!, :stemplot2!
+end
diff --git a/lib/unicode_plot/styled_printer.rb b/lib/unicode_plot/styled_printer.rb
index b3ad81c..1a6c660 100644
--- a/lib/unicode_plot/styled_printer.rb
+++ b/lib/unicode_plot/styled_printer.rb
@@ -58,7 +58,7 @@ module UnicodePlot
     COLOR_DECODE = COLOR_ENCODE.map {|k, v| [v, k] }.to_h.freeze
 
     def print_styled(out, *args, bold: false, color: :normal)
-      return out.print(*args) unless color?(out)
+      return out.print(*args) unless out.color?
 
       str = StringIO.open {|sio| sio.print(*args); sio.close; sio.string }
       color = :nothing if bold && color == :bold
@@ -83,9 +83,5 @@ module UnicodePlot
       color = COLOR_DECODE[color]
       print_styled(out, *args, color: color)
     end
-
-    def color?(out)
-      out&.tty? || false
-    end
   end
 end
diff --git a/lib/unicode_plot/version.rb b/lib/unicode_plot/version.rb
index 4a461e5..7f2e07e 100644
--- a/lib/unicode_plot/version.rb
+++ b/lib/unicode_plot/version.rb
@@ -1,5 +1,5 @@
 module UnicodePlot
-  VERSION = "0.0.4"
+  VERSION = "0.0.5"
 
   module Version
     numbers, TAG = VERSION.split("-", 2)
diff --git a/test/helper/with_term.rb b/test/helper/with_term.rb
index 6620037..4aae79d 100644
--- a/test/helper/with_term.rb
+++ b/test/helper/with_term.rb
@@ -2,22 +2,28 @@ require 'stringio'
 
 module Helper
   module WithTerm
-    def with_term(tty=true)
+    def with_sio(tty: true)
       sio = StringIO.new
       def sio.tty?; true; end if tty
 
-      orig_stdout, $stdout = $stdout, sio
-      orig_env = ENV.to_h.dup
-      ENV['TERM'] = 'xterm-256color'
-
-      result = yield
+      result = yield(sio)
       sio.close
 
       [result, sio.string]
-    ensure
-      $stdout.close
-      $stdout = orig_stdout
-      ENV.replace(orig_env) if orig_env
+    end
+
+    def with_term(tty=true)
+      with_sio(tty: tty) do |sio|
+        begin
+          orig_stdout, $stdout = $stdout, sio
+          orig_env = ENV.to_h.dup
+          ENV['TERM'] = 'xterm-256color'
+          yield
+        ensure
+          $stdout = orig_stdout
+          ENV.replace(orig_env) if orig_env
+        end
+      end
     end
   end
 end
diff --git a/test/test-barplot.rb b/test/test-barplot.rb
index dddbff9..c3bba4a 100644
--- a/test/test-barplot.rb
+++ b/test/test-barplot.rb
@@ -12,6 +12,14 @@ class BarplotTest < Test::Unit::TestCase
       end
     end
 
+    sub_test_case("with invalid arguments") do
+      test("unknown border type") do
+        assert_raise(ArgumentError.new("unknown border type: invalid_border_name")) do
+          UnicodePlot.barplot(data: {bar: 23, foo: 37}, border: :invalid_border_name)
+        end
+      end
+    end
+
     test("colored") do
       data = { bar: 23, foo: 37 }
       plot = UnicodePlot.barplot(data: data)
diff --git a/test/test-boxplot.rb b/test/test-boxplot.rb
index 89874fc..db6bdf7 100644
--- a/test/test-boxplot.rb
+++ b/test/test-boxplot.rb
@@ -3,6 +3,14 @@ class BoxplotTest < Test::Unit::TestCase
   include Helper::WithTerm
 
   sub_test_case("UnicodePlot.boxplot") do
+    sub_test_case("with invalid arguments") do
+      test("unknown border type") do
+        assert_raise(ArgumentError.new("unknown border type: invalid_border_name")) do
+          UnicodePlot.boxplot([1, 2, 3, 4, 5], border: :invalid_border_name)
+        end
+      end
+    end
+
     sub_test_case("print to tty") do
       test("without name") do
         plot = UnicodePlot.boxplot([1, 2, 3, 4, 5])
diff --git a/test/test-canvas.rb b/test/test-canvas.rb
index 59cd75e..971759b 100644
--- a/test/test-canvas.rb
+++ b/test/test-canvas.rb
@@ -6,7 +6,8 @@ module CanvasTestCases
     ascii: UnicodePlot::AsciiCanvas,
     braille: UnicodePlot::BrailleCanvas,
     density: UnicodePlot::DensityCanvas,
-    dot: UnicodePlot::DotCanvas
+    dot: UnicodePlot::DotCanvas,
+    block: UnicodePlot::BlockCanvas
   }.freeze
 
   def self.included(mod)
@@ -54,11 +55,11 @@ module CanvasTestCases
 
       test("empty") do
         if self.class::CANVAS_NAME == :braille
-          _, output = with_term { @canvas.show($stdout) }
+          _, output = with_term { @canvas.show(UnicodePlot::IOContext.new($stdout)) }
           assert_equal(fixture_path("canvas/empty_braille_show.txt").read,
                        output)
         else
-          _, output = with_term { @canvas.show($stdout) }
+          _, output = with_term { @canvas.show(UnicodePlot::IOContext.new($stdout)) }
           assert_equal(fixture_path("canvas/empty_show.txt").read,
                        output)
         end
@@ -80,31 +81,31 @@ module CanvasTestCases
         end
 
         test("print_row") do
-          _, output = with_term { @canvas.print_row($stdout, 2) }
+          _, output = with_term { @canvas.print_row(UnicodePlot::IOContext.new($stdout), 2) }
           assert_equal(fixture_path("canvas/#{self.class::CANVAS_NAME}_printrow.txt").read,
                        output)
         end
 
         test("print") do
-          _, output = with_term { @canvas.print($stdout) }
+          _, output = with_term { @canvas.print(UnicodePlot::IOContext.new($stdout)) }
           assert_equal(fixture_path("canvas/#{self.class::CANVAS_NAME}_print.txt").read,
                        output)
         end
 
         test("print_nocolor") do
-          _, output = with_term(false) { @canvas.print($stdout) }
+          _, output = with_term(false) { @canvas.print(UnicodePlot::IOContext.new($stdout)) }
           assert_equal(fixture_path("canvas/#{self.class::CANVAS_NAME}_print_nocolor.txt").read,
                        output)
         end
 
         test("sow") do
-          _, output = with_term { @canvas.show($stdout) }
+          _, output = with_term { @canvas.show(UnicodePlot::IOContext.new($stdout)) }
           assert_equal(fixture_path("canvas/#{self.class::CANVAS_NAME}_show.txt").read,
                        output)
         end
 
         test("show_nocolor") do
-          _, output = with_term(false) { @canvas.show($stdout) }
+          _, output = with_term(false) { @canvas.show(UnicodePlot::IOContext.new($stdout)) }
           assert_equal(fixture_path("canvas/#{self.class::CANVAS_NAME}_show_nocolor.txt").read,
                        output)
         end
@@ -136,3 +137,9 @@ class DotCanvasTest < Test::Unit::TestCase
 
   include CanvasTestCases
 end
+
+class BlockCanvasTest < Test::Unit::TestCase
+  CANVAS_NAME = :block
+
+  include CanvasTestCases
+end
diff --git a/test/test-densityplot.rb b/test/test-densityplot.rb
index fa3f92e..9a85873 100644
--- a/test/test-densityplot.rb
+++ b/test/test-densityplot.rb
@@ -10,6 +10,14 @@ class DensityplotTest < Test::Unit::TestCase
     assert_equal(1000, @dy.length)
   end
 
+  sub_test_case("with invalid arguments") do
+    test("unknown border type") do
+      assert_raise(ArgumentError.new("unknown border type: invalid_border_name")) do
+        UnicodePlot.densityplot(@dx, @dy, border: :invalid_border_name)
+      end
+    end
+  end
+
   test("default") do
     plot = UnicodePlot.densityplot(@dx, @dy)
     dx2 = @dx.map {|x| x + 2 }
diff --git a/test/test-histogram.rb b/test/test-histogram.rb
index 284e315..31c9178 100644
--- a/test/test-histogram.rb
+++ b/test/test-histogram.rb
@@ -7,6 +7,14 @@ class HistogramTest < Test::Unit::TestCase
       @x = fixture_path("randn.txt").read.lines.map(&:to_f)
     end
 
+    sub_test_case("with invalid arguments") do
+      test("unknown border type") do
+        assert_raise(ArgumentError.new("unknown border type: invalid_border_name")) do
+          UnicodePlot.histogram(@x, border: :invalid_border_name)
+        end
+      end
+    end
+
     test("default") do
       plot = UnicodePlot.histogram(@x)
       _, output = with_term { plot.render($stdout) }
diff --git a/test/test-lineplot.rb b/test/test-lineplot.rb
index a6b43f0..cb13823 100644
--- a/test/test-lineplot.rb
+++ b/test/test-lineplot.rb
@@ -23,6 +23,14 @@ class LineplotTest < Test::Unit::TestCase
       assert_raise(ArgumentError) { UnicodePlot.lineplot(1..3, 1..2) }
     end
 
+    sub_test_case("with invalid arguments") do
+      test("unknown border type") do
+        assert_raise(ArgumentError.new("unknown border type: invalid_border_name")) do
+          UnicodePlot.lineplot(@x, @y, border: :invalid_border_name)
+        end
+      end
+    end
+
     sub_test_case("with numeric array") do
       test("default") do
         plot = UnicodePlot.lineplot(@x, @y)
@@ -192,8 +200,84 @@ class LineplotTest < Test::Unit::TestCase
                    output)
     end
 
+    test("fixed line y-interpolation bug (issue 32)") do
+      ys = [261, 272, 277, 283, 289, 294, 298, 305, 309, 314, 319, 320, 322, 323, 324]
+      xs = ys.size.times.to_a
+      plot = UnicodePlot.lineplot(xs, ys, height: 26, ylim: [0, 700])
+      _, output = with_term { plot.render($stdout, newline: false) }
+      assert_equal(fixture_path("lineplot/issue32_fix.txt").read,
+                   output)
+    end
+
     # TODO: functions
 
-    # TODO: stairs
+    sub_test_case("stairs") do
+      def setup
+        @sx = [1, 2, 4, 7, 8]
+        @sy = [1, 3, 4, 2, 7]
+      end
+
+      test("pre") do
+        plot = UnicodePlot.stairs(@sx, @sy, style: :pre)
+        _, output = with_term { plot.render($stdout, newline: false) }
+        assert_equal(fixture_path("lineplot/stairs_pre.txt").read, output)
+      end
+
+      test("post") do
+        # inferred post
+        plot = UnicodePlot.stairs(@sx, @sy)
+        _, output = with_term { plot.render($stdout, newline: false) }
+        assert_equal(fixture_path("lineplot/stairs_post.txt").read, output)
+        # explicit post
+        plot = UnicodePlot.stairs(@sx, @sy, style: :post)
+        _, output = with_term { plot.render($stdout, newline: false) }
+        assert_equal(fixture_path("lineplot/stairs_post.txt").read, output)
+      end
+
+      test("with parameters") do
+        plot = UnicodePlot.stairs(@sx, @sy, title: "Foo", color: :red, xlabel: "x", name: "1")
+        sx2 = @sx.map { |val| val - 0.2 }
+        sy2 = @sy.map { |val| val + 1.5 }
+        plot2 = UnicodePlot.stairs!(plot, sx2, sy2, name: "2")
+        assert_equal(plot.class, plot2.class)
+        _, output = with_term { plot.render($stdout, newline: false) }
+        assert_equal(fixture_path("lineplot/stairs_parameters.txt").read, output)
+
+        # add a 3rd staircase and check again
+        plot3 = UnicodePlot.stairs!(plot, @sx, @sy, name: "3", style: :pre)
+        assert_equal(plot.class, plot3.class)
+        _, output = with_term { plot.render($stdout, newline: false) }
+        assert_equal(fixture_path("lineplot/stairs_parameters2.txt").read, output)
+
+        # check with color disabled
+        output = StringIO.open do |sio|
+          plot.render(sio)
+          sio.close
+          sio.string
+        end
+        assert_equal("\n", output[-1])
+        assert_equal(fixture_path("lineplot/stairs_parameters2_nocolor.txt").read,
+                     output.chomp)
+      end
+
+      test("special weird case") do
+        plot = UnicodePlot.stairs(@sx, [1, 3, 4, 2, 7000])
+        _, output = with_term { plot.render($stdout, newline: false) }
+        assert_equal(fixture_path("lineplot/stairs_edgecase.txt").read, output)
+      end
+
+      test("annotations") do
+        plot = UnicodePlot.stairs(@sx, @sy, width: 10, padding: 3)
+        plot.annotate!(:tl, "Hello")
+        plot.annotate!(:t, "how are")
+        plot.annotate!(:tr, "you?")
+        plot.annotate!(:bl, "Hello")
+        plot.annotate!(:b, "how are")
+        plot.annotate!(:br, "you?")
+        UnicodePlot.lineplot!(plot, 1, 0.5)
+        _, output = with_term { plot.render($stdout, newline: false) }
+        assert_equal(fixture_path("lineplot/squeeze_annotations.txt").read, output)
+      end
+    end
   end
 end
diff --git a/test/test-plot.rb b/test/test-plot.rb
index 6888cbe..6702df4 100644
--- a/test/test-plot.rb
+++ b/test/test-plot.rb
@@ -1,6 +1,8 @@
 require 'stringio'
 
 class TestPlot < Test::Unit::TestCase
+  include Helper::WithTerm
+
   sub_test_case("#render") do
     test("render to $stdout when no arguments") do
       sio = StringIO.new
@@ -16,5 +18,19 @@ class TestPlot < Test::Unit::TestCase
         $stdout = save_stdout
       end
     end
+
+    test("color: true") do
+      _, tty_output   = with_sio(tty: true)  {|sio| UnicodePlot.barplot(data: {a: 23, b: 37}).render(sio) }
+      _, notty_output = with_sio(tty: false) {|sio| UnicodePlot.barplot(data: {a: 23, b: 37}).render(sio, color: true) }
+
+      assert_equal(tty_output, notty_output)
+    end
+
+    test("color: false") do
+      _, tty_output   = with_sio(tty: true)  {|sio| UnicodePlot.barplot(data: {a: 23, b: 37}).render(sio, color: false) }
+      _, notty_output = with_sio(tty: false) {|sio| UnicodePlot.barplot(data: {a: 23, b: 37}).render(sio) }
+
+      assert_equal(tty_output, notty_output)
+    end
   end
 end
diff --git a/test/test-result.rb b/test/test-result.rb
deleted file mode 100644
index e69de29..0000000
diff --git a/test/test-scatterplot.rb b/test/test-scatterplot.rb
index 9634239..1334c7e 100644
--- a/test/test-scatterplot.rb
+++ b/test/test-scatterplot.rb
@@ -22,6 +22,14 @@ class ScatterplotTest < Test::Unit::TestCase
     end
   end
 
+  sub_test_case("with invalid arguments") do
+    test("unknown border type") do
+      assert_raise(ArgumentError.new("unknown border type: invalid_border_name")) do
+        UnicodePlot.scatterplot(@x, @y, border: :invalid_border_name)
+      end
+    end
+  end
+
   test("default") do
     plot = UnicodePlot.scatterplot(@x, @y)
     _, output = with_term { plot.render($stdout, newline: false) }
diff --git a/test/test-stemplot.rb b/test/test-stemplot.rb
new file mode 100644
index 0000000..6dc518f
--- /dev/null
+++ b/test/test-stemplot.rb
@@ -0,0 +1,95 @@
+# coding: utf-8
+class HistogramTest < Test::Unit::TestCase
+  include Helper::Fixture
+  include Helper::WithTerm
+
+  sub_test_case("UnicodePlot.stemplot") do
+    def setup
+      @randoms = fixture_path("randn.txt").read.lines.take(50).map(&:to_f)
+      
+      @int80a = [40, 53, 33, 8, 30, 78, 68, 63, 80, 75, 73, 75, 61, 24, 84, 84, 51, 31, 94, 63, 72, 9, 80, 1, 84, 1, 13, 55, 46, 41, 99, 100, 39, 41, 10, 70, 67, 21, 50, 41, 49, 24, 32, 42, 32, 37, 44, 10, 48, 64, 41, 46, 94, 15, 15, 5, 6, 97, 48, 14, 2, 92, 10, 2, 91, 89, 20, 98, 19, 66, 43, 95, 90, 34, 71, 42, 31, 92, 95, 30]
+      
+      @int80b = [23, 24, 70, 61, 26, 57, 28, 18, 69, 53, 92, 11, 33, 38, 85, 58, 38, 27, 14, 62, 57, 38, 91, 11, 66, 23, 63, 28, 98, 9, 53, 26, 1, 93, 96, 49, 8, 89, 19, 18, 68, 51, 4, 57, 79, 90, 72, 99, 41, 57, 100, 94, 5, 13, 24, 76, 5, 60, 26, 41, 89, 99, 22, 81, 41, 48, 65, 67, 38, 53, 96, 85, 75, 89, 35, 75, 88, 50, 14, 33]
+      
+      @int300 = [-30, -7, 37, -42, -15, 8, 62, 22, -3, -32, -35, -48, -29, 8, -75, 27, 84, 81, -21, 9, 23, 86, -30, 29, 47, 89, -3, 38, 22, 31, 49, 84, -5, -28, -26, -66, 68, 59, -92, -3, -23, 100, -73, 19, -37, 89, -21, 23, 71, 14, -98, 49, -3, -1, -8, 67, -55, -30, -55, 93, 69, -20, -79, -91, -23, -46, 84, -49, -12, 35, -74, -2, -84, -34, -28, 98, -70, -72, 71, 86, 24, 64, -99, -76, -37, 54, 53, 72, -31, -53, 85, 10, -17, -8, -35, -16, -3, 26, 68, -92, 20, -70, 87, -78, 95, -88, -85, -7, 67, -47, -46, 0, -24, -48, 53, -53, -26, 79, -51, -40, -95, -37, 44, 77, -66, 85, 14, 5, -25, -85, -54, -17, -94, -52, -75, -79, 79, -53, -16, -27, -43, -94, -66, -56, -47, -56, -21, 7, 73, -19, 87, -64, -46, -81, 19, -13, -44, -87, -22, 18, 54, -9, 84, 50, 85, 62, 8, 99, -94, -65, 59, 93, 84, 16, 82, -16, 77, 55, -63, -13, -34, -77, 55, 87, -99, -82, 9, 73, 75, -94, -6, 9, 89, 27, 70, 56, -38, -67, -46, 59, 91, -26, 30, -81, -6, -29, -66, 25, 17, -25, -9, -90, 20, -71, 1, -47, -76, 39, -29, -19, -45, 91, -92, -6, -59, 34, 51, -61, -41, -90, 77, 83, -83, -25, -29, -64, 16, -91, -14, 7, -71, -57, -71, 76, -9, -43, -89, 86, 56, 56, 27, 3, -5, 13, -99, 13, -97, -68, 94, -15, 6, -8, -28, -52, 38, -96, -54, 13, -36, 78, -24, 32, 96, -57, -56, -27, -79, -73, -18, 44, -48, -65, -4, 80, -69, -26, -38, 66, 62, 65, -83, -100, -37, 1, -99, 75, 33, 19, 0, -70]
+      
+      @words_1 = %w[apple junk ant age bee bar baz dog egg a]
+      @words_2 = %w[ape flan can cat juice elf gnome child fruit]
+      
+    end
+
+    test("range input") do
+      _, output = with_term { UnicodePlot.stemplot(-100..100) }
+      assert_equal(fixture_path("stemplot/range.txt").read, output)
+    end
+
+    test("positive integers") do
+      _, output = with_term { UnicodePlot.stemplot(@int80a) }
+      assert_equal(fixture_path("stemplot/pos_ints.txt").read, output)
+    end
+    
+    test("with happy divider") do
+      _, output = with_term { UnicodePlot.stemplot(@int80a, divider:  "😄") }
+      assert_equal(fixture_path("stemplot/ints_divider.txt").read, output)
+    end
+
+    test("positive and negative integers") do
+      _, output = with_term { UnicodePlot.stemplot(@int300) }
+      assert_equal(fixture_path("stemplot/posneg_ints.txt").read, output)
+    end
+
+    test("floats") do
+      x10 = @randoms.map {|a| a * 10 }
+      #p x10.sort
+      #UnicodePlot.stemplot(x10)
+      _, output = with_term { UnicodePlot.stemplot(x10) }
+      assert_equal(fixture_path("stemplot/float.txt").read, output)
+    end
+
+    
+    test("floats, scale=1") do
+      floats = (-8..8).to_a.map { |a| a / 2.0 }
+      _, output = with_term { UnicodePlot.stemplot(floats, scale: 1) }
+      assert_equal(fixture_path("stemplot/float_scale1.txt").read, output)
+    end
+
+
+    test("back-to-back stemplot with integers") do
+      _, output = with_term { UnicodePlot.stemplot(@int80a, @int80b) }
+      assert_equal(fixture_path("stemplot/b2b_integers.txt").read, output)
+    end
+
+    test("stemplot with strings") do
+      _, output = with_term { UnicodePlot.stemplot(@words_1) }
+      assert_equal(fixture_path("stemplot/strings.txt").read, output)
+    end
+
+    test("back-to-back stemplot with strings") do
+      _, output = with_term { UnicodePlot.stemplot(@words_1, @words_2) }
+      assert_equal(fixture_path("stemplot/b2b_strings.txt").read, output)
+    end
+
+    test("stemplot with strings, two-char scale") do
+      _, output = with_term {  UnicodePlot.stemplot(@words_1, scale: 100, trim: true) }
+      assert_equal(fixture_path("stemplot/strings_2c.txt").read, output)
+    end
+
+    test("stemplot with strings, two-char scale, string_padchar") do
+      _, output = with_term {  UnicodePlot.stemplot(@words_1, string_padchar: '?') }
+      assert_equal(fixture_path("stemplot/strings_pad.txt").read, output)
+    end
+
+    test("string stemplot cannot take scale less than 10") do
+      assert_raise(ArgumentError) do
+        UnicodePlot.stemplot(@words_1, scale: 9)
+      end
+    end
+
+    test("cannot mix string/number in back to back stemplot") do
+      assert_raise(ArgumentError) do
+        UnicodePlot.stemplot(@words_1, @int80a)
+      end
+    end
+
+  end
+end
diff --git a/test/test-utils.rb b/test/test-utils.rb
new file mode 100644
index 0000000..796f9cb
--- /dev/null
+++ b/test/test-utils.rb
@@ -0,0 +1,13 @@
+class UnicodePlotTest < Test::Unit::TestCase
+  test("UnicodePlot.canvas_types") do
+    available_canvas_types = [:ascii, :block, :braille, :density, :dot]
+    assert_equal(available_canvas_types.sort,
+                 UnicodePlot.canvas_types.sort)
+  end
+
+  test("UnicodePlot.border_types") do
+    available_border_types = [:solid, :corners, :barplot]
+    assert_equal(available_border_types.sort,
+                 UnicodePlot.border_types.sort)
+  end
+end
diff --git a/unicode_plot.gemspec b/unicode_plot.gemspec
index a8dfd78..bb4bf62 100644
--- a/unicode_plot.gemspec
+++ b/unicode_plot.gemspec
@@ -19,6 +19,9 @@ Gem::Specification.new do |spec|
   spec.homepage = "https://github.com/red-data-tools/unicode_plot.rb"
   spec.license = "MIT"
 
+  spec.metadata ||= {}
+  spec.metadata["documentation_uri"] = "https://red-data-tools.github.io/unicode_plot.rb/#{spec.version}/"
+
   spec.files = ["README.md", "Rakefile", "Gemfile", "#{spec.name}.gemspec"]
   spec.files << "LICENSE.txt"
   spec.files.concat Dir.glob("lib/**/*.rb")
@@ -34,4 +37,5 @@ Gem::Specification.new do |spec|
   spec.add_development_dependency "bundler", ">= 1.17"
   spec.add_development_dependency "rake"
   spec.add_development_dependency "test-unit"
+  spec.add_development_dependency "yard"
 end