plotille 6.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of plotille might be problematic. Click here for more details.
- plotille/__init__.py +41 -0
- plotille/_canvas.py +443 -0
- plotille/_cmaps.py +124 -0
- plotille/_cmaps_data.py +1601 -0
- plotille/_colors.py +379 -0
- plotille/_data_metadata.py +103 -0
- plotille/_dots.py +202 -0
- plotille/_figure.py +982 -0
- plotille/_figure_data.py +295 -0
- plotille/_graphs.py +373 -0
- plotille/_input_formatter.py +251 -0
- plotille/_util.py +92 -0
- plotille/data.py +100 -0
- plotille-6.0.0.dist-info/METADATA +644 -0
- plotille-6.0.0.dist-info/RECORD +16 -0
- plotille-6.0.0.dist-info/WHEEL +4 -0
plotille/_figure_data.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# The MIT License
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2017 - 2025 Tammo Ippen, tammo.ippen@posteo.de
|
|
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
|
|
13
|
+
# all 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
|
|
21
|
+
# THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
"""Data container classes for plotille.
|
|
24
|
+
|
|
25
|
+
Architecture Note:
|
|
26
|
+
------------------
|
|
27
|
+
All input data (X, Y values) is normalized to float immediately upon construction:
|
|
28
|
+
|
|
29
|
+
- Numeric values (int, float) are converted to float
|
|
30
|
+
- Datetime values are converted to timestamps (float)
|
|
31
|
+
- Original type information is preserved in DataMetadata objects
|
|
32
|
+
- This allows type-safe internal operations while maintaining a flexible
|
|
33
|
+
public API
|
|
34
|
+
|
|
35
|
+
The normalization happens in each class's __init__ method using InputFormatter.
|
|
36
|
+
Display formatting (axis labels, etc.) uses the metadata to format values
|
|
37
|
+
correctly for the original type.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from collections.abc import Sequence
|
|
41
|
+
from typing import Literal, final
|
|
42
|
+
|
|
43
|
+
from plotille._canvas import Canvas
|
|
44
|
+
from plotille._colors import ColorDefinition
|
|
45
|
+
from plotille._data_metadata import DataMetadata
|
|
46
|
+
from plotille._input_formatter import InputFormatter
|
|
47
|
+
|
|
48
|
+
from . import Colormap, _cmaps
|
|
49
|
+
from ._util import DataValues, hist
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Plot:
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
X: DataValues,
|
|
56
|
+
Y: DataValues,
|
|
57
|
+
lc: ColorDefinition,
|
|
58
|
+
interp: Literal["linear"] | None,
|
|
59
|
+
label: str | None,
|
|
60
|
+
marker: str | None,
|
|
61
|
+
formatter: InputFormatter | None = None,
|
|
62
|
+
) -> None:
|
|
63
|
+
if len(X) != len(Y):
|
|
64
|
+
raise ValueError("X and Y dim have to be the same.")
|
|
65
|
+
if interp not in ("linear", None):
|
|
66
|
+
raise ValueError('Only "linear" and None are allowed values for `interp`.')
|
|
67
|
+
|
|
68
|
+
self._formatter = formatter if formatter is not None else InputFormatter()
|
|
69
|
+
self.X_metadata = DataMetadata.from_sequence(X)
|
|
70
|
+
self.Y_metadata = DataMetadata.from_sequence(Y)
|
|
71
|
+
self.X = [self._formatter.convert(x) for x in X]
|
|
72
|
+
self.Y = [self._formatter.convert(y) for y in Y]
|
|
73
|
+
|
|
74
|
+
self.lc = lc
|
|
75
|
+
self.interp = interp
|
|
76
|
+
self.label = label
|
|
77
|
+
self.marker = marker
|
|
78
|
+
|
|
79
|
+
def width_vals(self) -> list[float]:
|
|
80
|
+
"""Return X values as floats for limit calculation."""
|
|
81
|
+
return self.X
|
|
82
|
+
|
|
83
|
+
def height_vals(self) -> list[float]:
|
|
84
|
+
"""Return Y values as floats for limit calculation."""
|
|
85
|
+
return self.Y
|
|
86
|
+
|
|
87
|
+
def write(self, canvas: Canvas, with_colors: bool, in_fmt: InputFormatter) -> None:
|
|
88
|
+
from_points = zip(self.X, self.Y, strict=True)
|
|
89
|
+
to_points = zip(self.X, self.Y, strict=True)
|
|
90
|
+
|
|
91
|
+
# remove first point of to_points
|
|
92
|
+
(x0, y0) = next(to_points)
|
|
93
|
+
|
|
94
|
+
color = self.lc if with_colors else None
|
|
95
|
+
|
|
96
|
+
# print first point
|
|
97
|
+
canvas.point(x0, y0, color=color, marker=self.marker)
|
|
98
|
+
|
|
99
|
+
# plot other points and lines
|
|
100
|
+
for (x0, y0), (x, y) in zip(from_points, to_points, strict=False):
|
|
101
|
+
canvas.point(x, y, color=color, marker=self.marker)
|
|
102
|
+
if self.interp == "linear":
|
|
103
|
+
# no marker for interpolated values
|
|
104
|
+
canvas.line(x0, y0, x, y, color=color)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@final
|
|
108
|
+
class Histogram:
|
|
109
|
+
def __init__(self, X: DataValues, bins: int, lc: ColorDefinition) -> None:
|
|
110
|
+
# Normalize data first
|
|
111
|
+
self._formatter = InputFormatter()
|
|
112
|
+
self.X_metadata = DataMetadata.from_sequence(X)
|
|
113
|
+
self.X = [self._formatter.convert(x) for x in X]
|
|
114
|
+
# Histogram Y values are always numeric (frequency counts)
|
|
115
|
+
self.Y_metadata = DataMetadata(is_datetime=False, timezone=None)
|
|
116
|
+
|
|
117
|
+
# Compute histogram on normalized data
|
|
118
|
+
frequencies, buckets = hist(self.X, bins)
|
|
119
|
+
|
|
120
|
+
# Store everything
|
|
121
|
+
self.bins = bins
|
|
122
|
+
self.frequencies = frequencies
|
|
123
|
+
self.buckets = buckets
|
|
124
|
+
self.lc = lc
|
|
125
|
+
|
|
126
|
+
def width_vals(self) -> list[float]:
|
|
127
|
+
"""Return normalized X values as floats."""
|
|
128
|
+
return self.X
|
|
129
|
+
|
|
130
|
+
def height_vals(self) -> list[int]:
|
|
131
|
+
"""Return histogram frequencies."""
|
|
132
|
+
return self.frequencies
|
|
133
|
+
|
|
134
|
+
def write(self, canvas: Canvas, with_colors: bool, in_fmt: InputFormatter) -> None:
|
|
135
|
+
# how fat will one bar of the histogram be
|
|
136
|
+
x_diff = canvas.dots_between(self.buckets[0], 0, self.buckets[1], 0)[0] or 1
|
|
137
|
+
bin_size = (self.buckets[1] - self.buckets[0]) / x_diff
|
|
138
|
+
|
|
139
|
+
color = self.lc if with_colors else None
|
|
140
|
+
for i in range(self.bins):
|
|
141
|
+
# for each bucket
|
|
142
|
+
if self.frequencies[i] > 0:
|
|
143
|
+
for j in range(x_diff):
|
|
144
|
+
# print bar
|
|
145
|
+
x_ = self.buckets[i] + j * bin_size
|
|
146
|
+
|
|
147
|
+
if canvas.xmin <= x_ <= canvas.xmax:
|
|
148
|
+
canvas.line(x_, 0, x_, self.frequencies[i], color=color)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@final
|
|
152
|
+
class Text:
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
X: DataValues,
|
|
156
|
+
Y: DataValues,
|
|
157
|
+
texts: Sequence[str],
|
|
158
|
+
lc: ColorDefinition,
|
|
159
|
+
formatter: InputFormatter | None = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
if len(X) != len(Y) != len(texts):
|
|
162
|
+
raise ValueError("X, Y and texts dim have to be the same.")
|
|
163
|
+
|
|
164
|
+
self._formatter = formatter if formatter is not None else InputFormatter()
|
|
165
|
+
self.X_metadata = DataMetadata.from_sequence(X)
|
|
166
|
+
self.Y_metadata = DataMetadata.from_sequence(Y)
|
|
167
|
+
self.X = [self._formatter.convert(x) for x in X]
|
|
168
|
+
self.Y = [self._formatter.convert(y) for y in Y]
|
|
169
|
+
self.texts = texts
|
|
170
|
+
self.lc = lc
|
|
171
|
+
|
|
172
|
+
def width_vals(self) -> list[float]:
|
|
173
|
+
"""Return X values as floats for limit calculation."""
|
|
174
|
+
return self.X
|
|
175
|
+
|
|
176
|
+
def height_vals(self) -> list[float]:
|
|
177
|
+
"""Return Y values as floats for limit calculation."""
|
|
178
|
+
return self.Y
|
|
179
|
+
|
|
180
|
+
def write(self, canvas: Canvas, with_colors: bool, in_fmt: InputFormatter) -> None:
|
|
181
|
+
points = zip(self.X, self.Y, self.texts, strict=True)
|
|
182
|
+
|
|
183
|
+
color = self.lc if with_colors else None
|
|
184
|
+
|
|
185
|
+
# plot texts with color
|
|
186
|
+
for x, y, text in points:
|
|
187
|
+
canvas.text(x, y, text, color=color)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class Span:
|
|
191
|
+
def __init__(
|
|
192
|
+
self,
|
|
193
|
+
xmin: float,
|
|
194
|
+
xmax: float,
|
|
195
|
+
ymin: float,
|
|
196
|
+
ymax: float,
|
|
197
|
+
lc: ColorDefinition | None = None,
|
|
198
|
+
):
|
|
199
|
+
if not (0 <= xmin <= xmax <= 1):
|
|
200
|
+
raise ValueError(
|
|
201
|
+
"xmin has to be <= xmax and both have to be within [0, 1]."
|
|
202
|
+
)
|
|
203
|
+
if not (0 <= ymin <= ymax <= 1):
|
|
204
|
+
raise ValueError(
|
|
205
|
+
"ymin has to be <= ymax and both have to be within [0, 1]."
|
|
206
|
+
)
|
|
207
|
+
self.xmin: float = xmin
|
|
208
|
+
self.xmax: float = xmax
|
|
209
|
+
self.ymin: float = ymin
|
|
210
|
+
self.ymax: float = ymax
|
|
211
|
+
self.lc: ColorDefinition | None = lc
|
|
212
|
+
|
|
213
|
+
def write(self, canvas: Canvas, with_colors: bool) -> None:
|
|
214
|
+
color = self.lc if with_colors else None
|
|
215
|
+
|
|
216
|
+
# plot texts with color
|
|
217
|
+
xdelta = canvas.xmax_inside - canvas.xmin
|
|
218
|
+
assert xdelta > 0
|
|
219
|
+
|
|
220
|
+
ydelta = canvas.ymax_inside - canvas.ymin
|
|
221
|
+
assert ydelta > 0
|
|
222
|
+
|
|
223
|
+
canvas.rect(
|
|
224
|
+
canvas.xmin + self.xmin * xdelta,
|
|
225
|
+
canvas.ymin + self.ymin * ydelta,
|
|
226
|
+
canvas.xmin + self.xmax * xdelta,
|
|
227
|
+
canvas.ymin + self.ymax * ydelta,
|
|
228
|
+
color=color,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
HeatInput = Sequence[Sequence[float]] | Sequence[Sequence[Sequence[float]]]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@final
|
|
236
|
+
class Heat:
|
|
237
|
+
def __init__(self, X: HeatInput, cmap: str | Colormap | None = None):
|
|
238
|
+
"""Initialize a Heat-class.
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
X: array-like
|
|
243
|
+
The image data. Supported array shapes are:
|
|
244
|
+
- (M, N): an image with scalar data. The values are mapped
|
|
245
|
+
to colors using a colormap. The values have to be in
|
|
246
|
+
the 0-1 (float) range. Out of range, invalid type and
|
|
247
|
+
None values are handled by the cmap.
|
|
248
|
+
- (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
|
|
249
|
+
|
|
250
|
+
The first two dimensions (M, N) define the rows and columns of the image.
|
|
251
|
+
|
|
252
|
+
cmap: cmapstr or Colormap, default: 'viridis'
|
|
253
|
+
The Colormap instance or registered colormap name used
|
|
254
|
+
to map scalar data to colors. This parameter is ignored
|
|
255
|
+
for RGB data.
|
|
256
|
+
"""
|
|
257
|
+
assert len(X)
|
|
258
|
+
assert cmap is None or isinstance(cmap, (str, _cmaps.Colormap))
|
|
259
|
+
len_first = len(X[0])
|
|
260
|
+
assert all(len(x) == len_first for x in X)
|
|
261
|
+
self._X: HeatInput = X
|
|
262
|
+
|
|
263
|
+
if cmap is None:
|
|
264
|
+
cmap = "viridis"
|
|
265
|
+
|
|
266
|
+
if isinstance(cmap, str):
|
|
267
|
+
cmap = _cmaps.cmaps[cmap]()
|
|
268
|
+
self.cmap = cmap
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def X(self) -> HeatInput:
|
|
272
|
+
return self._X
|
|
273
|
+
|
|
274
|
+
def write(self, canvas: Canvas) -> None:
|
|
275
|
+
assert len(self.X)
|
|
276
|
+
assert canvas.height == len(self.X)
|
|
277
|
+
assert canvas.width == len(self.X[0])
|
|
278
|
+
|
|
279
|
+
flat = [x for xs in self.X for x in xs]
|
|
280
|
+
try:
|
|
281
|
+
assert all(len(pixel) == 3 for pixel in flat) # type: ignore[arg-type]
|
|
282
|
+
# assume rgb
|
|
283
|
+
if all(
|
|
284
|
+
isinstance(v, float) and 0 <= v <= 1
|
|
285
|
+
for pixel in flat
|
|
286
|
+
for v in pixel # type: ignore[union-attr]
|
|
287
|
+
):
|
|
288
|
+
# 0 - 1 values => make 0-255 int values
|
|
289
|
+
flat = [ # type: ignore[misc]
|
|
290
|
+
(round(r * 255), round(g * 255), round(b * 255)) for r, g, b in flat
|
|
291
|
+
]
|
|
292
|
+
canvas.image(flat) # type: ignore[arg-type]
|
|
293
|
+
except TypeError:
|
|
294
|
+
# cannot call len on a float
|
|
295
|
+
canvas.image(self.cmap(flat)) # type: ignore[arg-type]
|
plotille/_graphs.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
# The MIT License
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2017 - 2025 Tammo Ippen, tammo.ippen@posteo.de
|
|
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
|
|
13
|
+
# all 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
|
|
21
|
+
# THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
from collections.abc import Sequence
|
|
25
|
+
from datetime import timedelta
|
|
26
|
+
from math import log
|
|
27
|
+
from typing import Literal
|
|
28
|
+
|
|
29
|
+
from ._colors import ColorDefinition, ColorMode, color
|
|
30
|
+
from ._data_metadata import DataMetadata
|
|
31
|
+
from ._figure import Figure
|
|
32
|
+
from ._input_formatter import InputFormatter
|
|
33
|
+
from ._util import DataValue, DataValues
|
|
34
|
+
from ._util import hist as compute_hist
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def hist_aggregated(
|
|
38
|
+
counts: list[int],
|
|
39
|
+
bins: Sequence[float],
|
|
40
|
+
width: int = 80,
|
|
41
|
+
log_scale: bool = False,
|
|
42
|
+
linesep: str = os.linesep,
|
|
43
|
+
lc: ColorDefinition = None,
|
|
44
|
+
bg: ColorDefinition = None,
|
|
45
|
+
color_mode: ColorMode = "names",
|
|
46
|
+
meta: DataMetadata | None = None,
|
|
47
|
+
) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Create histogram for aggregated data.
|
|
50
|
+
|
|
51
|
+
Parameters:
|
|
52
|
+
counts: List[int] Counts for each bucket.
|
|
53
|
+
bins: List[float] Limits for the bins for the provided counts: limits for
|
|
54
|
+
bin `i` are `[bins[i], bins[i+1])`.
|
|
55
|
+
Hence, `len(bins) == len(counts) + 1`.
|
|
56
|
+
width: int The number of characters for the width (columns).
|
|
57
|
+
log_scale: bool Scale the histogram with `log` function.
|
|
58
|
+
linesep: str The requested line separator. default: os.linesep
|
|
59
|
+
lc: ColorDefinition Give the line color.
|
|
60
|
+
bg: ColorDefinition Give the background color.
|
|
61
|
+
color_mode: ColorMode Specify color input mode; 'names' (default), 'byte' or
|
|
62
|
+
'rgb' see plotille.color.__docs__
|
|
63
|
+
meta: DataMetadata | None For conversion of datetime values.
|
|
64
|
+
Returns:
|
|
65
|
+
str: histogram over `X` from left to right.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def _scale(a: int) -> float | int:
|
|
69
|
+
if log_scale and a > 0:
|
|
70
|
+
return log(a)
|
|
71
|
+
return a
|
|
72
|
+
|
|
73
|
+
if meta is None:
|
|
74
|
+
meta = DataMetadata(is_datetime=False)
|
|
75
|
+
|
|
76
|
+
h = counts
|
|
77
|
+
b = bins
|
|
78
|
+
|
|
79
|
+
ipf = InputFormatter()
|
|
80
|
+
h_max = _scale(max(h)) or 1
|
|
81
|
+
max_ = b[-1]
|
|
82
|
+
min_ = b[0]
|
|
83
|
+
# bins are always normalized to float
|
|
84
|
+
delta = max_ - min_
|
|
85
|
+
delta_display = timedelta(seconds=delta) if meta.is_datetime else delta
|
|
86
|
+
|
|
87
|
+
bins_count = len(h)
|
|
88
|
+
|
|
89
|
+
canvas = [" bucket | {} {}".format("_" * width, "Total Counts")]
|
|
90
|
+
lasts = ["", "⠂", "⠆", "⠇", "⡇", "⡗", "⡷", "⡿"]
|
|
91
|
+
for i in range(bins_count):
|
|
92
|
+
height = int(width * 8 * _scale(h[i]) / h_max)
|
|
93
|
+
canvas += [
|
|
94
|
+
"[{}, {}) | {} {}".format(
|
|
95
|
+
ipf.fmt(
|
|
96
|
+
meta.convert_for_display(b[i]),
|
|
97
|
+
delta=delta_display,
|
|
98
|
+
chars=8,
|
|
99
|
+
left=True,
|
|
100
|
+
),
|
|
101
|
+
ipf.fmt(
|
|
102
|
+
meta.convert_for_display(b[i + 1]),
|
|
103
|
+
delta=delta_display,
|
|
104
|
+
chars=8,
|
|
105
|
+
left=False,
|
|
106
|
+
),
|
|
107
|
+
color(
|
|
108
|
+
"⣿" * (height // 8) + lasts[height % 8],
|
|
109
|
+
fg=lc,
|
|
110
|
+
bg=bg,
|
|
111
|
+
mode=color_mode,
|
|
112
|
+
)
|
|
113
|
+
+ color(
|
|
114
|
+
"\u2800" * (width - (height // 8) + int(height % 8 == 0)),
|
|
115
|
+
bg=bg,
|
|
116
|
+
mode=color_mode,
|
|
117
|
+
),
|
|
118
|
+
h[i],
|
|
119
|
+
)
|
|
120
|
+
]
|
|
121
|
+
canvas += ["‾" * (2 * 8 + 2 + 3 + width + 12)]
|
|
122
|
+
return linesep.join(canvas)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def hist(
|
|
126
|
+
X: DataValues,
|
|
127
|
+
bins: int = 40,
|
|
128
|
+
width: int = 80,
|
|
129
|
+
log_scale: bool = False,
|
|
130
|
+
linesep: str = os.linesep,
|
|
131
|
+
lc: ColorDefinition = None,
|
|
132
|
+
bg: ColorDefinition = None,
|
|
133
|
+
color_mode: ColorMode = "names",
|
|
134
|
+
) -> str:
|
|
135
|
+
"""Create histogram over `X` from left to right
|
|
136
|
+
|
|
137
|
+
The values on the left are the center of the bucket, i.e. `(bin[i] + bin[i+1]) / 2`.
|
|
138
|
+
The values on the right are the total counts of this bucket.
|
|
139
|
+
|
|
140
|
+
Parameters:
|
|
141
|
+
X: List[float] The items to count over.
|
|
142
|
+
bins: int The number of bins to put X entries in (rows).
|
|
143
|
+
width: int The number of characters for the width (columns).
|
|
144
|
+
log_scale: bool Scale the histogram with `log` function.
|
|
145
|
+
linesep: str The requested line separator. default: os.linesep
|
|
146
|
+
lc: ColorDefinition Give the line color.
|
|
147
|
+
bg: ColorDefinition Give the background color.
|
|
148
|
+
color_mode: ColorMode Specify color input mode; 'names' (default), 'byte' or
|
|
149
|
+
'rgb' see plotille.color.__docs__
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
str: histogram over `X` from left to right.
|
|
153
|
+
"""
|
|
154
|
+
# Normalize data to float before computing histogram
|
|
155
|
+
formatter = InputFormatter()
|
|
156
|
+
metadata = DataMetadata.from_sequence(X)
|
|
157
|
+
X_floats = [formatter.convert(x) for x in X]
|
|
158
|
+
|
|
159
|
+
counts, bins_list = compute_hist(X_floats, bins)
|
|
160
|
+
|
|
161
|
+
# bins_list are floats, use metadata for display
|
|
162
|
+
return hist_aggregated(
|
|
163
|
+
counts=counts,
|
|
164
|
+
bins=bins_list,
|
|
165
|
+
width=width,
|
|
166
|
+
log_scale=log_scale,
|
|
167
|
+
linesep=linesep,
|
|
168
|
+
lc=lc,
|
|
169
|
+
bg=bg,
|
|
170
|
+
color_mode=color_mode,
|
|
171
|
+
meta=metadata,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def histogram(
|
|
176
|
+
X: DataValues,
|
|
177
|
+
bins: int = 160,
|
|
178
|
+
width: int = 80,
|
|
179
|
+
height: int = 40,
|
|
180
|
+
X_label: str = "X",
|
|
181
|
+
Y_label: str = "Counts",
|
|
182
|
+
linesep: str = os.linesep,
|
|
183
|
+
x_min: DataValue | None = None,
|
|
184
|
+
x_max: DataValue | None = None,
|
|
185
|
+
y_min: DataValue | None = None,
|
|
186
|
+
y_max: DataValue | None = None,
|
|
187
|
+
lc: ColorDefinition = None,
|
|
188
|
+
bg: ColorDefinition = None,
|
|
189
|
+
color_mode: ColorMode = "names",
|
|
190
|
+
) -> str:
|
|
191
|
+
"""Create histogram over `X`
|
|
192
|
+
|
|
193
|
+
In contrast to `hist`, this is the more `usual` histogram from bottom
|
|
194
|
+
to up. The X-axis represents the values in `X` and the Y-axis is the
|
|
195
|
+
corresponding frequency.
|
|
196
|
+
|
|
197
|
+
Parameters:
|
|
198
|
+
X: List[float] The items to count over.
|
|
199
|
+
bins: int The number of bins to put X entries in (columns).
|
|
200
|
+
height: int The number of characters for the height (rows).
|
|
201
|
+
X_label: str Label for X-axis.
|
|
202
|
+
Y_label: str Label for Y-axis. max 8 characters.
|
|
203
|
+
linesep: str The requested line separator. default: os.linesep
|
|
204
|
+
x_min, x_max: float Limits for the displayed X values.
|
|
205
|
+
y_min, y_max: float Limits for the displayed Y values.
|
|
206
|
+
lc: ColorDefinition Give the line color.
|
|
207
|
+
bg: ColorDefinition Give the background color.
|
|
208
|
+
color_mode: ColorMode Specify color input mode; 'names' (default), 'byte' or
|
|
209
|
+
'rgb' see plotille.color.__docs__
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
str: histogram over `X`.
|
|
213
|
+
"""
|
|
214
|
+
fig = Figure()
|
|
215
|
+
fig.width = width
|
|
216
|
+
fig.height = height
|
|
217
|
+
fig.x_label = X_label
|
|
218
|
+
fig.y_label = Y_label
|
|
219
|
+
fig.linesep = linesep
|
|
220
|
+
if x_min is not None:
|
|
221
|
+
fig.set_x_limits(min_=x_min)
|
|
222
|
+
if x_max is not None:
|
|
223
|
+
fig.set_x_limits(max_=x_max)
|
|
224
|
+
if y_min is not None:
|
|
225
|
+
fig.set_y_limits(min_=y_min)
|
|
226
|
+
if y_max is not None:
|
|
227
|
+
fig.set_y_limits(max_=y_max)
|
|
228
|
+
fig.background = bg
|
|
229
|
+
fig.color_mode = color_mode
|
|
230
|
+
|
|
231
|
+
if lc is None and bg is None:
|
|
232
|
+
fig.with_colors = False
|
|
233
|
+
|
|
234
|
+
fig.histogram(X, bins, lc)
|
|
235
|
+
|
|
236
|
+
return fig.show()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def scatter(
|
|
240
|
+
X: DataValues,
|
|
241
|
+
Y: DataValues,
|
|
242
|
+
width: int = 80,
|
|
243
|
+
height: int = 40,
|
|
244
|
+
X_label: str = "X",
|
|
245
|
+
Y_label: str = "Y",
|
|
246
|
+
linesep: str = os.linesep,
|
|
247
|
+
x_min: DataValue | None = None,
|
|
248
|
+
x_max: DataValue | None = None,
|
|
249
|
+
y_min: DataValue | None = None,
|
|
250
|
+
y_max: DataValue | None = None,
|
|
251
|
+
lc: ColorDefinition = None,
|
|
252
|
+
bg: ColorDefinition = None,
|
|
253
|
+
color_mode: ColorMode = "names",
|
|
254
|
+
origin: bool = True,
|
|
255
|
+
marker: str | None = None,
|
|
256
|
+
) -> str:
|
|
257
|
+
"""Create scatter plot with X , Y values
|
|
258
|
+
|
|
259
|
+
Basically plotting without interpolation:
|
|
260
|
+
`plot(X, Y, ... , interp=None)`
|
|
261
|
+
|
|
262
|
+
Parameters:
|
|
263
|
+
X: List[float] X values.
|
|
264
|
+
Y: List[float] Y values. X and Y must have the same number of entries.
|
|
265
|
+
width: int The number of characters for the width (columns) of the
|
|
266
|
+
canvas.
|
|
267
|
+
height: int The number of characters for the hight (rows) of the
|
|
268
|
+
canvas.
|
|
269
|
+
X_label: str Label for X-axis.
|
|
270
|
+
Y_label: str Label for Y-axis. max 8 characters.
|
|
271
|
+
linesep: str The requested line separator. default: os.linesep
|
|
272
|
+
x_min, x_max: float Limits for the displayed X values.
|
|
273
|
+
y_min, y_max: float Limits for the displayed Y values.
|
|
274
|
+
lc: ColorDefinition Give the line color.
|
|
275
|
+
bg: ColorDefinition Give the background color.
|
|
276
|
+
color_mode: ColorMode Specify color input mode; 'names' (default), 'byte' or
|
|
277
|
+
'rgb' see plotille.color.__docs__
|
|
278
|
+
origin: bool Whether to print the origin. default: True
|
|
279
|
+
marker: str Instead of braille dots set a marker char.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
str: scatter plot over `X`, `Y`.
|
|
283
|
+
"""
|
|
284
|
+
return plot(
|
|
285
|
+
X,
|
|
286
|
+
Y,
|
|
287
|
+
width,
|
|
288
|
+
height,
|
|
289
|
+
X_label,
|
|
290
|
+
Y_label,
|
|
291
|
+
linesep,
|
|
292
|
+
None,
|
|
293
|
+
x_min,
|
|
294
|
+
x_max,
|
|
295
|
+
y_min,
|
|
296
|
+
y_max,
|
|
297
|
+
lc,
|
|
298
|
+
bg,
|
|
299
|
+
color_mode,
|
|
300
|
+
origin,
|
|
301
|
+
marker,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def plot(
|
|
306
|
+
X: DataValues,
|
|
307
|
+
Y: DataValues,
|
|
308
|
+
width: int = 80,
|
|
309
|
+
height: int = 40,
|
|
310
|
+
X_label: str = "X",
|
|
311
|
+
Y_label: str = "Y",
|
|
312
|
+
linesep: str = os.linesep,
|
|
313
|
+
interp: Literal["linear"] | None = "linear",
|
|
314
|
+
x_min: DataValue | None = None,
|
|
315
|
+
x_max: DataValue | None = None,
|
|
316
|
+
y_min: DataValue | None = None,
|
|
317
|
+
y_max: DataValue | None = None,
|
|
318
|
+
lc: ColorDefinition = None,
|
|
319
|
+
bg: ColorDefinition = None,
|
|
320
|
+
color_mode: ColorMode = "names",
|
|
321
|
+
origin: bool = True,
|
|
322
|
+
marker: str | None = None,
|
|
323
|
+
) -> str:
|
|
324
|
+
"""Create plot with X , Y values and linear interpolation between points
|
|
325
|
+
|
|
326
|
+
Parameters:
|
|
327
|
+
X: List[float] X values.
|
|
328
|
+
Y: List[float] Y values. X and Y must have the same number of entries.
|
|
329
|
+
width: int The number of characters for the width (columns) of the
|
|
330
|
+
canvas.
|
|
331
|
+
height: int The number of characters for the hight (rows) of the
|
|
332
|
+
canvas.
|
|
333
|
+
X_label: str Label for X-axis.
|
|
334
|
+
Y_label: str Label for Y-axis. max 8 characters.
|
|
335
|
+
linesep: str The requested line separator. default: os.linesep
|
|
336
|
+
interp: Optional[str] Specify interpolation; values None, 'linear'
|
|
337
|
+
x_min, x_max: float Limits for the displayed X values.
|
|
338
|
+
y_min, y_max: float Limits for the displayed Y values.
|
|
339
|
+
lc: ColorDefinition Give the line color.
|
|
340
|
+
bg: ColorDefinition Give the background color.
|
|
341
|
+
color_mode: ColorMode Specify color input mode; 'names' (default), 'byte' or
|
|
342
|
+
'rgb' see plotille.color.__docs__
|
|
343
|
+
origin: bool Whether to print the origin. default: True
|
|
344
|
+
marker: str Instead of braille dots set a marker char for actual
|
|
345
|
+
values.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
str: plot over `X`, `Y`.
|
|
349
|
+
"""
|
|
350
|
+
fig = Figure()
|
|
351
|
+
fig.width = width
|
|
352
|
+
fig.height = height
|
|
353
|
+
fig.x_label = X_label
|
|
354
|
+
fig.y_label = Y_label
|
|
355
|
+
fig.linesep = linesep
|
|
356
|
+
fig.origin = origin
|
|
357
|
+
if x_min is not None:
|
|
358
|
+
fig.set_x_limits(min_=x_min)
|
|
359
|
+
if x_max is not None:
|
|
360
|
+
fig.set_x_limits(max_=x_max)
|
|
361
|
+
if y_min is not None:
|
|
362
|
+
fig.set_y_limits(min_=y_min)
|
|
363
|
+
if y_max is not None:
|
|
364
|
+
fig.set_y_limits(max_=y_max)
|
|
365
|
+
fig.background = bg
|
|
366
|
+
fig.color_mode = color_mode
|
|
367
|
+
|
|
368
|
+
if lc is None and bg is None:
|
|
369
|
+
fig.with_colors = False
|
|
370
|
+
|
|
371
|
+
fig.plot(X, Y, lc, interp, marker=marker)
|
|
372
|
+
|
|
373
|
+
return fig.show()
|