Skip to content

Commit

Permalink
Remove hard dependency on pendulum (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
tammoippen authored Mar 28, 2019
1 parent 7e7dc60 commit 61b209f
Show file tree
Hide file tree
Showing 17 changed files with 162 additions and 63 deletions.
15 changes: 13 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ jobs:
test_2_7_pypy:
executor:
name: python
image: "pypy:2-6.0.0"
image: "pypy:2-7.1.0"
steps:
- tester
test_3_5:
Expand All @@ -99,7 +99,7 @@ jobs:
test_3_5_pypy:
executor:
name: python
image: "pypy:3-6.0.0"
image: "pypy:3.5-7.0.0"
steps:
- tester
test_3_6:
Expand All @@ -108,6 +108,12 @@ jobs:
image: "circleci/python:3.6.7"
steps:
- tester
test_3_6_pypy:
executor:
name: python
image: "pypy:3.6-7.1.0"
steps:
- tester
test_3_7:
executor:
name: python
Expand Down Expand Up @@ -159,6 +165,10 @@ workflows:
filters:
tags:
only: /.*/
- test_3_6_pypy:
filters:
tags:
only: /.*/
- test_3_7:
filters:
tags:
Expand All @@ -170,6 +180,7 @@ workflows:
- test_3_5
- test_3_5_pypy
- test_3_6
- test_3_6_pypy
- test_3_7
filters:
branches:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
venv/
.venv/
.venv*/
.vscode/
data/
build/
Expand All @@ -18,3 +18,4 @@ README.rst
poetry.lock
Pipfile.lock
cov_html/
pip-wheel-metadata/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Coverage Status](https://coveralls.io/repos/github/tammoippen/plotille/badge.svg?branch=master)](https://coveralls.io/github/tammoippen/plotille?branch=master)
[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/tammoippen/plotille.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/tammoippen/plotille/context:python)
[![Tested CPython Versions](https://img.shields.io/badge/cpython-2.7%2C%203.5%2C%203.6%2C%203.7-brightgreen.svg)](https://img.shields.io/badge/cpython-2.7%2C%203.5%2C%203.6%2C%203.7-brightgreen.svg)
[![Tested PyPy Versions](https://img.shields.io/badge/pypy-2.7--6.0.0%2C%203.5--6.0.0-brightgreen.svg)](https://img.shields.io/badge/pypy-2.7--6.0.0%2C%203.5--6.0.0-brightgreen.svg)
[![Tested PyPy Versions](https://img.shields.io/badge/pypy-2.7--7.1.0%2C%203.5--7.0.0%2C%203.6--7.1.0-brightgreen.svg)](https://img.shields.io/badge/pypy-2.7--6.0.0%2C%203.5--6.0.0-brightgreen.svg)
[![PyPi version](https://img.shields.io/pypi/v/plotille.svg)](https://pypi.python.org/pypi/plotille)
[![PyPi license](https://img.shields.io/pypi/l/plotille.svg)](https://pypi.python.org/pypi/plotille)

Expand All @@ -23,7 +23,7 @@ Similar to other libraries:
* like [termgraph](https://github.com/sgeisler/termgraph) (not on pypi), but very different style.
* like [terminalplot](https://github.com/kressi/terminalplot), but with braille, X/Y-axis, histogram, linear interpolation.

Basic support for timeseries plotting is provided with release 3.2: for any `X` or `Y` values you can also add `datetime.datetime` or `pendulum.datetime` values (internally I work with `pendulum.datetime`, as the python 2/3 consistency is much better). Labels are generated respecting the difference of of `x_limits` and `y_limits`.
Basic support for timeseries plotting is provided with release 3.2: for any `X` or `Y` values you can also add `datetime.datetime`, `pendulum.datetime` or `numpy.datetime64` values. Labels are generated respecting the difference of `x_limits` and `y_limits`.

## Documentation

Expand Down
39 changes: 18 additions & 21 deletions plotille/_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
# THE SOFTWARE.

from collections import namedtuple
from datetime import datetime
from datetime import timedelta
from itertools import cycle
import os

Expand All @@ -33,7 +33,7 @@
from ._canvas import Canvas
from ._colors import color
from ._input_formatter import InputFormatter
from ._util import dt2pendulum_dt, hist, is_datetimes, make_datetimes
from ._util import hist, mk_timedelta, timestamp

# TODO documentation!!!
# TODO tests
Expand Down Expand Up @@ -151,12 +151,6 @@ def set_y_limits(self, min_=None, max_=None):
self._y_min, self._y_max = self._set_limits(self._y_min, self._y_max, min_, max_)

def _set_limits(self, init_min, init_max, min_=None, max_=None):
if isinstance(min_, datetime):
min_ = dt2pendulum_dt(min_)

if isinstance(max_, datetime):
max_ = dt2pendulum_dt(max_)

if min_ is not None and max_ is not None:
if min_ >= max_:
raise ValueError('min_ is larger or equal than max_.')
Expand Down Expand Up @@ -196,7 +190,11 @@ def _limits(self, low_set, high_set, is_height):
return _choose(low, high, low_set, high_set)

def _y_axis(self, ymin, ymax, label='Y'):
y_delta = abs((ymax - ymin) / self.height)
delta = abs(ymax - ymin)
if isinstance(delta, timedelta):
y_delta = mk_timedelta(timestamp(delta) / self.height)
else:
y_delta = delta / self.height

res = [self._in_fmt.fmt(i * y_delta + ymin, abs(ymax - ymin), chars=10) + ' | '
for i in range(self.height)]
Expand All @@ -211,14 +209,18 @@ def _y_axis(self, ymin, ymax, label='Y'):
return list(reversed(res))

def _x_axis(self, xmin, xmax, label='X', with_y_axis=False):
x_delta = abs((xmax - xmin) / self.width)
delta = abs(xmax - xmin)
if isinstance(delta, timedelta):
x_delta = mk_timedelta(timestamp(delta) / self.width)
else:
x_delta = delta / self.width
starts = ['', '']
if with_y_axis:
starts = ['-' * 11 + '|-', ' ' * 11 + '| ']
res = []

res += [starts[0] + '|---------' * (self.width // 10) + '|-> (' + label + ')']
res += [starts[1] + ' '.join(self._in_fmt.fmt(i * 10 * x_delta + xmin, abs(xmax - xmin), left=True, chars=9)
res += [starts[1] + ' '.join(self._in_fmt.fmt(i * 10 * x_delta + xmin, delta, left=True, chars=9)
for i in range(self.width // 10 + 1))]
return res

Expand Down Expand Up @@ -302,12 +304,6 @@ def create(cls, X, Y, lc, interp, label): # noqa: N803
if interp not in ('linear', None):
raise ValueError('Only "linear" and None are allowed values for `interp`.')

if is_datetimes(X):
X = make_datetimes(X) # noqa: N806

if is_datetimes(Y):
Y = make_datetimes(Y) # noqa: N806

return cls(X, Y, lc, interp, label)

def width_vals(self):
Expand Down Expand Up @@ -337,9 +333,6 @@ def write(self, canvas, with_colors, in_fmt):
class Histogram(namedtuple('Histogram', ['X', 'bins', 'frequencies', 'buckets', 'lc'])):
@classmethod
def create(cls, X, bins, lc): # noqa: N803
if is_datetimes(X):
X = make_datetimes(X) # noqa: N806

frequencies, buckets = hist(X, bins)

return cls(X, bins, frequencies, buckets, lc)
Expand Down Expand Up @@ -387,7 +380,11 @@ def _diff(low, high):
else:
return abs(low * 0.1)
else:
return abs(high - low) * 0.1
delta = abs(high - low)
if isinstance(delta, timedelta):
return mk_timedelta(timestamp(delta) * 0.1)
else:
return delta * 0.1


def _default(low_set, high_set):
Expand Down
36 changes: 28 additions & 8 deletions plotille/_input_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
# THE SOFTWARE.

from collections import OrderedDict
from datetime import datetime, timedelta
import math

from pendulum import DateTime, Duration, Period
import six

from ._util import roundeven
from ._util import roundeven, timestamp


class InputFormatter(object):
Expand All @@ -40,14 +40,22 @@ def __init__(self):
for int_type in six.integer_types:
self.formatters[int_type] = _num_formatter

self.formatters[DateTime] = _datetime_formatter
self.formatters[datetime] = _datetime_formatter

self.converters = OrderedDict()
self.converters[float] = _convert_numbers
for int_type in six.integer_types:
self.converters[int_type] = _convert_numbers

self.converters[DateTime] = _convert_datetime
self.converters[datetime] = _convert_datetime

try:
import numpy as np

self.converters[np.datetime64] = _convert_np_datetime
self.formatters[np.datetime64] = _np_datetime_formatter
except ImportError: # pragma: nocover
pass

def register_formatter(self, t, f):
self.formatters[t] = f
Expand All @@ -70,9 +78,16 @@ def convert(self, val):
return val


def _np_datetime_formatter(val, chars, delta, left=False):
# assert isinstance(val, np.datetime64)
# assert isinstance(delta, np.timedelta64)

return _datetime_formatter(val.item(), chars, delta.item(), left)


def _datetime_formatter(val, chars, delta, left=False):
assert isinstance(val, DateTime)
assert isinstance(delta, (Duration, Period))
assert isinstance(val, datetime)
assert isinstance(delta, timedelta)

if chars < 8:
raise ValueError('Not possible to display value "{}" with {} characters!'.format(val, chars))
Expand Down Expand Up @@ -174,6 +189,11 @@ def _convert_numbers(v):
return float(v)


def _convert_np_datetime(v):
# assert isinstance(v, np.datetime64)
return timestamp(v.item())


def _convert_datetime(v):
assert isinstance(v, DateTime)
return v.timestamp()
assert isinstance(v, datetime)
return timestamp(v)
62 changes: 44 additions & 18 deletions plotille/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import datetime
from datetime import datetime, timedelta, tzinfo
import math

import pendulum
import time


def roundeven(x):
Expand Down Expand Up @@ -65,32 +64,59 @@ def hist(X, bins): # noqa: N803
"""
assert bins > 0

if is_datetimes(X):
X = make_datetimes(X) # noqa: N806

xmin = min(X) if len(X) > 0 else 0.0
xmax = max(X) if len(X) > 0 else 1.0
xwidth = (xmax - xmin) / bins
delta = xmax - xmin
is_datetime = False
if isinstance(delta, timedelta):
is_datetime = True
delta = timestamp(delta)

xwidth = delta / bins

y = [0] * bins
for x in X:
x_idx = min(bins - 1, int((x - xmin) // xwidth))
delta = (x - xmin)
if isinstance(delta, timedelta):
delta = timestamp(delta)
x_idx = min(bins - 1, int(delta // xwidth))
y[x_idx] += 1

if is_datetime:
xwidth = mk_timedelta(xwidth)

return y, [i * xwidth + xmin for i in range(bins + 1)]


def make_datetimes(l):
return [dt2pendulum_dt(dt) for dt in l]
class _UTC(tzinfo):
"""UTC"""
_ZERO = timedelta(0)

def utcoffset(self, dt):
return self._ZERO

def tzname(self, dt):
return 'UTC'

def dst(self, dt):
return self._ZERO


_EPOCH = datetime(1970, 1, 1, tzinfo=_UTC())


def is_datetimes(l):
return (
all(isinstance(x, datetime.datetime) for x in l) and # all are datetimes,
any(not isinstance(x, pendulum.DateTime) for x in l) # but at least one is not pendulum datetime
)
def timestamp(v):
"""Get timestamp of `v` datetime in py2/3."""
if isinstance(v, datetime):
if v.tzinfo is None:
return time.mktime(v.timetuple()) + v.microsecond / 1e6
else:
return (v - _EPOCH).total_seconds()
elif isinstance(v, timedelta):
return v.total_seconds()


def dt2pendulum_dt(dt):
assert isinstance(dt, datetime.datetime) # also works on pendulum datetimes
return pendulum.datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo)
def mk_timedelta(v):
seconds = int(v)
microseconds = int((v - seconds) * 1e6)
return timedelta(seconds=seconds, microseconds=microseconds)
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tool.poetry]

name = "plotille"
version = "3.5"
version = "3.6"
description = "Plot in the terminal using braille dots."
authors = ["Tammo Ippen <tammo.ippen@posteo.de>"]
license = "MIT"
Expand Down Expand Up @@ -35,7 +35,6 @@ classifiers = [
python = "~2.7 || ^3.5"

six = "^1.12"
pendulum = "^2.0"


[tool.poetry.dev-dependencies]
Expand All @@ -51,6 +50,7 @@ flake8-polyfill = "^1.0.2"
flake8-quotes = "^1.0.0"
funcsigs = { version = "^1.0", python = "~2.7" }
mock = "^2.0"
pendulum = "^2.0"
pep8-naming = "^0.7"
pytest = "^3.7.3"
pytest-cov = "^2.5.1"
Expand Down
7 changes: 7 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
[coverage:report]
show_missing = True

[coverage:run]
branch = True

[flake8]
application_import_names = plotille
max-line-length = 120
import-order-style = google

Expand Down
3 changes: 2 additions & 1 deletion tests/test_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
from __future__ import absolute_import, division, print_function, unicode_literals

import numpy as np
from plotille import Canvas
import pytest
import six

from plotille import Canvas


def test_invalids():
with pytest.raises(AssertionError):
Expand Down
Loading

0 comments on commit 61b209f

Please sign in to comment.