Skip to content

Commit

Permalink
Merge pull request #81 from moshi4/develop
Browse files Browse the repository at this point in the history
Bump to v1.8.0
  • Loading branch information
moshi4 authored Jan 11, 2025
2 parents 0e7ef2d + 3296817 commit 4b434c6
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
rev: v0.9.1
hooks:
- id: ruff
name: ruff lint check
Expand Down
41 changes: 41 additions & 0 deletions docs/plot_api_example.ipynb

Large diffs are not rendered by default.

37 changes: 19 additions & 18 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ certifi==2024.12.14
# via requests
cfgv==3.4.0
# via pre-commit
charset-normalizer==3.4.0
charset-normalizer==3.4.1
# via requests
click==8.1.7
click==8.1.8
# via black
# via mkdocs
# via mkdocstrings
Expand All @@ -42,7 +42,7 @@ comm==0.2.2
# via ipykernel
contourpy==1.3.0
# via matplotlib
coverage==7.6.9
coverage==7.6.10
# via pytest-cov
cycler==0.12.1
# via matplotlib
Expand All @@ -67,9 +67,9 @@ fonttools==4.55.3
# via matplotlib
ghp-import==2.1.0
# via mkdocs
griffe==1.5.1
griffe==1.5.4
# via mkdocstrings-python
identify==2.6.3
identify==2.6.5
# via pre-commit
idna==3.10
# via requests
Expand All @@ -80,7 +80,7 @@ importlib-metadata==8.5.0
# via mkdocs-get-deps
# via mkdocstrings
# via nbconvert
importlib-resources==6.4.5
importlib-resources==6.5.2
# via matplotlib
iniconfig==2.0.0
# via pytest
Expand All @@ -90,7 +90,7 @@ ipython==8.18.1
# via ipykernel
jedi==0.19.2
# via ipython
jinja2==3.1.4
jinja2==3.1.5
# via mkdocs
# via mkdocs-material
# via mkdocstrings
Expand Down Expand Up @@ -142,7 +142,7 @@ mdurl==0.1.2
mergedeep==1.3.4
# via mkdocs
# via mkdocs-get-deps
mistune==3.0.2
mistune==3.1.0
# via nbconvert
mkdocs==1.6.1
# via mkdocs-autorefs
Expand All @@ -161,13 +161,13 @@ mkdocs-material-extensions==1.3.1
# via mkdocs-material
mkdocstrings==0.27.0
# via mkdocstrings-python
mkdocstrings-python==1.12.2
mkdocstrings-python==1.13.0
# via mkdocstrings
mypy-extensions==1.0.0
# via black
nbclient==0.10.2
# via nbconvert
nbconvert==7.16.4
nbconvert==7.16.5
# via mkdocs-jupyter
nbformat==5.10.4
# via jupytext
Expand Down Expand Up @@ -205,7 +205,7 @@ pathspec==0.12.1
# via mkdocs
pexpect==4.9.0
# via ipython
pillow==11.0.0
pillow==11.1.0
# via matplotlib
platformdirs==4.3.6
# via black
Expand All @@ -225,15 +225,15 @@ ptyprocess==0.7.0
pure-eval==0.2.3
# via stack-data
pygenomeviz==1.5.0
pygments==2.18.0
pygments==2.19.1
# via ipython
# via mkdocs-jupyter
# via mkdocs-material
# via nbconvert
pymdown-extensions==10.12
pymdown-extensions==10.14
# via mkdocs-material
# via mkdocstrings
pyparsing==3.2.0
pyparsing==3.2.1
# via matplotlib
pytest==8.3.4
# via pytest-cov
Expand Down Expand Up @@ -267,15 +267,15 @@ requests==2.32.3
rpds-py==0.22.3
# via jsonschema
# via referencing
ruff==0.8.4
ruff==0.9.1
six==1.17.0
# via python-dateutil
soupsieve==2.6
# via beautifulsoup4
stack-data==0.6.3
# via ipython
tinycss2==1.4.0
# via nbconvert
# via bleach
tomli==2.2.1
# via black
# via coverage
Expand All @@ -297,12 +297,13 @@ traitlets==5.14.3
typing-extensions==4.12.2
# via black
# via ipython
# via mistune
# via mkdocstrings
tzdata==2024.2
# via pandas
urllib3==2.2.3
urllib3==2.3.0
# via requests
virtualenv==20.28.0
virtualenv==20.28.1
# via pre-commit
watchdog==6.0.0
# via mkdocs
Expand Down
6 changes: 3 additions & 3 deletions requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ cycler==0.12.1
# via matplotlib
fonttools==4.55.3
# via matplotlib
importlib-resources==6.4.5
importlib-resources==6.5.2
# via matplotlib
kiwisolver==1.4.7
# via matplotlib
Expand All @@ -34,9 +34,9 @@ packaging==24.2
# via matplotlib
pandas==2.2.3
# via pycirclize
pillow==11.0.0
pillow==11.1.0
# via matplotlib
pyparsing==3.2.0
pyparsing==3.2.1
# via matplotlib
python-dateutil==2.9.0.post0
# via matplotlib
Expand Down
2 changes: 1 addition & 1 deletion src/pycirclize/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pycirclize.circos import Circos

