Skip to content

Commit e6426b7

Browse files
committed
First commit
0 parents  commit e6426b7

30 files changed

Lines changed: 2710 additions & 0 deletions

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*.cr]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
indent_style = space
8+
indent_size = 2
9+
trim_trailing_whitespace = true

.github/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "github-actions"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"

.github/workflows/docs.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: docs
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v6
14+
- uses: crystal-lang/install-crystal@v1
15+
- name: Install dependencies
16+
run: shards install
17+
- name: Generate document
18+
run: crystal docs
19+
- name: Deploy to GitHub Pages
20+
uses: peaceiris/actions-gh-pages@v4
21+
with:
22+
github_token: ${{ secrets.GITHUB_TOKEN }}
23+
publish_dir: ./docs

.github/workflows/test.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
jobs:
9+
test:
10+
strategy:
11+
fail-fast: false
12+
matrix:
13+
os: [ubuntu-latest, macos-latest]
14+
crystal: [latest]
15+
16+
runs-on: ${{ matrix.os }}
17+
18+
steps:
19+
- uses: actions/checkout@v6
20+
21+
- name: Install Crystal
22+
uses: crystal-lang/install-crystal@v1
23+
with:
24+
crystal: ${{ matrix.crystal }}
25+
26+
- name: Install shards
27+
run: shards install --without-development
28+
29+
- name: Run specs
30+
run: crystal spec
31+
32+
- name: Build examples
33+
run: |
34+
find examples -name "*.cr" | sort | while read f; do
35+
echo "Building $f ..."
36+
crystal build --no-codegen "$f"
37+
done
38+
39+
- name: Check formatting
40+
run: crystal tool format --check

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/docs/
2+
/lib/
3+
/bin/
4+
/.shards/
5+
*.dwarf
6+
7+
# Libraries don't need dependency lock
8+
# Dependencies will be locked in applications that use them
9+
/shard.lock

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 kojix2 <2xijok@gmail.com>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# unicode_plot.cr
2+
3+
Unicode terminal plots for Crystal — a port of Julia's [UnicodePlots.jl](https://github.com/JuliaPlots/UnicodePlots.jl).
4+
5+
The code was ported from Julia using an AI tool
6+
7+
## Installation
8+
9+
Add to your `shard.yml`:
10+
11+
```yaml
12+
dependencies:
13+
unicode_plot:
14+
github: kojix2/unicode_plot
15+
```
16+
17+
Then run `shards install`.
18+
19+
## Usage
20+
21+
```crystal
22+
require "unicode_plot"
23+
include UnicodePlot
24+
```
25+
26+
### Line plot
27+
28+
```crystal
29+
x = (0..62).map { |i| i * Math::PI / 31.0 }
30+
y = x.map { |v| Math.sin(v) }
31+
puts lineplot(x, y, title: "sin(x)", xlabel: "x", ylabel: "sin(x)", color: :blue)
32+
```
33+
34+
### Scatter plot
35+
36+
```crystal
37+
x1 = (1..15).map { Random.rand * 3.0 + 1.0 }
38+
y1 = (1..15).map { Random.rand * 3.0 + 1.0 }
39+
x2 = (1..15).map { Random.rand * 3.0 + 6.0 }
40+
y2 = (1..15).map { Random.rand * 3.0 + 6.0 }
41+
42+
p = scatterplot(x1, y1, name: "cluster A", color: :blue,
43+
title: "Two clusters", xlim: {0.0, 11.0}, ylim: {0.0, 11.0})
44+
scatterplot!(p, x2, y2, name: "cluster B", color: :red)
45+
puts p
46+
```
47+
48+
### Bar plot
49+
50+
```crystal
51+
cities = ["Tokyo", "Delhi", "Shanghai", "São Paulo", "Mexico City"]
52+
popmill = [13.96, 16.79, 24.18, 12.33, 9.21]
53+
puts barplot(cities, popmill, title: "City populations", xlabel: "population [mil]")
54+
```
55+
56+
You can also pass a `Hash(String, Float64)` directly (sorted by key):
57+
58+
```crystal
59+
puts barplot({"Ruby" => 95.0, "Python" => 98.0, "Crystal" => 72.0}, title: "Scores")
60+
```
61+
62+
### Histogram
63+
64+
```crystal
65+
data = (1..500).map { Random.rand * 10.0 }
66+
puts histogram(data, title: "Uniform [0, 10)", nbins: 15)
67+
```
68+
69+
### Multiple series / incremental plots
70+
71+
All plot types support a mutating `!` variant for adding series to an existing plot:
72+
73+
```crystal
74+
x = (1..20).map(&.to_f)
75+
p = lineplot(x, x.map { |v| Math.sqrt(v) }, name: "√x", color: :green, title: "Functions")
76+
lineplot!(p, x, x.map { |v| Math.log(v) }, name: "ln(x)", color: :red)
77+
puts p
78+
```
79+
80+
### Options
81+
82+
| Option | Description |
83+
|---|---|
84+
| `title` | Plot title |
85+
| `xlabel` / `ylabel` | Axis labels |
86+
| `xlim` / `ylim` | Axis limits as `{min, max}` tuple |
87+
| `color` | Line/point color (`:red`, `:blue`, `:green`, `:cyan`, `:magenta`, `:yellow`, `:normal`) |
88+
| `name` | Series name (shown in legend) |
89+
| `xscale` / `yscale` | Scale function (`:log2`, `:log10`, or a `Proc(Float64, Float64)`) |
90+
| `width` / `height` | Canvas size in characters |
91+
| `canvas` | Canvas type (`:braille`, `:block`, `:ascii`) |
92+
93+
See `examples/` for runnable demos.
94+
95+
## License
96+
97+
MIT — see [LICENSE](LICENSE).

