Compare commits

..

No commits in common. "main" and "v0.3.5" have entirely different histories.
main ... v0.3.5

24 changed files with 669 additions and 982 deletions

1
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
github: kojix2
ko_fi: kojix2 ko_fi: kojix2

View File

@ -1,17 +1,18 @@
name: test name: test
on: [push, pull_request, workflow_dispatch] on: [push, pull_request]
jobs: jobs:
build: build:
name: ${{ matrix.os }} Ruby ${{ matrix.ruby }} name: ${{ matrix.os }} Ruby ${{ matrix.ruby }}
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
strategy: strategy:
matrix: matrix:
os: ["ubuntu", "macos"] os: ['ubuntu', 'macos']
ruby: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3", "3.4"] ruby: [ '2.6', '2.7', '3.0' ]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1 - uses: ruby/setup-ruby@v1
with: with:
ruby-version: ${{ matrix.ruby }} ruby-version: ${{ matrix.ruby }}
bundler-cache: true - run: gem install bundler
- run: bundle install
- run: bundle exec rake test - run: bundle exec rake test

View File

@ -1,23 +0,0 @@
name: doc
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ruby
- name: Generate document
run: gem install -N yard && yard doc
- name: Publish Documentation on GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./doc

View File

@ -4,9 +4,3 @@ source 'https://rubygems.org'
# Specify your gem's dependencies in youplot.gemspec # Specify your gem's dependencies in youplot.gemspec
gemspec gemspec
group :test do
gem 'rake'
gem 'simplecov'
gem 'test-unit'
end

243
README.md
View File