__version__ = "1.7.2"
__version__ = "1.8.0"

__all__ = [
"Circos",
Expand Down
4 changes: 3 additions & 1 deletion src/pycirclize/circos.py
Original file line number Diff line number Diff line change
Expand Up @@ -1049,13 +1049,15 @@ def plotfig(
# Plot all patches
patches = []
for patch in self._get_all_patches():
# Set clip_on=False to enable Patch to be displayed outside of Axes
patch.set_clip_on(False)
# Collection cannot handle `zorder`, `hatch`
# Separate default or user-defined `zorder`, `hatch` property patch
if patch.get_zorder() == 1 and patch.get_hatch() is None:
patches.append(patch)
else:
ax.add_patch(patch)
ax.add_collection(PatchCollection(patches, match_original=True)) # type: ignore
ax.add_collection(PatchCollection(patches, match_original=True, clip_on=False)) # type: ignore

# Execute all plot functions
for plot_func in self._get_all_plot_funcs():
Expand Down
1 change: 1 addition & 0 deletions src/pycirclize/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
R_LIM = (MIN_R, MAX_R)
AXIS_FACE_PARAM = dict(zorder=0.99, ec="none", edgecolor="none")
AXIS_EDGE_PARAM = dict(zorder=1.01, fc="none", facecolor="none")
REL_TOL = 1e-10 # Relative Tolerance

# Circos Color Scheme
# http://circos.ca/tutorials/lessons/configuration/colors/
Expand Down
16 changes: 4 additions & 12 deletions src/pycirclize/sector.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
from copy import deepcopy
from pathlib import Path
from typing import Any, Callable
from urllib.parse import urlparse
from urllib.request import urlopen

import numpy as np
from matplotlib.patches import Patch
Expand Down Expand Up @@ -213,8 +211,8 @@ def x_to_rad(self, x: float, ignore_range_error: bool = False) -> float:
if not ignore_range_error:
# Apply relative torelance value to sector range to avoid
# unexpected invalid range error due to rounding errors (Issue #27, #67)
rel_tol = 1e-14
min_range, max_range = self.start - rel_tol, self.end + rel_tol
min_range = self.start - config.REL_TOL
max_range = self.end + config.REL_TOL
if not min_range <= x <= max_range:
err_msg = f"{x=} is invalid range of '{self.name}' sector.\n{self}"
raise ValueError(err_msg)
Expand Down Expand Up @@ -390,7 +388,7 @@ def raster(
Parameters
----------
img : str | Path | Image
Image for plotting (`File Path`|`URL`|`PIL Image`)
Image data (`File Path`|`URL`|`PIL Image`)
size : float, optional
Image size (ratio to overall figure size)
x : float | None, optional
Expand Down Expand Up @@ -421,13 +419,7 @@ def raster(
text_kws = {} if text_kws is None else deepcopy(text_kws)

# Load image data
if isinstance(img, str) and urlparse(img).scheme in ("http", "https"):
im = Image.open(urlopen(img))
elif isinstance(img, (str, Path)):
im = Image.open(str(img))
else:
im = img
im = im.convert("RGBA")
im = utils.load_image(img)

# Draw border on image
if border_width > 0:
Expand Down
78 changes: 72 additions & 6 deletions src/pycirclize/track.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from matplotlib.colors import Colormap, Normalize
from matplotlib.patches import Patch
from matplotlib.projections.polar import PolarAxes
from PIL import Image

from pycirclize import config, utils
from pycirclize.parser import StackedBarTable
Expand Down Expand Up @@ -289,7 +290,9 @@ def rect(
rad = min(rad_rect_start, rad_rect_end)
width = abs(rad_rect_end - rad_rect_start)
if r_lim is not None:
if not min(self.r_lim) <= min(r_lim) < max(r_lim) <= max(self.r_lim):
min_range = min(self.r_lim) - config.REL_TOL
max_range = max(self.r_lim) + config.REL_TOL
if not min_range <= min(r_lim) < max(r_lim) <= max_range:
raise ValueError(f"{r_lim=} is invalid track range.\n{self}")
radr, height = (rad, min(r_lim)), max(r_lim) - min(r_lim)
elif ignore_pad:
Expand Down Expand Up @@ -332,7 +335,9 @@ def arrow(
if r_lim is None:
r, dr = min(self.r_plot_lim), self.r_plot_size
else:
if not min(self.r_lim) <= min(r_lim) < max(r_lim) <= max(self.r_lim):
min_range = min(self.r_lim) - config.REL_TOL
max_range = max(self.r_lim) + config.REL_TOL
if not min_range <= min(r_lim) < max(r_lim) <= max_range:
raise ValueError(f"{r_lim=} is invalid track range.\n{self}")
r, dr = min(r_lim), max(r_lim) - min(r_lim)
arc_arrow = ArcArrow(
Expand Down Expand Up @@ -575,14 +580,12 @@ def yticks(
x_lim = (self.end, self.end + x_tick_length)
x_text = self.end + (x_tick_length + x_label_margin)
deg_text = math.degrees(self.x_to_rad(x_text, True))
is_lower_loc = -270 <= deg_text < -90 or 90 <= deg_text < 270
ha = "right" if is_lower_loc else "left"
ha = "right" if utils.plot.is_lower_loc(deg_text) else "left"
elif side == "left":
x_lim = (self.start, self.start - x_tick_length)
x_text = self.start - (x_tick_length + x_label_margin)
deg_text = math.degrees(self.x_to_rad(x_text, True))
is_lower_loc = -270 <= deg_text < -90 or 90 <= deg_text < 270
ha = "left" if is_lower_loc else "right"
ha = "left" if utils.plot.is_lower_loc(deg_text) else "right"
else:
raise ValueError(f"{side=} is invalid ('right' or 'left').")
# Plot yticks
Expand Down Expand Up @@ -1142,6 +1145,69 @@ def heatmap(
text_r = sum(rect_r_lim) / 2
self.text(text_value, text_x, text_r, **text_kws)

def raster(
self,
img: str | Path | Image.Image,
*,
w: float = 1.0,
h: float = 1.0,
rotate: bool = True,
**kwargs,
) -> None:
"""Plot raster image
Parameters
----------
img : str | Path | Image.Image
Image data (`File Path`|`URL`|`PIL Image`)
w : float, optional
Width ratio (`0.0 - 1.0`)
h : float, optional
Height ratio (`0.0 - 1.0`)
rotate : bool, optional
If True, rotate image 180 degrees if track is in lower location
(`-270 <= degree < -90`|`90 <= degree < 270`)
**kwargs : dict, optional
Axes.pcolormesh properties
<https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.pcolormesh.html>
"""
# Check range of value
if not 0.0 < w <= 1.0:
raise ValueError(f"{w=} is invalid (0.0 < w <= 1.0).")
if not 0.0 < h <= 1.0:
raise ValueError(f"{h=} is invalid (0.0 < h <= 1.0).")

# Calculate radian (size, start, end)
rad_size = self.rad_size * w
rad_pad = self.rad_size * ((1.0 - w) / 2)
rad_start, rad_end = min(self.rad_lim) + rad_pad, max(self.rad_lim) - rad_pad
# Calculate radius (size, start, end)
r_size = self.r_size * h
r_pad = self.r_size * ((1.0 - h) / 2)
r_start, r_end = min(self.r_lim) + r_pad, max(self.r_lim) - r_pad

# Load image
img = utils.load_image(img)

# Rotate image 180 degrees if track is in lower location
track_center_deg = sum(self.deg_lim) / 2
if rotate and utils.plot.is_lower_loc(track_center_deg):
img = img.rotate(180)

# Resize image
pixel_w = int(rad_size / (np.pi / 1000))
pixel_h = int(r_size * 10)
resize_img = img.resize((pixel_w, pixel_h))

# Setup radian & radius positions for plotting image by pcolormesh
rad_list = np.linspace(rad_start, rad_end, resize_img.width)
r_list = np.linspace(r_end, r_start, resize_img.height)

def plot_raster(ax: PolarAxes):
ax.pcolormesh(rad_list, r_list, np.array(resize_img), **kwargs)

self._plot_funcs.append(plot_raster)

def tree(
self,
tree_data: str | Path | Tree,
Expand Down
8 changes: 7 additions & 1 deletion src/pycirclize/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
load_example_tree_file,
load_prokaryote_example_file,
)
from pycirclize.utils.helper import ColorCycler, calc_group_spaces, is_pseudo_feature
from pycirclize.utils.helper import (
ColorCycler,
calc_group_spaces,
is_pseudo_feature,
load_image,
)

__all__ = [
"plot",
Expand All @@ -18,4 +23,5 @@
"ColorCycler",
"calc_group_spaces",
"is_pseudo_feature",
"load_image",
]
Loading

0 comments on commit 4b434c6

Please sign in to comment.