https://github.com/ckjellson/textalloc

Allocates text labels in matplotlib

https://github.com/ckjellson/textalloc

Science Score: 26.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (9.3%) to scientific vocabulary

Keywords

line-plot matplotlib plotting scatter-plot text-plotting
Last synced: 5 months ago · JSON representation

Repository

Allocates text labels in matplotlib

Basic Info
  • Host: GitHub
  • Owner: ckjellson
  • License: mit
  • Language: Jupyter Notebook
  • Default Branch: main
  • Homepage:
  • Size: 9.9 MB
Statistics
  • Stars: 89
  • Watchers: 3
  • Forks: 11
  • Open Issues: 0
  • Releases: 0
Topics
line-plot matplotlib plotting scatter-plot text-plotting
Created over 3 years ago · Last pushed 7 months ago
Metadata Files
Readme License

README.md

textalloc - Efficient matplotlib Text Allocation

plt.text|textalloc :-------------------------:|:-------------------------: | |

Scatterplot design in top row from scattertext (https://github.com/JasonKessler/scattertext)

Table of contents

  1. Quick-start
  2. Features
  3. Parameters
  4. Implementation and speed
  5. Examples

Quick-start

textalloc allocates text labels in matplotlib plots and is an alternative to adjustText (https://github.com/Phlya/adjustText).

Installation

pip install textalloc

Using textalloc

The code below generates the plot to the right:

``` import textalloc as ta import numpy as np import matplotlib.pyplot as plt

np.random.seed(0) x, y = np.random.random((2,30)) fig, ax = plt.subplots() ax.scatter(x, y, c='b') textlist = [f'Text{i}' for i in range(len(x))] ta.allocate(ax,x,y, textlist, xscatter=x, yscatter=y, textsize=10) plt.show() ```

plt.text|textalloc :-------------------------:|:-------------------------: |

Features

Avoids the following types of text label overlaps: - Lines - Points - Plot boundary - Other text labels - Arbitrary objects when providing the scatter plot object

Other supported features: 1. Setting min and max distances between text labels and objects 2. Drawing lines between label and the corresponding position, optionally also avoiding overlap with these lines 3. Draw all text labels, or only the subset that has no internal overlap 3. Setting direction of text labels w.r.t. the corresponding positions 4. Plotting in 3D by providing z-coordinates for a 3D axes 5. Plotting on images 6. Using custom transforms

Parameters

Text-boxes input parameters are x, y and textlist, which define the text-strings to be plotted and the positions that the texts should point to. xscatter, yscatter, xlines and y_lines define all points and lines in the plot that should not overlap with the text-boxes. Note that the scattered points do not have to be the same as x and y for the text-boxes, but can include more, or different scattered points.

``` ax: matplotlib axes used for plotting. x: (array-like): x-coordinates of texts. y: (array-like): y-coordinates of texts. textlist: (array-like): list of texts. z: (array-like), default None z-coordinates of texts in case of plotting in 3D. xscatter: (array-like), default None x-coordinates of all scattered points. yscatter: (array-like), default None y-coordinates of all scattered points. zscatter: (array-like), default None z-coordinates of all scattered points in case of plotting in 3D. xlines: (array-like), default None pairs of x-coordinates of all lines in the plot (start and endpoint). ylines: (array-like), default None pairs of y-coordinates of all lines in the plot (start and endpoint). zlines: (array-like), default None pairs of z-coordinates of all lines in the plot (start and endpoint) in case of plotting in 3D. scattersizes: (array-like), default None sizes of all scattered objects in plot list of 1d arrays/lists. scatterplot: provide a scatterplot object (scatterplot=ax.scatter(...)) for more exact placement instead of xscatter, yscatter, scattersizes etc.. default None. textscattersizes: (array-like), default None sizes of text scattered objects in plot list of 1d arrays/lists. textsize: (Union[int, List[int]]), default 10 Size of text. fontweight: (Union[int, str, List[int], List[str]]), default'normal' Weight of text font margin: (float), default 0.0 Parameter for margins between objects. Recommendation: keep this lower than mindistance. Increase for larger margins to points and lines. Given in proportion of x-ax dimensions (0-1) mindistance: (float), default 0.015 Parameter for min distance from textbox to its plotted position. Given in proportion of x-ax dimensions (0-1) maxdistance: (float), default 0.2 Parameter for max distance from textbox to its plotted position. Given in proportion of x-ax dimensions (0-1) verbose: (bool), default False prints progress using tqdm. drawlines: (bool), default True draws lines from original points to textboxes. linecolor: (Union[str, List[str]]), default "r" Color code of the lines between points and text-boxes. drawall: (bool), default True Draws all texts after allocating as many as possible despite overlap. nbrcandidates: (int), default 200 Sets the number of candidates used. linewidth: (float), default 1 Width of line between textbox and it's origin. textcolor: (Union[str, List[str]]), default "k" Color code of the text. direction: (str), default None Sets the preferred direction of the boxes with options: (south, north, east, west, northeast, northwest, southeast, southwest). prioritystrategy: (Union[int, str, Callable[[float, float], float]]), default None Sets priority strategy for text allocation (None / random seed / strategy name among ["largest"]). avoidlabellinesoverlap: (bool), default False If set to True, avoids overlap for drawn lines to text labels. avoidcrossinglabellines: (bool), default False If True, avoids crossing label lines. autoalign: (bool), default True If True, horizontally aligns text dependent on location relative to it's x and y coordinate. xlims: (Tuple[float, float], optional), default ax.getxlim() x-axis limits of the plot. ylims: (Tuple[float, float], optional), default ax.getylim() y-axis limits of the plot. plotkwargs: (dict), default None kwargs for the plt.plot of the lines if drawlines is True. **kwargs: () kwargs for the plt.text() call. If transform is used, it only needs to be provided here, i.e. not also in plotkwargs.

Returns: resulttextxy (List[Tuple[float, float]]): List of resulting (x,y) positions for text labels used in the plt.text call. resultlines (List[Tuple[float, float], Tuple[float, float]]): List of resulting (x,y) pairs used in the plt.plot call for drawing lines. textobjects (List[plt.Text]): List of plt.Text objects from the plt.text calls. line_objects (List[plt.Line2D]): List of plt.Line2D objects from the plt.plot calls. ```

The allocate call returns a tuple containing the resulting positions used to plot the text labels and the connecting label lines.

Implementation and speed

The implementation aims to plot as many text-boxes as possible in the free space in the plot. There are three main steps of the algorithm:

For each textbox to be plotted: 1. Generate a large number of candidate boxes near the original point with size that matches the fontsize. 2. Find the candidates that do not overlap any points, lines, plot boundaries, or already allocated boxes. 3. Allocate the text to the first candidate box with no overlaps.

Speed

The plot in the top of this Readme was generated in 2.1s on a laptop, and there are rarely more textboxes that fit into one plot. If the result is still too slow to render, try decreasing nbr_candidates.

The speed is greatly improved by usage of numpy broadcasting in all functions for computing overlap (see textalloc/overlap_functions and textalloc/find_non_overlapping). A simple example from the function non_overlapping_with_boxes which checks if the candidate boxes (expanded with xfrac, yfrac to provide a margin) overlap with already allocated boxes:

candidates[:, 0][:, None] - xfrac > box_arr[:, 2]

The code compares xmin coordinates of all candidates with xmax coordinates of all allocated boxes resulting in a boolean matrix of shape (Ncandidates, Nallocated) by use of indexing [:, None].

Examples

textalloc supports avoiding overlap with points, lines, and the plot boundary in addition to other text-boxes. See examples below and demo.py for all examples.

``` import textalloc as ta import numpy as np import matplotlib.pyplot as plt

xline = np.array([0.0, 0.03192317, 0.04101177, 0.26085659, 0.40261173, 0.42142198, 0.87160195, 1.00349979]) yline = np.array([0. , 0.2, 0.2, 0.4, 0.8, 0.6, 1. , 1. ]) text_list = ['0', '25', '50', '75', '100', '125', '150', '250'] np.random.seed(0) x, y = np.random.random((2,100))

fig,ax = plt.subplots(dpi=100) ax.plot(xline,yline,color="black") ax.scatter(x,y,c="b") ta.allocate(ax,xline,yline, textlist, xscatter=x, yscatter=y, xlines=[xline], ylines=[y_line]) plt.show() ```

plt.text|textalloc :-------------------------:|:-------------------------: |

``` import textalloc as ta import numpy as np import matplotlib.pyplot as plt

np.random.seed(2017) xdata = np.random.randomsample(100) ydata = np.random.randomintegers(10,50,(100))

f, ax = plt.subplots(dpi=200) bars = ax.bar(xdata, ydata, width=0.002, facecolor='k') ta.allocate(ax,xdata,ydata, [str(yy) for yy in list(ydata)], xlines=[np.array([xx,xx]) for xx in list(xdata)], ylines=[np.array([0,yy]) for yy in list(ydata)], textsize=8, margin=0.004, mindistance=0.005, linewidth=0.7, nbr_candidates=100, textcolor="b") plt.show() ```

plt.text|textalloc :-------------------------:|:-------------------------: |

Plotting on images and using transforms

textalloc now also supports plotting on images and using transforms. Below is an eample of using the PlateCarree transform to plot on top of a downloaded OSM-map (thank you @nebukadnezar for the example).

``` import numpy as np import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.io.img_tiles as cimgt import textalloc as ta

np.random.seed(1) x = np.random.rand(100) x += 150.5 y = np.random.rand(100) y -= 34.5

dpi = 72 fig = plt.figure(figsize=(800/dpi, 800/dpi), dpi=dpi)

zoom = 10 sitelon = np.mean(x) sitelat = np.mean(y) radius = (np.max(x) - np.min(x) ) / 1.5 lllon = sitelon - radius * (1/np.cos(np.radians(sitelat))) lllat = sitelat - radius urlon = sitelon + radius * (1/np.cos(np.radians(sitelat))) urlat = sitelat + radius extent = [lllon, urlon, lllat, urlat]

request = cimgt.OSM(desiredtileform="L") ax = plt.axes(projection=request.crs) ax.setextent(extent) ax.addimage(request, zoom, alpha=0.5, cmap='gray') ax.scatter(x, y, c='b', transform=ccrs.PlateCarree())

textlist = [f'Text{i}' for i in range(len(x))] ta.allocate(ax,x,y, textlist, xscatter=x, yscatter=y, textsize=10, drawlines=True, linewidth=0.5, drawall=False, transform=ccrs.PlateCarree(), avoidlabellines_overlap=True) plt.show() ```

Plotting in 3D

``` import textalloc as ta import numpy as np import matplotlib.pyplot as plt

fig = plt.figure(figsize=(10,10), dpi=200) ax = plt.axes(projection='3d') nPoints = 300 nLines = 50 z = 15 * np.random.random(nPoints) x = np.sin(z) + 0.1 * np.random.randn(nPoints)/2 y = np.cos(z) + 0.1 * np.random.randn(nPoints)/2 xlines = [[x, x] for _x in x[:nLines]] ylines = [[y, _y] for _y in y[:nLines]] zlines = [[0, z] for _z in z[:nLines]] textlist = [f'Text{i}' for i in range(len(x))] ax.scatter(x,y,z,c=z, cmap="brg", alpha=1) for xl,yl,zl in zip(xlines, ylines, zlines): ax.plot(xl, yl, zl, "k--") ta.allocate(ax,x,y,textlist,z=z, xscatter=x, yscatter=y, zscatter=z, xlines=xlines, ylines=ylines, zlines=zlines, avoidlabellinesoverlap=True, drawall=False, linewidth=0.7, textsize=8, maxdistance=0.07) plt.show() ```

plt.text|textalloc :-------------------------:|:-------------------------: |

Owner

  • Name: Christoffer Kjellson
  • Login: ckjellson
  • Kind: user
  • Location: Lund Sweden
  • Company: Axis Communications

Algorithm Developer

GitHub Events

Total
  • Issues event: 7
  • Watch event: 28
  • Issue comment event: 13
  • Push event: 9
  • Pull request review event: 2
  • Pull request review comment event: 3
  • Pull request event: 7
  • Fork event: 4
Last Year
  • Issues event: 7
  • Watch event: 28
  • Issue comment event: 13
  • Push event: 9
  • Pull request review event: 2
  • Pull request review comment event: 3
  • Pull request event: 7
  • Fork event: 4

Committers

Last synced: almost 3 years ago

All Time
  • Total Commits: 9
  • Total Committers: 2
  • Avg Commits per committer: 4.5
  • Development Distribution Score (DDS): 0.111
Top Committers
Name Email Commits
Christoffer Kjellson c****n@g****m 8
Christoffer Kjellson 3****n@u****m 1

Issues and Pull Requests

Last synced: 6 months ago

All Time
  • Total issues: 21
  • Total pull requests: 10
  • Average time to close issues: about 1 month
  • Average time to close pull requests: 2 days
  • Total issue authors: 19
  • Total pull request authors: 6
  • Average comments per issue: 3.05
  • Average comments per pull request: 0.4
  • Merged pull requests: 9
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 5
  • Pull requests: 4
  • Average time to close issues: 4 days
  • Average time to close pull requests: 1 day
  • Issue authors: 5
  • Pull request authors: 4
  • Average comments per issue: 2.0
  • Average comments per pull request: 0.25
  • Merged pull requests: 3
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • nebukadnezar (2)
  • ThomasParistech (2)
  • Surveyor4lyfe (1)
  • sigvaldm (1)
  • luisfgranada (1)
  • SunYong0821 (1)
  • Buuugle (1)
  • alirezaomidi (1)
  • OscarPellicer (1)
  • ews-ffarella (1)
  • JunruQ (1)
  • guidocioni (1)
  • ramanshah (1)
  • MarkusPic (1)
  • jordanplanders (1)
Pull Request Authors
  • ThomasParistech (12)
  • mrmbernardi (2)
  • DFEvans (2)
  • alirezaomidi (2)
  • kdheepak (1)
  • jiuntian (1)
Top Labels
Issue Labels
Pull Request Labels

Packages

  • Total packages: 1
  • Total downloads:
    • pypi 2,290 last-month
  • Total dependent packages: 5
  • Total dependent repositories: 1
  • Total versions: 32
  • Total maintainers: 1
pypi.org: textalloc

Efficient Text Allocation in matplotlib using NumPy Broadcasting

  • Versions: 32
  • Dependent Packages: 5
  • Dependent Repositories: 1
  • Downloads: 2,290 Last month
Rankings
Dependent packages count: 3.2%
Downloads: 12.5%
Average: 15.1%
Stargazers count: 15.6%
Dependent repos count: 21.6%
Forks count: 22.6%
Maintainers (1)
Last synced: 7 months ago