@ -1,46 +1,27 @@
<div align="center"> <p align="center">
<img src="logo.svg"> <img src="logo.svg" width="60%" height="60%" />
<hr> </7>
<a href="https://github.com/red-data-tools/YouPlot/actions/workflows/ci.yml"><img alt="Build Status" src="https://github.com/red-data-tools/YouPlot/workflows/test/badge.svg"></a>
<a href="https://rubygems.org/gems/youplot/"><img alt="Gem Version" src="https://badge.fury.io/rb/youplot.svg"></a>
<a href="https://zenodo.org/badge/latestdoi/283230219"><img alt="DOI" src="https://zenodo.org/badge/283230219.svg"></a>
<a href="https://rubydoc.info/gems/youplot/"><img alt="Docs Stable" src="https://img.shields.io/badge/docs-stable-blue.svg"></a>
<a href="LICENSE.txt"><img alt="The MIT License" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
YouPlot is a command line tool that draws plots on the terminal.
:bar_chart: Powered by [UnicodePlot](https://github.com/red-data-tools/unicode_plot.rb) ![Build Status](https://github.com/kojix2/youplot/workflows/test/badge.svg)
</div> [![Gem Version](https://badge.fury.io/rb/youplot.svg)](https://badge.fury.io/rb/youplot)
[![Docs Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://rubydoc.info/gems/youplot)
[![The MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
[![DOI](https://zenodo.org/badge/283230219.svg)](https://zenodo.org/badge/latestdoi/283230219)
YouPlot is a command line tool for Unicode Plotting working with data from standard stream.
:bar_chart: Powered by [UnicodePlot](https://github.com/red-data-tools/unicode_plot.rb)
## Installation ## Installation
``` ```
brew install youplot
```
```
gem install youplot
```
```
nix shell nixpkgs#youplot
```
```
guix install youplot
```
```
conda install -c conda-forge ruby
conda install -c conda-forge compilers
gem install youplot gem install youplot
``` ```
## Quick Start ## Quick Start
<img alt="barplot" src="https://user-images.githubusercontent.com/5798442/101999903-d36a2d00-3d24-11eb-9361-b89116f44122.png" width=160> <img alt="histogram" src="https://user-images.githubusercontent.com/5798442/101999820-21cafc00-3d24-11eb-86db-e410d19b07df.png" width=160> <img alt="scatter" src="https://user-images.githubusercontent.com/5798442/101999827-27284680-3d24-11eb-9903-551857eaa69c.png" width=160> <img alt="density" src="https://user-images.githubusercontent.com/5798442/101999828-2abbcd80-3d24-11eb-902c-2f44266fa6ae.png" width=160> <img alt="boxplot" src="https://user-images.githubusercontent.com/5798442/101999830-2e4f5480-3d24-11eb-8891-728c18bf5b35.png" width=160> * `cat data.tsv | uplot <command> [options]` or
* `uplot <command> [options] <data.tsv>`
`uplot <command> [options] <data.tsv>`
### barplot ### barplot
@ -52,23 +33,16 @@ curl -sL https://git.io/ISLANDScsv \
``` ```
<p align="center"> <p align="center">
<img alt="barplot" src="https://user-images.githubusercontent.com/5798442/101999903-d36a2d00-3d24-11eb-9361-b89116f44122.png"> <img alt="barplot" src="https://user-images.githubusercontent.com/5798442/101999903-d36a2d00-3d24-11eb-9361-b89116f44122.png">
</p> </p>
```sh
# For offline user: Sorts files in a directory by size and shows a bar graph.
ls -l | awk '{print $9, $5}' | sort -nk 2 | uplot bar -d ' '
```
### histogram ### histogram
```sh ```sh
echo -e "from numpy import random;" \ echo -e "from numpy import random;" \
"n = random.randn(10000);" \ "n = random.randn(10000);" \
"print('\\\n'.join(str(i) for i in n))" \ "print('\\\n'.join(str(i) for i in n))" \
| python3 \ | python \
| uplot hist --nbins 20 | uplot hist --nbins 20
``` ```
@ -88,14 +62,6 @@ curl -sL https://git.io/AirPassengers \
<img alt="lineplot" src="https://user-images.githubusercontent.com/5798442/101999825-24c5ec80-3d24-11eb-99f4-c642e8d221bc.png"> <img alt="lineplot" src="https://user-images.githubusercontent.com/5798442/101999825-24c5ec80-3d24-11eb-99f4-c642e8d221bc.png">
</p> </p>
```sh
# For offline users: Calculates sin values (0-2*pi) and plots a sine wave.
python3 -c '
from math import sin, pi
data = "\n".join(f"{i*pi/50}\t{sin(i*pi/50)}" for i in range(101))
print(data)' | uplot line
```
### scatter ### scatter
```sh ```sh
@ -108,12 +74,6 @@ curl -sL https://git.io/IRIStsv \
<img alt="scatter" src="https://user-images.githubusercontent.com/5798442/101999827-27284680-3d24-11eb-9903-551857eaa69c.png"> <img alt="scatter" src="https://user-images.githubusercontent.com/5798442/101999827-27284680-3d24-11eb-9903-551857eaa69c.png">
</p> </p>
```sh
# For offline users
cat test/fixtures/iris.csv | cut -f1-4 -d, | uplot scatter -H -d, -t IRIS
```
### density ### density
```sh ```sh
@ -126,11 +86,6 @@ curl -sL https://git.io/IRIStsv \
<img alt="density" src="https://user-images.githubusercontent.com/5798442/101999828-2abbcd80-3d24-11eb-902c-2f44266fa6ae.png"> <img alt="density" src="https://user-images.githubusercontent.com/5798442/101999828-2abbcd80-3d24-11eb-902c-2f44266fa6ae.png">
</p> </p>
```sh
# For offline users
cat test/fixtures/iris.csv | cut -f1-4 -d, | uplot density -H -d, -t IRIS
```
### boxplot ### boxplot
```sh ```sh
@ -143,55 +98,59 @@ curl -sL https://git.io/IRIStsv \
<img alt="boxplot" src="https://user-images.githubusercontent.com/5798442/101999830-2e4f5480-3d24-11eb-8891-728c18bf5b35.png"> <img alt="boxplot" src="https://user-images.githubusercontent.com/5798442/101999830-2e4f5480-3d24-11eb-8891-728c18bf5b35.png">
</p> </p>
```sh
# For offline users
cat test/fixtures/iris.csv | cut -f1-4 -d, | uplot boxplot -H -d, -t IRIS
```
### count ### count
Count processes by user ID. In this example, YouPlot counts the number of chromosomes where the gene is located from the human gene annotation file and it creates a bar chart. The human gene annotation file can be downloaded from the following website.
```sh * https://www.gencodegenes.org/human/
ps aux | awk '{print $1}' | uplot count
```
Count the number of chromosomes where genes are located.
```sh ```sh
cat gencode.v35.annotation.gff3 \ cat gencode.v35.annotation.gff3 \
| grep -v '#' | grep 'gene' | cut -f1 \ | grep -v '#' | grep 'gene' | cut -f1 | \
| uplot count -t "The number of human gene annotations per chromosome" -c blue uplot count -t "The number of human gene annotations per chromosome" -c blue
``` ```
<p align="center"> <p align="center">
<img alt="count" src="https://user-images.githubusercontent.com/5798442/101999832-30b1ae80-3d24-11eb-96fe-e5000bed1f5c.png"> <img alt="count" src="https://user-images.githubusercontent.com/5798442/101999832-30b1ae80-3d24-11eb-96fe-e5000bed1f5c.png">
</p> </p>
* [GENCODE - Human Release](https://www.gencodegenes.org/human/)
Note: `count` is not very fast because it runs in a Ruby script. Note: `count` is not very fast because it runs in a Ruby script.
This is fine in most cases, as long as the data size is small. If you want to visualize huge data, it is faster to use a combination of common Unix commands as shown below. This is fine in most cases, as long as the data size is small. If you want to visualize huge data, it is faster to use a combination of common Unix commands as shown below.
```sh ```sh
cat gencode.v35.annotation.gff3 | grep -v '#' | grep 'gene' | cut -f1 \ cat gencode.v35.annotation.gff3 | grep -v '#' | grep 'gene' | cut -f1 \
| sort | uniq -c | sort -nrk1 \ |sort | uniq -c | sort -nrk2 | awk '{print $2,$1}' \
| uplot bar --fmt yx -d ' ' -t "The number of human gene annotations per chromosome" -c blue | uplot bar -d ' ' -t "The number of human gene annotations per chromosome" -c blue
``` ```
## Usage ## Usage
### Commands ### Why YouPlot?
Wouldn't it be a pain to have to run R, Python, Julia, gnuplot or whatever REPL just to check your data?
YouPlot is a command line tool for this purpose. With YouPlot, you can continue working without leaving your terminal and shell.
### how to use YouPlot?
`uplot` is the shortened form of `youplot`. You can use either. `uplot` is the shortened form of `youplot`. You can use either.
| Command | Description | | | |
|------------------------------------------------|-----------------------------------| |-----------------------------------|------------------------------------------------|
| `cat data.tsv \| uplot <command> [options]` | Take input from stdin | | Reads data from standard input | `cat data.tsv \| uplot <command> [options]` |
| `uplot <command> [options] data.tsv ...` | Take input from files | | Reads data from files | `uplot <command> [options] data.tsv ...` |
| `pipeline1 \| uplot <command> -O \| pipeline2` | Outputs data from stdin to stdout | | Outputs data from stdin to stdout | `pipeline1 \| uplot <command> -O \| pipeline2` |
### Subcommands ### Where to output the plot?
By default, the plot is output to *standard error output*.
The output file or stream for the plot can be specified with the `-o` option.
### Where to output the input data?
By default, the input data is not shown anywhere.
The `-O` option, with no arguments, outputs the input data directly to the standard output. This is useful when passing data to a subsequent pipeline.
### What types of plots are available?
The following sub-commands are available. The following sub-commands are available.
@ -204,95 +163,67 @@ The following sub-commands are available.
| scatter | s | draw a scatter plot | | scatter | s | draw a scatter plot |
| density | d | draw a density plot | | density | d | draw a density plot |
| boxplot | box | draw a horizontal boxplot | | boxplot | box | draw a horizontal boxplot |
| | | |
| count | c | draw a barplot based on the number of occurrences (slow) |
| | | |
| colors | color | show the list of available colors |
### Output the plot See Quick Start for `count`.
* `-o` | command | short | how it works |
* By default, the plot is output to **standard error output**. |-----------|-------|----------------------------------------------------------|
* If you want to output to standard output, Use hyphen ` -o -` or no argument `uplot s -o | `. | count | c | draw a barplot based on the number of occurrences (slow) |
### Output the input data ### What if the header line is included?
* `-O` If your input data contains a header line, you need to specify the `-H` option.
* By default, the input data is not shown anywhere.
* If you want to pass the input data directly to the standard output, Use hyphen `-O -` or no argument `uplot s -O |`.
* This is useful when passing data to a subsequent pipeline.
### Header ### How to specify the delimiter?
* `-H` Use the `-d` option. To specify a blank space, you can use `uplot bar -d ' ' data.txt`. You do not need to use `-d` option for tab-delimited text since the default value is tab.
* If input data contains a header line, you need to specify the `-H` option.
### Delimiter ### Is there a way to specify a column as the x-axis or y-axis?
* `-d` Not yet. In principle, YouPlot treats the first column as the X axis and the second column as the Y axis. When working with multiple series, the first row is the X axis, the second row is series 1, the third row is series 2, and so on. If you pass only one column of data for `line` and `bar`, YouPlot will automatically use a sequential number starting from 1 as the X-axis. The `--fmt xyy`, `--fmt xyxy` and `--fmt yx` options give you a few more choices. See `youplot <command> --help` for more details. YouPlot has limited functionalities, but you can use shell scripts such as `awk '{print $2, $1}'` to swap lines.
* You do not need to use `-d` option for tab-delimited text since the default value is tab.
* To specify a blank space, you can use `uplot bar -d ' ' data.txt`.
### Real-time data ### How to plot real-time data?
* `-p` `--progress` Experimental progressive mode is currently under development.
* Experimental progressive mode is currently under development.
* `ruby -e 'loop{puts rand(100)}' | uplot line --progress`
### Show detailed options for subcommands ```sh
ruby -e 'loop{puts rand(100)}' | uplot line --progress
* `--help`
* The `--help` option will show more detailed options for each subcommand.
* `uplot hist --help`
### Set columns as x-axis or y-axis
* YouPlot treats the first column as the X axis and the second column as the Y axis. When working with multiple series, the first column is the X axis, the second column is series Y1, the third column is series Y2, and so on.
* If you pass only one column of data for `line` and `bar`, YouPlot will automatically use a sequential number starting from 1 as the X-axis.
* `--fmt`
* `--fmt xyy` `--fmt xyxy` `--fmt yx` options give you a few more choices. See `youplot <command> --help` for more details.
* The fmt option may be renamed in the future.
* The `-x` and `-y` options might be used to specify columns in the future.
* Use `awk '{print $2, $1}'` to swap columns. Use `paste` to concatenate series.
### Categorical data
* With GNU datamash, you can manage to handle categorized data.
* `cat test/fixtures/iris.csv | sed '/^$/d' | datamash --header-in --output-delimiter=: -t, -g5 collapse 3,4 | cut -f2-3 -d: | sed 's/:/\n/g' | uplot s -d, -T --fmt xyxy`
* This is not so easy...
### Time series
* Not yet supported.
### YouPlot Configuration (youplotrc)
You can specify default options in a configuration file in YAML format. For more information, enter the following command.
```
uplot --config
``` ```
## Tools that are useful to use with YouPlot ### How to view detailed command line options?
* [csvtk](https://github.com/shenwei356/csvtk) Use `--help` to print command-specific options.
* [GNU datamash](https://www.gnu.org/software/datamash/)
* [awk](https://www.gnu.org/software/gawk/) `uplot hist --help`
* [xsv](https://github.com/BurntSushi/xsv)
```
Usage: uplot histogram [options] <in.tsv>
Options for histogram:
--symbol VAL character to be used to plot the bars
--closed VAL side of the intervals to be closed [left]
-n, --nbins VAL approximate number of bins
Options:
...
```
### How to view the list of available colors?
```sh
uplot colors
```
## Contributing ## Contributing
YouPlot is a library under development, so even small improvements like typofix are welcome! YouPlot is a library under development, so even small improvements like typofix are welcome! Please feel free to send us your pull requests.
Please feel free to send us your pull requests.
* [Report bugs](https://github.com/red-data-tools/YouPlot/issues) * [Report bugs](https://github.com/kojix2/youplot/issues)
* Fix bugs and [submit pull requests](https://github.com/red-data-tools/YouPlot/pulls) * Fix bugs and [submit pull requests](https://github.com/kojix2/youplot/pulls)
* Write, clarify, or fix documentation * Write, clarify, or fix documentation
* English corrections by native speakers are welcome. * English corrections by native speakers are welcome.
* Suggest or add new features * Suggest or add new features
* Make a donation
### Development ### Development
@ -302,15 +233,11 @@ git clone https://github.com/your_name/YouPlot
bundle install # Install the gem dependencies bundle install # Install the gem dependencies
bundle exec rake test # Run the test bundle exec rake test # Run the test
bundle exec rake install # Installation from source code bundle exec rake install # Installation from source code
bundle exec exe/uplot # Run youplot (Try out the edited code)
``` ```
Do you need commit rights to my repository?
Do you want to get admin rights and take over the project?
If so, please feel free to contact us.
### Acknowledgements ### Acknowledgements
* [Red Data Tools](https://github.com/red-data-tools) - Technical support
* [sampo grafiikka](https://jypg.net/sampo_grafiikka) - Project logo creation * [sampo grafiikka](https://jypg.net/sampo_grafiikka) - Project logo creation
* [yutaas](https://github.com/yutaas) - English proofreading * [yutaas](https://github.com/yutaas) - English proofreading

View File

@ -1,17 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'youplot/version' require 'unicode_plot'
require_relative 'youplot/dsv' require 'youplot/version'
require_relative 'youplot/parameters' require 'youplot/dsv'
require_relative 'youplot/command' require 'youplot/command'
module YouPlot module YouPlot
# @run_as_executable = true / false
# YouPlot behaves slightly differently when run as a command line tool
# and when run as a script (e.g. for testing). In the event of an error,
# when run as a command line tool, YouPlot will display a short error message
# and exit abnormally. When run as a script, it will just raise an error.
@run_as_executable = false
class << self class << self
attr_accessor :run_as_executable attr_accessor :run_as_executable
@ -19,4 +13,5 @@ module YouPlot
@run_as_executable @run_as_executable
end end
end end
@run_as_executable = false
end end

View File

@ -6,30 +6,18 @@ module YouPlot
module Processing module Processing
module_function module_function
def count_values(arr, tally: true, reverse: false) def count_values(arr, tally: true)
# tally was added in Ruby 2.7 # tally was added in Ruby 2.7
result = \ if tally && Enumerable.method_defined?(:tally)
if tally && Enumerable.method_defined?(:tally) arr.tally
arr.tally else
else # https://github.com/marcandre/backports
# value_counts Enumerable::Statistics arr.each_with_object(Hash.new(0)) { |item, res| res[item] += 1 }
arr.value_counts(dropna: false) .tap { |h| h.default = nil }
end
# sorting
result = result.sort do |a, b|
# compare values
r = b[1] <=> a[1]
# If the values are the same, compare by name
r = a[0] <=> b[0] if r.zero?
r
end end
.sort { |a, b| a[1] <=> b[1] }
# --reverse option .reverse
result.reverse! if reverse .transpose
# prepare for barplot
result.transpose
end end
end end
end end

View File

@ -1,46 +1,22 @@
# frozen_string_literal: true # frozen_string_literal: true
# UnicodePlot - Plot your data by Unicode characters
# https://github.com/red-data-tools/unicode_plot.rb
require_relative 'processing' require_relative 'processing'
require 'unicode_plot' require 'unicode_plot'
# If the line color is specified as a number, the program will display an error
# message to the user and exit. Remove this patch when UnicodePlot is improved.
module UnicodePlot
class << self
alias lineplot_original lineplot
def lineplot(*args, **kw)
if kw[:color].is_a? Numeric
warn <<~EOS
YouPlot: Line colors cannot be specified by numerical values.
For more information, please see the following issue.
https://github.com/red-data-tools/unicode_plot.rb/issues/34
EOS
YouPlot.run_as_executable ? exit(1) : raise(Error)
end
lineplot_original(*args, **kw)
end
end
end
module YouPlot module YouPlot
# plotting functions. # plotting functions.
module Backends module Backends
module UnicodePlot module UnicodePlotBackend
class Error < StandardError; end class Error < StandardError; end
module_function module_function
def barplot(data, params, fmt = nil, count: false, reverse: false) def barplot(data, params, fmt = nil, count: false)
headers = data.headers headers = data.headers
series = data.series series = data.series
# `uplot count` # `uplot count`
if count if count
series = Processing.count_values(series[0], reverse: reverse) series = Processing.count_values(series[0])
params.title = headers[0] if headers params.title = headers[0] if headers
end end
if series.size == 1 if series.size == 1
@ -63,7 +39,7 @@ module YouPlot
labels = series[x_col] labels = series[x_col]
values = series[y_col].map(&:to_f) values = series[y_col].map(&:to_f)
end end
::UnicodePlot.barplot(labels, values, **params.to_hc) UnicodePlot.barplot(labels, values, **params.to_hc)
end end
def histogram(data, params) def histogram(data, params)
@ -71,7 +47,7 @@ module YouPlot
series = data.series series = data.series
params.title ||= data.headers[0] if headers params.title ||= data.headers[0] if headers
values = series[0].map(&:to_f) values = series[0].map(&:to_f)
::UnicodePlot.histogram(values, **params.to_hc) UnicodePlot.histogram(values, **params.to_hc)
end end
def line(data, params, fmt = nil) def line(data, params, fmt = nil)
@ -81,7 +57,7 @@ module YouPlot
# If there is only one series, it is assumed to be sequential data. # If there is only one series, it is assumed to be sequential data.
params.ylabel ||= headers[0] if headers params.ylabel ||= headers[0] if headers
y = series[0].map(&:to_f) y = series[0].map(&:to_f)
::UnicodePlot.lineplot(y, **params.to_hc) UnicodePlot.lineplot(y, **params.to_hc)
else else
# If there are 2 or more series... # If there are 2 or more series...
if fmt == 'yx' if fmt == 'yx'
@ -99,7 +75,7 @@ module YouPlot
end end
x = series[x_col].map(&:to_f) x = series[x_col].map(&:to_f)
y = series[y_col].map(&:to_f) y = series[y_col].map(&:to_f)
::UnicodePlot.lineplot(x, y, **params.to_hc) UnicodePlot.lineplot(x, y, **params.to_hc)
end end
end end
@ -118,26 +94,27 @@ module YouPlot
end end
params.xlim ||= series[0].flatten.minmax # why need? params.xlim ||= series[0].flatten.minmax # why need?
params.ylim ||= series[1..-1].flatten.minmax # why need? params.ylim ||= series[1..-1].flatten.minmax # why need?
plot = ::UnicodePlot.public_send(method1, series[0], series[1], **params.to_hc) plot = UnicodePlot.public_send(method1, series[0], series[1], **params.to_hc)
2.upto(series.size - 1) do |i| 2.upto(series.size - 1) do |i|
::UnicodePlot.public_send(method2, plot, series[0], series[i], name: headers&.[](i)) UnicodePlot.public_send(method2, plot, series[0], series[i], name: headers&.[](i))
end end
plot plot
end end
def plot_xyxy(data, method1, params) def plot_xyxy(data, method1, params)
headers = data.headers headers = data.headers
series2 = data.series series = data.series
.map { |s| s.map(&:to_f) }
.each_slice(2).to_a
method2 = get_method2(method1) method2 = get_method2(method1)
series.map! { |s| s.map(&:to_f) }
series2 = series.each_slice(2).to_a
series = nil
params.name ||= headers[0] if headers params.name ||= headers[0] if headers
params.xlim ||= series2.map(&:first).flatten.minmax # why need? params.xlim ||= series2.map(&:first).flatten.minmax # why need?
params.ylim ||= series2.map(&:last).flatten.minmax # why need? params.ylim ||= series2.map(&:last).flatten.minmax # why need?
x1, y1 = series2.shift x1, y1 = series2.shift
plot = ::UnicodePlot.public_send(method1, x1, y1, **params.to_hc) plot = UnicodePlot.public_send(method1, x1, y1, **params.to_hc)
series2.each_with_index do |(xi, yi), i| series2.each_with_index do |(xi, yi), i|
::UnicodePlot.public_send(method2, plot, xi, yi, name: headers&.[]((i + 1) * 2)) UnicodePlot.public_send(method2, plot, xi, yi, name: headers&.[]((i + 1) * 2))
end end
plot plot
end end
@ -175,13 +152,13 @@ module YouPlot
series = data.series series = data.series
headers ||= (1..series.size).map(&:to_s) headers ||= (1..series.size).map(&:to_s)
series.map! { |s| s.map(&:to_f) } series.map! { |s| s.map(&:to_f) }
::UnicodePlot.boxplot(headers, series, **params.to_hc) UnicodePlot.boxplot(headers, series, **params.to_hc)
end end
def colors(color_names = false) def colors(color_names = false)
# FIXME # FIXME
s = String.new s = String.new
::UnicodePlot::StyledPrinter::TEXT_COLORS.each do |k, v| UnicodePlot::StyledPrinter::TEXT_COLORS.each do |k, v|
s << v s << v
s << k.to_s s << k.to_s
unless color_names unless color_names
@ -202,7 +179,7 @@ module YouPlot
series = data.series series = data.series
if series.size == 1 if series.size == 1
warn <<~EOS warn <<~EOS
YouPlot: There is only one series of input data. Please check the delimiter. youplot: There is only one series of input data. Please check the delimiter.
Headers: \e[35m#{data.headers.inspect}\e[0m Headers: \e[35m#{data.headers.inspect}\e[0m
The first item is: \e[35m\"#{series[0][0]}\"\e[0m The first item is: \e[35m\"#{series[0][0]}\"\e[0m

View File

@ -1,10 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'dsv' require_relative 'dsv'
require_relative 'parser' require_relative 'command/parser'
# FIXME # FIXME
require_relative 'backends/unicode_plot' require_relative 'backends/unicode_plot_backend'
module YouPlot module YouPlot
Data = Struct.new(:headers, :series) Data = Struct.new(:headers, :series)
@ -19,7 +19,7 @@ module YouPlot
@command = nil @command = nil
@params = nil @params = nil
@options = nil @options = nil
@backend = YouPlot::Backends::UnicodePlot @backend = YouPlot::Backends::UnicodePlotBackend
end end
def run_as_executable def run_as_executable
@ -33,77 +33,49 @@ module YouPlot
@options ||= parser.options @options ||= parser.options
@params ||= parser.params @params ||= parser.params
# color command
if %i[colors color colours colour].include? @command if %i[colors color colours colour].include? @command
plot = create_plot plot = create_plot
output_plot(plot) output_plot(plot)
return elsif options[:progressive]
end
# progressive mode
if options[:progressive]
stop = false stop = false
Signal.trap(:INT) { stop = true } Signal.trap(:INT) { stop = true }
options[:output].print "\e[?25l" # make cursor invisible
# make cursor invisible
options[:output].print "\e[?25l"
# mainloop
while (input = Kernel.gets) while (input = Kernel.gets)
n = main_progressive(input) n = main_progressive(input)
break if stop break if stop
options[:output].print "\e[#{n}F" options[:output].print "\e[#{n}F"
end end
options[:output].print "\e[0J" options[:output].print "\e[0J"
# make cursor visible options[:output].print "\e[?25h" # make cursor visible
options[:output].print "\e[?25h"
# normal mode
else else
# Sometimes the input file does not end with a newline code. # Sometimes the input file does not end with a newline code.
begin while (input = Kernel.gets(nil))
begin
input = Kernel.gets(nil)
rescue Errno::ENOENT => e
warn e.message
next
end
main(input) main(input)
end until input end
end end
end end
private private
def main(input) def main(input)
# Outputs input data to a file or stdout.
output_data(input) output_data(input)
@data = parse_dsv(input) @data = read_dsv(input)
# Debug mode, show parsed results
pp @data if options[:debug] pp @data if options[:debug]
# When run as a program instead of a library
if YouPlot.run_as_executable? if YouPlot.run_as_executable?
begin begin
plot = create_plot plot = create_plot
rescue ArgumentError => e rescue ArgumentError => e
# Show only one line of error.
warn e.backtrace[0] warn e.backtrace[0]
# Show error message in purple
warn "\e[35m#{e}\e[0m" warn "\e[35m#{e}\e[0m"
# Explicitly terminated with exit code: 1
exit 1 exit 1
end end
# When running YouPlot as a library (e.g. for testing)
else else
plot = create_plot plot = create_plot
end end
output_plot(plot) output_plot(plot)
end end
@ -123,33 +95,15 @@ module YouPlot
@raw_data << input @raw_data << input
# FIXME # FIXME
@data = parse_dsv(@raw_data) @data = read_dsv(@raw_data)
plot = create_plot plot = create_plot
output_plot_progressive(plot) output_plot_progressive(plot)
end end
def parse_dsv(input) def read_dsv(input)
# If encoding is specified, convert to UTF-8 input = input.dup.force_encoding(options[:encoding]).encode('utf-8') if options[:encoding]
if options[:encoding] DSV.parse(input, options[:delimiter], options[:headers], options[:transpose])
input.force_encoding(options[:encoding])
.encode!('utf-8')
end
begin
data = DSV.parse(input, options[:delimiter], options[:headers], options[:transpose])
rescue CSV::MalformedCSVError => e
warn 'Failed to parse the text. '
warn 'Please try to set the correct character encoding with --encoding option.'
warn e.backtrace.grep(/youplot/).first
exit 1
rescue ArgumentError => e
warn 'Failed to parse the text. '
warn e.backtrace.grep(/youplot/).first
exit 1
end
data
end end
def create_plot def create_plot
@ -157,12 +111,12 @@ module YouPlot
when :bar, :barplot when :bar, :barplot
@backend.barplot(data, params, options[:fmt]) @backend.barplot(data, params, options[:fmt])
when :count, :c when :count, :c
@backend.barplot(data, params, count: true, reverse: options[:reverse]) @backend.barplot(data, params, count: true)
when :hist, :histogram when :hist, :histogram
@backend.histogram(data, params) @backend.histogram(data, params)
when :line, :lineplot, :l when :line, :lineplot
@backend.line(data, params, options[:fmt]) @backend.line(data, params, options[:fmt])
when :lines, :lineplots, :ls when :lines, :lineplots
@backend.lines(data, params, options[:fmt]) @backend.lines(data, params, options[:fmt])
when :scatter, :s when :scatter, :s
@backend.scatter(data, params, options[:fmt]) @backend.scatter(data, params, options[:fmt])
@ -180,7 +134,7 @@ module YouPlot
def output_data(input) def output_data(input)
# Pass the input to subsequent pipelines # Pass the input to subsequent pipelines
case options[:pass] case options[:pass]
when IO, StringIO when IO
options[:pass].print(input) options[:pass].print(input)
else else
if options[:pass] if options[:pass]
@ -193,9 +147,9 @@ module YouPlot
def output_plot(plot) def output_plot(plot)
case options[:output] case options[:output]
when IO, StringIO when IO
plot.render(options[:output]) plot.render(options[:output])
when String, Tempfile else
File.open(options[:output], 'w') do |f| File.open(options[:output], 'w') do |f|
plot.render(f) plot.render(f)
end end
@ -204,7 +158,7 @@ module YouPlot
def output_plot_progressive(plot) def output_plot_progressive(plot)
case options[:output] case options[:output]
when IO, StringIO when IO
# RefactorMe # RefactorMe
out = StringIO.new(String.new) out = StringIO.new(String.new)
def out.tty? def out.tty?

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module YouPlot
class Command
Options = Struct.new(
:delimiter,
:transpose,
:headers,
:pass,
:output,
:fmt,
:progressive,
:encoding,
:color_names,
:debug,
keyword_init: true
)
end
end

View File

@ -0,0 +1,305 @@
# frozen_string_literal: true
require 'optparse'
require_relative 'options'
require_relative 'plot_params'
module YouPlot
class Command
class Parser
class Error < StandardError; end
attr_reader :command, :options, :params,
:main_parser, :sub_parser
def initialize
@command = nil
@options = Options.new(
delimiter: "\t",
transpose: false,
headers: nil,
pass: false,
output: $stderr,
fmt: 'xyy',
progressive: false,
encoding: nil,
color_names: false,
debug: false
)
@params = PlotParams.new
end
def create_default_parser
OptionParser.new do |parser|
parser.program_name = 'YouPlot'
parser.version = YouPlot::VERSION
parser.summary_width = 24
parser.on_tail('') # Add a blank line at the end
parser.separator('')
parser.on('Common options:')
parser.on('-O', '--pass [FILE]', 'file to output input data to [stdout]',
'for inserting YouPlot in the middle of Unix pipes') do |v|
options[:pass] = v || $stdout
end
parser.on('-o', '--output [FILE]', 'file to output plots to [stdout]',
'If no option is specified, plot will print to stderr') do |v|
options[:output] = v || $stdout
end
parser.on('-d', '--delimiter DELIM', String, 'use DELIM instead of [TAB] for field delimiter') do |v|
options[:delimiter] = v
end
parser.on('-H', '--headers', TrueClass, 'specify that the input has header row') do |v|
options[:headers] = v
end
parser.on('-T', '--transpose', TrueClass, 'transpose the axes of the input data') do |v|
options[:transpose] = v
end
parser.on('-t', '--title STR', String, 'print string on the top of plot') do |v|
params.title = v
end
parser.on('-x', '--xlabel STR', String, 'print string on the bottom of the plot') do |v|
params.xlabel = v
end
parser.on('-y', '--ylabel STR', String, 'print string on the far left of the plot') do |v|
params.ylabel = v
end
parser.on('-w', '--width INT', Integer, 'number of characters per row') do |v|
params.width = v
end
parser.on('-h', '--height INT', Numeric, 'number of rows') do |v|
params.height = v
end
border_options = UnicodePlot::BORDER_MAP.keys.join(', ')
parser.on('-b', '--border STR', String, 'specify the style of the bounding box', "(#{border_options})") do |v|
params.border = v.to_sym
end
parser.on('-m', '--margin INT', Numeric, 'number of spaces to the left of the plot') do |v|
params.margin = v
end
parser.on('--padding INT', Numeric, 'space of the left and right of the plot') do |v|
params.padding = v
end
parser.on('-c', '--color VAL', String, 'color of the drawing') do |v|
params.color = v =~ /\A[0-9]+\z/ ? v.to_i : v.to_sym
end
parser.on('--[no-]labels', TrueClass, 'hide the labels') do |v|
params.labels = v
end
parser.on('-p', '--progress', TrueClass, 'progressive mode [experimental]') do |v|
options[:progressive] = v
end
parser.on('-C', '--color-output', TrueClass, 'colorize even if writing to a pipe') do |v|
UnicodePlot::StyledPrinter.define_method(:color?){ |o| true }
end
parser.on('-M', '--monochrome', TrueClass, 'no colouring even if writing to a tty') do |v|
UnicodePlot::StyledPrinter.define_method(:color?){ |o| false }
end
parser.on('--encoding STR', String, 'Specify the input encoding') do |v|
options[:encoding] = v
end
# Optparse adds the help option, but it doesn't show up in usage.
# This is why you need the code below.
parser.on('--help', 'print sub-command help menu') do
puts parser.help
exit if YouPlot.run_as_executable?
end
parser.on('--debug', TrueClass, 'print preprocessed data') do |v|
options[:debug] = v
end
# yield opt if block_given?
end
end
def create_main_parser
@main_parser = create_default_parser
main_parser.banner = \
<<~MSG
Program: YouPlot (Tools for plotting on the terminal)
Version: #{YouPlot::VERSION} (using UnicodePlot #{UnicodePlot::VERSION})
Source: https://github.com/kojix2/youplot
Usage: uplot <command> [options] <in.tsv>
Commands:
barplot bar draw a horizontal barplot
histogram hist draw a horizontal histogram
lineplot line draw a line chart
lineplots lines draw a line chart with multiple series
scatter s draw a scatter plot
density d draw a density plot
boxplot box draw a horizontal boxplot
colors color show the list of available colors
count c draw a baplot based on the number of
occurrences (slow)
General options:
--help print command specific help menu
--version print the version of YouPlot
MSG
# Help for the main parser is simple.
# Simply show the banner above.
main_parser.on('--help', 'print sub-command help menu') do
puts main_parser.banner
puts
exit if YouPlot.run_as_executable?
end
end
def sub_parser_add_symbol
sub_parser.on_head('--symbol STR', String, 'character to be used to plot the bars') do |v|
params.symbol = v
end
end
def sub_parser_add_xscale
xscale_options = UnicodePlot::ValueTransformer::PREDEFINED_TRANSFORM_FUNCTIONS.keys.join(', ')
sub_parser.on_head('--xscale STR', String, "axis scaling (#{xscale_options})") do |v|
params.xscale = v.to_sym
end
end
def sub_parser_add_canvas
sub_parser.on_head('--canvas STR', String, 'type of canvas') do |v|
params.canvas = v.to_sym
end
end
def sub_parser_add_xlim
sub_parser.on_head('--xlim FLOAT,FLOAT', Array, 'plotting range for the x coordinate') do |v|
params.xlim = v
end
end
def sub_parser_add_ylim
sub_parser.on_head('--ylim FLOAT,FLOAT', Array, 'plotting range for the y coordinate') do |v|
params.ylim = v
end
end
def sub_parser_add_grid
sub_parser.on_head('--[no-]grid', TrueClass, 'draws grid-lines at the origin') do |v|
params.grid = v
end
end
def create_sub_parser
@sub_parser = create_default_parser
sub_parser.banner = \
<<~MSG
Usage: YouPlot #{command} [options] <in.tsv>
Options for #{command}:
MSG
case command
# If you type only `uplot` in the terminal.
when nil
warn main_parser.banner
warn "\n"
exit 1 if YouPlot.run_as_executable?
when :barplot, :bar
sub_parser_add_symbol
sub_parser.on_head('--fmt STR', String, 'xy : header is like x, y...', 'yx : header is like y, x...') do |v|
options[:fmt] = v
end
sub_parser_add_xscale
when :count, :c
sub_parser_add_symbol
sub_parser_add_xscale
when :histogram, :hist
sub_parser_add_symbol
sub_parser.on_head('--closed STR', String, 'side of the intervals to be closed [left]') do |v|
params.closed = v
end
sub_parser.on_head('-n', '--nbins INT', Numeric, 'approximate number of bins') do |v|
params.nbins = v
end
when :lineplot, :line
sub_parser_add_canvas
sub_parser_add_grid
sub_parser.on_head('--fmt STR', String, 'xy : header is like x, y...', 'yx : header is like y, x...') do |v|
options[:fmt] = v
end
sub_parser_add_ylim
sub_parser_add_xlim
when :lineplots, :lines
sub_parser_add_canvas
sub_parser_add_grid
sub_parser.on_head('--fmt STR', String, 'xyxy : header is like x1, y1, x2, y2, x3, y3...',
'xyy : header is like x, y1, y2, y2, y3...') do |v|
options[:fmt] = v
end
sub_parser_add_ylim
sub_parser_add_xlim
when :scatter, :s
sub_parser_add_canvas
sub_parser_add_grid
sub_parser.on_head('--fmt STR', String, 'xyxy : header is like x1, y1, x2, y2, x3, y3...',
'xyy : header is like x, y1, y2, y2, y3...') do |v|
options[:fmt] = v
end
sub_parser_add_ylim
sub_parser_add_xlim
when :density, :d
sub_parser_add_canvas
sub_parser_add_grid
sub_parser.on('--fmt STR', String, 'xyxy : header is like x1, y1, x2, y2, x3, y3...',
'xyy : header is like x, y1, y2, y2, y3...') do |v|
options[:fmt] = v
end
sub_parser_add_ylim
sub_parser_add_xlim
when :boxplot, :box
sub_parser_add_xlim
when :colors, :color, :colours, :colour
sub_parser.on_head('-n', '--names', 'show color names only', TrueClass) do |v|
options[:color_names] = v
end
else
error_message = "uplot: unrecognized command '#{command}'"
if YouPlot.run_as_executable?
warn error_message
exit 1
else
raise Error, error_message
end
end
end
def parse_options(argv = ARGV)
begin
create_main_parser.order!(argv)
rescue OptionParser::ParseError => e
warn "uplot: #{e.message}"
exit 1 if YouPlot.run_as_executable?
end
@command = argv.shift&.to_sym
begin
create_sub_parser&.parse!(argv)
rescue OptionParser::ParseError => e
warn "uplot: #{e.message}"
exit 1 if YouPlot.run_as_executable?
end
end
end
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
module YouPlot
class Command
# UnicodePlot parameters.
# * Normally in a Ruby program, you might use hash for the parameter object.
# * Here, I use Struct for 2 safety reason.
# * The keys are static in Struct.
# * Struct does not conflict with keyword arguments. Hash dose.
PlotParams = Struct.new(
# Sort me!
:title,
:width,
:height,
:border,
:margin,
:padding,
:color,
:xlabel,
:ylabel,
:labels,
:symbol,
:xscale,
:nbins,
:closed,
:canvas,
:xlim,
:ylim,
:grid,
:name
) do
def to_hc
to_h.compact
end
end
end
end

View File

@ -3,49 +3,41 @@
require 'csv' require 'csv'
module YouPlot module YouPlot
# Module to handle DSV (Delimiter-separated values) format. # Read and interpret Delimiter-separated values format file or stream.
# Extract header and series.
module DSV module DSV
module_function module_function
def parse(input, delimiter, headers, transpose) def parse(input, delimiter, headers, transpose)
# Parse as CSV arr = parse_as_csv(input, delimiter)
arr = CSV.parse(input, col_sep: delimiter)
# Remove blank lines
arr.delete_if do |i|
i == [] or i.all?(&:nil?)
end
# get header
headers = get_headers(arr, headers, transpose) headers = get_headers(arr, headers, transpose)
# get series
series = get_series(arr, headers, transpose) series = get_series(arr, headers, transpose)
if headers.nil?
# Return if No header Data.new(headers, series)
return Data.new(headers, series) if headers.nil? else
if headers.include?(nil)
# Warn if header contains nil warn "\e[35mHeaders contains nil in it.\e[0m"
warn "\e[35mHeaders contains nil in it.\e[0m" if headers.include?(nil) elsif headers.include? ''
warn "\e[35mHeaders contains \"\" in it.\e[0m"
# Warn if header contains '' end
warn "\e[35mHeaders contains \"\" in it.\e[0m" if headers.include? '' h_size = headers.size
s_size = series.size
# Make sure the number of elements in the header matches the number of series. if h_size == s_size
h_size = headers.size Data.new(headers, series)
s_size = series.size elsif h_size > s_size
warn "\e[35mThe number of headers is greater than the number of series.\e[0m"
if h_size > s_size exit 1 if YouPlot.run_as_executable?
warn "\e[35mThe number of headers is greater than the number of series.\e[0m" elsif h_size < s_size
exit 1 if YouPlot.run_as_executable? warn "\e[35mThe number of headers is less than the number of series.\e[0m"
exit 1 if YouPlot.run_as_executable?
elsif h_size < s_size end
warn "\e[35mThe number of headers is less than the number of series.\e[0m"
exit 1 if YouPlot.run_as_executable?
end end
end
Data.new(headers, series) if h_size == s_size def parse_as_csv(input, delimiter)
CSV.parse(input, col_sep: delimiter)
.delete_if do |i|
i == [] or i.all? nil
end
end end
# Transpose different sized ruby arrays # Transpose different sized ruby arrays
@ -55,33 +47,31 @@ module YouPlot
end end
def get_headers(arr, headers, transpose) def get_headers(arr, headers, transpose)
# header(-) if headers
return nil unless headers if transpose
arr.map(&:first)
# header(+) trenspose(+) else
return arr.map(&:first) if transpose arr[0]
end
# header(+) transpose(-) end
arr[0]
end end
def get_series(arr, headers, transpose) def get_series(arr, headers, transpose)
# header(-) if headers
unless headers if arr.size > 1
return arr if transpose if transpose
arr.map { |row| row[1..-1] }
return transpose2(arr) else
transpose2(arr[1..-1])
end
else
Array.new(arr[0].size, [])
end
elsif transpose
arr
else
transpose2(arr)
end end
# header(+) but no element in the series.
# TODO: should raise error?
return Array.new(arr[0].size, []) if arr.size == 1
# header(+) transpose(+)
return arr.map { |row| row[1..-1] } if transpose
# header(+) transpose(-)
transpose2(arr[1..-1])
end end
end end
end end

View File

@ -1,18 +0,0 @@
# frozen_string_literal: true
module YouPlot
# Command line options that are not Plot parameters
Options = Struct.new(
:delimiter,
:transpose,
:headers,
:pass,
:output,
:fmt,
:progressive,
:encoding,
:reverse, # count
:color_names, # color
:debug
)
end

View File

@ -1,34 +0,0 @@
# frozen_string_literal: true
module YouPlot
# UnicodePlot parameters.
# Why Struct, not Hash?
# * The keys are static in Struct.
# * Struct does not conflict with keyword arguments. Hash dose.
Parameters = Struct.new(
# Sort me!
:title,
:width,
:height,
:border,
:margin,
:padding,
:color,
:xlabel,
:ylabel,
:labels,
:symbol,
:xscale,
:nbins,
:closed,
:canvas,
:xlim,
:ylim,
:grid,
:name
) do
def to_hc
to_h.compact
end
end
end

View File

@ -1,410 +0,0 @@
# frozen_string_literal: true
require 'optparse'
require_relative 'options'
module YouPlot
# Class for parsing command line options
class Parser
class Error < StandardError; end
attr_reader :command, :options, :params,
:main_parser, :sub_parser,
:config_file, :config
def initialize
@command = nil
@options = Options.new(
"\t", # elimiter:
false, # transpose:
nil, # headers:
false, # pass:
$stderr, # output:
'xyy', # fmt:
false, # progressive:
nil, # encoding:
false, # color_names:
false # debug:
)
@params = Parameters.new
end
def apply_config_file
return if !config_file && find_config_file.nil?
read_config_file
configure
end
def config_file_candidate_paths
# keep the order of the paths
paths = []
paths << ENV['MYYOUPLOTRC'] if ENV['MYYOUPLOTRC']
paths << '.youplot.yml'
paths << '.youplotrc'
if ENV['HOME']
paths << File.join(ENV['HOME'], '.youplotrc')
paths << File.join(ENV['HOME'], '.youplot.yml')
paths << File.join(ENV['HOME'], '.config', 'youplot', 'youplotrc')
paths << File.join(ENV['HOME'], '.config', 'youplot', 'youplot.yml')
end
paths
end
def find_config_file
config_file_candidate_paths.each do |file|
path = File.expand_path(file)
next unless File.exist?(path)
@config_file = path
ENV['MYYOUPLOTRC'] = path
return @config_file
end
nil
end
def read_config_file
require 'yaml'
@config = YAML.load_file(config_file)
end
def configure
option_members = @options.members
param_members = @params.members
# It would be more useful to be able to configure by plot type
config.each do |k, v|
k = k.to_sym
if option_members.include?(k)
@options[k] ||= v
elsif param_members.include?(k)
@params[k] ||= v
else
raise Error, "Unknown option/param in config file: #{k}"
end
end
end
def create_base_parser
OptionParser.new do |parser|
parser.program_name = 'YouPlot'
parser.version = YouPlot::VERSION
parser.summary_width = 23
parser.on_tail('') # Add a blank line at the end
parser.separator('')
parser.on('Common options:')
parser.on('-O', '--pass [FILE]', 'file to output input data to [stdout]',
'for inserting YouPlot in the middle of Unix pipes') do |v|
options[:pass] = v || $stdout
end
parser.on('-o', '--output [FILE]', 'file to output plots to [stdout]',
'If no option is specified, plot will print to stderr') do |v|
options[:output] = v || $stdout
end
parser.on('-d', '--delimiter DELIM', String, 'use DELIM instead of [TAB] for field delimiter') do |v|
options[:delimiter] = v
end
parser.on('-H', '--headers', TrueClass, 'specify that the input has header row') do |v|
options[:headers] = v
end
parser.on('-T', '--transpose', TrueClass, 'transpose the axes of the input data') do |v|
options[:transpose] = v
end
parser.on('-t', '--title STR', String, 'print string on the top of plot') do |v|
params.title = v
end
parser.on('--xlabel STR', String, 'print string on the bottom of the plot') do |v|
params.xlabel = v
end
parser.on('--ylabel STR', String, 'print string on the far left of the plot') do |v|
params.ylabel = v
end
parser.on('-w', '--width INT', Numeric, 'number of characters per row') do |v|
params.width = v
end
parser.on('-h', '--height INT', Numeric, 'number of rows') do |v|
params.height = v
end
border_options = UnicodePlot::BORDER_MAP.keys.join(', ')
parser.on('-b', '--border STR', String, 'specify the style of the bounding box', "(#{border_options})") do |v|
params.border = v.to_sym
end
parser.on('-m', '--margin INT', Numeric, 'number of spaces to the left of the plot') do |v|
params.margin = v
end
parser.on('--padding INT', Numeric, 'space of the left and right of the plot') do |v|
params.padding = v
end
parser.on('-c', '--color VAL', String, 'color of the drawing') do |v|
params.color = v =~ /\A[0-9]+\z/ ? v.to_i : v.to_sym
end
parser.on('--[no-]labels', TrueClass, 'hide the labels') do |v|
params.labels = v
end
parser.on('-p', '--progress', TrueClass, 'progressive mode [experimental]') do |v|
options[:progressive] = v
end
parser.on('-C', '--color-output', TrueClass, 'colorize even if writing to a pipe') do |_v|
UnicodePlot::IOContext.define_method(:color?) { true } # FIXME
end
parser.on('-M', '--monochrome', TrueClass, 'no colouring even if writing to a tty') do |_v|
UnicodePlot::IOContext.define_method(:color?) { false } # FIXME
end
parser.on('--encoding STR', String, 'specify the input encoding') do |v|
options[:encoding] = v
end
# Optparse adds the help option, but it doesn't show up in usage.
# This is why you need the code below.
parser.on('--help', 'print sub-command help menu') do
puts parser.help
exit if YouPlot.run_as_executable?
end
parser.on('--config FILE', 'specify a config file') do |v|
@config_file = v
end
parser.on('--debug', TrueClass, 'print preprocessed data') do |v|
options[:debug] = v
end
# yield opt if block_given?
end
end
def create_main_parser
@main_parser = create_base_parser
main_parser.banner = \
<<~MSG
Program: YouPlot (Tools for plotting on the terminal)
Version: #{YouPlot::VERSION} (using UnicodePlot #{UnicodePlot::VERSION})
Source: https://github.com/red-data-tools/YouPlot
Usage: uplot <command> [options] <in.tsv>
Commands:
barplot bar draw a horizontal barplot
histogram hist draw a horizontal histogram
lineplot line draw a line chart
lineplots lines draw a line chart with multiple series
scatter s draw a scatter plot
density d draw a density plot
boxplot box draw a horizontal boxplot
count c draw a barplot based on the number of
occurrences (slow)
colors color show the list of available colors
General options:
--config print config file info
--help print command specific help menu
--version print the version of YouPlot
MSG
# Help for the main parser is simple.
# Simply show the banner above.
main_parser.on('--help', 'print sub-command help menu') do
show_main_help
end
main_parser.on('--config', 'show config file info') do
show_config_info
end
end
def show_main_help(out = $stdout)
out.puts main_parser.banner
out.puts
exit if YouPlot.run_as_executable?
end
def show_config_info
if ENV['MYYOUPLOTRC']
puts "config file : #{ENV['MYYOUPLOTRC']}"
puts config.inspect
else
puts <<~EOS
Configuration file not found.
It should be a YAML file, like this example:
width : 40
height : 20
By default, YouPlot will look for the configuration file in these locations:
#{config_file_candidate_paths.map { |s| ' ' + s }.join("\n")}
If you have the file elsewhere, you can specify its location with the `MYYOUPLOTRC` environment variable.
EOS
end
exit if YouPlot.run_as_executable?
end
def sub_parser_add_symbol
sub_parser.on_head('--symbol STR', String, 'character to be used to plot the bars') do |v|
params.symbol = v
end
end
def sub_parser_add_xscale
xscale_options = UnicodePlot::ValueTransformer::PREDEFINED_TRANSFORM_FUNCTIONS.keys.join(', ')
sub_parser.on_head('--xscale STR', String, "axis scaling (#{xscale_options})") do |v|
params.xscale = v.to_sym
end
end
def sub_parser_add_canvas
canvas_types = UnicodePlot::Canvas::CANVAS_CLASS_MAP.keys.join(', ')
sub_parser.on_head('--canvas STR', String, 'type of canvas', "(#{canvas_types})") do |v|
params.canvas = v.to_sym
end
end
def sub_parser_add_xlim
sub_parser.on_head('--xlim FLOAT,FLOAT', Array, 'plotting range for the x coordinate') do |v|
params.xlim = v.map(&:to_f)
end
end
def sub_parser_add_ylim
sub_parser.on_head('--ylim FLOAT,FLOAT', Array, 'plotting range for the y coordinate') do |v|
params.ylim = v.map(&:to_f)
end
end
def sub_parser_add_grid
sub_parser.on_head('--[no-]grid', TrueClass, 'draws grid-lines at the origin') do |v|
params.grid = v
end
end
def sub_parser_add_fmt_xyxy
sub_parser.on_head('--fmt STR', String,
'xyxy : header is like x1, y1, x2, y2, x3, y3...',
'xyy : header is like x, y1, y2, y2, y3...') do |v|
options[:fmt] = v
end
end
def sub_parser_add_fmt_yx
sub_parser.on_head('--fmt STR', String,
'xy : header is like x, y...',
'yx : header is like y, x...') do |v|
options[:fmt] = v
end
end
def create_sub_parser
@sub_parser = create_base_parser
sub_parser.banner = \
<<~MSG
Usage: YouPlot #{command} [options] <in.tsv>
Options for #{command}:
MSG
case command
# If you type only `uplot` in the terminal.
# Output help to standard error output.
when nil
show_main_help($stderr)
# Output help to standard output.
when :help
show_main_help
when :barplot, :bar
sub_parser_add_symbol
sub_parser_add_fmt_yx
sub_parser_add_xscale
when :count, :c
sub_parser.on_head('-r', '--reverse', TrueClass, 'reverse the result of comparisons') do |v|
options.reverse = v
end
sub_parser_add_symbol
sub_parser_add_xscale
when :histogram, :hist
sub_parser_add_symbol
sub_parser.on_head('--closed STR', String, 'side of the intervals to be closed [left]') do |v|
params.closed = v
end
sub_parser.on_head('-n', '--nbins INT', Numeric, 'approximate number of bins') do |v|
params.nbins = v
end
when :lineplot, :line, :l
sub_parser_add_canvas
sub_parser_add_grid
sub_parser_add_fmt_yx
sub_parser_add_ylim
sub_parser_add_xlim
when :lineplots, :lines, :ls
sub_parser_add_canvas
sub_parser_add_grid
sub_parser_add_fmt_xyxy
sub_parser_add_ylim
sub_parser_add_xlim
when :scatter, :s
sub_parser_add_canvas
sub_parser_add_grid
sub_parser_add_fmt_xyxy
sub_parser_add_ylim
sub_parser_add_xlim
when :density, :d
sub_parser_add_canvas
sub_parser_add_grid
sub_parser_add_fmt_xyxy
sub_parser_add_ylim
sub_parser_add_xlim
when :boxplot, :box
sub_parser_add_xlim
when :colors, :color, :colours, :colour
sub_parser.on_head('-n', '--names', TrueClass, 'show color names only') do |v|
options[:color_names] = v
end
# Currently it simply displays the configuration file,
# but in the future this may be changed to open a text editor like Vim
# to edit the configuration file.
when :config
show_config_info
else
error_message = "YouPlot: unrecognized command '#{command}'"
raise Error, error_message unless YouPlot.run_as_executable?
warn error_message
exit 1
end
end
def parse_options(argv = ARGV)
begin
create_main_parser.order!(argv)
rescue OptionParser::ParseError => e
warn "YouPlot: #{e.message}"
exit 1 if YouPlot.run_as_executable?
end
@command = argv.shift&.to_sym
begin
create_sub_parser&.parse!(argv)
rescue OptionParser::ParseError => e
warn "YouPlot: #{e.message}"
exit 1 if YouPlot.run_as_executable?
end
begin
apply_config_file
rescue StandardError => e
warn "YouPlot: #{e.message}"
exit 1 if YouPlot.run_as_executable?
end
end
end
end

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
module YouPlot module YouPlot
VERSION = '0.4.6' VERSION = '0.3.5'
end end

View File

@ -1,38 +1,38 @@
sepal_length sepal_length
┌ ┐ ┌ ┐
5.0 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 10.0 5.0 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 10.0
5.1 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 9.0
6.3 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 9.0 6.3 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 9.0
5.7 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■ 8.0 5.1 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 9.0
6.7 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■ 8.0 6.7 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■ 8.0
5.7 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■ 8.0
6.4 ┤■■■■■■■■■■■■■■■■■■■■■■■■ 7.0
5.5 ┤■■■■■■■■■■■■■■■■■■■■■■■■ 7.0 5.5 ┤■■■■■■■■■■■■■■■■■■■■■■■■ 7.0
5.8 ┤■■■■■■■■■■■■■■■■■■■■■■■■ 7.0 5.8 ┤■■■■■■■■■■■■■■■■■■■■■■■■ 7.0
6.4 ┤■■■■■■■■■■■■■■■■■■■■■■■■ 7.0
4.9 ┤■■■■■■■■■■■■■■■■■■■■ 6.0
5.4 ┤■■■■■■■■■■■■■■■■■■■■ 6.0
5.6 ┤■■■■■■■■■■■■■■■■■■■■ 6.0 5.6 ┤■■■■■■■■■■■■■■■■■■■■ 6.0
6.0 ┤■■■■■■■■■■■■■■■■■■■■ 6.0
6.1 ┤■■■■■■■■■■■■■■■■■■■■ 6.0 6.1 ┤■■■■■■■■■■■■■■■■■■■■ 6.0
4.8 ┤■■■■■■■■■■■■■■■■■ 5.0 6.0 ┤■■■■■■■■■■■■■■■■■■■■ 6.0
5.4 ┤■■■■■■■■■■■■■■■■■■■■ 6.0
4.9 ┤■■■■■■■■■■■■■■■■■■■■ 6.0
6.5 ┤■■■■■■■■■■■■■■■■■ 5.0 6.5 ┤■■■■■■■■■■■■■■■■■ 5.0
4.6 ┤■■■■■■■■■■■■■■ 4.0 4.8 ┤■■■■■■■■■■■■■■■■■ 5.0
5.2 ┤■■■■■■■■■■■■■■ 4.0 7.7 ┤■■■■■■■■■■■■■■ 4.0
6.2 ┤■■■■■■■■■■■■■■ 4.0 6.2 ┤■■■■■■■■■■■■■■ 4.0
6.9 ┤■■■■■■■■■■■■■■ 4.0 6.9 ┤■■■■■■■■■■■■■■ 4.0
7.7 ┤■■■■■■■■■■■■■■ 4.0 5.2 ┤■■■■■■■■■■■■■■ 4.0
4.4 ┤■■■■■■■■■■ 3.0 4.6 ┤■■■■■■■■■■■■■■ 4.0
5.9 ┤■■■■■■■■■■ 3.0
6.8 ┤■■■■■■■■■■ 3.0
7.2 ┤■■■■■■■■■■ 3.0 7.2 ┤■■■■■■■■■■ 3.0
4.7 ┤■■■■■■■ 2.0 6.8 ┤■■■■■■■■■■ 3.0
5.9 ┤■■■■■■■■■■ 3.0
4.4 ┤■■■■■■■■■■ 3.0
6.6 ┤■■■■■■■ 2.0 6.6 ┤■■■■■■■ 2.0
4.3 ┤■■■ 1.0 4.7 ┤■■■■■■■ 2.0
4.5 ┤■■■ 1.0
5.3 ┤■■■ 1.0
7.0 ┤■■■ 1.0
7.1 ┤■■■ 1.0
7.3 ┤■■■ 1.0
7.4 ┤■■■ 1.0
7.6 ┤■■■ 1.0
7.9 ┤■■■ 1.0 7.9 ┤■■■ 1.0
7.4 ┤■■■ 1.0
7.3 ┤■■■ 1.0
7.6 ┤■■■ 1.0
7.1 ┤■■■ 1.0
7.0 ┤■■■ 1.0
5.3 ┤■■■ 1.0
4.5 ┤■■■ 1.0
4.3 ┤■■■ 1.0
└ ┘ └ ┘

View File

@ -1,12 +0,0 @@
┌ ┐
-10 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.0
-20 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.0
-30 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.0
-40 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.0
-50 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.0
10 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.0
20 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.0
30 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.0
40 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.0
50 ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1.0
└ ┘

View File

@ -2,7 +2,7 @@
require_relative '../../test_helper' require_relative '../../test_helper'
class ProcessingTest < Test::Unit::TestCase class YouPlotCommandTest < Test::Unit::TestCase
test :count_values do test :count_values do
@m = YouPlot::Backends::Processing @m = YouPlot::Backends::Processing
assert_equal([%i[a b c], [3, 2, 1]], @m.count_values(%i[a a a b b c])) assert_equal([%i[a b c], [3, 2, 1]], @m.count_values(%i[a a a b b c]))

View File

@ -0,0 +1,6 @@
# frozen_string_literal: true
require_relative '../../test_helper'
class YouPlotPlotTest < Test::Unit::TestCase
end

View File

@ -28,23 +28,25 @@ class YouPlotIRISTest < Test::Unit::TestCase
def teardown def teardown
@stderr_file.close @stderr_file.close
@stdout_file.close
end end
def fixture(fname) def fixture(fname)
File.read(File.expand_path("../fixtures/#{fname}", __dir__)) File.read(File.expand_path("../fixtures/#{fname}", __dir__))
end end
test :bar do
YouPlot::Command.new(['bar', '-H', '-d,', '-t', 'IRIS-BARPLOT']).run
assert_equal fixture('iris-barplot.txt'), @stderr_file.read
end
test :barplot do test :barplot do
YouPlot::Command.new(['barplot', '-H', '-d,', '-t', 'IRIS-BARPLOT']).run YouPlot::Command.new(['barplot', '-H', '-d,', '-t', 'IRIS-BARPLOT']).run
assert_equal fixture('iris-barplot.txt'), @stderr_file.read assert_equal fixture('iris-barplot.txt'), @stderr_file.read
end end
# barplot doesn't make sense, but just to make sure it works. test :hist do
YouPlot::Command.new(['hist', '-H', '-d,', '-t', 'IRIS-HISTOGRAM']).run
test :bar do assert_equal fixture('iris-histogram.txt'), @stderr_file.read
YouPlot::Command.new(['bar', '-H', '-d,', '-t', 'IRIS-BARPLOT']).run
assert_equal fixture('iris-barplot.txt'), @stderr_file.read
end end
test :histogram do test :histogram do
@ -52,58 +54,34 @@ class YouPlotIRISTest < Test::Unit::TestCase
assert_equal fixture('iris-histogram.txt'), @stderr_file.read assert_equal fixture('iris-histogram.txt'), @stderr_file.read
end end
test :hist do
YouPlot::Command.new(['hist', '-H', '-d,', '-t', 'IRIS-HISTOGRAM']).run
assert_equal fixture('iris-histogram.txt'), @stderr_file.read
end
# Yeah, lineplot/lineplots don't make sense too.
test :lineplot do
YouPlot::Command.new(['lineplot', '-H', '-d,', '-t', 'IRIS-LINEPLOT']).run
assert_equal fixture('iris-lineplot.txt'), @stderr_file.read
end
test :line do test :line do
YouPlot::Command.new(['line', '-H', '-d,', '-t', 'IRIS-LINEPLOT']).run YouPlot::Command.new(['line', '-H', '-d,', '-t', 'IRIS-LINEPLOT']).run
assert_equal fixture('iris-lineplot.txt'), @stderr_file.read assert_equal fixture('iris-lineplot.txt'), @stderr_file.read
end end
# l is an undocumented alias of lineplot. test :lineplot do
test :l do YouPlot::Command.new(['lineplot', '-H', '-d,', '-t', 'IRIS-LINEPLOT']).run
YouPlot::Command.new(['l', '-H', '-d,', '-t', 'IRIS-LINEPLOT']).run
assert_equal fixture('iris-lineplot.txt'), @stderr_file.read assert_equal fixture('iris-lineplot.txt'), @stderr_file.read
end end
test :lineplots do
YouPlot::Command.new(['lineplots', '-H', '-d,', '-t', 'IRIS-LINEPLOTS']).run
assert_equal fixture('iris-lineplots.txt'), @stderr_file.read
end
test :lines do test :lines do
YouPlot::Command.new(['lines', '-H', '-d,', '-t', 'IRIS-LINEPLOTS']).run YouPlot::Command.new(['lines', '-H', '-d,', '-t', 'IRIS-LINEPLOTS']).run
assert_equal fixture('iris-lineplots.txt'), @stderr_file.read assert_equal fixture('iris-lineplots.txt'), @stderr_file.read
end end
# ls is an undocumented alias of lineplots. test :lineplots do
test :ls do YouPlot::Command.new(['lineplots', '-H', '-d,', '-t', 'IRIS-LINEPLOTS']).run
YouPlot::Command.new(['lines', '-H', '-d,', '-t', 'IRIS-LINEPLOTS']).run
assert_equal fixture('iris-lineplots.txt'), @stderr_file.read assert_equal fixture('iris-lineplots.txt'), @stderr_file.read
end end
test :scatter do
YouPlot::Command.new(['scatter', '-H', '-d,', '-t', 'IRIS-SCATTER']).run
assert_equal fixture('iris-scatter.txt'), @stderr_file.read
end
test :s do test :s do
YouPlot::Command.new(['s', '-H', '-d,', '-t', 'IRIS-SCATTER']).run YouPlot::Command.new(['s', '-H', '-d,', '-t', 'IRIS-SCATTER']).run
assert_equal fixture('iris-scatter.txt'), @stderr_file.read assert_equal fixture('iris-scatter.txt'), @stderr_file.read
end end
test :density do test :scatter do
YouPlot::Command.new(['density', '-H', '-d,', '-t', 'IRIS-DENSITY']).run YouPlot::Command.new(['scatter', '-H', '-d,', '-t', 'IRIS-SCATTER']).run
assert_equal fixture('iris-density.txt'), @stderr_file.read assert_equal fixture('iris-scatter.txt'), @stderr_file.read
end end
test :d do test :d do
@ -111,9 +89,9 @@ class YouPlotIRISTest < Test::Unit::TestCase
assert_equal fixture('iris-density.txt'), @stderr_file.read assert_equal fixture('iris-density.txt'), @stderr_file.read
end end
test :boxplot do test :density do
YouPlot::Command.new(['boxplot', '-H', '-d,', '-t', 'IRIS-BOXPLOT']).run YouPlot::Command.new(['density', '-H', '-d,', '-t', 'IRIS-DENSITY']).run
assert_equal fixture('iris-boxplot.txt'), @stderr_file.read assert_equal fixture('iris-density.txt'), @stderr_file.read
end end
test :box do test :box do
@ -121,20 +99,20 @@ class YouPlotIRISTest < Test::Unit::TestCase
assert_equal fixture('iris-boxplot.txt'), @stderr_file.read assert_equal fixture('iris-boxplot.txt'), @stderr_file.read
end end
# Yeah, lineplot/lineplots don't make sense too. test :boxplot do
# Just checking the behavior. YouPlot::Command.new(['boxplot', '-H', '-d,', '-t', 'IRIS-BOXPLOT']).run
assert_equal fixture('iris-boxplot.txt'), @stderr_file.read
test :c do
YouPlot::Command.new(['count', '-H', '-d,']).run
assert_equal fixture('iris-count.txt'), @stderr_file.read
end end
test :count do # test :c do
YouPlot::Command.new(['c', '-H', '-d,']).run # YouPlot::Command.new(['count', '-H', '-d,']).run
assert_equal fixture('iris-count.txt'), @stderr_file.read # assert_equal fixture('iris-count.txt'), @stderr_file.read
end # end
# Output options. # test :count do
# YouPlot::Command.new(['c', '-H', '-d,']).run
# assert_equal fixture('iris-count.txt'), @stderr_file.read
# end
test :plot_output_stdout do test :plot_output_stdout do
YouPlot::Command.new(['bar', '-o', '-H', '-d,', '-t', 'IRIS-BARPLOT']).run YouPlot::Command.new(['bar', '-o', '-H', '-d,', '-t', 'IRIS-BARPLOT']).run
@ -163,7 +141,7 @@ class YouPlotIRISTest < Test::Unit::TestCase
end end
test :unrecognized_command do test :unrecognized_command do
assert_raise(YouPlot::Parser::Error) do assert_raise(YouPlot::Command::Parser::Error) do
YouPlot::Command.new(['abracadabra', '--hadley', '--wickham']).run YouPlot::Command.new(['abracadabra', '--hadley', '--wickham']).run
end end
assert_equal '', @stderr_file.read assert_equal '', @stderr_file.read

View File

@ -28,31 +28,22 @@ class YouPlotSimpleTest < Test::Unit::TestCase
def teardown def teardown
@stderr_file.close @stderr_file.close
@stdout_file.close
end end
def fixture(fname) def fixture(fname)
File.read(File.expand_path("../fixtures/#{fname}", __dir__)) File.read(File.expand_path("../fixtures/#{fname}", __dir__))
end end
# Single command
# The goal is to verify that the command works without any options.
test :barplot do
assert_raise(ArgumentError) do
YouPlot::Command.new(['barplot']).run
end
end
test :bar do test :bar do
assert_raise(ArgumentError) do assert_raise(ArgumentError) do
YouPlot::Command.new(['bar']).run YouPlot::Command.new(['bar']).run
end end
end end
test :histogram do test :barplot do
YouPlot::Command.new(['histogram']).run assert_raise(ArgumentError) do
assert_equal fixture('simple-histogram.txt'), @stderr_file.read YouPlot::Command.new(['barplot']).run
end
end end
test :hist do test :hist do
@ -60,9 +51,9 @@ class YouPlotSimpleTest < Test::Unit::TestCase
assert_equal fixture('simple-histogram.txt'), @stderr_file.read assert_equal fixture('simple-histogram.txt'), @stderr_file.read
end end
test :lineplot do test :histogram do
YouPlot::Command.new(['lineplot']).run YouPlot::Command.new(['histogram']).run
assert_equal fixture('simple-lineplot.txt'), @stderr_file.read assert_equal fixture('simple-histogram.txt'), @stderr_file.read
end end
test :line do test :line do
@ -70,45 +61,45 @@ class YouPlotSimpleTest < Test::Unit::TestCase
assert_equal fixture('simple-lineplot.txt'), @stderr_file.read assert_equal fixture('simple-lineplot.txt'), @stderr_file.read
end end
test :lineplots do test :lineplot do
assert_raise(YouPlot::Backends::UnicodePlot::Error) do YouPlot::Command.new(['lineplot']).run
YouPlot::Command.new(['lineplots']).run assert_equal fixture('simple-lineplot.txt'), @stderr_file.read
end
end end
test :lines do test :lines do
assert_raise(YouPlot::Backends::UnicodePlot::Error) do assert_raise(YouPlot::Backends::UnicodePlotBackend::Error) do
YouPlot::Command.new(['lines']).run YouPlot::Command.new(['lines']).run
end end
end end
test :scatter do test :lineplots do
assert_raise(YouPlot::Backends::UnicodePlot::Error) do assert_raise(YouPlot::Backends::UnicodePlotBackend::Error) do
YouPlot::Command.new(['scatter']).run YouPlot::Command.new(['lineplots']).run
end end
end end
test :s do test :s do
assert_raise(YouPlot::Backends::UnicodePlot::Error) do assert_raise(YouPlot::Backends::UnicodePlotBackend::Error) do
YouPlot::Command.new(['s']).run YouPlot::Command.new(['s']).run
end end
end end
test :density do test :scatter do
assert_raise(YouPlot::Backends::UnicodePlot::Error) do assert_raise(YouPlot::Backends::UnicodePlotBackend::Error) do
YouPlot::Command.new(['density']).run YouPlot::Command.new(['scatter']).run
end end
end end
test :d do test :d do
assert_raise(YouPlot::Backends::UnicodePlot::Error) do assert_raise(YouPlot::Backends::UnicodePlotBackend::Error) do
YouPlot::Command.new(['d']).run YouPlot::Command.new(['d']).run
end end
end end
test :boxplot do test :density do
YouPlot::Command.new(['boxplot']).run assert_raise(YouPlot::Backends::UnicodePlotBackend::Error) do
assert_equal fixture('simple-boxplot.txt'), @stderr_file.read YouPlot::Command.new(['density']).run
end
end end
test :box do test :box do
@ -116,15 +107,22 @@ class YouPlotSimpleTest < Test::Unit::TestCase
assert_equal fixture('simple-boxplot.txt'), @stderr_file.read assert_equal fixture('simple-boxplot.txt'), @stderr_file.read
end end
test :count do test :boxplot do
YouPlot::Command.new(['c']).run YouPlot::Command.new(['boxplot']).run
assert_equal fixture('simple-count.txt'), @stderr_file.read assert_equal fixture('simple-boxplot.txt'), @stderr_file.read
end end
test :c do # test :c do
YouPlot::Command.new(['count']).run # omit
assert_equal fixture('simple-count.txt'), @stderr_file.read # YouPlot::Command.new(['count', '-H', '-d,']).run
end # assert_equal fixture('iris-count.txt'), @stderr_file.read
# end
# test :count do
# omit
# YouPlot::Command.new(['c', '-H', '-d,']).run
# assert_equal fixture('iris-count.txt'), @stderr_file.read
# end
test :plot_output_stdout do test :plot_output_stdout do
YouPlot::Command.new(['line', '-o']).run YouPlot::Command.new(['line', '-o']).run
@ -155,11 +153,21 @@ class YouPlotSimpleTest < Test::Unit::TestCase
assert_equal fixture('simple-lineplot-xlabel.txt'), @stderr_file.read assert_equal fixture('simple-lineplot-xlabel.txt'), @stderr_file.read
end end
test :line_x do
YouPlot::Command.new(['line', '-x', 'X-LABEL']).run
assert_equal fixture('simple-lineplot-xlabel.txt'), @stderr_file.read
end
test :line_ylabel do test :line_ylabel do
YouPlot::Command.new(['line', '--ylabel', 'Y-LABEL']).run YouPlot::Command.new(['line', '--ylabel', 'Y-LABEL']).run
assert_equal fixture('simple-lineplot-ylabel.txt'), @stderr_file.read assert_equal fixture('simple-lineplot-ylabel.txt'), @stderr_file.read
end end
test :line_y do
YouPlot::Command.new(['line', '-y', 'Y-LABEL']).run
assert_equal fixture('simple-lineplot-ylabel.txt'), @stderr_file.read
end
test :line_width do test :line_width do
YouPlot::Command.new(['line', '--width', '17']).run YouPlot::Command.new(['line', '--width', '17']).run
assert_equal fixture('simple-lineplot-width-17.txt'), @stderr_file.read assert_equal fixture('simple-lineplot-width-17.txt'), @stderr_file.read

View File

@ -10,15 +10,19 @@ Gem::Specification.new do |spec|
spec.summary = 'A command line tool for Unicode Plotting' spec.summary = 'A command line tool for Unicode Plotting'
spec.description = 'A command line tool for Unicode Plotting' spec.description = 'A command line tool for Unicode Plotting'
spec.homepage = 'https://github.com/red-data-tools/YouPlot' spec.homepage = 'https://github.com/kojix2/youplot'
spec.license = 'MIT' spec.license = 'MIT'
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0') spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
spec.files = Dir['*.{md,txt}', '{lib,exe}/**/*'] spec.files = Dir['*.{md,txt}', '{lib,exe}/**/*']
spec.bindir = 'exe' spec.bindir = 'exe'
spec.executables = %w[uplot youplot] spec.executables = %w[uplot youplot]
spec.require_paths = ['lib'] spec.require_paths = ['lib']
spec.add_dependency 'csv' spec.add_runtime_dependency 'unicode_plot'
spec.add_dependency 'unicode_plot', '>= 0.0.5' spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'rubocop'
spec.add_development_dependency 'simplecov'
spec.add_development_dependency 'test-unit'
end end