From a8b28ffd6fdff6eae4fd134475e90c9962e1628a Mon Sep 17 00:00:00 2001 From: Savino Piccolomo Date: Sat, 24 Sep 2022 15:51:05 +0100 Subject: [PATCH] added version 2.0.2 --- README.md | 243 ++++++---- plotext/__init__.py | 5 +- plotext/nums.py | 113 ----- plotext/plot.py | 1094 +++++++++++++++++++++++-------------------- plotext/test.py | 0 plotext/ticks.py | 297 ++++++++++++ plotext/utility.py | 180 +++++++ setup.cfg | 7 - setup.py | 2 +- 9 files changed, 1217 insertions(+), 724 deletions(-) delete mode 100644 plotext/nums.py delete mode 100644 plotext/test.py create mode 100644 plotext/ticks.py create mode 100644 plotext/utility.py delete mode 100644 setup.cfg diff --git a/README.md b/README.md index 25651ea..34703d5 100644 --- a/README.md +++ b/README.md @@ -2,164 +2,215 @@ The package **`plotext`** allows to plot data directly on terminal. # Basic Example -You can use `plotext` to plot directly on terminal, as you would normally with `matplotlib`. Here is a basic example on how to use it: +You can use `plotext` to plot data as you would with `matplotlib`. Here is a basic example of a scatter plot: ``` -import plotext.plot as plx -plx.scatter(x, y) -plx.show() +import plotext as plt +plt.scatter(x, y) +plt.show() ``` -where `x` and `y` are the lists for the of points coordinates; optionally, a single `y` list could be provided. Alternatively, you could plot data points with lines connecting them using the `plot` function instead of the `scatter` one. Multiple data set could also be plotted using consecutive `scatter` or `plot` functions. Here is an output example: +where `x` and `y` are the coordinates of the points to be plotted; optionally, a single `y` list could be provided. -![example](https://github.com/piccolomo/plotext/raw/master/images/example.png) +Alternatively, you could plot the lines between each point using the `plot` function instead. +Multiple data set could be plotted using consecutive `scatter` or `plot` functions. Here is a plot example: -Each data point is represented by a character (in this case a `•`). +![example](https://raw.githubusercontent.com/piccolomo/plotext/master/images/example.png) + +Each data point is represented by a character (in this case `•`). -## Installation -To install the latest version of the `plotext` package use the following command: -``` -sudo -H pip install plotext -``` # Parameters -You can personalize the plots in different ways using the `scatter` and `plot` function parameters. Here they are: +You can personalize the plots in many ways using the following parameters (of both the `plot` and `scatter` functions): + +- **xlim** +It sets the minimum and maximum limits of the plot in the x axis. It requires a list of two numbers, where the first sets the left (minimum) limit and the second the right (maximum) limit. If one or both values are not provided, they are calculated automatically. Alternatively use `set_xlim(xlim)` after the scatter function. + +- **ylim** +It sets the minimum and maximum limits of the plot in the y axis. It requires a list of two numbers, where the first sets the lower (minimum) limit and the second the upper (maximum) limit. If one or both values are not provided, they are calculated automatically. Alternatively use `set_ylim(ylim)` after the scatter function. - **cols** -It sets the number of columns of the plot. Only integers are allowed. By default, it is set to the highest value allowed by the the terminal size. Alternatively you could set the number of rows using `set_cols(cols)` after the scatter function. +It sets the number of columns of the plot. By default, it is the highest value allowed by the terminal size. Alternatively use `set_cols(cols)` or `set_canvas_size(cols, rows)` after the scatter function. - **rows** -It sets the number of rows of the plot. Only integers are allowed. By default, it is set to the highest value allowed by the the terminal size. Alternatively you could set the number of columns using `set_rows(rows)` after the `scatter` or `plot` function. +It sets the number of rows of the plot. By default, it is the highest value allowed by the terminal size. Alternatively use `set_rows(rows)` or `set_canvas_size(cols, rows)` after the scatter function. - **force_size** -The plot dimensions are limited by the terminal size, when `force_size `is False and are allowed to be bigger otherwise. The default value is `False`. Alternatively you could set `force_size` using `set_force_size(force_size)` after the `scatter` or `plot` function but before `set_cols(cols)` and `set_rows(rows)`. - -- **xlim** -It sets the minimum and maximum limits of the plot in the `x` axis. It requires a list of two numbers, where the first sets the left (minimum) limit and the second the right (maximum) limit. If one or both values are not provided, they are calculated automatically. Alternatively you could use `set_xlim(xlim)` after the `scatter` or `plot` function. - -- **ylim** -It sets the minimum and maximum limits of the plot in the `y` axis. It requires a list of two numbers, where the first sets the lower (minimum) limit and the second the upper (maximum) limit. If one or both values are not provided, they are calculated automatically. Alternatively you could use `set_ylim(ylim)` after the `scatter` or `plot` function. - -- **point** -When `True`, the plot shows the scatter data points. The default value is `True`. +By default, the plot dimensions are limited by the terminal size. Set force_size to True in order to allow bigger plots. Alternatively use `set_force_size(True)` after the scatter function (but before the functions `set_cols` and `set_rows`, if present). +Note: plots bigger then the terminal size may not be readable. - **point_marker** -It sets the marker used to identify each data point on the plot. Only single characters are allowed (eg: `'*'`). The default value is `'•'`. +It sets the marker used to identify each data point plotted. It should be a single character and it could be a different value for each data set. If an empty string is provided, no data point is plotted. The default value is '•'. This parameter can only be set internally, because it is associated to the current data set plotted. -- **point_color** -It sets the color used for the point marker. Use `get_colors()` to find the available color codes. The default value is `'norm'`. +- **line_marker** +It sets the marker used to identify the lines between two consecutive data points. It should be a single character and it could be a different value for each data set. If an empty string is provided (as by default), no lines are plotted. This parameter can only be set internally, because it is associated to the current data set plotted. -- **line** -When True, the plot shows the lines between each data points. The default value is `False`. +- **canvas_color** +It sets the canvas background color, without affecting the axes color. Alternatively use `set_canvas_color(color)` after the scatter function. Use `get_colors()` to find the available color codes. The default value is 'norm'. -- **line_marker** -It sets the marker used to identify the lines between data points. Only single characters are allowed (eg: `'*')`. The default value is `'•'`. +- **point_color** +It sets the color of the data points. Use `get_colors()` to find the available color codes. The default value is 'norm'. This parameter can only be set internally, because it is associated to the current data set plotted. - **line_color** -It sets the color used for the line marker. Use `get_colors()` to find the available color codes. The default value is `'norm'`. - -- **background** -It sets the plot background color. Use `get_colors()` to find the available color codes. The default value is `'norm'`. Alternatively you could set the background color using `set_background(background)` after the `scatter` or `plot` function. +It sets the color of the lines, if plotted. Use `get_colors()` to find the available color codes. The default value is 'norm'. This parameter can only be set internally, because it is associated to the current data set plotted. - **axes** -When True, the `x` and `y` axes are added to the plot. A list of two Boolean will set the `x` and `y` axes separately (eg: `axes = [True, False]`). The default value is `True`. +When set to True (as by default), the x and y axes are added to the plot. A list of two Booleans will set the x and y axis independently (eg: `axes = [True, False]`). Alternatively, use `set_axes(axes)` after the scatter function. - **axes_color** -It sets the color of the axes, ticks and equations, when present. Use `get_colors()` to find the available color codes. The default value is `'norm'`. Alternatively you could set the axes color using `set_axes_color(axes_color)` after the scatter function. - -- **ticks** -When `True`, the `x` and `y` ticks are added to the respective axes (even when absent). A list of two Boolean will set the `x` and `y` ticks separately (eg: `ticks = [True, False]`). The default value is `True`. +It sets the axes color, without affecting the canvas. The same color is applied to the axes ticks, labels, equations, legend and title, if present. Use `get_colors()` to find the available color codes. The default value is 'norm'. If a list of two colors is provided, the second is interpreted as a background color. Alternatively use `set_axes_color(color)` after the scatter function. -- **spacing** -It sets the spacing between the `x` and `y` ticks. When a list of two numbers is given, the spacing of the `x` and `y` ticks are set separately (eg: `spacing = [5, 8]`). Only positive integers are allowed. The default value is `[10, 5]`. Alternatively you could use `set_spacing(spacing)` after the `scatter` or `plot` function. +- **ticks_number** +It sets the number of data ticks printed on each axis. If a list of two values is provided, the number of ticks for each axis is set independently (eg: `ticks_number = [5, 6]`). If set to 0, no ticks are printed. The default value is 5. Alternatively use `set_ticks_number(num)` after the scatter function. +Note: you could also directly provide all ticks coordinates and labels for each axis using the functions `set_xticks` and `set_yticks`. Access their docstrings for further guidance. In this case the value of `ticks_number` would not affect what is printed. -- **equations** -When `True`, the equations - needed to to find the real `x` and `y` values from the plot coordinates - are added at the end of the plot. The default value is `False`. +- **ticks_length** +It sets the maximum allowed number of characters of all data ticks on each axes. If the number of characters of any of the ticks coordinates is longer then this value, the ticks are re-scaled and an equation would appear at the end of the plot to clarify the conversion. If a list of two values is provided, the ticks length are set independently (eg: `ticks_length = [5, 6]`). If set to 0, no ticks are printed. The default value is 4. Alternatively use `set_ticks_length(num)` after the scatter function. +Note: you could also directly provide all ticks coordinates and labels for each exes using the functions `set_xticks` and `set_yticks`. Access their docstrings for further guidance. In this case the value of `ticks_length` needs to be higher that the maximum number of characters of the chosen ticks, otherwise some of the characters could be cut out of the plot. -- **decimals** -It sets the number of decimal points shown in the equations. Only positive integers are allowed. The default value is `2`. Alternatively you could set the decimal points using `set_decimals(decimals)` after the `scatter` or `plot` function. +- **title** +It sets the title of the plot. Alternatively use `set_title(label)` after the scatter function. +- **xlabel** +It sets the label of the x axis. Alternatively use `set_xlabel(label)` after the scatter function. -## Save Plot -You can save your plot, as a text file, using `plx.savefig(path)` where `path` is the file address where the data will be written. Note that (for now), this function doesn't preserve the plot colors. +- **ylabel** +It sets the label of the y axis. Alternatively use `set_ylabel(label)` after the scatter function. +- **label** +It sets the label of the current data set, which will appear in the legend at the end of the plot. The default value is an empty string. If all labels are an empty string no legend is printed. Alternatively use `set_legend(labels)` to set all labels (as a list of strings) after the scatter function. -## Streaming Data -The following functions are useful for example when continuously plotting a stream of data. -In order to clear the plot canvas use: +## Usefull Functions +- **set_xticks** and **set_yticks** +respectivelly set the ticks on the x and y axis. The ticks should be provided as a list of values. If two lists are provided, the second is intended as the list of labels to be printed at the coordinates given by the first list. Here is an example: ``` -plx.clear_plot() +plt.scatter(data) +plt.set_xticks(xticks, xlabels) +plt.set_yticks(yticks, ylabels) +plt.show() ``` -and to clear the terminal use: +If no list is provided, the ticks will be calculated automatically. If the ticks are not calculated automatically, any value given to the `ticks_number` parameter of the scatter or plot function will not affect the ticks plotted. On the contrary, the value of the `ticks_length` parameter needs to be higher that the maximum number of characters of the chosen ticks (or labels if present), otherwise some or all of the characters could be cut out of the plot. +- **set_legend** +sets the labels of each plot (as a list of strings) to be printed as a legend. If all labels are an empty string, no legend will be printed. Here is an example: ``` -plx.clear_terminal() +plt.scatter(y1) +plt.plot(y2) +plt.set_legend(["signal 1", "signal2"]) +plt.show() ``` -A common problem when plotting streaming data is the screen flickering. In order to remove or reduce this problem use: +Alternatively the labels can be provided directly inside the scatter or plot function. Here is an example: ``` -plx.sleep(time) +plt.scatter(y1, label = "signal 1") +plt.plot(y2, label = "signal 2") +plt.show() ``` -which adds a sleeping time to the computation. An input of, for example, `0.01` would add approximately `0.01` secs to the computation. The `time` parameters will depend on your processor speed and it needs some manual tweaking. -Here is an example of plotting a continuous stream of data: +- **clear_terminal** +It clears the terminal screen and it useful to make the plot cleared as it would be printed at the beginning of a clean terminal. Here is an example: +``` +plt.clear_terminal() +plt.scatter(x, y) +plt.show() +``` +- **clear_plot** +is similar to `cla` and `clf` in `matplotlib`, as it resets all the plot parameters to their default value, including the data coordinates. Here is an example: +``` +plt.scatter(y1, cols = 10) +plt.show() -![stream](https://github.com/piccolomo/plotext/raw/master/images/animation.gif) +plt.clear_plot() +plt.plot(y2, rows = 20, title = "new plot") +plt.show() +``` +which would print two independent plots. Without `clear_plot`, the previous code would result in a single plot including both data sets (y1 and y2). +- **save_fig** +saves your plot as a text file. Here is an example: +``` +plt.scatter(y) +plt.show() +plt.save_fig(path) +``` +where `path` is the file address where the data will be written. Note that (for now), this function doesn't preserve the plot colors. -## Equations -As previously written, you could add two equations at the end of the plot to transform the column and row coordinatesdisplayeddisplayed into real `x` and `y` coordinates. Here is an example of a plot with equations at the end: +- **sleep** +A common problem when plotting streaming data is the screen flickering. In order to remove or reduce this problem use: +``` +plt.sleep(time) +``` +which adds a sleeping time to the computation. An input of, for example, `0.01` would add approximately `0.01` secs to the computation. The `time` parameters will depend on your processor speed and it needs some manual tweaking. -![example_old](https://github.com/piccolomo/plotext/raw/master/images/example_old.png) +Here is an example of plotting a continuous stream of data: -The errors in the equations are due to the fact that the pixel size reduces the resolution of the data displayed. +![stream](https://raw.githubusercontent.com/piccolomo/plotext/master/images/animation.gif) -Alternatively you could access the functions `plx.get_x_from_col(col)` and `plx.get_y_from_row(row)` to make python do the transformation from your chosen column and row to real, respectively, `x` and `y` coordinates. +Plotting the same data using `matplotlib` was roughly 15 to 50 times slower on a linux machine. ## Colors You can access the function `plx.get_colors()` in order to find the available full ground and background color codes. Here is the output for simplicity: +![colors](https://raw.githubusercontent.com/piccolomo/plotext/master/images/colors.png) -![colors](https://github.com/piccolomo/plotext/raw/master/images/colors.png) - -## Test -You can run a simple initial test of your newly installed package, to check that `plotext` works well in your machine. Just use `plx.run_test()` - - -## Version -In order to check the installed version of the package use the command `plx.get_version()` +## Installation +In Windows, to install the latest version of the `plotext` package use this command : +``` +pip install plotext --upgrade +``` +while in Linux: +``` +sudo -H pip install plotext --upgrade +``` +In order to check the version of the installed package use: +``` +import plotext as plt +plt.get_version() +``` ## Other Documentation - -The full documentation of any of the functions shown above could be accessed using commands like these: +Other relevant documentation could be accessed using commands like these: ``` print(plx.scatter.__doc__) print(plx.plot.__doc__) +print(plx.set_xticks.__doc__) +print(plx.set_yticks.__doc__) print(plx.show.__doc__) +print(plx.clear_terminal.__doc__) +print(plx.clear_plot.__doc__) +print(plx.sleep.__doc__) +print(plx.save_fig.__doc__) ``` ### Main Updates: -- `plotext` now works also in **Windows** comand line (CMD) with colors -- `plotext` now works also using Python IDLE3 but with no colors and no adaptive dimensions -- new color codes with **background** codes added -- **`force_size`** option added -- **`savefig`** function added -- **`get_version`** function added -- **`run_test`** function added -- no need for `numpy` or `time` packages -- the code has been updated and it is more legible -- the documentation has been updated -- `equations` options now is set by default to `False` -- When `thick` is `False`, the axes non numerical thicks are also removed -- Removed get functions for plot parameters - - -### Creator -- Author: Savino Piccolomo -- e-mail: piccolomo@gmail.com +- the plot now shows the actual data ticks, which was more complicated then expected as the ticks should adapt to a limited amount of characters available (`ticks_length`). This is a new features and it may require some adjustments. Please communicate any issues regarding data ticks. +- `set_xticks` and `set_yticks` functions added to set the ticks values, directly. +- labels can be added to the axes. +- a title can be added to the plot. +- a legend can be shown when plotting multiple data sets. +- set functions involving a list of two parameters can be used in two different ways. For example `set_xlim([xmin, xmax])` is equivalent to `set_xlim(xmin, xmax)`. +- `point` and `line` parameters removed. To remove the data points (or lines), it is sufficient to set the point marker (or line marker) to an empty string. +- `background` parameter removed and `canvas_color` takes his place, but can set only the canvas background color. +- `axes_color` could now also be a list of two colors where the second sets the axes background color. +- `spacing` parameter removed, `ticks_number` takes his place. +- `equations `parameter removed as the equations will be printed automatically if needed. +- `decimals` parameter removed. +- code restructured and revised. + + +### Future Plans: +Any help on the following or new ideas is more then welcomed. + +- creation of an *histogram plot*. +- data ticks for time based data. +- color and terminal size support for IDLE python editor and compiler. +- same as previous point but for Spider. +- add new colors using ansi codes. +- saving text files with color. +- adding an optional grid (not sure how). ### Contributors -- Dominik Wetzel, Schmetzler for the Windows support -- Dominik Wetzel for force_size idea -- Kexul, Madrian for their inputs regarding plotting multiple lines +- Dominik Wetzel, Schmetzler for the Windows support. +- Dominik Wetzel for force_size idea. +- Kexul, Madrian for their inputs regarding plotting multiple lines. \ No newline at end of file diff --git a/plotext/__init__.py b/plotext/__init__.py index 76d7eaf..d129999 100644 --- a/plotext/__init__.py +++ b/plotext/__init__.py @@ -1,5 +1,6 @@ -"""The plotext package allows you to plot data directly on terminal.""" +"""plotext plots data on terminal.""" from plotext.plot import * + name = "plotext" -__version__ = "1.0.11" \ No newline at end of file +__version__ = "2.0.2" \ No newline at end of file diff --git a/plotext/nums.py b/plotext/nums.py deleted file mode 100644 index 0933992..0000000 --- a/plotext/nums.py +++ /dev/null @@ -1,113 +0,0 @@ -def decimal_characters(characters = 3): - dec_characters = characters - 2 - return dec_characters - -def decimal_points(characters = 3): - dec_characters = decimal_characters(characters) - if characters == 1 or characters == 2: - dec_characters = 0 - return dec_characters - -def delta(level = 0, characters = 3): - dec_characters = decimal_characters(characters) - if characters == 1: - dec_characters = 0 - return 10 ** (level - dec_characters) - -def levels(characters = 3): - dec_characters = decimal_characters(characters) - if characters == 1: - dec_characters = 0 - return dec_characters + 1 - -def lower_bound(level = 0, characters = 3): - dec_characters = decimal_characters(characters) - if level == 0: - return 0 - elif level >= 1: - return 10 ** level - -def upper_bound(level = 0, characters = 3): - dec_characters = decimal_characters(characters) - if level < dec_characters : - return 10 ** (level + 1) - delta(level, dec_characters) - elif level == dec_characters or characters == 1: - return 10 ** (dec_characters + 2) - 1 - -def steps(level = 0, characters = 3): - dec_characters = decimal_characters(characters) - if characters == 1: - dec_characters = 0 - if characters == 2: - dec_characters = 1 - if level == 0: - return 10 ** (dec_characters + 1) - elif 0 < level < dec_characters: - return 9 * 10 ** dec_characters - elif level == dec_characters : - return 99 * 10 ** dec_characters - -def signature(data): - sign = 1 - for i in range(len(data)): - sign = int(sign and data[i] >= 0) - if sign == 0: - sign = -1 - return sign - -def round_dec(num, decimal_points): - num_rounded = round(num * 10 ** decimal_points) / 10 ** decimal_points - if num_rounded - round(num_rounded) == 0.0: - num_rounded = round(num_rounded) - return num_rounded - -def numbers(characters = 3): - tot = [] - dec_points = decimal_points(characters) - for level in range(levels(characters)): - new = [lower_bound(level, characters) + l * delta(level, characters) for l in range(steps(level, characters))] - new = [round_dec(el, dec_points) for el in new] - tot += new - return tot - - - -m, M = 1, 10 -n = 2 -data = [(M - m) / (n - 1) * k + m for k in range(n)] -print("data", data, "\n") - -characters = 4 -sign = signature(data) -# dec_characters = decimal_characters(characters, sign) -# #print(dec_characters) - -x_first, x_last = min(data), max(data) - -# n = len(data) -# for level in range(dec_characters + 1): -# d = delta(level, characters, sign) -# lower = lower_bound(level, characters, sign) -# lvs = levels(level, characters, sign) -# #m = delta * (n - 1) / (x_last - x_first) -# #k0 = x_first / delta -# #print(delta, " ",k0) -# #k0=(a * m - li) / dni -# # b = li - a * m + round(k0) * dni -# # b_list.append(b) -# e = 1 -# if x_first != 0 and lower != 0: -# e = math.log(lower / x_first, 10) -# e = round(e) -# k0 = ((10 ** e) * x_first - lower) / d -# t = ((10 ** e) * x_last - lower - d * k0) / (d * (n - 1)) - -# m = (t * d) * (n - 1) / (x_last - x_first) -# c = lower + d * k0 - m * x_first -# test = lower + d * (t * n + k0) < lower + d * (lvs - 1) -# #if not test: -# data_new = [cut(lower + d * (t * k + k0)) for k in range(n)] -# print(level, test, data_new, k0, t, " - ", 1/m, -c/m) -# # data_new = [round(a * el + b, 14) for el in data] -# #print(i) -# #print(data_new) diff --git a/plotext/plot.py b/plotext/plot.py index 080f716..d0e4de1 100644 --- a/plotext/plot.py +++ b/plotext/plot.py @@ -1,175 +1,110 @@ # /usr/bin/env python3 # -*- coding: utf-8 -*- -import sys -import os - ############################################## -########### Basic Functions ############ +########### Initialization ############# ############################################## - -def scatter(*args, **kwargs): - _set_platform() - _set_data(*args) - - _set_axes(kwargs.get("axes")) - _set_ticks(kwargs.get("ticks")) - _set_equations(kwargs.get("equations")) - - _set_terminal_size() - _set_cols_max() - _set_rows_max() - - set_force_size(kwargs.get("force_size")) - set_cols(kwargs.get("cols")) - set_rows(kwargs.get("rows")) - - set_xlim(kwargs.get("xlim")) - set_ylim(kwargs.get("ylim")) - - _set_point(kwargs.get("point")) - _set_point_marker(kwargs.get("point_marker")) - _set_point_color(kwargs.get("point_color")) - _set_line(kwargs.get("line")) - _set_line_marker(kwargs.get("line_marker")) - _set_line_color(kwargs.get("line_color")) - set_background(kwargs.get("background")) - - set_axes_color(kwargs.get("axes_color")) - set_spacing(kwargs.get("spacing")) - set_decimals(kwargs.get("decimals")) - -def plot(*args, force_size = None, cols = None, rows = None, xlim = None, ylim = None, point = False, point_marker = None, point_color = None, line = True, line_marker = None, line_color = None, background = None, axes = None, axes_color = None, ticks = None, spacing = None, equations = None, decimals = None): - - scatter(*args, cols = cols, rows = rows, xlim = xlim, ylim = ylim, point = point, point_marker = point_marker, point_color = point_color, line = line, line_marker = line_marker, line_color = line_color, background = background, axes = axes, ticks = ticks, axes_color = axes_color, spacing = spacing, equations = equations, decimals = decimals) - -def show(): - _set_xlim() - _set_ylim() - _set_grid() - _add_yaxis() - _add_xaxis() - _set_canvas() - _add_equations() - _print_canvas() - #clear_plot() - -def clear_terminal(): - _print('\033c') - -def clear_plot(): - _vars.__init__() +import sys as _sys +import os as _os +_sys.path.append(_os.path.abspath(_os.path.dirname(__file__))) +import utility as ut -def sleep(time): - [i for i in range(int(time*15269989))] - -def savefig(path): - with open(path , "w+", encoding = "utf-8") as file: - file.write(_remove_color(_get_canvas())) - print("plot saved as", path) - -def get_colors(): - fg_colors = [_set_color(fg_c, fg_c, "norm") for fg_c in _fg_colors] - _print("\nFullground colors: " + ", ".join(fg_colors)) - bg_colors = [_set_color(bg_c, "norm", bg_c) for bg_c in _bg_colors] - _print("\n\nBackground colors: " + ", ".join(bg_colors) + '\n') - -def get_version(): - init_path = "__init__.py" - here = os.path.abspath(os.path.dirname(__file__)) - with open(os.path.join(here, init_path), 'r') as fp: - lines = fp.read() - for line in lines.splitlines(): - if line.startswith('__version__'): - delim = '"' if '"' in line else "'" - return line.split(delim)[1] - else: - print("Unable to find version string.") - -def get_x_from_col(col=0): - return 1. * _vars.dx * col + _vars.xmin + _vars.dx/2. - -def get_y_from_row(row=0): - return 1. * _vars.dy * row + _vars.ymin + _vars.dy/2. - -def run_test(): - clear_terminal() - l = 7 - y1 = list(range(7)) + list(range(7, 0, -1)) - y2 = list(range(7, 0, -1)) + list(range(7)) - scatter(y1, point_color = "red", point_marker = "o") - plot(y2, line_color = "blue", line_marker = '=', ticks=1, axes=1) - #set_force_size(True) - set_cols(300) - set_rows(300) - set_xlim([-1, 2 * l + 1]) - set_ylim([-1, l + 1]) - set_spacing([10, 5]) - set_axes_color("green") - set_background("black") - show() - #savefig(r"test.txt") - clear_plot() - -############################################## -######### Internal Variables ########### -############################################## - -class _vars_class(): +class _variables(): def __init__(self): - self.ip = 6 #internal precision self.x = [] self.y = [] - self.xmin = 0 - self.xmax = 0 - self.dx = 0 - self.ymin = 0 - self.ymax = 0 - self.dy = 0 - - self.cols_term = None - self.rows_term = None - self.cols_max = None - self.rows_max = None - self.force_size = False + + self.plot_xmin = None + self.plot_xmax = None + self.plot_ymin = None + self.plot_ymax = None + + self.force_size = [False, False] self.cols = None self.rows = None - - self.point = [] + self.point_marker = [] - self.point_color = [] - self.line = [] self.line_marker = [] + + self.canvas_color = [] + self.point_color = [] self.line_color = [] - self.background = "norm" self.axes = [True, True] - self.axes_color = "norm" - self.ticks = [True, True] - self.spacing = [10, 5] - self.equations = False - self.decimals = 2 + self.axes_color = [] + self.ticks_number = [5, 5] + self.ticks_length = [4, 4] + + self.title = "" + self.xlabel = "" + self.ylabel = "" + + self.label = [] + + # Constants + self.default_marker = "•" + self.empty_marker = "" + self.xticks = None + self.yticks = None + self.abx = [1, 0] + self.aby = [1, 0] + self.grid = [[]] - self.no_color = False self.canvas = "" + + +var = _variables() + +def _set_platform(): + var.platform = "linux" + var.nocolor = False + if "win" in _sys.platform: + var.platform = "windows" + import subprocess + subprocess.call('', shell = True) + if ('idlelib.run' in _sys.modules): + var.nocolor = True + +_set_platform() +if var.platform == "windows": + from shutil import get_terminal_size as get_terminal_size_windows -_vars = _vars_class() ############################################## -########## Called Functions ########### +######### Scatter Function ############# ############################################## +def scatter(*args, **kwargs): + _set_data(*args) + set_xlim(kwargs.get("xlim")) + set_ylim(kwargs.get("ylim")) -def _set_platform(): - if "win" in sys.platform: - import subprocess - subprocess.call('', shell = True) - if ('idlelib.run' in sys.modules): - _vars.no_color = True - # _vars.force_size = True + set_force_size(kwargs.get("force_size")) + set_cols(kwargs.get("cols")) + set_rows(kwargs.get("rows")) + + _set_point_marker(kwargs.get("point_marker")) + _set_line_marker(kwargs.get("line_marker")) + + set_canvas_color(kwargs.get("canvas_color")) + _set_point_color(kwargs.get("point_color")) + _set_line_color(kwargs.get("line_color")) + + set_axes(kwargs.get("axes")) + set_axes_color(kwargs.get("axes_color")) -def _print(string): - sys.stdout.write(string) + set_ticks_number(kwargs.get("ticks_number")) + set_ticks_length(kwargs.get("ticks_length")) + set_title(kwargs.get("title")) + set_xlabel(kwargs.get("xlabel")) + set_ylabel(kwargs.get("ylabel")) + + _set_label(kwargs.get("label")) + + +############################################## +###### Scatter Called Functions ####### +############################################## def _set_data(*args): if len(args) == 0: x, y = [], [] @@ -179,401 +114,578 @@ def _set_data(*args): else: x = args[0] y = args[1] + x, y = list(x), list(y) length = min(len(x), len(y)) if len(x) != len(y): x = x[ : length] y = y[ : length] - _vars.x.append(x) - _vars.y.append(y) - -def _set_axes(axes = None): - axes =_set_var_if_none(axes, [True, True]) - if type(axes) == list: - axes = [axes[0], axes[1]] - else: - axes = [axes, axes] - _vars.axes = axes - -def _set_var_if_none(var, value): - if var == None: - return value - else: - return var - -def _set_ticks(ticks = None): - ticks =_set_var_if_none(ticks, [True, True]) - if type(ticks) == list: - ticks = [ticks[0], ticks[1]] - else: - ticks = [ticks, ticks] - _vars.ticks = ticks - -def _set_equations(equations = None): - _vars.equations =_set_var_if_none(equations, False) - -def _set_terminal_size(): - _vars.cols_term, _vars.rows_term = get_terminal_size() - -def get_terminal_size(): - if "win" in sys.platform: - import shutil - return shutil.get_terminal_size() - elif 'idlelib.run' in sys.modules: - return [185, 45] - else: - return os.get_terminal_size() + var.x.append(x) + var.y.append(y) + + + +def set_xlim(*plot_xlim): + plot_xmin, plot_xmax = ut.set_vars(plot_xlim) + if plot_xmin != None: + var.plot_xmin = plot_xmin + if plot_xmax != None: + var.plot_xmax = plot_xmax + var.plot_xlim = [var.plot_xmin, var.plot_xmax] + +def set_ylim(*plot_ylim): + plot_ymin, plot_ymax = ut.set_vars(plot_ylim) + if plot_ymin != None: + var.plot_ymin = plot_ymin + if plot_ymax != None: + var.plot_ymax = plot_ymax + var.plot_ylim = [var.plot_ymin, var.plot_ymax] + +def set_force_size(*force_size): + force_xsize, force_ysize = ut.set_vars(force_size) + if force_xsize != None: + var.force_size[0] = bool(force_xsize) + if force_ysize != None: + var.force_size[1] = bool(force_ysize) -def _set_cols_max(): - _vars.cols_max = _vars.cols_term - 2 * _vars.ticks[1] - 1 * _vars.axes[1] - 0 - -def _set_rows_max(): - _vars.rows_max = _vars.rows_term - 1 * _vars.ticks[0] - 1 * _vars.axes[0] - 2 * _vars.equations - 2 - -def set_force_size(force_size = False): - _vars.force_size = _set_var_if_none(force_size, False) - def set_cols(cols = None): - cols = _set_var_if_none(cols, _vars.cols_max) - if cols <= 0: - cols = 1 - if cols > _vars.cols_max and not _vars.force_size: - _vars.cols = _vars.cols_max - else: - _vars.cols = max(1, abs(int(cols))) + var.cols = cols def set_rows(rows = None): - rows = _set_var_if_none(rows, _vars.rows_max) - if rows <= 0: - rows = 1 - if rows > _vars.rows_max and not _vars.force_size: - _vars.rows = _vars.rows_max - else: - _vars.rows = max(1, abs(int(rows))) + var.rows = rows + +def _set_point_marker(point_marker = None): + if point_marker == None: + point_marker = var.default_marker + var.point_marker.append(point_marker[0:1]) -def set_xlim(xlim = None): - _vars.xmin, _vars.xmax = _set_var_if_none(xlim, [None, None]) +def _set_line_marker(line_marker = None): + if line_marker == None: + line_marker = var.empty_marker + var.line_marker.append(line_marker[0:1]) -def set_ylim(ylim = None): - _vars.ymin, _vars.ymax = _set_var_if_none(ylim, [None, None]) +def set_canvas_color(canvas_color = None): + if canvas_color == None: + canvas_color = "norm" + var.canvas_color = canvas_color -def _set_point(point = None): - _vars.point.append(_set_var_if_none(point, True)) +def _set_point_color(point_color = None): + if point_color == None: + point_color = "norm" + var.point_color.append(point_color) -def _set_point_marker(point_marker = None): - point_marker = _set_var_if_none(point_marker, "•") - _vars.point_marker.append(point_marker[0]) +def _set_line_color(line_color = None): + if line_color == None: + line_color = "norm" + var.line_color.append(line_color) + +def set_axes(*axes): + xaxis, yaxis = ut.set_vars(axes) + if xaxis != None: + var.axes[0] = bool(xaxis) + if yaxis != None: + var.axes[1] = bool(yaxis) + +def set_axes_color(*axes_color): + axes_color = ut.set_vars(axes_color, "norm") + var.axes_color = axes_color + +def set_ticks_number(*ticks_number): + xticks_number, yticks_number = ut.set_vars(ticks_number) + if xticks_number != None: + var.ticks_number[0] = xticks_number + if yticks_number != None: + var.ticks_number[1] = yticks_number + +def set_ticks_length(*ticks_length): + xticks_length, yticks_length = ut.set_vars(ticks_length) + if xticks_length != None: + var.ticks_length[0] = xticks_length + if yticks_length != None: + var.ticks_length[1] = yticks_length + +def set_title(label = None): + if label == None: + label = "" + var.title = label + +def set_xlabel(label = None): + if label == None: + label = "" + var.xlabel = label + +def set_ylabel(label = None): + if label == None: + label = "" + var.ylabel = label + +def _set_label(label = None): + if label == None: + label = "" + var.label.append(label) -def _set_point_color(point_color = None): - _vars.point_color.append(_set_var_if_none(point_color, "norm")) -def _set_line(line = None): - _vars.line.append(_set_var_if_none(line, False)) +############################################## +######## Other Set Functions ########## +############################################## +def set_canvas_size(*size): + cols, rows = ut.set_vars(size) + set_cols(cols) + set_rows(rows) -def _set_line_marker(line_marker = None): - line_marker = _set_var_if_none(line_marker, "•") - _vars.line_marker.append(line_marker[0]) +def set_legend(legend = None): + if legend == None: + legend = [None] * len(var.legend) + var.label = legend -def _set_line_color(line_color = None): - _vars.line_color.append(_set_var_if_none(line_color, "norm")) +############################################## +############ Show Function ############ +############################################## +def show(): + _set_terminal_size() + _set_cols_max() + _set_rows_max() + _set_cols() + _set_rows() -def set_background(background = None): - _vars.background = _set_var_if_none(background, "norm") + _set_data_lim() + _set_xlim() + _set_ylim() -def set_axes_color(axes_color = None): - _vars.axes_color = _set_var_if_none(axes_color, "norm") + _set_grid() + + _get_yticks() + _get_xticks() + _add_yaxis() + _add_xaxis() -def set_spacing(spacing = None): - spacing = _set_var_if_none(spacing, [10, 5]) - if type(spacing) == list: - spacing = [spacing[0], spacing[1]] - else: - spacing = [spacing, spacing] - _vars.spacing = spacing + _set_canvas() + + _add_title() + _add_axes_labels() + _add_legend() + _add_equations() -def set_decimals(decimals = None): - _vars.decimals = _set_var_if_none(decimals, 2) + _print_canvas() + #clear_plot() + +############################################## +######## Show Called Functions ######## +############################################## +def _set_terminal_size(): + var.cols_term, var.rows_term = get_terminal_size() + +def _set_cols_max(): + var.cols_yaxis = int(var.axes[1]) + var.cols_yticks = var.ticks_length[1] * bool(var.ticks_number[1]) + var.cols_yticks += bool(var.ticks_length[1] *var.ticks_number[1]) + var.cols_max = var.cols_term - var.cols_yaxis - var.cols_yticks + +def _set_rows_max(): + var.rows_xaxis = int(var.axes[0]) + var.rows_xticks = bool(var.ticks_number[0] * var.ticks_length[0]) + var.rows_equations = 1 + var.rows_legend = not (var.label == [""] * len(var.label)) + var.rows_axes_labels = not (var.xlabel == "") + var.rows_title = not (var.title == "") + var.rows_max = var.rows_term - var.rows_xaxis - var.rows_xticks - var.rows_equations - var.rows_legend - var.rows_axes_labels - var.rows_title - 1 + +def _set_cols(): + cols = var.cols + if cols == None: + cols = var.cols_max + cols = abs(int(cols)) + if cols > var.cols_max and not var.force_size[0]: + cols = var.cols_max + var.cols = cols + var.cols_plot = var.cols + var.cols_yaxis + var.cols_yticks + +def _set_rows(): + rows = var.rows + if rows == None: + rows = var.rows_max + rows = abs(int(rows)) + if rows > var.rows_max and not var.force_size[1]: + rows = var.rows_max + var.rows = rows + var.rows_plot = var.rows + var.rows_xaxis + var.rows_xticks + var.rows_equations + var.rows_legend + var.rows_axes_labels + +def _set_data_lim(): + var.xmin = min(map(min, var.x)) + var.xmax = max(map(max, var.x)) + var.xlim = [var.xmin, var.xmax] + + var.ymin = min(map(min, var.y)) + var.ymax = max(map(max, var.y)) + var.ylim = [var.ymin, var.ymax] def _set_xlim(xlim = None): - _vars.xmin, _vars.xmax =_set_lim(_vars.x, _vars.xmin, _vars.xmax, _vars.cols) - _vars.dx = 1. * (_vars.xmax - _vars.xmin) / _vars.cols - _vars.dx = round(_vars.dx, _vars.ip) - -# the set minimum (maximum) value should be inside the first (last) data bin -# this changes the actual minimum (maximum) value by a bin_offset -def _set_lim(z = [], zmin = None, zmax = None, bins = 2): - dzmin, dzmax = zmin == None, zmax == None - zmin = _set_var_if_none(zmin, min(map(min, z))) - zmax = _set_var_if_none(zmax, max(map(max, z))) - if zmin == zmax: - zm = [0.5 * zmin, 1.5 * zmax] - zm.sort() - zmin, zmax = zm - if bins - 1 == 0: - bins += 1 - dz = (zmax - zmin) / (bins - 1) - return zmin - dzmin * dz / 2, zmax + dzmax * dz / 2 + var.plot_xlim = ut.set_lim(var.xlim, var.plot_xlim, var.cols) + var.dx = (var.plot_xlim[1] - var.plot_xlim[0]) / var.cols def _set_ylim(ylim = None): - _vars.ymin, _vars.ymax = _set_lim(_vars.y, _vars.ymin, _vars.ymax, _vars.rows) - _vars.dy = 1.*(_vars.ymax - _vars.ymin) / _vars.rows - _vars.dy = round(_vars.dy, _vars.ip) - -def _set_grid(): - space = _set_color(" ", background = _vars.background) - _vars.grid = [[space for c in range(_vars.cols)] for r in range(_vars.rows)] - for s in range(len(_vars.x)): - if _vars.line[s]: - _add_to_grid(*_get_line(_vars.x[s], _vars.y[s]), _vars.line_marker[s], _vars.line_color[s]) - if _vars.point[s]: - _add_to_grid(_vars.x[s], _vars.y[s], _vars.point_marker[s], _vars.point_color[s]) - -_fg_colors = ['norm', 'black', 'gray', 'red', 'green', 'yellow', 'orange', 'blue', 'violet', 'cyan', 'bold'] -_fg_color_codes = [0, 30, 2, 91, 92, 93, 33, 94, 95, 96, 1] -_bg_colors = ['norm', 'black', 'gray', 'red', 'green', 'yellow', 'orange', 'blue', 'violet', 'cyan', 'white'] -_bg_color_codes = [28, 40, 100, 41, 42, 103, 43, 44, 45, 106, 47] + var.plot_ylim = ut.set_lim(var.ylim, var.plot_ylim, var.rows) + var.dy = (var.plot_ylim[1] - var.plot_ylim[0]) / var.rows -# it applies the proper color codes to a string -def _set_color(text = "", color = "norm", background = "norm"): - code = '\033[' - if type(color) == str: - for c in range(len(_fg_colors)): - if color == _fg_colors[c]: - code += str(_fg_color_codes[c]) - code += 'm' - code += '\033[' - if type(background) == str: - for c in range(len(_bg_colors)): - if background == _bg_colors[c]: - code += str(_bg_color_codes[c]) - code += 'm' - return code + text + '\033[0m' - -def _add_to_grid(x, y, marker, color): - for n in range(len(x)): - c = int(round((x[n] - _vars.xmin) / _vars.dx, _vars.ip)) - r = int(round((y[n] - _vars.ymin) / _vars.dy, _vars.ip)) - if 0 <= r < _vars.rows and 0 <= c < _vars.cols: - _vars.grid[r][c] = _set_color(marker, color, _vars.background) - return _vars.grid - -# it returns all the lines connecting the data points -def _get_line(x, y): - x_line = [] - y_line = [] - for n in range(len(x) - 1): - slope = 1. * (y[n + 1] - y[n]) / (x[n + 1] - x[n]) - dy = slope * _vars.dx - x_line_n = _range(x[n], x[n + 1], _vars.dx) - if dy == 0: - y_line_n = [y[n]] * len(x_line_n) - else: - #y_line_n = _range(y[n], y[n + 1], dy) - y_line_n = [(x_line_n[i]-x[n])*slope+y[n] for i in range(len(x_line_n))] - x_line.extend(x_line_n) - y_line.extend(y_line_n) - return x_line, y_line - -def _range(start, stop, step = 1): - res = [] - i = start - while i < stop: - res.append(i) - i = i+step - #i=_round(i,14) - return res - -def _round(n, dec): - return round(n * 10 ** dec) / 10 ** dec +def _set_grid(): + space = ut.add_color(" ", ["norm", var.canvas_color]) + var.grid = [[space for c in range(var.cols)] for r in range(var.rows)] + for s in range(len(var.x)): + + if var.line_marker[s] != var.empty_marker: + x, y = ut.get_line(var.x[s], var.y[s], var.dx) + marker = ut.add_color(var.line_marker[s], [var.line_color[s], var.canvas_color]) + x = ut.discretization(x, var.plot_xlim, var.cols) + y = ut.discretization(y, var.plot_ylim, var.rows) + var.grid = ut.update_grid(var.grid, x, y, marker) + + if var.point_marker[s] != var.empty_marker: + x, y = var.x[s], var.y[s] + marker = ut.add_color(var.point_marker[s], [var.point_color[s], var.canvas_color]) + x = ut.discretization(x, var.plot_xlim, var.cols) + y = ut.discretization(y, var.plot_ylim, var.rows) + var.grid = ut.update_grid(var.grid, x, y, marker) + +def _get_xticks(): + if var.xticks == None: + inp = var.ticks_number[0], var.ticks_length[0], var.plot_xlim, var.dx + ticks, labels, ab =ut.get_ticks(*inp) + set_xticks(ticks, labels) + var.abx = ab + +def _get_yticks(): + if var.yticks == None: + inp = [var.ticks_number[1], var.ticks_length[1], var.plot_ylim, var.dy] + ticks, labels, ab =ut.get_ticks(*inp) + set_yticks(ticks, labels) + var.aby = ab + +def set_xticks(ticks = None, labels = None): + if ticks == None: + ticks = [] + if labels == None: + labels = ticks + var.xticks, var.xlabels = ticks, labels + +def set_yticks(ticks = None, labels = None): + if ticks == None: + ticks = [] + if labels == None: + labels = ticks + var.yticks, var.ylabels = ticks, labels def _add_yaxis(): - spacing = _vars.spacing[1] * _vars.ticks[1] - axis = ["│" for r in range(_vars.rows)] - dr = len(str(_vars.rows)) - ticks = [" " * dr for r in range(_vars.rows)] - for r in range(_vars.rows): - if spacing != 0 and r % spacing == 0: + axis = ["│" for r in range(var.rows)] + l = var.ticks_length[1] + ticks = [" " * (l + 1) for r in range(var.rows)] + rticks = ut.discretization(var.yticks, var.plot_ylim, var.rows) + + for i in range(len(rticks)): + r = rticks[i] + if 0 <= r < var.rows and var.ylabels != []: axis[r] = "├" - space = " " * (dr - len(str(r))) - ticks[r] = num_to_string(_vars.ymin + r * _vars.dy + _vars.dy / 2) + space - ticks[r] = str(r) + space - axis[r] = _set_color(axis[r], _vars.axes_color, _vars.background) - ticks[r] = _set_color(ticks[r], _vars.axes_color, _vars.background) - if _vars.axes[1]: - _vars.grid[r].append(axis[r]) - if _vars.ticks[1] * _vars.spacing[1]: - _vars.grid[r].append(ticks[r]) - -def num_to_string(num): - if abs(num) < 10 ** 5: - num = str(round(num, 1)) - else: - exp = len(str(abs(num))) - 1 - num = num / 10 ** exp - num = str(num) + 'e' + str(exp) - return num + ticks[r] = str(var.ylabels[i])[:l] + ticks[r] = " " + ticks[r] + " " * (l - len(ticks[r])) + + axis = [ut.add_color(el, var.axes_color) for el in axis] + ticks = [ut.add_color(el, var.axes_color) for el in ticks] + + for r in range(var.rows): + if var.axes[1]: + var.grid[r].append(axis[r]) + if bool(var.ticks_length[1] * var.ticks_number[1]): + var.grid[r].append(ticks[r]) def _add_xaxis(): - spacing = _vars.spacing[0] * _vars.ticks[0] - axis = ["─" for r in range(_vars.cols)] - ticks = [" " for r in range(_vars.cols)] - final_spaces = " " * _vars.ticks[1] * len(str(_vars.rows)) - axis += "┘" * _vars.axes[1] + final_spaces - ticks += " " * _vars.axes[1] + final_spaces - c = 0 - while c < _vars.cols: - dc = 1 - if spacing != 0 and c % spacing == 0: - new = list(num_to_string(_vars.xmin + c * _vars.dx + _vars.dx / 2)) - new = list(str(c)) - dc = len(new) - if c + dc <= _vars.cols : - ticks[c : c + dc] = new - axis[c : c + dc] = "┬" + "─" * (dc - 1) - c += dc - axis = [_set_color(el, _vars.axes_color, _vars.background) for el in axis] - ticks = [_set_color(el, _vars.axes_color, _vars.background) for el in ticks] - if _vars.axes[0]: - _vars.grid.insert(0, axis) - if _vars.ticks[0] * _vars.spacing[0]: - _vars.grid.insert(0, ticks) + axis = ["─" for r in range(var.cols)] + axis += ["┘"] + axis += [" "] * (var.cols_plot - len(axis)) + + l = var.ticks_length[0] + labels = [str(el)[:l] for el in var.xlabels] + labels = [el + " " * (l + 1 - len(el)) for el in labels] + labels = [list(el) for el in labels] + ticks = [" " for r in range(var.cols_plot)] + cticks = ut.discretization(var.xticks, var.plot_xlim, var.cols) + + for i in range(len(cticks)): + c = cticks[i] + if c <= var.cols and c + l + 1 <= var.cols_plot and ticks[c - 1] == " ": + ticks[c:c+l+1] = labels[i] + if c < var.cols: + axis[c] = "┬" + if c == var.cols: + axis[c] = "┤" + + axis = [ut.add_color(el, var.axes_color) for el in axis] + ticks = [ut.add_color(el, var.axes_color) for el in ticks] + + if var.axes[0]: + var.grid.insert(0, axis) + if bool(var.ticks_length[0] * var.ticks_number[0]): + var.grid.insert(0, ticks) def _set_canvas(): - canvas = '\n' - for r in range(len(_vars.grid) -1, -1, -1): - canvas += "".join(_vars.grid[r]) + '\n' - _vars.canvas = canvas[:-1] + canvas = '' + for r in range(len(var.grid) -1, -1, -1): + canvas += "".join(var.grid[r]) + '\n' + var.canvas = canvas def _add_equations(): - dx, dy = _add_spaces(_vars.dx, _vars.dy, _vars.decimals, True) - cx, cy = _add_spaces(_vars.xmin + _vars.dx / 2, _vars.ymin + _vars.dy / 2, _vars.decimals) - ex, ey = _add_spaces(_vars.dx / 2, _vars.dy / 2, _vars.decimals) - x_eq = "x = " + dx + " × col " + cx + " " + chr(177) + " " + ex[2:] - y_eq = "y = " + dy + " × row " + cy + " " + chr(177) + " " + ey[2:] - final_spaces = " " * (_vars.cols + _vars.axes[1] + _vars.ticks[1] * len(str(_vars.rows)) - len(x_eq)) - x_eq = '\n' + _set_color(x_eq + final_spaces, _vars.axes_color, _vars.background) - y_eq = '\n' + _set_color(y_eq + final_spaces, _vars.axes_color, _vars.background) - if _vars.equations: - _vars.canvas += x_eq + y_eq - -def _add_spaces(a, b, decimals = 2, negative_only = False): - a_round, b_round = _round_with_zeros(a, decimals), _round_with_zeros(b, decimals) - space = int_length(a) - int_length(b) - a = sign(a, negative_only) + ' ' * (0 - space) + a_round - b = sign(b, negative_only) + ' ' * (0 + space) + b_round - return a, b - -def _round_with_zeros (num, decimals): #It rounds to the specified decimal points - num = str(round(num, decimals)) - if decimals == 0: - num = str(int(float(num_round))) - floats = "0" - if float(num) != int(float(num)): - floats = num[num.index(".") + 1 : num.index(".") + 1 + decimals] - zeros = "0" * (decimals-len(floats)) - return num + zeros - -def sign(num, negative_only = False): # Similar to to str(numpy.sign) - if num == 0: - return "+ " - elif num / abs(num) > 0: - if negative_only: - return "" - else: - return "+ " - else: - return "- " - -def int_length(num): - return len(str(int(abs(float(num))))) + label = ["x", "y"] + ab = [var.abx, var.aby] + eq = [] + for i in range(2): + eq.append(ut.get_equation(label[i], *ab[i], 7)) + eq = ut.get_opposite_labels(eq, var.cols_plot, var.axes_color) + var.canvas = var.canvas + eq + +def _add_title(): + if var.rows_title == 1: + label = ut.get_centered_label(var.title, var.cols_plot, var.axes_color) + var.canvas = ''.join(label) + '\n' + var.canvas + +def _add_axes_labels(): + labels = [var.xlabel, var.ylabel] + labels = ut.get_opposite_labels(labels, var.cols_plot, var.axes_color) + var.canvas = var.canvas + labels + +def _add_legend(): + if var.rows_legend == 1: + legend = "legend: " + legend_string_length = len(legend) + axes_color = var.axes_color + canvas_color = var.canvas_color + legend = ut.add_color(legend, axes_color) + sep = 4 + legend_length = len(var.label) + for i in range(legend_length): + label = var.label[i] + if label == "": + label = "signal" + str(i + 1) + label_length = len(label) + label_color = var.line_color[i] + if var.line_marker[i] == "": + label_color = var.point_color[i] + label_color = [label_color, canvas_color] + label = ut.add_color(label, label_color) + legend_string_length += label_length + if i != legend_length - 1: + legend_string_length += sep + label += ut.add_color(" " * sep, axes_color) + legend += label + space = " " * (var.cols_plot - legend_string_length) + space = ut.add_color(space, axes_color) + legend += space + var.canvas = var.canvas + legend + '\n' def _get_canvas(): - return _vars.canvas + return var.canvas def _print_canvas(): canvas = _get_canvas() - if _vars.no_color: - canvas=_remove_color(canvas) - _print(canvas+"\n") + if var.nocolor: + canvas = ut.remove_color(canvas) + print("color removed") + ut.print(canvas) + +############################################## +########## Basic Function ############## +############################################## + +def plot(*args, + xlim = None, + ylim = None, + force_size = None, + cols = None, + rows = None, + point_marker = var.empty_marker, + line_marker = var.default_marker, + canvas_color = None, + point_color = None, + line_color = None, + axes = None, + axes_color = None, + ticks_number = None, + ticks_length = None, + title = None, + xlabel = None, + ylabel = None, + label = None): + scatter(*args, + xlim = xlim, + ylim = ylim, + force_size = force_size, + cols = cols, + rows = rows, + point_marker = point_marker, + line_marker = line_marker, + canvas_color = canvas_color, + point_color = point_color, + line_color = line_color, + axes = axes, + axes_color = axes_color, + ticks_number = ticks_number, + ticks_length = ticks_length, + title = title, + xlabel = xlabel, + ylabel = ylabel, + label = label) + +def clear_terminal(): + ut.print('\033c') + +def clear_plot(): + var.__init__() + +def sleep(time): + [i for i in range(int(time * 15269989))] + +def get_terminal_size(): + if var.platform == "windows": + return get_terminal_size_windows() + elif var.nocolor: + return [185, 45] + else: + return list(_os.get_terminal_size()) + +def savefig(path): + path = ut.check_path(path) + with open(path , "w+", encoding = "utf-8") as file: + file.write(ut.remove_color(_get_canvas())) + ut.print("plot saved as " + path) -def _remove_color(string): - for color_code in _fg_color_codes + _bg_color_codes: - string = string.replace('\x1b[' + str(color_code) + 'm', '') - return string +def get_colors(): + fg_colors = [ut.add_color(fg_c, [fg_c, "norm"]) for fg_c in ut.fg_colors] + ut.print("\nFullground colors: " + ", ".join(fg_colors)) + bg_colors = [ut.add_color(bg_c, ["norm", bg_c]) for bg_c in ut.bg_colors] + ut.print("\nBackground colors: " + ", ".join(bg_colors) + '\n') + +def get_version(): + init_path = "__init__.py" + here = _os.path.abspath(_os.path.dirname(__file__)) + with open(_os.path.join(here, init_path), 'r') as fp: + lines = fp.read() + for line in lines.splitlines(): + if line.startswith('__version__'): + delim = '"' if '"' in line else "'" + version = line.split(delim)[1] + print("plotext version:", version) + return version + else: + print("Unable to find version string.") + ############################################## ############ Docstrings ############## ############################################## - scatter.__doc__ = """ -It creates a scatter plot of coordinates given by x and y lists. Here is a basic example: - - \x1b[92mimport plotext as plx\x1b[0m - \x1b[92mplx.scatter(x, y)\x1b[0m - \x1b[92mplx.show()\x1b[0m +It creates a scatter plot of coordinates given by the x and y lists. Optionally, a single y list could be provided. Here is a basic example: -Optionally, a single y list could be provided. Multiple data set could be plotted with consecutive scatter functions. Here are all other parameters: + \x1b[92mimport plotext as plt\x1b[0m + \x1b[92mplt.scatter(x, y)\x1b[0m + \x1b[92mplt.show()\x1b[0m -\x1b[94mcols\x1b[0m -It sets the number of columns of the plot. Only integers are allowed. By default, it is set to the highest value allowed by the the terminal size. Alternatively you could set the number of rows using set_cols(cols) after the scatter function. +Multiple data sets could be plotted using consecutive scatter functions. Here is an example: -\x1b[94mrows\x1b[0m -It sets the number of rows of the plot. Only integers are allowed. By default, it is set to the highest value allowed by the the terminal size. Alternatively you could set the number of columns using set_rows(rows) after the scatter function. + \x1b[92mplt.scatter(x1, y1)\x1b[0m + \x1b[92mplt.scatter(y2)\x1b[0m + \x1b[92mplt.show()\x1b[0m -\x1b[94mforce_size\x1b[0m -The plot dimensions are limited by the terminal size, when force_size is False and are allowed to be bigger otherwise. The default value is False. Alternatively you could set force_size using set_force_size(force_size) after the scatter function but before set_cols(cols) and set_rows(rows). +Here are all the parameters of the scatter function: \x1b[94mxlim\x1b[0m -It sets the minimum and maximum limits of the plot in the x axis. It requires a list of two numbers, where the first sets the left (minimum) limit and the second the right (maximum) limit. If one or both values are not provided, they are calculated automatically. Alternatively you could use set_xlim(xlim) after the scatter function. +It sets the minimum and maximum limits of the plot in the x axis. It requires a list of two numbers, where the first sets the left (minimum) limit and the second the right (maximum) limit. If one or both values are not provided, they are calculated automatically. Alternatively use set_xlim(xlim) after the scatter function. \x1b[94mylim\x1b[0m -It sets the minimum and maximum limits of the plot in the y axis. It requires a list of two numbers, where the first sets the lower (minimum) limit and the second the upper (maximum) limit. If one or both values are not provided, they are calculated automatically. Alternatively you could use set_ylim(ylim) after the scatter function. +It sets the minimum and maximum limits of the plot in the y axis. It requires a list of two numbers, where the first sets the lower (minimum) limit and the second the upper (maximum) limit. If one or both values are not provided, they are calculated automatically. Alternatively use set_ylim(ylim) after the scatter function. -\x1b[94mpoint\x1b[0m -When True, the plot shows the scatter data points. The default value is True. +\x1b[94mcols\x1b[0m +It sets the number of columns of the plot. By default, it is the highest value allowed by the terminal size. Alternatively use set_cols(cols) or set_canvas_size(cols, rows) after the scatter function. -\x1b[94mpoint_marker\x1b[0m -It sets the marker used to identify each data point on the plot. Only single characters are allowed (eg: '*'). The default value is '•'. +\x1b[94mrows\x1b[0m +It sets the number of rows of the plot. By default, it is the highest value allowed by the terminal size. Alternatively use set_rows(rows) or set_canvas_size(cols, rows) after the scatter function. -\x1b[94mpoint_color\x1b[0m -It sets the color used for the point marker. Use get_colors() to find the available color codes. The default value is 'norm'. +\x1b[94mforce_size\x1b[0m +By default, the plot dimensions are limited by the terminal size. Set force_size to True in order to allow bigger plots. Alternatively use set_force_size(True) after the scatter function (but before the functions set_cols and set_rows, if present). +Note: plots bigger then the terminal size may not be readable. -\x1b[94mline\x1b[0m -When True, the plot shows the lines between each data points. The default value is False. +\x1b[94mpoint_marker\x1b[0m +It sets the marker used to identify each data point plotted. It should be a single character and it could be a different value for each data set. If an empty string is provided, no data point is plotted. The default value is '•'. This parameter can only be set internally, because it is associated to the current data set plotted. \x1b[94mline_marker\x1b[0m -It sets the marker used to identify the lines between data points. Only single characters are allowed (eg: '*'). The default value is '•'. +It sets the marker used to identify the lines between two consecutive data points. It should be a single character and it could be a different value for each data set. If an empty string is provided (as by default), no lines are plotted. This parameter can only be set internally, because it is associated to the current data set plotted. -\x1b[94mline_color\x1b[0m -It sets the color used for the line marker. Use get_colors() to find the available color codes. The default value is 'norm'. +\x1b[94mcanvas_color\x1b[0m +It sets the canvas background color, without affecting the axes color. Alternatively use set_canvas_color(color) after the scatter function. Use get_colors() to find the available color codes. The default value is 'norm'. + +\x1b[94mpoint_color\x1b[0m +It sets the color of the data points. Use get_colors() to find the available color codes. The default value is 'norm'. This parameter can only be set internally, because it is associated to the current data set plotted. -\x1b[94mbackground\x1b[0m -It sets the plot background color. Use get_colors() to find the available color codes. The default value is 'norm'. Alternatively you could set the background color using set_background(background) after the scatter function. +\x1b[94mline_color\x1b[0m +It sets the color of the lines, if plotted. Use get_colors() to find the available color codes. The default value is 'norm'. This parameter can only be set internally, because it is associated to the current data set plotted. \x1b[94maxes\x1b[0m -When True, the x and y axes are added to the plot. A list of two Booleans will set the x and y axes separately (eg: axes=[True, False]). The default value is True. +When set to True (as by default), the x and y axes are added to the plot. A list of two Booleans will set the x and y axis independently (eg: axes = [True, False]). Alternatively, use set_axes(axes) after the scatter function. \x1b[94maxes_color\x1b[0m -It sets the color of the axes, ticks and equations, when present. Use get_colors() to find the available color codes. The default value is 'norm'. Alternatively you could set the axes color using set_axes_color(axes_color) after the scatter function. +It sets the axes color, without affecting the canvas. The same color is applied to the axes ticks, labels, equations, legend and title, if present. Use get_colors() to find the available color codes. The default value is 'norm'. If a list of two colors is provided, the second is interpreted as a background color. Alternatively use set_axes_color(color) after the scatter function. + +\x1b[94mticks_number\x1b[0m +It sets the number of data ticks printed on each axis. If a list of two values is provided, the number of ticks for each axis is set independently (eg: ticks_number = [5, 6]). If set to 0, no ticks are printed. The default value is 5. Alternatively use set_ticks_number(num) after the scatter function. +Note: you could also directly provide all ticks coordinates and labels for each axis using the functions set_xticks and set_yticks. Access their docstrings for further guidance. In this case the value of ticks_number would not affect what is printed. -\x1b[94mticks\x1b[0m -When True, the x and y ticks are added to the respective axes (even when absent). A list of two Booleans will set the x and y ticks separately (eg: ticks=[True, False]). The default value is True. +\x1b[94mticks_length\x1b[0m +It sets the maximum allowed number of characters of all data ticks on each axes. If the number of characters of any of the ticks coordinates is longer then this value, the ticks are re-scaled and an equation would appear at the end of the plot to clarify the conversion. If a list of two values is provided, the ticks length are set independently (eg: ticks_length = [5, 6]). If set to 0, no ticks are printed. The default value is 4. Alternatively use set_ticks_length(num) after the scatter function. +Note: you could also directly provide all ticks coordinates and labels for each exes using the functions set_xticks and set_yticks. Access their docstrings for further guidance. -\x1b[94mspacing\x1b[0m -It sets the spacing between the x and y ticks. When a list of two numbers is given, the spacing of the x and y ticks are set separately (eg: spacing=[5, 8]). Only positive integers are allowed. The default value is [10, 5]. Alternatively you could use set_spacing(spacing) after the scatter function. +\x1b[94mtitle\x1b[0m +It sets the title of the plot. Alternatively use set_title(label) after the scatter function. -\x1b[94mequations\x1b[0m -When True, the equations - needed to to find the real x and y values from the plot coordinates - are added at the end of the plot. The default value is False. +\x1b[94mxlabel\x1b[0m +It sets the label of the x axis. Alternatively use set_xlabel(label) after the scatter function. -\x1b[94mdecimals\x1b[0m -It sets the number of decimal points shown in the equations. Only positive integers are allowed. The default value is 2. Alternatively you could set the decimal points using set_decimals(decimals) after the scatter function. +\x1b[94mylabel\x1b[0m +It sets the label of the y axis. Alternatively use set_ylabel(label) after the scatter function. + +\x1b[94mlabel\x1b[0m +It sets the label of the current data set, which will appear in the legend at the end of the plot. The default value is an empty string. If all labels are an empty string no legend is printed. Alternatively use set_legend(labels) to set all labels (as a list of strings) after the scatter function. """ plot.__doc__ = """ -It is equivalent to the scatter function with the point option set to False and the line option set to True. See the scatter function docstring for further documentation. +It is equivalent to the scatter function with the point_marker option set to "" and the line option set to True. This means that no data points will be plotted, and the lines between consecutive points will be plotted instead. Here is a basic example: + + \x1b[92mimport plotext as plt\x1b[0m + \x1b[92mplt.plot(x, y)\x1b[0m + \x1b[92mplt.show()\x1b[0m - \x1b[92mimport plotext as plx\x1b[0m - \x1b[92mplx.scatter(x, y)\x1b[0m - \x1b[92mplx.show()\x1b[0m +Access the scatter function docstring for further documentation. +""" + +set_xticks.__doc__ = """ +It sets the data ticks on the x axis. The ticks should be provided as a list of values. If two lists are provided, the second is intended as the list of labels to be printed at the coordinates given by the first list. Here is an example: + + \x1b[92mplt.scatter(data)\x1b[0m + \x1b[92mplt.set_xticks(xticks, xlabels)\x1b[0m + \x1b[92mplt.show()\x1b[0m + +If no list is provided, the ticks will be calculated automatically. +If the ticks are not calculated automatically, any value given to the ticks_number parameter of the scatter or plot function will not affect the ticks plotted. On the contrary, the value of the ticks_length parameter needs to be higher that the maximum number of characters of the chosen ticks (or labels if present), otherwise some or all of the characters could be cut out of the plot. +""" + +set_yticks.__doc__ = """ +It sets the data ticks on the y axis. The ticks should be provided as a list of values. If two lists are provided, the second is intended as the list of labels to be printed at the coordinates given by the first list. Here is an example: + + \x1b[92mplt.scatter(data)\x1b[0m + \x1b[92mplt.set_yticks(yticks, ylabels)\x1b[0m + \x1b[92mplt.show()\x1b[0m + +If no list is provided, the ticks will be calculated automatically. +If the ticks are not calculated automatically, any value given to the ticks_number parameter of the scatter or plot function will not affect the ticks plotted. On the contrary, the value of the ticks_length parameter needs to be higher that the maximum number of characters of the chosen ticks (or labels if present), otherwise some or all of the characters could be cut out of the plot. +""" + + +set_legend.__doc__ = """ +It sets the labels of each plot (as a list of strings) to be printed as a legend. If all labels are an empty string, no legend will be printed. """ show.__doc__ = """ @@ -585,11 +697,11 @@ def _remove_color(string): """ clear_plot.__doc__ = """ -It clears the plot canvas. +It resets all the plot parameters to their default value, including the data coordinates. """ sleep.__doc__ = """ -It adds a sleeping time to the computation and it is useful when plotting continuously updating data to remove screen flickering. An input of, for example, 0.01 would add approximately 0.01 secs to the computation. Manually tweak this value to reduce the flickering. + It adds a sleeping time to the computation and it is useful when continuously plotting updating data in order to decrease a possible screen flickering. An input of, for example, 0.01 would add approximately 0.01 secs to the computation. Manually tweak this value to reduce the possible flickering. """ savefig.__doc__ = """ @@ -601,44 +713,16 @@ def _remove_color(string): """ get_version.__doc__ = """ -It returns the version of current plotext package. -""" - -get_x_from_col.__doc__ = """ -It returns the estimated x value from the column in which it is plotted, provided as input. -""" - -get_y_from_row.__doc__ = """ -It returns the estimated y value from the row in which it is plotted, provided as input. -""" - -run_test.__doc__ = """ -It runs a simple test of the plotext package. +It returns the version_ of current plotext package. """ -if 'idlelib.run' in sys.modules: - functions = [scatter, plot, show, clear_terminal, clear_plot, sleep, savefig, get_colors, get_version, get_x_from_col, get_y_from_row, run_test] +if var.nocolor: + functions = [scatter, plot, set_xticks, set_yticks, show, clear_terminal, clear_plot, sleep, savefig, get_colors, get_version] for fun in functions: - fun.__doc__ = _remove_color(fun.__doc__) + fun.__doc__ = ut.remove_color(fun.__doc__) -############################################## if __name__=="__main__": pass - #run_test() - - import matplotlib.pyplot as plt - import numpy as np - - l1=3*10**10 - l2=3.0000000000001*10**10 - y=np.linspace(l1,l2,10) - plt.cla() - plt.plot(y) - #plt.show(block=0) - - scatter(y, cols=170, rows=200, spacing=[2,1]) - for i in range(1): - clear_terminal() - show() - + import plot as plt + print(plt.set_yticks.__doc__) diff --git a/plotext/test.py b/plotext/test.py deleted file mode 100644 index e69de29..0000000 diff --git a/plotext/ticks.py b/plotext/ticks.py new file mode 100644 index 0000000..a5f5344 --- /dev/null +++ b/plotext/ticks.py @@ -0,0 +1,297 @@ +# /usr/bin/env python3 +# -*- coding: utf-8 -*- + +# This script is focused on solving the problem of representing the each x or y tick number within a finite number of characters c. +# This is necessary for example for very long tick numbers, which could occupy too much space in the terminal. +# In general we want to limit each number to a maximum number of characters c, and transform it in case it surpass this limit. The transformation undergone would be clarified with a formula appearing underneath the plot. + +# At first let's formalize the representation of a positive number n (>=0) within c characters. +# If for example we use c = 4 number of characters, considering that one characters is dedicated to the comma ".", we can represent the following numbers: +# - from 0.00 to 9.99 with 0.01 spacing between each number +# - from 10.0 to 99.9 with 0.1 spacing +# - from 100. to 9999 with 1 spacing +# In this case we have 3 levels of representation with increasing spacing. This behavior can be generalized with the formula: +# n[k] = k * delta[l] +# where l is the level of representation, n the number represented with c characters, delta[l] is the spacing of level i, and k takes values from a lower to a upper bound dependent to the band, in other words k in the following set +# I[k] = [lower[l], upper[l]]. +# For negative numbers the representation is the same with 1 initial characters dedicated to the "-" sign and c - 1 to the number representation. +# The exact formulas are here evaluated: + +# The numbers of representation levels correspondent to c characters. +# Eg: levels(4) = 3 +def levels(c): + if c == 1: + c += 1 + return c - 1 + +# The spacing for level l and characters c. Note that l must be in [0, levels(c) - 1]. +# Eg: delta(0, 4) = 0.01; delta(1, 4) = 0.1; delta(2, 4) = 1 +def delta(l, c): + if c == 1: + c += 1 + return 10 ** (2 - c + l) + +# The lower bound for level l and characters c. Note that l must be in [0, levels(c) - 1]. +# Eg: lower(0, 4) * delta(0, 4) = 0; lower(1, 4) * delta(1, 4) = 10.0; lower(2, 4) * delta(2, 4) = 100. +def lower(l, c): + if l == 0: + return 0 + else: + return 10 ** (c - 2) + +# The upper bound for level l and characters c. Note that l must be in [0, levels(c) - 1]. +# Eg: upper(0, 4) * delta(0, 4) = 9.99; upper(1, 4) * delta(1, 4) = 99.9; upper(2, 4) * delta(2, 4) = 9999 +def upper(l, c): + if c == 1: + return 9 + if l != c - 2: + return 10 * 10 ** (c - 2) - 1 + else: + return 100 * 10 ** (c - 2) - 1 + +# This function evaluates the number of steps in each level l. Note: this function is not really necessary for the rest. +# Eg: steps(0, 4) = 1000, steps(1, 4) = 900, steps(2, 4) = 9900 +def steps(l, c): + return upper(l, c) - lower(l, c) + 1 + +# This function round a number to c number of characters (regardless if they are decimal or not). +# Eg: _round(0.369, 4) = 0.37; _round(12345, 3) = 123 +def _round(n, c): + sign = +1 + if n < 0: + if c > 1: + c = c - 1 + sign = -1 + if n <=0: + n = abs(n) + d = c - len(str(int(n))) - 1 + if d <= 0 or n == float(int(n)): + return sign * round(float(str(n)[:c])) + else: + return sign * round(n, d) + +# This function evaluates all possible numbers that can be represented using c number of characters. +def numbers(c): + tot = [] + for l in range(levels(c)): + new = [k * delta(l, c) for k in range(lower(l, c), upper(l, c) + 1)] + new = [_round(el, c) for el in new] + tot += new + return tot + +# This test function checks whatever all the numbers represented with c characters have in fact a number of characters <= c +def test_numbers(c): + return all([len(str(el)) <= c for el in numbers(c)]) + +# And here is the test +# un-comment to run ! +# for c in range(7): +# pass +# print(test_numbers(c)) + + +# Let's formalize the representation of a data set within c number of characters. The solution would be different if the data set is positive (>=0), negative (<=0), or with mixed signs. +# These are the functions used to differentiate these cases: + +# This function evaluates the signature of a data set (already sorted in ascending order). +# Eg: _signature([1, 2, 3]) = 1; _signature([-3, -2, -1]) = -1; _signature([-1, 0, 1]) = 0 +def _signature(data): + if data[0] >=0: + sign = 1 + elif data[-1] <= 0: + sign = -1 + elif data[-1] > 0: + sign = 0 + return sign + +# We represent the data points using the formula: +# m[j] = dm * j + m0 for j in [0, M - 1] +# where dm is the spacing between each data point, m0 the initial value and M the number of points. +# In general, the transformed point n[j] would be connected to m[j] using the linear formula: +# m[j] = a * n[j] + b +# where a is the slope and b the intercept. The goal is to find a and b for all cases. + +# This function redirecting the solution to the problem (of representing a data set within c characters) for each case (depending on the signature of the data). +# Note: if each data point of the original data is already within c characters, no transformation is necessary, and so a = 1 and b = 0. +# The solution for negative numbers can be easily found using the solution for positive numbers, while for mixed signs a different solution is necessary. + +def _round_data(data, c): + data = sorted([round(el, 13) for el in data]) + data_c = [_round(el, c) for el in data] + if data_c == data: + return data_c, (1, 0) + #print("rounding ...") + sign = _signature(data) + if sign == 1: + return _round_positive_data(data, c) + elif sign == - 1: + return _round_negative_data(data, c) + else: + return _round_mixed_data(data, c - 1) + +# Let's consider first the simpler case of positive data (m[j] >= 0). We want each data point to be represented by c characters. +# A note: the data may need some initial optional re-scaling if it is either too big or too small for c characters representation. More specifically, if the last value m[M - 1] is either bigger then the biggest number (that can be represented by c characters) or smaller then the smallest number (that can be represented by c characters), then the data is multiplied by the minimum power of 10 such that m[M - 1] can be represented by c characters. The reason a power of 10 is chosen is that the re-scaled value would contain the same digits as the original values, just shifted left or right with respect to the comma, making their reading more straightforward. So the first transformation would be from m[j] to 10 ** exp * m[j] where exp = 0 if no initial transformation takes place. +# Extending the formula for a number represented by c characters n[k] = k * dn[l], allowing k to be linearly dependent on j: +# k = k0 + dk * j +# we get n[j] = (k0 + dk * j) * delta[l] +# and we require that m[j] ~ n[j] which gives rise to the following 2 conditions: +# k0 * delta[l] ~ m0 +# dk * delta[l] ~ dm +# The solution is k0 = int[m0 / dn[l]] and dk = int[dm / dn[l]] +# An important extra condition is that k0 + dk * (M - 1) is in [lower[l], upper[l]]. +# A way to solve this is to find the solution of those 2 equations (plus the extra condition) for all levels l and to chose the one with smallest overall error err = sum_j abs[m_j - n_j] for j in [0, M - 1] +# In order to find a and b, we see that: +# a * n[j] + b = a * dk * delta[l] * j + (a * k0 * delta[l] + b) +# comparing to 10 ** -exp * m[j] = 10 ** -exp * dm * j + 10 ** -exp * m0 we get: +# a = 10 ** -exp * dm / (dk * delta[l]) +# b = 10 ** -exp * m0 - a * k0 * delta[l] + +# The following functions are necessary to represent positive data points (>=0) using c characters. + +from math import log10, ceil # necessary for the initial optional re-scaling + +def _round_positive_data(data, c): + m = len(data) + ml = data[-1] # the last data point + levs = list(range(levels(c))) # all possible levels + if levs == []: + return [], [1, 0] + lo = [lower(l, c) for l in levs] # the lower bound for each level + up = [upper(l, c) for l in levs] # the upper bound for each level + dn = [delta(l, c) for l in levs] # the spacing for each level + + # the optional re-scaling + exp = 0 + if ml > up[-1] * dn[-1]: + exp = log10(ml / (up[-1] * dn[-1])) + exp = -ceil(exp) + if ml < dn[0]: + exp = log10(dn[0] / ml) + exp = ceil(exp) + data = [el * 10 ** ceil(exp) for el in data] + + m0 = data[0] # the smallest value in the data set + dm = data[1] - data[0] # the spacing of the data set + + k0 = [int(m0 / dn[l]) for l in levs] # the solution for k0 for all levels + dk = [max(1, int(dm / dn[l])) for l in levs] # the solution for dk for all levels + sol = [[(k0[l] + dk[l] * j) * dn[l] for j in range(m)] for l in levs] # all possible solutions n[j] + err = [sum([abs(data[j] - sol[l][j]) for j in range(m)]) for l in levs] # the error for all levels + lim = [k0[l] + dk[l] * (m - 1) <= up[l] for l in levs] # the extra condition for each level + for l in levs: # modify the errors so that only when the extra condition is verified, the solution is considered + if not lim[l]: + err[l] = 2 * abs(max(err)) + 1 + l = position(err, min(err))[0] # the level where the error is minimum + data = sol[l] + a = 10 ** (-exp) * dm / (dk[l] * dn[l]) + b = 10 ** -exp * m0 - a * k0[l] * dn[l] + for l in levs: + pass + #print(" ", l, "-", sol[l]) + return [data, (a, b)] + +# This function finds the positions in a data set of certain value. +def position(data, value): + res = [] + for i in range(len(data)): + if data[i] == value: + res.append(i) + return res + +# In the case of negative data values (<=0), it is easy to find a solution using the results for positive values. +# All it takes is a proper sign inversion. +# Note that the number of characters is one less then c because the first character is assigned to the "-" sign. +# Note also that in this case a * n[j] + b = -m[j] and so a * (-n[j]) -b = m[j], and so only b changes sign. +def _round_negative_data(data, c): + data = sorted([-el for el in data]) + data, (a, b) = _round_positive_data(data, c - 1) + data = sorted([-el for el in data], key = lambda x: x) + return [data, (a, -b)] + +# In the case of mixed data, the formula n[k] = k * delta[l] can be extended to negative numbers if k is in the set +# I[k] = [- upper[l], -lower[l]] + [lower[l], upper[l]] +# The main problem with this set is that going from k = -lower[l] to k = lower[l] there is a jump of 2 * lower[l] while for all other values a regular jump of 1. +# A way to deal with this is to introduce this new variable k[t] such that: +# k[t] = t for l = 0 +# k[t] = (2 * t + 1) * lower[l] for l != 0 +# For l = 0 there is no jump problem since +- lower[l] = 0 +# For l!= 0, k = -lower[l], implies t = -1, while k = lower[l] implies t = 0 with a regular jump of 1 in t. +# The upper limiting condition for t is that: +# abs[t] <= upper[0] for l = 0 +# - 0.5 * (upper[l] / lower[l] + 1) <= t <= 0.5 * (upper[l] / lower[l] - 1) for l != 0 +# In conclusion with the definitions given, we consider numbers n[t] written as: +# n[t] = k[t] * delta[l] +# which are both positive and negative. Let's write k[t] in a single formula as: +# k[t] = (s[l] * t + i[l]) * low[l] +# where s[0] = 1, s[l!=0] = 2; i[0] = 0, i[l!=0] = 1; low[0] = 1, low[l!=0] = lower[l] +# The initial optional re-scaling of the data is similar to the positive case with the difference that one needs to check whatever m[0] OR m[M-1] are bigger then the biggest number (that can be represented by c characters) or smaller then the smallest number (that can be represented by c characters) before re-scaling by the minimum 10 ** exp. +# If we want to apply this to a data set m[j], then we allow t to be: +# t = t0 + dt * j +# so that n[j] = (s[l] * (t0 + dt * j) + i[l]) * low[l] * delta[l] = t0 * s[l] * low[l] * delta[l] + dt * s[l] * low[l] * delta[l] * j +# and equating with m[j] = m0 + dm * j we get the 2 following conditions: +# t0 * s[l] * low[l] * delta[l] ~ m0 +# dt * s[l] * low[l] * delta[l] ~ dm +# with solutions given by t0 = int(m0 / (s[l] * low[l] * delta[l])) and dt = int(dm / (s[l] * low[l] * delta[l])). +# The extra condition is that k[t0 + dt * (M - 1)] is in [lower[l], upper[l]]. +# Similarly to the positive case, the way to solve this is to find the solution of those 2 equations (plus the extra condition) for all levels l and to chose the one with smallest overall error err = sum_j abs[m_j - n_j] for j in [0, M - 1] +# In order to find a and b in this case, we see that: +# a * n[j] + b = a * dt * s[l] * low[l] * delta[l] * j + a * t0 * s[l] * low[l] * delta[l] + b +# comparing to 10 ** -exp * m[j] = 10 ** -exp * dm * j + 10 ** -exp * m0 we get: +# a = 10 ** -exp * dm / (dt * s[l] * low[l] * delta[l]) +# b = 10 ** -exp * m0 - a * t0 * s[l] * low[l] * delta[l]) + +def _round_mixed_data(data, c): + m = len(data) + m0 = data[0] + ml = data[-1] # the last data point + levs = list(range(levels(c))) # all possible levels + if levs == []: + return [], [1, 0] + s = [1 if l == 0 else 2 for l in levs] # the slope in k[t] for each level + i = [0 if l == 0 else 1 for l in levs] # the slope in k[t] for each level + lo = [1 if l == 0 else lower(l, c) for l in levs] # the lower bound for each level + up = [upper(l, c) for l in levs] # the upper bound for each level + dn = [delta(l, c) for l in levs] # the spacing for each level + + # the optional re-scaling + exp = 0 + m_max = max(-m0, ml) + #m_min = min(-m0, ml) + if m_max > up[-1] * dn[-1]: + exp = log10(m_max / (up[-1] * dn[-1])) + exp = -ceil(exp) + if m_max < dn[0]: + exp = log10(dn[0] / m_max) + exp = ceil(exp) + data = [el * 10 ** ceil(exp) for el in data] + + m0 = data[0] # the smallest value in the data set + dm = data[1] - data[0] # the spacing of the data set + + t0 = [int(m0 / (s[l] * lo[l] * dn[l])) for l in levs] # the solution for t0 for all levels + dt = [max(1, int(dm / (s[l] * lo[l] * dn[l]))) for l in levs] # the solution for dt for all levels + #print("dt", dt) + sol = [[(s[l] * (t0[l] + dt[l] * j) + i[l]) * lo[l] * dn[l] for j in range(m)] for l in levs] # all possible solutions n[j] + err = [sum([abs(data[j] - sol[l][j]) for j in range(m)]) for l in levs] # the error for all levels + lim = [(s[l] * (m - 1) + i[l]) * lo[l] <= up[l] for l in levs] # the extra condition for each level + for l in levs: # modify the errors so that only when the extra condition is verified, the solution is considered + if not lim[l]: + err[l] = 2 * abs(max(err)) + 1 + l = position(err, min(err))[0] # the level where the error is minimum + #print("l", l) + data = sol[l] + a = 10 ** (-exp) * dm / (dt[l] * s[l] * lo[l] * dn[l]) + b = 10 ** -exp * m0 - a * t0[l] * s[l] * lo[l] * dn[l] + for l in levs: + pass + #print(" ", l, "-", sol[l]) + return [data, (a, b)] + +if __name__=="__main__": + data = sorted([-0, 1.5, 3, 4.5]) + c = 2 + data_r, (a, b) = _round_data(data, c) + data_n = [a * el + b for el in data_r] + print(data, data_r) + if data_n != data: + print(data_n) diff --git a/plotext/utility.py b/plotext/utility.py new file mode 100644 index 0000000..55c9c92 --- /dev/null +++ b/plotext/utility.py @@ -0,0 +1,180 @@ +# /usr/bin/env python3 +# -*- coding: utf-8 -*- +import sys as _sys +import os as _os +from ticks import _round_data + +############################################## +###### Hidden Utility Functions ####### +############################################## +def print(string): + _sys.stdout.write(string) + +def set_vars(var, pad = None): + if len(var) == 1: + var = var[0] + if type(var) != list: + if var == None: + var = pad + var = [var, pad] + elif len(var) == 2: + var = list(var) + return var + +def set_lim(data_lim, plot_lim = [None, None], bins = 2): + plot_min, plot_max = plot_lim + data_min, data_max = data_lim + + if data_min == data_max: + if data_min == 0: + data_min, data_max = [-1, 1] + else: + data_min, data_max = [0.5 * data_min, 1.5 * data_max] + if bins == 1: + bins = 2 + dz = (data_max - data_min) / (bins - 1) + if plot_min == None: + plot_min = data_min - dz / 2 + if plot_max == None: + plot_max = data_max + dz / 2 + plot_lim = plot_min, plot_max + return plot_lim + +def get_ticks(ticks_number, ticks_length, plot_lims, dz): + m, M = plot_lims + m = m + dz / 2 + M = M - dz / 2 + if ticks_number == 0 or ticks_length == 0: + values = [] + if ticks_number == 1: + values = [0.5 * (m + M)] + else: + step = (M - m) / (ticks_number - 1) + values = arange(m, M + step, step) + labels, ab = _round_data(values, ticks_length) + return values, labels, ab + +def arange(start, stop, step = 1): + res = [] + i = start + while i < stop: + res.append(i) + i = i+step + #i=_round(i,14) + return res + +# it returns all the lines connecting the data points +def get_line(x, y, dx): + x_line = [] + y_line = [] + for n in range(len(x) - 1): + slope = 1. * (y[n + 1] - y[n]) / (x[n + 1] - x[n]) + dy = slope * dx + x_line_n = arange(x[n], x[n + 1], dx) + if dy == 0: + y_line_n = [y[n]] * len(x_line_n) + else: + #y_line_n = _range(y[n], y[n + 1], dy) + y_line_n = [(x_line_n[i] - x[n]) * slope + y[n] for i in range(len(x_line_n))] + x_line.extend(x_line_n) + y_line.extend(y_line_n) + return x_line, y_line + +def discretization(data, plot_lim, bins): + dz = (plot_lim[1] - plot_lim[0]) / bins + data = [int((el - plot_lim[0]) / dz) for el in data] + return data + +def update_grid(grid, x, y, marker): + rows, cols = len(grid), len(grid[0]) + for i in range(len(x)): + c, r = x[i], y[i] + if 0 <= c < cols and 0 <= r < rows: + grid[r][c] = marker + return grid + +fg_colors = ['norm', 'black', 'gray', 'red', 'green', 'yellow', 'orange', 'blue', 'violet', 'cyan', 'bold'] +fg_color_codes = [0, 30, 2, 91, 92, 93, 33, 94, 95, 96, 1] +bg_colors = ['norm', 'black', 'gray', 'red', 'green', 'yellow', 'orange', 'blue', 'violet', 'cyan', 'white'] +bg_color_codes = [28, 40, 100, 41, 42, 103, 43, 44, 45, 106, 47] + +# it applies the proper color codes to a string +def add_color(text = "", color = "norm"): + background = "norm" + if type(color) == list: + color, background = color + if color == "norm" and background == "norm": + return text + code = '\033[' + if type(color) == str: + for c in range(len(fg_colors)): + if color == fg_colors[c]: + code += str(fg_color_codes[c]) + code += 'm' + code += '\033[' + if type(background) == str: + for c in range(len(bg_colors)): + if background == bg_colors[c]: + code += str(bg_color_codes[c]) + code += 'm' + return code + text + '\033[0m' + +def remove_color(string): + for color_code in fg_color_codes + bg_color_codes: + string = string.replace('\x1b[' + str(color_code) + 'm', '') + return string + +def sign(num,): # Similar to to str(numpy.sign) + if num < 0: + return "- " + elif num >= 0: + return "+ " + +def str_num(num, d): + i = int(num) + f = abs(round(num - i, d)) + i, f = str(i), str(f)[2:] + #f += "0" * (d - len(f)) + s = sign(num) + if i=='0' and f=='0': + num="" + else: + num = s+ i + "." + f + return num + +def get_equation(label = "x", a = 1, b = 0, d = 5): + a, b = round(float(a), d), round(float(b), d) + if [a, b] == [1, 0]: + return "" + a, b = str_num(a, d), str_num(b, d) + eq = label + " = " + a + " × " + label + "_tick " + b + return eq + +def get_centered_label(label, length, color): + label = label[:length] + label_c = [add_color(add_color(el, color), "bold") for el in label] + space1 = int((length - len(label)) / 2) + space2 = length - len(label) - space1 + space = [add_color(" ", color)] + label = space * space1 + label_c + space * space2 + return label + +def get_opposite_labels(labels, length, color): + if labels == ["", ""]: + return "" + labels[0] = labels[0] + " " + labels[1] = " " + labels[1] + space = length - sum(map(len, labels)) + labels = labels[0] + " " * space + labels[1] + labels = add_color(labels, color) + return labels + '\n' + +def check_path(path): + basedir = _os.path.dirname(path) + if _os.path.exists(basedir): + return path + else: + print("warning: parent directory doesn't exists.") + home = _os.path.expanduser("~") + path = _os.path.join(home, _os.path.basename(path)) + return path diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 9f88734..0000000 --- a/setup.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[metadata] -description-file = README.md - -[egg_info] -tag_build = -tag_date = 0 - diff --git a/setup.py b/setup.py index 639ac56..6ffe667 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ author="Savino Piccolomo", author_email="piccolomo@gmail.com", name='plotext', - version='1.0.11', + version='2.0.2', description='plotext plots data directly on terminal', long_description=README, long_description_content_type="text/markdown",