Giter Site home page Giter Site logo

ishi's Introduction

石の上にも三年

GitHub Release Build Status Documentation

Graph plotting package with a small API and sensible defaults powered by gnuplot.

Requires gnuplot.

Installation

  1. Add the dependency to your shard.yml:
dependencies:
  ishi:
    github: toddsundsted/ishi
  1. Run shards install

Usage

To display a line chart of the data points in xdata (the x values) and ydata (the corresponding y values):

require "ishi"

ishi = Ishi.new
ishi.plot(xdata, ydata)
ishi.show

Or, if you prefer command-style syntax:

require "ishi"

Ishi.new do
  plot(xdata, ydata)
end

A chart can display multiple plots. The following code displays two plots in one chart: one derived from discrete data points and the other from the equation of a line.

require "ishi"

Ishi.new do
  plot([1, 2, 3, 4, 5], [1.0, 1.4, 1.9, 2.4, 2.6], "ko", title: "data")
  plot("0.4 * x + 0.7", "b--")
end

two plots

The etc/examples directory contains examples of usage.

plot

plot plots points and lines. It takes data in several formats:

  • plot(ydata) - y values in ydata with x values ranging from 0 to ydata.size - 1
  • plot(xdata, ydata) - x values in xdata and corresponding y values in ydata
  • plot(xdata, ydata, zdata) - x values in xdata, y values in ydata, and z values in zdata
  • plot(expression) - any gnuplot-supported mathematical expression

xdata, ydata and zdata may be any type that implements Indexable(Number). Chart dimensionality (2D or 3D) is inferred from the data.

All plot methods/commands accept the optional named arguments title, style, dashtype (dt), linecolor (lc), linewidth (lw), pointsize (ps), pointtype (pt), linestyle (ls), fillstyle (fs) and format.

title specifies the title of the plot in the chart key.

style explicitly specifies the style of the plot (:lines, :points, etc.). Ishi will try to infer the style from the data and the other named arguments (for example, if both linewidth and pointsize are provided, the style will be :linespoints).

By default, plots are rendered with solid lines. dashtype (dt) specifies the pattern of dashes to use instead. dashtype may be an array of pairs of numbers that specify the length of a solid line followed by the length of an empty space (e.g. [2, 3, 5, 7]), or it may be a string composed of . (dot), - (hyphen), _ (underscore) and (space), which is then converted into an array as follows: each "." becomes [2, 5], "-" becomes [10, 10], "_" becomes [20, 10] and " " adds 10 to the previous empty value.

linecolor specifies the color to use for lines and points. Colors may be specified by name (e.g. "red", "blue", or "green") or by hexadecimal color value (e.g. "#AARRGGBB" or "#RRGGBB").

linewidth and pointsize scale the width of lines and the size of points, respectively. A value of 2.0 is twice as big as the default width or size.

The following code demonstrates the use of dashtype, linecolor and linewidth:

require "ishi"

Ishi.new do
  plot("x + 4.0", dashtype: "-", linewidth: 2)
  plot("x + 3.0", dashtype: "_", linewidth: 2)
  plot("x + 2.0", dashtype: ".", linewidth: 2)
  plot("x + 1.0", dashtype: "..._", linewidth: 2)
  plot("x + 0.0", dashtype: [30, 10, 50, 10], linewidth: 2, linecolor: "#88001100")
end

lines

pointtype selects the type of point to render. Available types depend on the gnuplot terminal device used, but commonly supported values are . (dot), + (plus sign), x (multiplication sign), * (asterisk), s (square), o (circle), ^ (up triangle), and v (down triangle) and d (diamond).

The following code demonstrates the use of pointtype and pointsize:

require "ishi"

Ishi.new do
  plot([1, 2, 3, 4], [5, 6, 7, 8], pointtype: 1, pointsize: 2)
  plot([1, 2, 3, 4], [4, 5, 6, 7], pointtype: "o", pointsize: 2)
  plot([1, 2, 3, 4], [3, 4, 5, 6], pointtype: "s", pointsize: 2)
  plot([1, 2, 3, 4], [2, 3, 4, 5], pointtype: "^", pointsize: 2)
  plot([1, 2, 3, 4], [1, 2, 3, 4], pointtype: "v", pointsize: 2, linecolor: "#88001100")
end

points

The format argument is a short string used to specify color, point and line, simultaneously.

Color is a letter from the set b (blue), g (green), r (red), c (cyan), m (magenta), y (yellow), k (black) or w (white). Point is a letter from the set . (dot), + (plus sign), x (multiplication sign), * (asterisk), s (square), c (circle), ^ (up triangle), v (down triangle) or d (diamond). Line may be - (solid line), -- (dashed line), ! (dash-dot line) and : (dotted line).

Given these rules, the format string "b" is a solid blue line, "or" is red circles, "^k:" is black triangles connected by dotted black lines, "g-" is a solid green line and "--" is a dashed line.