examples/barplot.cr

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
require "../src/unicode_plot"
2+
3+
include UnicodePlot
4+
5+
# Basic bar plot
6+
cities = ["Tokyo", "Delhi", "Shanghai", "São Paulo", "Mexico City"]
7+
popmill = [13.96, 16.79, 24.18, 12.33, 9.21]
8+
9+
p = barplot(cities, popmill,
10+
title: "City populations", xlabel: "population [mil]")
11+
puts p
12+
puts
13+
14+
# From a Hash (sorted by key automatically)
15+
data = {"Ruby" => 95.0, "Python" => 98.0, "Crystal" => 72.0, "Julia" => 68.0, "Rust" => 85.0}
16+
p = barplot(data, title: "Language popularity index", xlabel: "score", color: :cyan)
17+
puts p
18+
puts
19+
20+
# Gradient bar symbols (sub-character resolution)
21+
langs = ["C", "Go", "Java", "JavaScript", "TypeScript"]
22+
vals = [100.0, 88.5, 75.2, 92.1, 89.4]
23+
p = barplot(langs, vals,
24+
title: "Benchmark scores",
25+
symbols: [' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'],
26+
color: :green)
27+
puts p
28+
puts
29+
30+
# Adding bars to an existing plot with barplot!
31+
p = barplot(["Alpha"], [42.0], title: "Growing chart")
32+
barplot!(p, "Beta", 67.0)
33+
barplot!(p, "Gamma", 31.0, color: :yellow)
34+
puts p

examples/histogram.cr

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
require "../src/unicode_plot"
2+
3+
include UnicodePlot
4+
5+
# Box-Muller transform for normal samples
6+
def normal_sample(rng : Random, mean : Float64 = 0.0, std : Float64 = 1.0) : Float64
7+
u1 = rng.rand
8+
u2 = rng.rand
9+
z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math::PI * u2)
10+
mean + std * z
11+
end
12+
13+
# Normal distribution sample
14+
rng = Random.new(42)
15+
normal = (1..500).map { normal_sample(rng) }
16+
17+
p = histogram(normal, title: "Normal distribution (n=500)", xlabel: "value")
18+
puts p
19+
puts
20+
21+
# Specify bin count
22+
p = histogram(normal, nbins: 20, color: :blue,
23+
title: "Normal distribution (20 bins)")
24+
puts p
25+
puts
26+
27+
# Uniform distribution
28+
uniform = (1..300).map { Random.rand * 10.0 }
29+
p = histogram(uniform, title: "Uniform distribution [0, 10)")
30+
puts p
31+
puts
32+
33+
# Vertical histogram
34+
p = histogram(normal, nbins: 15, vertical: true,
35+
title: "Vertical histogram", ylabel: "count")
36+
puts p

examples/lineplot.cr

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
require "../src/unicode_plot"
2+
3+
include UnicodePlot
4+
5+
# Basic line plot
6+
p = lineplot([1.0, 2.0, 3.0, 4.0, 5.0], [1.0, 4.0, 9.0, 16.0, 25.0],
7+
title: "y = x²", xlabel: "x", ylabel: "y")
8+
puts p
9+
puts
10+
11+
# Sine wave
12+
x = (0..62).map { |i| i * Math::PI / 31.0 }
13+
y = x.map { |v| Math.sin(v) }
14+
p = lineplot(x, y, title: "sin(x)", xlabel: "x", ylabel: "sin(x)", color: :blue)
15+
puts p
16+
puts
17+
18+
# Multiple series with lineplot!
19+
x = (1..20).map(&.to_f)
20+
p = lineplot(x, x.map { |v| Math.sqrt(v) }, name: "√x", color: :green,
21+
title: "Functions", xlabel: "x")
22+
lineplot!(p, x, x.map { |v| Math.log(v) }, name: "ln(x)", color: :red)
23+
puts p
24+
puts
25+
26+
# Plotting a function
27+
p = lineplot(-3.0, 3.0, ->(t : Float64) { Math.exp(-t * t / 2.0) / Math.sqrt(2 * Math::PI) },
28+
title: "Normal distribution", xlabel: "x", ylabel: "pdf")
29+
puts p
30+
puts
31+
32+
# Log scale
33+
x = (1..20).map(&.to_f)
34+
p = lineplot(x, x.map { |v| v * v }, xscale: :log10, yscale: :log10,
35+
title: "Power law (log-log)", xlabel: "x", ylabel: "")
36+
puts p

0 commit comments

Comments
 (0)