format may also be a single word color name or hexadecimal color value, in which case point and line may not be specified.

Returning to the first example, the code uses format to customize the plots:

require "ishi"

Ishi.new do
  plot([1, 2, 3, 4, 5], [1.0, 1.4, 1.9, 2.4, 2.6], "ko", title: "data")
  plot("0.4 * x + 0.7", "b--")
end

The format "ko" could also be expressed explicitly (and much more verbosely) with the named arguments linecolor: "black", pointtype: 7, and the format "b--" with linecolor: "blue", dashtype: 2.

scatter

scatter draws scatter plots. It takes data in several formats:

  • scatter(xdata, ydata) - x values in xdata and corresponding y values in ydata
  • scatter(xdata, ydata, zdata) - x values in xdata, y values in ydata, and z values in zdata

Chart dimensionality (2D or 3D) is inferred from the data. By default, scatter places a . (dot) at each point.

All scatter methods/commands accept the optional named arguments title, style, dashtype (dt), linecolor (lc), linewidth (lw), pointsize (ps), pointtype (pt), linestyle (ls) and format.

The following code demonstrates the use of scatter:

require "ishi"

Ishi.new do
  scatter(xdata, zdata, lc: "#4b03a1")
  scatter(ydata, zdata, lc: "#b5367a")
end

scatter

imshow

imshow displays data as a pseudocolor, heatmapped image:

  • imshow(data) - data is two-dimensional scalar data, which will be rendered as an image

The following code demonstrates the use of imshow:

require "ishi"

Ishi.new do
  palette(:inferno)
  imshow(data)
  margin(0, 0, 0, 0)
  show_colorbox(false)
  show_border(false)
  show_xtics(false)
  show_ytics(false)
  show_key(false)
end

imshow

charts

charts changes the number of charts in the figure:

  • charts(rows, cols) - rows and cols are the number of rows and columns in the figure

By default a figure has one chart. This call changes the number of charts in the figure. The original chart is preserved and becomes the chart in the first row, first column of the new layout.

When called without a block, charts returns the charts in the figure.

require "ishi"

figure = Ishi.new
charts = figure.charts(2, 2)
charts[0].plot([1, 2, 3, 4])
charts[1].plot([1, 1, 3, 2])
charts[2].plot([1, 1, 1, 1])
charts[3].plot([4, 3, 2, 1])
figure.show

When called with a block, charts Yields each chart as the default receiver of the supplied block. Block arguments are i (the i-th chart in the figure), row and col (the row and column of the chart).

require "ishi"

figure = Ishi.new
figure.charts(2, 2) do |i, row, col|
  plot([1, 2, 3, 4].rotate(i), title: "#{row},#{col}")
end
figure.show

Non-numeric labels on the x-axis

xtics sets non-numeric labels for positions on the x-axis.

The following code demonstrates the use of xtics:

require "ishi"

Ishi.new do
  x = [1, 2, 3]
  y = [65, 30, 5]
  plot(x, y, title: "Visits", style: :boxes, fs: 0.25)
    .boxwidth(0.5)
    .ylabel("Visits (%)")
    .xlabel("Device")
    .xtics({
      1.0 => "mobile",
      2.0 => "desktop",
      3.0 => "tablet"
    })
end

xtics

MXNet::NDArray

MXNet::NDArray is an indexable, multi-dimensional array that efficiently supports numerical operations (transposition, matrix multiplication, etc.).

All appropriate methods/commands accept instances of MXNet::NDArray with the correct shape (1-dimensional vectors for x-values, for example).

require "ishi"
require "mxnet"

m = MXNet::NDArray.array(
  [[0, 0, 1, 1],
   [1, 2, 3, 4],
   [1, 1, 2, 2]]
)

figure = Ishi.new
charts = figure.charts(1, 2)
charts[0].plot(m[0], m[1], m[2])
charts[1].plot(m[.., 0], m[.., 1], m[.., 2])
figure.show

See MXNet.cr for more information on the complete library.

Extensions

By default, Ishi pops open a window to display charts. However, Ishi comes with extensions that render charts as text, HTML, PNG or inline images in the terminal; or that write to other IO destinations. Note: terminal image display only works with ITerm2.

To plot the sin(x) function as text:

require "ishi/text" # or "ishi/html" or "ishi/iterm2"

Ishi.new do
  plot("sin(x)")
end

This produces:

    1 +--------------------------------------------------------------------+
      |                *  *              +  *  **         +       *  *     |
  0.8 |-+             *   *                 *    *          sin(x* *******-|
      |              *     *                *    *               *    *    |
  0.6 |-+            *      *              *     *               *     * +-|
      |              *      *             *       *             *       *  |
  0.4 |*+            *      *             *       *             *       *+-|
      |*            *        *            *        *           *        *  |
  0.2 |*+           *        *            *        *           *        *+-|
      | *          *          *          *         *          *          * |
    0 |-*          *          *          *         *          *          *-|
      |  *         *          *         *           *         *           *|
 -0.2 |-+*         *          *         *           *         *          +*|
      |  *        *            *       *             *       *            *|
 -0.4 |-+*        *            *       *             *       *           +*|
      |   *      *              *      *             *      *              |
 -0.6 |-+ *     *               *     *              *      *            +-|
      |    *    *               *     *               *     *              |
 -0.8 |-+   *   *                *   *                 *   *             +-|
      |     *  *       +         **  *   +             *  *                |
   -1 +--------------------------------------------------------------------+
     -10              -5                 0                5                10

You can pass in an IO instance. Chart data will be written to the IO instance instead of STDOUT.

require "ishi/text" # or "ishi/html" or "ishi/png"

io = IO::Memory.new

Ishi.new(io) do
  plot("sin(x)")
end

Contributors

ishi's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

lbarasti shinzlet

ishi's Issues

corrupted output when fibers are used

When using fibers with PNG, we get a corrupted output

The following example writes 3 images sequentially and using fibers for the same data:

require "ishi/png"

def draw_graph(x, y, image_name)
  io = IO::Memory.new
  png = Ishi::Png.new(io)
  png.plot(x, y, "ko", title: "data")
  png.show
  File.write(image_name, io.to_slice)
end

xs = Array(Array(Int32)).new
xs << [1, 2, 3, 4, 5]
xs << [10, 20, 30, 40, 50]
xs << [5, 8, 9, 12, 16]

ys = Array(Array(Int32)).new
# ys << [1.0, 1.4, 1.9, 2.4, 2.6]
ys << [1, 1, 1, 2, 2]
ys << [4, 8, 10, 12, 18]
ys << [10, 10, 25, 30, 32]

def test_seq(xs, ys)
  index = 0
  while index < xs.size
    draw_graph(x: xs[index], y: ys[index], image_name: "out_seq_#{index}.png")
    index += 1
  end
end

def test_fibers(xs, ys, chan)
  index = 0
  while index < xs.size
    proc = ->(index : Int32) do
      puts index
      spawn do
        begin
          draw_graph(x: xs[index], y: ys[index], image_name: "out_fib_#{index}.png")
          chan.send(true)
        rescue exception
          puts "err #{exception}"
          chan.send(false)
          raise exception
        end
      end
    end
    proc.call(index)
    index += 1
  end
end

def wait_fibers(xs, chan)
  index = 0
  while index < xs.size
    res = chan.receive
    puts res
    index += 1
  end
end

# test seq
test_seq(xs, ys)

# test fibers
chan = Channel(Bool).new
test_fibers(xs, ys, chan)
wait_fibers(xs, chan)

@waj did some investigation, I'm quoting his findings:

The IO is stored in a class variable, thus overriding the value on each instantiation:

ishi/src/ishi/term.cr

Lines 8 to 14 in 167fd33

@@io : IO = STDOUT
def self.io
@@io
end
def initialize(@term : String, @@io : IO = STDOUT)

The global IO is then used while rendering:

IO.copy(previous_def(chart), Png.io)

Example not using STDOUT?

require "ishi/png"
require "kemal"

Kemal.config.port = 3001

get "/stats.png" do |env|
  io = IO::Memory.new
  # png = Ishi::Png.new io
  c = Ishi.new() do
    canvas_size(480, 360)

    x = [1, 2, 3]
    y = [65, 30, 5]
    plot(x, y, title: "Visits", style: :boxes, fs: 0.25)
      .boxwidth(0.5)
      .ylabel("Visits (%)")
      .xlabel("Device")
      .xtics({1.0 => "mobile", 2.0 => "desktop", 3.0 => "tablet"})
  end
  # c.io = io
  c.show
  # c
  # io
end

Kemal.run

I have this code, but there doesn't seem to be a way to tell it to use an IO that's not STDOUT.

Picture of presumably valid png binary dumped to STDOUT:
Screenshot from 2021-01-14 22-14-00

Setting output IO for Ishi::Png has no effect

Scenario: I wish to use Ishi to output a chart into a PNG file.

Sample code:

require "ishi/png"

File.open("test.png", "w") do |file|
  ishi = Ishi::Png.new(file)
  ishi.plot([1, 2, 3, 4, 5], [1.0, 1.4, 1.9, 2.4, 2.6], "ko", title: "data")
  ishi.show
end

Expected result: A file called test.png is created, containing the chart.

Actual result: The PNG bitstream is written to the standard output. The file test.png is left empty.

Underlying cause of the error: The constructor in Ishi::Png calls the parent constructor, which sets @@io. However, due to the way class variables work in Crystal, it only sets the class variable @@io of class Png instead of @@io in Term, where class Gnuplot reads it from. Thus, whatever parameter is passed to Png.new has no effect on Ishi's behavior.

Extra point plotted

There is always an "extra" point getting plotted at approximately (1, 0.8). This occurs when the title is present (haven't figured out how to disable that without getting the '-' in its place), and can be seen in the examples on the main page.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.