Showing preview only (444K chars total). Download the full file or copy to clipboard to get everything.
Repository: chalk-diagrams/chalk
Branch: master
Commit: 6899b958f11e
Files: 117
Total size: 413.2 KB
Directory structure:
gitextract_pp7itskp/
├── .github/
│ └── workflows/
│ ├── checks.yml
│ ├── mkdocs-publish-ghpages.yml
│ └── selfassign.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── chalk/
│ ├── __init__.py
│ ├── align.py
│ ├── arrow.py
│ ├── backend/
│ │ ├── __init__.py
│ │ ├── cairo.py
│ │ ├── svg.py
│ │ └── tikz.py
│ ├── combinators.py
│ ├── core.py
│ ├── envelope.py
│ ├── model.py
│ ├── monoid.py
│ ├── py.typed
│ ├── shapes/
│ │ ├── __init__.py
│ │ ├── arc.py
│ │ ├── arrowheads.py
│ │ ├── image.py
│ │ ├── latex.py
│ │ ├── path.py
│ │ ├── segment.py
│ │ ├── shape.py
│ │ └── text.py
│ ├── style.py
│ ├── subdiagram.py
│ ├── trace.py
│ ├── trail.py
│ ├── transform.py
│ ├── types.py
│ ├── utils.py
│ └── visitor.py
├── doc/
│ └── crop-images.sh
├── docs/
│ ├── about/
│ │ ├── index.md
│ │ └── license.md
│ ├── api/
│ │ ├── alignment.py
│ │ ├── combinators.py
│ │ ├── internals.md
│ │ ├── names.py
│ │ ├── rendering.py
│ │ ├── shapes.py
│ │ ├── style.py
│ │ ├── trails.py
│ │ ├── transformations.py
│ │ └── utils.md
│ ├── assets/
│ │ ├── css-js/
│ │ │ ├── as-fastapi/
│ │ │ │ ├── chat.js
│ │ │ │ ├── custom.css
│ │ │ │ ├── custom.js
│ │ │ │ ├── termynal.css
│ │ │ │ └── termynal.js
│ │ │ ├── general/
│ │ │ │ ├── css/
│ │ │ │ │ ├── progressbar.css
│ │ │ │ │ └── social-color-stryle.css
│ │ │ │ └── js/
│ │ │ │ ├── highlight-config.js
│ │ │ │ ├── material-extra/
│ │ │ │ │ ├── extra-uml.js
│ │ │ │ │ ├── material-extra-theme.js
│ │ │ │ │ └── uml.js
│ │ │ │ └── tables.js
│ │ │ ├── mkdocs-tooltips/
│ │ │ │ └── css/
│ │ │ │ └── custom.css
│ │ │ ├── pymdownx-extras/
│ │ │ │ ├── css/
│ │ │ │ │ └── extra.css
│ │ │ │ └── js/
│ │ │ │ └── extra-uml.js
│ │ │ └── termynal/
│ │ │ ├── css/
│ │ │ │ ├── custom.css
│ │ │ │ └── termynal.css
│ │ │ ├── js/
│ │ │ │ ├── custom.js
│ │ │ │ └── termynal.js
│ │ │ └── readme.md
│ │ └── snippets/
│ │ ├── macros/
│ │ │ └── .gitkeep
│ │ └── notifications/
│ │ └── videos/
│ │ └── disclaimer.md
│ ├── examples/
│ │ ├── hanoi.py
│ │ ├── koch.py
│ │ ├── lenet.py
│ │ ├── squares.py
│ │ └── tensor.py
│ ├── index.md
│ ├── overrides/
│ │ └── main.html
│ └── quickstart/
│ └── .gitkeep
├── examples/
│ ├── arrows.py
│ ├── bigben.py
│ ├── comparison.tex
│ ├── escher_square_limit.py
│ ├── hanoi.py
│ ├── hex_variation.py
│ ├── hilbert.py
│ ├── intro.py
│ ├── isometric.py
│ ├── koch.py
│ ├── latex.py
│ ├── lattice.py
│ ├── lenet.py
│ ├── logo.py
│ ├── normalized.py
│ ├── output/
│ │ ├── index.html
│ │ ├── path.tex
│ │ └── path.tex.tex
│ ├── path.py
│ ├── rectangle_parade.py
│ ├── squares.py
│ ├── subdiagrams.py
│ ├── tensor.py
│ ├── tests/
│ │ └── index.tpl.html
│ ├── tournament-network.py
│ └── tree.py
├── mkdocs.yml
├── pyproject.toml
├── requirements/
│ ├── dev.txt
│ └── docs.txt
├── requirements.txt
├── setup.cfg
├── setup.py
└── tests/
├── test_envelope.py
└── test_reverse_trail.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/checks.yml
================================================
name: checks.yml
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.10"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
sudo apt update && sudo apt upgrade -y
sudo apt-get install libcairo2-dev pdf2svg texlive texlive-science texlive-latex-recommended texlive-latex-extra
python -m pip install --upgrade pip
pip install -U pip wheel
make install.dev install.base install.e install.docs
- name: Format with [[ isort ]]
run: |
# stop the build if there are errors raised by isort
make isort
- name: Format with [[ black ]]
run: |
# stop the build if there are errors raised by black
make black
- name: Lint with [[ flake8 ]]
run: |
# stop the build if there are Python syntax errors or undefined names
make flake
- name: Docstring linting -- type check against function/method signature with [[ darglint ]]
run: |
# stop the build if docstring type-mismatch detected
make darglint
- name: Doctest coverage check with [[ interrogate ]]
run: |
# stop the build if doctest threshold does not meet
make interrogate
- name: Type check with [[ mypy ]]
run: |
# stop the build if there are type errors
make type
- name: Unit test with [[ pytest ]]
run: |
# stop the build if there are type errors
make test
- name: Make sure examples run
run: |
make images
- name: Make sure docs run
run: |
make docsapi
================================================
FILE: .github/workflows/mkdocs-publish-ghpages.yml
================================================
name: "MkDocs Publish Docs on GitHub Pages CI"
on:
# Manually trigger workflow
workflow_dispatch:
inputs:
branch:
description: Build MkDocs from Branch (Optional)
required: false
# Trigger when a push happens
# to select branches.
push:
branches:
- master
env:
PYTHON_VERSION: "3.8"
USER_SPECIFIED_BRANCH: ${{ github.event.inputs.branch }}
REPO_OWNER: ${{ github.repository_owner }} # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Python runtime
uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install Python dependencies for MkDocs
run: |
echo "REPO_OWNER: ${{ env.REPO_OWNER }}"
ls
pip install -r requirements/docs.txt
- name: Install all other dependencies
run: |
pip install -r requirements.txt
- name: Deploy documentation
env:
FONTAWESOME_KIT: ${{ secrets.FONTAWESOME_KIT }}
run: |
# Check if user-provided branch exists
# then switch to that branch.
if [[ -z $(git branch --list "${{ env.USER_SPECIFIED_BRANCH }}") ]]; \
then (\
echo "Switching to branch: ${{ env.USER_SPECIFIED_BRANCH }}" && \
git checkout ${{ env.USER_SPECIFIED_BRANCH }} \
); else USER_SPECIFIED_BRANCH=${GITHUB_REF##*/} ; fi && \
echo "Current Git Branch: ${USER_SPECIFIED_BRANCH}"
# Install chalk from current branch
python -m pip install "."
# Begin Deploying MkDocs
mkdocs gh-deploy --force
mkdocs --version
================================================
FILE: .github/workflows/selfassign.yml
================================================
# Allow users to automatically tag themselves to issues
#
# Usage:
# - a github user (a member of the repo) needs to comment
# with "#self-assign" on an issue to be assigned to them.
#------------------------------------------------------------
name: Self-assign
on:
issue_comment:
types: created
jobs:
one:
runs-on: ubuntu-latest
if: >-
(github.event.comment.body == '#take' ||
github.event.comment.body == '#self-assign')
steps:
- run: |
echo "Assigning issue ${{ github.event.issue.number }} to ${{ github.event.comment.user.login }}"
curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-d '{"assignees": ["${{ github.event.comment.user.login }}"]}' \
https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/assignees
echo "Done 🔥 "
================================================
FILE: .gitignore
================================================
# User Defined
.vscode
# Temporary files
*.swp
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
.archives/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
================================================
FILE: CHANGELOG.md
================================================
## `v0.2.2` – 2024-06-14
Added:
- Isometric example (#130)
- Associative reduce (#129)
- Subdiagram (#116)
- Property-based testing (#111)
Changed:
- Rewrite of core (#132)
- Bug fixes and typos (#110, #127, #128)
## `v0.2.1` — 2022-09-07
Added:
- Traces and envelopes
- TikZ backend
- Arrows and connections
- More examples (e.g, Big Ben)
- Gallery of examples
- Documentation
Changed:
- Use `planar` library for geometry
- Major code refactoring
## `v0.1.2` — 2022-05-20
Other changes:
- Add license (MIT license)
- Remove dependency on `streamlit`
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 Dan Oneață and Alexander Rush
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
include README.md
include chalk/py.typed # marker file for PEP 561
include CHANGELOG.md
include changelog.md
include LICENSE
include LICENSE.txt
include CITATION.cff
include *.cff # citation info
include MANIFEST.in
include pyproject.toml
include setup.py
include setup.cfg
include requirements.txt
recursive-include requirements *.txt
recursive-exclude examples *
recursive-exclude tests *
recursive-exclude docs *
recursive-exclude site *
recursive-exclude apps *
recursive-exclude .archives *
exclude .flake8
exclude .gitignore
exclude .pre-commit-config.yaml
exclude mkdocs.yml
exclude Makefile
================================================
FILE: Makefile
================================================
# maintenance
.PHONY: isort flake black test type interrogate darglint \
clean cleanall style docs check
# installation
.PHONY: install installplus install.e install.all
install.base install.dev install.docs
# uninstallation
.PHONY: uninstall uninstallplus uninstall.e uninstall.all \
uninstall.base uninstall.dev uninstall.docs
# documentation
.PHONY: pregendocs.doc pregendocs.examples pregendocs.local pregendocs.remote \
gendocs \
postgendocs.doc postgendocs.local postgendocs.remote gendocsall.local
# generate examples
.PHONY: intro squares hanoi escher_square lattice lenet logo \
hilbert koch tensor latex hex_variation images \
tree serve
####------------------------------------------------------------####
# libname is either same as PACKAGE_NAME or
# as on PYPI (replace - with _)
LIBNAME := chalk_diagrams
PACKAGE_NAME := chalk
TESTPYPI_DOWNLOAD_URL := "https://test.pypi.org/simple/"
PYPIPINSTALL := "python -m pip install -U --index-url"
PIPINSTALL_PYPITEST := "$(PYPIPINSTALL) $(TESTPYPI_DOWNLOAD_URL)"
PKG_INFO := "import pkginfo; dev = pkginfo.Develop('.'); print((dev.$${FIELD}))"
# This is where you store the eggfile
# and other generated archives
ARCHIVES_DIR := ".archives"
# Folder path for tests
TESTS_DIR := "tests"
# Interrogate will flag the test as FAILED if
# % success threshold is under the following value
INTERROGATE_FAIL_UNDER := 0 # ideally this should be 100
# Specify paths of various dependency files
REQ_FOLDER := "requirements"
# location: requirements.txt
REQ_FILE := "requirements.txt"
# location: requirements/dev.txt
DEV_REQ_FILE := "dev.txt"
# location: requirements/docs.txt
DOCS_REQ_FILE := "docs.txt"
####------------------------------------------------------------####
### Code maintenance
## Run isort
isort:
@ echo "✨ Applying import sorter: isort ... ⏳"
# The settings are maintained in setup.cfg file.
isort $(PACKAGE_NAME) setup.py \
tests \
## Run black
black:
@ echo "✨ Applying formatter: black ... ⏳"
black --target-version py38 --line-length 79 $(PACKAGE_NAME) setup.py \
tests \
## Run flake8
flake:
@ echo "✨ Applying formatter: flake8 ... ⏳"
flake8 --show-source $(PACKAGE_NAME) setup.py \
tests \
## Run pytest
test:
@ echo "✨ Run tests: pytest ... ⏳"
@if [ -d "$(TESTS_DIR)" ]; then pytest $(TESTS_DIR); else echo "\n\t🔥 No tests configured yet. Skipping tests.\n"; fi
## Run mypy
type:
@ echo "✨ Applying type checker: mypy ... ⏳"
mypy --strict --ignore-missing-imports --no-warn-unused-ignores $(PACKAGE_NAME) \
tests \
## Run darglint
darglint:
@ echo "✨ Applying docstring type checker: darglint ... ⏳"
# The settings are maintained in setup.cfg file.
darglint -v 2 $(PACKAGE_NAME) --ignore-properties
## Run interrogate
interrogate:
@ echo "✨ Applying doctest checker: interrogate ... ⏳"
$(eval INTERROGATE_CONFIG := -vv --ignore-nested-functions --ignore-semiprivate --ignore-private --ignore-magic --ignore-module --ignore-init-method --fail-under $(INTERROGATE_FAIL_UNDER))
$(eval INTERROGATE_COMMAND := interrogate $(INTERROGATE_CONFIG))
# Check tests folder
@if [ -d "$(TESTS_DIR)" ]; then $(INTERROGATE_COMMAND) $(TESTS_DIR); else echo "\n\t🔥 No tests configured yet. Skipping tests.\n"; fi
# Check package folder
@$(INTERROGATE_COMMAND) $(PACKAGE_NAME)
## Cleanup
#
# Instruction:
#
# make clean : if only cleaning artifacts created after running,
# code, tests, etc.
# make cleanall : if cleaning all artifacts (including the ones
# generated after creating dist and wheels).
#
# Note: archives created (dist and wheels) are stored in
# $(ARCHIVES_DIR). This is defined at the top of this Makefile.
#--------------------------------------------------------------------
clean:
@ echo "🪣 Cleaning repository ... ⏳"
rm -rf \
.ipynb_checkpoints **/.ipynb_checkpoints \
.pytest_cache **/.pytest_cache \
**/__pycache__ **/**/__pycache__
cleanall: clean
@ echo "🪣 Cleaning dist/archive files ... ⏳"
rm -rf build/* dist/* $(PACKAGE_NAME).egg-info/* $(ARCHIVES_DIR)/*
## Style Checks and Unit Tests
style: clean isort black flake clean
docs: clean darglint interrogate clean
check: style docs type test clean
####------------------------------------------------------------####
### Code Installation
## Install for development (from local repository)
#
# Instruction: Contributors will need to run...
#
# - "make installplus": if installing for the first time or want to
# update to the latest dev-requirements or
# other extra dependencies.
# - "make install.e" : if only installing the local source (after
# making some changes) to the source code.
#--------------------------------------------------------------------
# .PHONY: install.e
install.e: clean
@echo "📀🟢🔵 Installing $(PACKAGE_NAME) from local source ... ⏳"
python -m pip install -Ue .[tikz,latex,png,svg]
# .PHONY: install
install: clean install.base install.e
@echo "📀🟢🟡🔵 Installing $(PACKAGE_NAME) and base-dependencies from PyPI ... ⏳"
# .PHONY: installplus
installplus: install.all install.e
@echo "📀🟢🟡🔵🟠 Installing $(PACKAGE_NAME) and all-dependencies from PyPI ... ⏳"
# .PHONY: install.all
install.all: clean install.base install.dev install.docs
@echo "📀🟢🟡 Installing $(PACKAGE_NAME)'s all-dependencies from PyPI ... ⏳"
# .PHONY: install.base
install.base:
@echo "📀🟢🟡 Installing from: $(DEV_REQ_FILE) ... ⏳"
if [ -f $(REQ_FILE) ]; then python -m pip install -U -r $(REQ_FILE); fi
# .PHONY: install.dev
install.dev:
@echo "📀🟢🟡 Installing from: $(DEV_REQ_FILE) ... ⏳"
if [ -f $(REQ_FOLDER)/$(DEV_REQ_FILE) ]; then python -m pip install -U -r $(REQ_FOLDER)/$(DEV_REQ_FILE); fi
# .PHONY: install.docs
install.docs:
@echo "📀🟢🟡 Installing from: $(DOCS_REQ_FILE) ... ⏳"
@if [ -f $(REQ_FOLDER)/$(DOCS_REQ_FILE) ]; then python -m pip install -U -r $(REQ_FOLDER)/$(DOCS_REQ_FILE); fi
## Uninstall from dev-environment
# .PHONY: uninstall.e
uninstall.e: clean
@echo "📀🟢🔵 Uninstalling $(PACKAGE_NAME)' local editable version ... ⏳"
@# https://stackoverflow.com/questions/48826015/uninstall-a-package-installed-with-pip-install
rm -rf "$(LIBNAME).egg-info"
# .PHONY: uninstall
uninstall: clean uninstall.base uninstall.e
@echo "📀🔴🟡🔵 Uninstalling $(PACKAGE_NAME) and base-dependencies from PyPI ... ⏳"
# .PHONY: uninstallplus
uninstallplus: uninstall.all uninstall.e
@echo "📀🔴🟡🔵🟠 Uninstalling $(PACKAGE_NAME) and all-dependencies from PyPI ... ⏳"
# .PHONY: uninstall.all
uninstall.all: clean uninstall.base uninstall.dev uninstall.docs clean
@echo "📀🔴🟡 Uninstalling $(PACKAGE_NAME)'s all-dependencies from PyPI ... ⏳"
# .PHONY: uninstall.base
uninstall.base:
@echo "📀🔴🟡 Uninstalling from: $(DEV_REQ_FILE) ... ⏳"
if [ -f $(REQ_FILE) ]; then python -m pip uninstall -r $(REQ_FILE); fi
# .PHONY: uninstall.dev
uninstall.dev:
@echo "📀🔴🟡 Uninstalling from: $(DEV_REQ_FILE) ... ⏳"
if [ -f $(REQ_FOLDER)/$(DEV_REQ_FILE) ]; then python -m pip uninstall -r $(REQ_FOLDER)/$(DEV_REQ_FILE); fi
# .PHONY: uninstall.docs
uninstall.docs:
@echo "📀🔴🟡 Uninstalling from: $(DEV_REQ_FILE) ... ⏳"
@if [ -f $(REQ_FOLDER)/$(DOCS_REQ_FILE) ]; then python -m pip uninstall -r $(REQ_FOLDER)/$(DOCS_REQ_FILE); fi
## Install from test.pypi.org
#
# Instruction:
#
# 🔥 This is useful if you want to test the latest released package
# from the TestPyPI, before you push the release to PyPI.
#--------------------------------------------------------------------
pipinstalltest:
@echo "💿 Installing $(PACKAGE_NAME) from TestPyPI ($(TESTPYPI_DOWNLOAD_URL)) ... ⏳"
# Example Usage:
# 👉 To run a command like:
# > python -m pip install -U --index-url https://test.pypi.org/simple/ $(PACKAGE_NAME)==$(VERSION)
# 👉 Run the following command:
# > make pipinstalltest VERSION="0.1.0"
# 👉 Specifying VERSION="#.#.#" installs a specific version.
# If no version is specified, the latest version is installed from TestPyPI.
@if [ $(VERSION) ]; then $(PIPINSTALL_PYPITEST) $(PACKAGE_NAME)==$(VERSION); else $(PIPINSTALL_PYPITEST) $(PACKAGE_NAME); fi;
## Gendocs
# .PHONY: gendocs
gendocs:
@echo "🔥 Generate documentation with MkDocs ... ⏳"
# generate documentation
mkdocs serve --dirtyreload
## Postgendocs
# .PHONY: postgendocs.doc
postgendocs.doc:
#echo "Cleanup docs... ⏳"
rm -rf docs/doc
# .PHONY: postgendocs.local
postgendocs.local: postgendocs.doc
# .PHONY: postgendocs.remote
postgendocs.remote: postgendocs.doc
# .PHONY: gendocsall.local
gendocsall.local: pregendocs.local gendocs postgendocs.local
# .PHONY: gendocsall.remote
# gendocsall.remote: pregendocs.remote gendocs postgendocs.remote
# @ # Use mkdocs-publish-ghpages.yml action instead of this make command
####------------------------------------------------------------####
### Generate Output for Examples
intro:
python examples/intro.py
squares:
python examples/squares.py
hanoi:
python examples/hanoi.py
escher_square:
python examples/escher_square_limit.py
lattice:
python examples/lattice.py
lenet:
python examples/lenet.py
logo:
python examples/logo.py
hilbert:
python examples/hilbert.py
koch:
python examples/koch.py
tensor:
python examples/tensor.py
latex:
python examples/latex.py
hex_variation:
python examples/hex_variation.py
tree:
python examples/tree.py
tournament:
python examples/tournament-network.py
parade:
python examples/rectangle_parade.py
arrows:
python examples/arrows.py
path:
python examples/path.py
images: squares hanoi intro hilbert koch tensor hex_variation tree tournament parade arrows path lenet escher_square logo
@echo "🎁 Generate all examples ... ⏳"
serve:
python -m http.server 8080 -d examples/output/
docsapi:
python docs/api/*.py
================================================
FILE: README.md
================================================
<p align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/examples/output/logo-sm.png" width=300></p>
Chalk is a declarative drawing library.
The API draws heavy inspiration from
Haskell's [diagrams](https://diagrams.github.io/),
Scala's [doodle](https://github.com/creativescala/doodle/) and
Jeremy Gibbons's lecture notes on [Functional Programming for Domain−Specific Languages](http://www.cs.ox.ac.uk/publications/publication7583-abstract.html).
The documentation is available at [https://chalk-diagrams.github.io](https://chalk-diagrams.github.io).
⚠️ The library is still very much work in progress and subject to change.
## Installation
The library is available on PyPI as `chalk-diagrams` and can be installed with `pip`:
```bash
pip install git+https://github.com/chalk-diagrams/chalk/
```
On Debian (or Colab) you will need to install Cairo for [PyCairo](https://pycairo.readthedocs.io)
```bash
sudo apt-get install libcairo2-dev
```
If you want to use the LaTeX extension, run:
```bash
pip install chalk-diagrams[latex]
```
For the LaTeX extension you might need to install `pdf2svg` and `texlive`;
on Debian these dependencies can be installed as follows:
```bash
sudo apt-get install pdf2svg texlive texlive-science texlive-latex-recommended texlive-latex-extra
```
**Installation with Conda**
You can install the library with **conda** from `conda-forge` channel.
```powershell
conda install -c conda-forge chalk-diagrams
```
## Overview
Below we provide a brief introduction of the main functionality of the library.
These examples are available in the `examples/intro.py` file.
We start by importing the [`colour`](https://github.com/vaab/colour) module and the `diagrams` functions:
```python
from colour import Color
from chalk import *
```
We also define some colors that will be shortly used:
```python
papaya = Color("#ff9700")
blue = Color("#005FDB")
```
We can easily create basic shapes (the functions `circle`, `square`, `triangle`) and style them with various attributes (the methods`fill_color`, `line_color`, `line_width`).
For example:
```python
d = circle(1).fill_color(papaya)
```

The diagram can be saved to an image using the `render` method:
```python
d.render("examples/output/intro-01.png", height=64)
```
We can glue together two diagrams using the combinators `atop` (or `+`), `beside` (or `|`), `above` (or `/`).
For example:
```python
circle(0.5).fill_color(papaya) | square(1).fill_color(blue)
```
which is equivalent to
```python
circle(0.5).fill_color(papaya).beside(square(1).fill_color(blue), unit_x)
```
This code produces the following image:

We also provide combinators for a list of diagrams:
`hcat` for horizontal composition, `vcat` for vertical composition.
For example:
```python
hcat(circle(0.1 * i) for i in range(1, 6)).fill_color(blue)
```

We can use Python functions to build more intricate diagrams:
```python
def sierpinski(n: int, size: int) -> Diagram:
if n <= 1:
return triangle(size)
else:
smaller = sierpinski(n - 1, size / 2)
return smaller.above((smaller | smaller).center_xy())
d = sierpinski(5, 4).fill_color(papaya)
```

### Gallery of examples
For more examples, please check the `examples` folder;
their output is illustrated below:
<table>
<tr>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/squares.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/squares.py">squares.py</a></code></td>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/logo.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/logo.py">logo.py</a></code></td>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/escher-square-limit.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/escher_square_limit.py">escher_square_limit.py</a></code></td>
</tr>
<tr>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/hilbert.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/hilbert.py">hilbert.py</a></code></td>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/koch.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/koch.py">koch.py</a></code></td>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/hex-variation.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/hex_variation.py">hex-variation.py</a></code></td>
</tr>
<tr>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/lenet.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/lenet.py">lenet.py</a></code></td>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/tensor.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/tensor.py">tensor.py</a></code></td>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/hanoi.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/hanoi.py">hanoi.py</a></code></td>
</tr>
<tr>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/tree.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/tree.py">tree.py</a></code></td>
<td align="center"><img src="https://raw.githubusercontent.com/chalk-diagrams/chalk/master/doc/imgs/lattice.png"><br><code><a href="https://github.com/chalk-diagrams/chalk/tree/master/examples/lattice.py">lattice.py</a></code></td>
</tr>
<!--<tr>
</tr>
-->
</table>
These scripts can be run as follows:
```bash
python examples/squares.py
```
## Authors
- [Dan Oneață](http://doneata.bitbucket.io/)
- [Alexander Rush](http://rush-nlp.com/)
Special thanks to:
- [Sugato Ray](https://github.com/sugatoray/), for his significant contributions and suggestions;
- [Ionuț G. Stan](http://igstan.ro/), for providing many useful insights and comments.
================================================
FILE: chalk/__init__.py
================================================
import math
import sys
from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple, Union
if sys.version_info >= (3, 8):
from importlib import metadata
else:
import importlib_metadata as metadata
import chalk.align as align
from chalk.align import * # noqa: F403
from chalk.arrow import ArrowOpts, arrow_at, arrow_between, arrow_v
from chalk.combinators import * # noqa: F403
from chalk.core import set_svg_draw_height, set_svg_height
from chalk.envelope import Envelope
from chalk.monoid import Maybe, MList, Monoid
from chalk.shapes import * # noqa: F403
from chalk.style import Style
from chalk.subdiagram import Name
from chalk.trail import Trail
from chalk.transform import (
P2,
V2,
Affine,
BoundingBox,
from_radians,
origin,
to_radians,
unit_x,
unit_y,
)
from chalk.types import Diagram
if not TYPE_CHECKING:
# Set library name the same as on PyPI
# must be the same as setup.py:setup(name=?)
__libname__: str = "chalk-diagrams" # custom dunder attribute
__version__: str = metadata.version(__libname__)
================================================
FILE: chalk/align.py
================================================
from chalk.transform import V2, Affine, origin, unit_x, unit_y
from chalk.types import Diagram
# Functions mirroring Diagrams.Align and Diagrams.2d.Align
def align_to(self: Diagram, v: V2) -> Diagram:
envelope = self.get_envelope()
t = Affine.translation(-envelope.envelope_v(v))
return self.apply_transform(t)
def snug(self: Diagram, v: V2) -> Diagram:
"Align based on the trace."
trace = self.get_trace()
d = trace.trace_v(origin, v)
assert d is not None
t = Affine.translation(-d)
return self.apply_transform(t)
def center(self: Diagram) -> Diagram:
return self.center_xy()
# 2D versions
def align_t(self: Diagram) -> Diagram:
return align_to(self, -unit_y)
def align_b(self: Diagram) -> Diagram:
return align_to(self, unit_y)
def align_r(self: Diagram) -> Diagram:
return align_to(self, unit_x)
def align_l(self: Diagram) -> Diagram:
return align_to(self, -unit_x)
def align_tl(self: Diagram) -> Diagram:
return align_l(align_t(self))
def align_br(self: Diagram) -> Diagram:
return align_r(align_b(self))
def align_tr(self: Diagram) -> Diagram:
return align_r(align_t(self))
def align_bl(self: Diagram) -> Diagram:
return align_l(align_b(self))
def center_xy(self: Diagram) -> Diagram:
envelope = self.get_envelope()
if envelope.is_empty:
return self
t = Affine.translation(-envelope.center)
return self.apply_transform(t)
def scale_uniform_to_x(self: Diagram, x: float) -> Diagram:
"""Apply uniform scaling along the x-axis.
Args:
self (Diagram): Diagram object.
x (float): Amount of scaling along the x-axis.
Returns:
Diagram: A diagram object.
"""
envelope = self.get_envelope()
if envelope.is_empty:
return self
α = x / envelope.width
return self.scale(α)
def scale_uniform_to_y(self: Diagram, y: float) -> Diagram:
"""Apply uniform scaling along the y-axis.
Args:
self (Diagram): Diagram object.
y (float): Amount of scaling along the y-axis.
Returns:
Diagram: A diagram object.
"""
envelope = self.get_envelope()
if envelope.is_empty:
return self
α = y / envelope.height
return self.scale(α)
================================================
FILE: chalk/arrow.py
================================================
from dataclasses import dataclass, field
from typing import List, Optional
from colour import Color
from chalk.shapes import ArcSegment, ArrowHead, arc_seg, dart
from chalk.style import Style
from chalk.subdiagram import Name, Subdiagram
from chalk.trail import Trail
from chalk.transform import P2, V2, unit_x
from chalk.types import Diagram
black = Color("black")
# arrow heads
@dataclass
class ArrowOpts:
head_style: Style = field(default_factory=Style)
head_pad: float = 0.0
tail_pad: float = 0.0
head_arrow: Optional[Diagram] = None
shaft_style: Style = field(default_factory=Style)
trail: Optional[Trail] = None
arc_height: float = 0.0
# Arrow connections.
def connect(
self: Diagram, name1: Name, name2: Name, style: ArrowOpts = ArrowOpts()
) -> Diagram:
def f(subs: List[Subdiagram], dia: Diagram) -> Diagram:
sub1, sub2 = subs
ps = sub1.get_location()
pe = sub2.get_location()
return dia + arrow_between(ps, pe, style)
return self.with_names([name1, name2], f)
def connect_outside(
self: Diagram, name1: Name, name2: Name, style: ArrowOpts = ArrowOpts()
) -> Diagram:
def f(subs: List[Subdiagram], dia: Diagram) -> Diagram:
sub1, sub2 = subs
loc1 = sub1.get_location()
loc2 = sub2.get_location()
tr1 = sub1.get_trace()
tr2 = sub2.get_trace()
v = loc2 - loc1
midpoint = loc1 + v / 2
ps = tr1.trace_p(midpoint, -v)
pe = tr2.trace_p(midpoint, v)
assert ps is not None, "Cannot connect"
assert pe is not None, "Cannot connect"
return dia + arrow_between(ps, pe, style)
return self.with_names([name1, name2], f)
def connect_perim(
self: Diagram,
name1: Name,
name2: Name,
v1: V2,
v2: V2,
style: ArrowOpts = ArrowOpts(),
) -> Diagram:
def f(subs: List[Subdiagram], dia: Diagram) -> Diagram:
sub1, sub2 = subs
loc1 = sub1.get_location()
loc2 = sub2.get_location()
tr1 = sub1.get_trace()
tr2 = sub2.get_trace()
ps = tr1.max_trace_p(loc1, v1)
pe = tr2.max_trace_p(loc2, v2)
assert ps is not None, "Cannot connect"
assert pe is not None, "Cannot connect"
return dia + arrow_between(ps, pe, style)
return self.with_names([name1, name2], f)
# Arrow primitives
def arrow(length: float, style: ArrowOpts = ArrowOpts()) -> Diagram:
from chalk.core import Primitive
if style.head_arrow is None:
arrow: Diagram = Primitive.from_shape(ArrowHead(dart()))
else:
arrow = style.head_arrow
arrow = arrow._style(style.head_style)
t = style.tail_pad
l_adj = length - style.head_pad - t
if style.trail is None:
segment = arc_seg(P2(l_adj, 0), style.arc_height)
shaft = segment.stroke()
if isinstance(segment.segments[-1], ArcSegment):
seg = segment.segments[-1]
tan = -(seg.q - seg.center.reflect_y()).perpendicular() # type: ignore
φ = tan.angle
arrow = arrow.rotate(φ)
if style.arc_height < 0:
arrow = arrow.rotate(180)
else:
shaft = style.trail.stroke().scale_uniform_to_x(l_adj).fill_opacity(0)
if isinstance(style.trail.segments[-1], ArcSegment):
arrow = arrow.rotate(-style.trail.segments[-1].angle)
return shaft._style(style.shaft_style).translate_by(
t * unit_x
) + arrow.translate_by((l_adj + t) * unit_x)
def arrow_v(vec: V2, style: ArrowOpts = ArrowOpts()) -> Diagram:
arr = arrow(vec.length, style)
return arr.rotate(-vec.angle)
def arrow_at(base: P2, vec: V2, style: ArrowOpts = ArrowOpts()) -> Diagram:
return arrow_v(vec, style).translate_by(base)
def arrow_between(
start: P2, end: P2, style: ArrowOpts = ArrowOpts()
) -> Diagram:
return arrow_at(start, end - start, style)
================================================
FILE: chalk/backend/__init__.py
================================================
================================================
FILE: chalk/backend/cairo.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Optional
from chalk.monoid import MList
from chalk.shapes import (
ArcSegment,
ArrowHead,
Image,
Latex,
Path,
Segment,
SegmentLike,
Spacer,
Text,
from_pil,
)
from chalk.style import Style
from chalk.transform import P2, Affine, to_radians, unit_x, unit_y
from chalk.types import Diagram
from chalk.visitor import DiagramVisitor, ShapeVisitor
if TYPE_CHECKING:
from chalk.core import ApplyName, ApplyStyle, ApplyTransform, Primitive
Ident = Affine.identity()
PyCairoContext = Any
EMPTY_STYLE = Style.empty()
def tx_to_cairo(affine: Affine) -> Any:
import cairo
def convert(a, b, c, d, e, f): # type: ignore
return cairo.Matrix(a, d, b, e, c, f) # type: ignore
return convert(*affine[:6]) # type: ignore
class ToList(DiagramVisitor[MList[Any], Affine]):
"""Compiles a `Diagram` to a list of `Primitive`s. The transformation `t`
is accumulated upwards, from the tree's leaves.
"""
A_type = MList[Any]
def visit_primitive(
self, diagram: Primitive, t: Affine = Ident
) -> MList[Primitive]:
return MList([diagram.apply_transform(t)])
def visit_apply_transform(
self, diagram: ApplyTransform, t: Affine = Ident
) -> MList[Primitive]:
t_new = t * diagram.transform
return MList(
[
prim.apply_transform(t_new)
for prim in diagram.diagram.accept(self, t).data
]
)
def visit_apply_style(
self, diagram: ApplyStyle, t: Affine = Ident
) -> MList[Primitive]:
return MList(
[
prim.apply_style(diagram.style)
for prim in diagram.diagram.accept(self, t).data
]
)
def visit_apply_name(
self, diagram: ApplyName, t: Affine = Ident
) -> MList[Primitive]:
return MList([prim for prim in diagram.diagram.accept(self, t).data])
class ToCairoShape(ShapeVisitor[None]):
def render_segment(
self, seg: SegmentLike, ctx: PyCairoContext, p: P2
) -> None:
q = seg.q + p
if isinstance(seg, Segment):
ctx.line_to(q.x, q.y)
elif isinstance(seg, ArcSegment):
end = seg.angle + seg.dangle
ctx.save()
matrix = tx_to_cairo(Affine.translation(p) * seg.t)
ctx.transform(matrix)
if seg.dangle < 0:
ctx.arc_negative(
0.0, 0.0, 1.0, to_radians(seg.angle), to_radians(end)
)
else:
ctx.arc(0.0, 0.0, 1.0, to_radians(seg.angle), to_radians(end))
ctx.restore()
def visit_path(
self,
path: Path,
ctx: PyCairoContext = None,
style: Style = EMPTY_STYLE,
) -> None:
if not path.loc_trails[0].trail.closed:
style.fill_opacity_ = 0
for loc_trail in path.loc_trails:
for i, (seg, p) in enumerate(loc_trail.located_segments()):
if i == 0:
ctx.move_to(p.x, p.y)
self.render_segment(seg, ctx, p)
if loc_trail.trail.closed:
ctx.close_path()
def visit_latex(
self,
shape: Latex,
ctx: PyCairoContext = None,
style: Style = EMPTY_STYLE,
) -> None:
raise NotImplementedError("Latex is not implemented")
def visit_text(
self,
shape: Text,
ctx: PyCairoContext = None,
style: Style = EMPTY_STYLE,
) -> None:
ctx.select_font_face("sans-serif")
if shape.font_size is not None:
ctx.set_font_size(shape.font_size)
extents = ctx.text_extents(shape.text)
ctx.move_to(-(extents.width / 2), (extents.height / 2))
ctx.text_path(shape.text)
def visit_spacer(
self,
shape: Spacer,
ctx: PyCairoContext = None,
style: Style = EMPTY_STYLE,
) -> None:
return
def visit_arrowhead(
self,
shape: ArrowHead,
ctx: PyCairoContext = None,
style: Style = EMPTY_STYLE,
) -> None:
assert style.output_size
scale = 0.01 * (15 / 500) * style.output_size
render_cairo_prims(shape.arrow_shape.scale(scale), ctx, style)
def visit_image(
self,
shape: Image,
ctx: PyCairoContext = None,
style: Style = EMPTY_STYLE,
) -> None:
surface = from_pil(shape.im)
ctx.set_source_surface(
surface, -(shape.width / 2), -(shape.height / 2)
)
ctx.paint()
def render_cairo_prims(
base: Diagram, ctx: PyCairoContext, style: Style
) -> None:
base = base._style(style)
shape_renderer = ToCairoShape()
for prim in base.accept(ToList(), Ident):
# apply transformation
matrix = tx_to_cairo(prim.transform)
ctx.transform(matrix)
prim.shape.accept(shape_renderer, ctx=ctx, style=prim.style)
# undo transformation
matrix.invert()
ctx.transform(matrix)
prim.style.render(ctx)
ctx.stroke()
def render(
self: Diagram, path: str, height: int = 128, width: Optional[int] = None
) -> None:
"""Render the diagram to a PNG file.
Args:
self (Diagram): Given ``Diagram`` instance.
path (str): Path of the .png file.
height (int, optional): Height of the rendered image.
Defaults to 128.
width (Optional[int], optional): Width of the rendered image.
Defaults to None.
"""
import cairo
envelope = self.get_envelope()
assert envelope is not None
pad = 0.05
# infer width to preserve aspect ratio
width = width or int(height * envelope.width / envelope.height)
# determine scale to fit the largest axis in the target frame size
if envelope.width - width <= envelope.height - height:
α = height / ((1 + pad) * envelope.height)
else:
α = width / ((1 + pad) * envelope.width)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
ctx = cairo.Context(surface)
s = self.scale(α).center_xy().pad(1 + pad)
e = s.get_envelope()
assert e is not None
s = s.translate(e(-unit_x), e(-unit_y))
render_cairo_prims(s, ctx, Style.root(max(width, height)))
surface.write_to_png(path)
================================================
FILE: chalk/backend/svg.py
================================================
from __future__ import annotations
import xml.etree.ElementTree as ET
from typing import TYPE_CHECKING, Optional
import svgwrite
from svgwrite import Drawing
from svgwrite.base import BaseElement
from svgwrite.shapes import Rect
from chalk import transform as tx
from chalk.shapes import (
ArcSegment,
ArrowHead,
Image,
Latex,
Path,
Segment,
SegmentLike,
Spacer,
Text,
)
from chalk.style import Style
from chalk.transform import P2, unit_x, unit_y
from chalk.types import Diagram
from chalk.visitor import DiagramVisitor, ShapeVisitor
if TYPE_CHECKING:
from chalk.core import (
ApplyName,
ApplyStyle,
ApplyTransform,
Compose,
Empty,
Primitive,
)
EMPTY_STYLE = Style.empty()
def tx_to_svg(affine: tx.Affine) -> str:
def convert(
a: float, b: float, c: float, d: float, e: float, f: float
) -> str:
return f"matrix({a}, {d}, {b}, {e}, {c}, {f})"
return convert(*affine[:6])
class Raw(Rect): # type: ignore
"""Shape class.
A fake SVG node for importing latex.
"""
def __init__(self, st: str):
ET.register_namespace("", "http://www.w3.org/2000/svg")
self.xml = ET.fromstring(st)
def get_xml(self) -> ET.Element:
return self.xml
class ToSVG(DiagramVisitor[BaseElement, Style]):
A_type = BaseElement
def __init__(self, dwg: Drawing):
self.dwg = dwg
self.shape_renderer = ToSVGShape(dwg)
def visit_primitive(
self, diagram: Primitive, style: Style = EMPTY_STYLE
) -> BaseElement:
style_new = diagram.style.merge(style)
style_svg = style_new.to_svg()
transform = tx_to_svg(diagram.transform)
inner = diagram.shape.accept(self.shape_renderer, style=style_new)
if not style_svg and not transform:
return inner
else:
if not style_svg:
style_svg = ";"
g = self.dwg.g(transform=transform, style=style_svg)
g.add(inner)
return g
def visit_empty(
self, diagram: Empty, style: Style = EMPTY_STYLE
) -> BaseElement:
return self.dwg.g()
def visit_compose(
self, diagram: Compose, style: Style = EMPTY_STYLE
) -> BaseElement:
g = self.dwg.g()
for d in diagram.diagrams:
g.add(d.accept(self, style))
return g
def visit_apply_transform(
self, diagram: ApplyTransform, style: Style = EMPTY_STYLE
) -> BaseElement:
g = self.dwg.g(transform=tx_to_svg(diagram.transform))
g.add(diagram.diagram.accept(self, style))
return g
def visit_apply_style(
self, diagram: ApplyStyle, style: Style = EMPTY_STYLE
) -> BaseElement:
return diagram.diagram.accept(self, diagram.style.merge(style))
def visit_apply_name(
self, diagram: ApplyName, style: Style = EMPTY_STYLE
) -> BaseElement:
g = self.dwg.g()
g.add(diagram.diagram.accept(self, style))
return g
class ToSVGShape(ShapeVisitor[BaseElement]):
def __init__(self, dwg: Drawing):
self.dwg = dwg
def render_segment(self, seg: SegmentLike, p: P2) -> str:
q = seg.q + p
if isinstance(seg, Segment):
return f"L {q.x} {q.y}"
elif isinstance(seg, ArcSegment):
"https://www.w3.org/TR/SVG/implnote.html#ArcConversionCenterToEndpoint"
f_A = 1 if abs(seg.dangle) > 180 else 0
det: float = seg.t.determinant # type: ignore
f_S = 1 if det * seg.dangle > 0 else 0
return f"A {seg.r_x} {seg.r_y} {seg.rot} {f_A} {f_S} {q.x} {q.y}"
def visit_path(
self, path: Path, style: Style = EMPTY_STYLE
) -> BaseElement:
extra_style = ""
if not path.loc_trails[0].trail.closed:
extra_style = "fill:none;"
line = self.dwg.path(
style="vector-effect: non-scaling-stroke;" + extra_style,
)
for loc_trail in path.loc_trails:
p = loc_trail.location
line.push(f"M {p.x} {p.y}")
for i, (seg, p) in enumerate(loc_trail.located_segments()):
line.push(self.render_segment(seg, p))
if loc_trail.trail.closed:
line.push("Z")
return line
def visit_latex(
self, shape: Latex, style: Style = EMPTY_STYLE
) -> BaseElement:
dx, dy = -shape.width / 2, -shape.height / 2
g = self.dwg.g(transform=f"scale(0.05) translate({dx} {dy})")
g.add(Raw(shape.content))
return g
def visit_text(
self, shape: Text, style: Style = EMPTY_STYLE
) -> BaseElement:
dx = -(shape.get_bounding_box().width / 2)
return self.dwg.text(
shape.text,
transform=f"translate({dx}, 0)",
style=f"""text-align:center; text-anchor:middle; dominant-baseline:middle;
font-family:sans-serif; font-weight: bold;
font-size:{shape.font_size}px;
vector-effect: non-scaling-stroke;""",
)
raise NotImplementedError("Text is not implemented")
def visit_spacer(
self, shape: Spacer, style: Style = EMPTY_STYLE
) -> BaseElement:
return self.dwg.g()
def visit_arrowhead(
self, shape: ArrowHead, style: Style = EMPTY_STYLE
) -> BaseElement:
assert style.output_size
scale = 0.01 * (15 / 500) * style.output_size
return to_svg(shape.arrow_shape.scale(scale), self.dwg, style)
def visit_image(
self, shape: Image, style: Style = EMPTY_STYLE
) -> BaseElement:
dx = -shape.width / 2
dy = -shape.height / 2
return self.dwg.image(
href=shape.url_path, transform=f"translate({dx}, {dy})"
)
def to_svg(self: Diagram, dwg: Drawing, style: Style) -> BaseElement:
return self.accept(ToSVG(dwg), style)
def render(
self: Diagram,
path: str,
height: int = 128,
width: Optional[int] = None,
draw_height: Optional[int] = None,
) -> None:
"""Render the diagram to an SVG file.
Args:
self (Diagram): Given ``Diagram`` instance.
path (str): Path of the .svg file.
height (int, optional): Height of the rendered image.
Defaults to 128.
width (Optional[int], optional): Width of the rendered image.
Defaults to None.
draw_height (Optional[int], optional): Override the height for
line width.
"""
pad = 0.05
envelope = self.get_envelope()
# infer width to preserve aspect ratio
assert envelope is not None
width = width or int(height * envelope.width / envelope.height)
# determine scale to fit the largest axis in the target frame size
if envelope.width - width <= envelope.height - height:
α = height / ((1 + pad) * envelope.height)
else:
α = width / ((1 + pad) * envelope.width)
dwg = svgwrite.Drawing(path, size=(width, height))
outer = dwg.g(style="fill:white;")
# Arrow marker
marker = dwg.marker(
id="arrow", refX=5.0, refY=1.7, size=(5, 3.5), orient="auto"
)
marker.add(dwg.polygon([(0, 0), (5, 1.75), (0, 3.5)]))
dwg.defs.add(marker)
dwg.add(outer)
s = self.center_xy().pad(1 + pad).scale(α)
e = s.get_envelope()
assert e is not None
s = s.translate(e(-unit_x), e(-unit_y))
if draw_height is None:
draw_height = max(height, width)
style = Style.root(output_size=draw_height)
outer.add(to_svg(s, dwg, style))
dwg.save()
================================================
FILE: chalk/backend/tikz.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING, Any, List
from chalk import transform as tx
from chalk.monoid import MList
from chalk.shapes import (
ArcSegment,
ArrowHead,
Image,
Latex,
Path,
Segment,
SegmentLike,
Spacer,
Text,
)
from chalk.style import Style
from chalk.transform import P2, origin
from chalk.types import Diagram
from chalk.visitor import DiagramVisitor, ShapeVisitor
if TYPE_CHECKING:
from chalk.core import ApplyStyle, ApplyTransform, Primitive
PyLatex = Any
PyLatexElement = Any
EMPTY_STYLE = Style.empty()
def tx_to_tikz(affine: tx.Affine) -> str:
def convert(
a: float, b: float, c: float, d: float, e: float, f: float
) -> str:
return f"{{{a}, {d}, {b}, {e}, ({c}, {f})}}"
return convert(*affine[:6])
class ToTikZ(DiagramVisitor[MList[PyLatexElement], Style]):
A_type = MList[PyLatexElement]
def __init__(self, pylatex: PyLatex):
self.pylatex = pylatex
self.shape_renderer = ToTikZShape(pylatex)
def visit_primitive(
self, diagram: Primitive, style: Style = EMPTY_STYLE
) -> MList[PyLatexElement]:
transform = tx_to_tikz(diagram.transform)
style_new = diagram.style.merge(style)
inner = diagram.shape.accept(self.shape_renderer, style=style_new)
if not style_new and not transform:
return MList([inner])
else:
options = {"cm": tx_to_tikz(diagram.transform)}
s = self.pylatex.TikZScope(
options=self.pylatex.TikZOptions(**options)
)
s.append(inner)
return MList([s])
def visit_apply_transform(
self, diagram: ApplyTransform, style: Style = EMPTY_STYLE
) -> MList[PyLatexElement]:
options = {"cm": tx_to_tikz(diagram.transform)}
s = self.pylatex.TikZScope(options=self.pylatex.TikZOptions(**options))
for x in diagram.diagram.accept(self, style).data:
s.append(x)
return MList([s])
def visit_apply_style(
self, diagram: ApplyStyle, style: Style = EMPTY_STYLE
) -> MList[PyLatexElement]:
style_new = diagram.style.merge(style)
return diagram.diagram.accept(self, style_new)
class ToTikZShape(ShapeVisitor[PyLatexElement]):
def __init__(self, pylatex: PyLatex):
self.pylatex = pylatex
def render_segment(
self, pts: PyLatexElement, seg: SegmentLike, p: P2
) -> None:
q = seg.q + p
if isinstance(seg, Segment):
pts.append("--")
pts.append(self.pylatex.TikZCoordinate(q.x, q.y))
elif isinstance(seg, ArcSegment):
start = (-seg.center).angle
end = (seg.q - seg.center).angle
det: float = seg.t.determinant # type: ignore
if det * seg.dangle < 0 and end > start:
end = end - 360
if det * seg.dangle > 0 and end < start:
end = end + 360
end_ang = end - seg.rot
pts._arg_list.append(
self.pylatex.TikZUserPath(
f"""{{[rotate={seg.rot}] arc [
start angle={start-seg.rot}, end angle={end_ang},
x radius={seg.r_x}, y radius={seg.r_y}]}}"""
)
)
def visit_path(
self, path: Path, style: Style = EMPTY_STYLE
) -> PyLatexElement:
pts = self.pylatex.TikZPathList()
if not path.loc_trails[0].trail.closed:
style = style.fill_opacity(0)
for loc_trail in path.loc_trails:
for i, (seg, p) in enumerate(loc_trail.located_segments()):
if i == 0:
pts.append(self.pylatex.TikZCoordinate(p.x, p.y))
self.render_segment(pts, seg, p)
if loc_trail.trail.closed:
pts.append("--")
pts._arg_list.append(self.pylatex.TikZUserPath("cycle"))
return self.pylatex.TikZDraw(
pts,
options=self.pylatex.TikZOptions(**style.to_tikz(self.pylatex)),
)
def visit_latex(
self, shape: Latex, style: Style = EMPTY_STYLE
) -> PyLatexElement:
raise NotImplementedError("Latex is not implemented")
def visit_text(
self, shape: Text, style: Style = EMPTY_STYLE
) -> PyLatexElement:
opts = {}
opts["font"] = "\\small\\sffamily"
opts["scale"] = str(
3.5 * (1 if shape.font_size is None else shape.font_size)
)
styles = style.to_tikz(self.pylatex)
if styles.get("fill", None) is not None:
opts["text"] = styles["fill"]
return self.pylatex.TikZNode(
text=shape.text,
# Scale parameters based on observations
options=self.pylatex.TikZOptions(**opts),
)
def visit_spacer(
self, shape: Spacer, style: Style = EMPTY_STYLE
) -> PyLatexElement:
left = origin.x - shape.width / 2
top = origin.y - shape.height / 2
return self.pylatex.TikZPath(
[
self.pylatex.TikZCoordinate(left, top),
"rectangle",
self.pylatex.TikZCoordinate(
left + shape.width, top + shape.height
),
]
)
def visit_arrowhead(
self, shape: ArrowHead, style: Style = EMPTY_STYLE
) -> PyLatexElement:
assert style.output_size
scale = 0.01 * 3 * (15 / 500) * style.output_size
s = self.pylatex.TikZScope()
for inner in to_tikz(
shape.arrow_shape.scale(scale), self.pylatex, style
):
s.append(inner)
return s
def visit_image(
self, shape: Image, style: Style = EMPTY_STYLE
) -> PyLatexElement:
assert False, "No tikz image renderer"
def to_tikz(
self: Diagram, pylatex: PyLatex, style: Style
) -> List[PyLatexElement]:
return self.accept(ToTikZ(pylatex), style).data
def render(self: Diagram, path: str, height: int = 128) -> None:
# Hack: Convert roughly from px to pt. Assume 300 dpi.
heightpt = height / 4.3
try:
import pylatex
except ImportError:
print("Render PDF requires pylatex installation.")
return
pad = 0.05
envelope = self.get_envelope()
assert envelope is not None
# infer width to preserve aspect ratio
width = heightpt * (envelope.width / envelope.height)
# determine scale to fit the largest axis in the target frame size
if envelope.width - width <= envelope.height - heightpt:
α = heightpt / ((1 + pad) * envelope.height)
else:
α = width / ((1 + pad) * envelope.width)
x, _ = pad * heightpt, pad * width
# create document
doc = pylatex.Document(documentclass="standalone")
# document_options= pylatex.TikZOptions(margin=f"{{{x}pt {x}pt {y}pt {y}pt}}"))
# add our sample drawings
diagram = self.scale(α).reflect_y().pad(1 + pad)
envelope = diagram.get_envelope()
assert envelope is not None
from chalk.core import Primitive
padding = Primitive.from_shape(
Spacer(envelope.width, envelope.height)
).translate(envelope.center.x, envelope.center.y)
diagram = diagram + padding
with doc.create(pylatex.TikZ()) as pic:
for x in to_tikz(diagram, pylatex, Style.root(max(height, width))):
pic.append(x)
doc.generate_tex(path.replace(".pdf", "") + ".tex")
doc.generate_pdf(path.replace(".pdf", ""), clean_tex=False)
================================================
FILE: chalk/combinators.py
================================================
from typing import Iterable, List, Optional, Tuple
from chalk.envelope import Envelope
from chalk.monoid import associative_reduce
from chalk.shapes import Path, Spacer
from chalk.transform import V2, Affine, origin, unit_x, unit_y
from chalk.types import Diagram
# Functions mirroring Diagrams.Combinators and Diagrams.2d.Combinators
def with_envelope(self: Diagram, other: Diagram) -> Diagram:
return self.compose(other.get_envelope())
# with_trace, phantom,
def strut(width: float, height: float) -> Diagram:
from chalk.core import Primitive
return Primitive.from_shape(Spacer(width, height))
def pad(self: Diagram, extra: float) -> Diagram:
"""Scale outward directed padding for a diagram.
Be careful using this if your diagram is not centered.
Args:
self (Diagram): Diagram object.
extra (float): Amount of padding to add.
Returns:
Diagram: A diagram object.
"""
envelope = self.get_envelope()
def f(d: V2) -> float:
assert envelope is not None
return envelope(d) * extra
new_envelope = Envelope(f, envelope.is_empty)
return self.compose(new_envelope)
def frame(self: Diagram, extra: float) -> Diagram:
"""Add outward directed padding for a diagram.
This padding is applied uniformly on all sides.
Args:
self (Diagram): Diagram object.
extra (float): Amount of padding to add.
Returns:
Diagram: A diagram object.
"""
envelope = self.get_envelope()
def f(d: V2) -> float:
assert envelope is not None
return envelope(d) + extra
new_envelope = Envelope(f, envelope.is_empty)
return self.compose(new_envelope)
# extrudeEnvelope, intrudeEnvelope
def atop(self: Diagram, other: Diagram) -> Diagram:
envelope1 = self.get_envelope()
envelope2 = other.get_envelope()
new_envelope = envelope1 + envelope2
return self.compose(new_envelope, other)
# beneath
def above(self: Diagram, other: Diagram) -> Diagram:
return beside(self, other, unit_y)
# appends
def beside(self: Diagram, other: Diagram, direction: V2) -> Diagram:
return atop(self, juxtapose(self, other, direction))
def place_at(
diagrams: Iterable[Diagram], points: List[Tuple[float, float]]
) -> Diagram:
return concat(d.translate(x, y) for d, (x, y) in zip(diagrams, points))
def place_on_path(diagrams: Iterable[Diagram], path: Path) -> Diagram:
return concat(
d.translate(p.x, p.y) for d, p in zip(diagrams, path.points())
)
# position, atPoints
def cat(
diagrams: Iterable[Diagram], v: V2, sep: Optional[float] = None
) -> Diagram:
diagrams = iter(diagrams)
start = next(diagrams, None)
sep_dia = hstrut(sep).rotate(v.angle)
if start is None:
return empty()
def fn(a: Diagram, b: Diagram) -> Diagram:
return a.beside(sep_dia, v).beside(b, v)
return fn(start, associative_reduce(fn, diagrams, empty()))
def concat(diagrams: Iterable[Diagram]) -> Diagram:
"""
Concat diagrams atop of each other with atop.
Args:
diagrams (Iterable[Diagram]): Diagrams to concat.
Returns:
Diagram: New diagram
"""
from chalk.core import BaseDiagram
return BaseDiagram.concat(diagrams) # type: ignore
def empty() -> Diagram:
"Create an empty diagram"
from chalk.core import BaseDiagram
return BaseDiagram.empty()
# CompaseAligned.
# 2D
def hstrut(width: Optional[float]) -> Diagram:
from chalk.core import Primitive
if width is None:
return empty()
return Primitive.from_shape(Spacer(width, 0))
def vstrut(height: Optional[float]) -> Diagram:
from chalk.core import Primitive
if height is None:
return empty()
return Primitive.from_shape(Spacer(0, height))
def hcat(diagrams: Iterable[Diagram], sep: Optional[float] = None) -> Diagram:
"""
Stack diagrams next to each other with `besides`.
Args:
diagrams (Iterable[Diagram]): Diagrams to stack.
sep (Optional[float]): Padding between diagrams.
Returns:
Diagram: New diagram
"""
return cat(diagrams, unit_x, sep)
def vcat(diagrams: Iterable[Diagram], sep: Optional[float] = None) -> Diagram:
"""
Stack diagrams above each other with `above`.
Args:
diagrams (Iterable[Diagram]): Diagrams to stack.
sep (Optional[float]): Padding between diagrams.
Returns:
Diagrams
"""
return cat(diagrams, unit_y, sep)
# Extra
def above2(self: Diagram, other: Diagram) -> Diagram:
"""Given two diagrams ``a`` and ``b``, ``a.above2(b)``
places ``a`` on top of ``b``. This moves ``a`` down to
touch ``b``.
💡 ``a.above2(b)`` is equivalent to ``a // b``.
Args:
self (Diagram): Diagram object.
other (Diagram): Another diagram object.
Returns:
Diagram: A diagram object.
"""
return beside(other, self, -unit_y)
def juxtapose_snug(self: Diagram, other: Diagram, direction: V2) -> Diagram:
trace1 = self.get_trace()
trace2 = other.get_trace()
d1 = trace1.trace_v(origin, direction)
d2 = trace2.trace_v(origin, -direction)
assert d1 is not None and d2 is not None
d = d1 - d2
t = Affine.translation(d)
return other.apply_transform(t)
def beside_snug(self: Diagram, other: Diagram, direction: V2) -> Diagram:
return atop(self, juxtapose_snug(self, other, direction))
def juxtapose(self: Diagram, other: Diagram, direction: V2) -> Diagram:
"""Given two diagrams ``a`` and ``b``, ``a.juxtapose(b, v)``
places ``b`` to touch ``a`` along angle ve .
Args:
self (Diagram): Diagram object.
other (Diagram): Another diagram object.
direction (V2): (Normalized) vector angle to juxtapose
Returns:
Diagram: Repositioned ``b`` diagram
"""
envelope1 = self.get_envelope()
envelope2 = other.get_envelope()
d = envelope1.envelope_v(direction) - envelope2.envelope_v(-direction)
t = Affine.translation(d)
return other.apply_transform(t)
def at_center(self: Diagram, other: Diagram) -> Diagram:
"""Center two given diagrams.
💡 `a.at_center(b)` means center of ``a`` is translated
to the center of ``b``, and ``b`` sits on top of
``a`` along the axis out of the plane of the image.
💡 In other words, ``b`` occludes ``a``.
Args:
self (Diagram): Diagram object.
other (Diagram): Another diagram object.
Returns:
Diagram: A diagram object.
"""
envelope1 = self.get_envelope()
envelope2 = other.get_envelope()
t = Affine.translation(envelope1.center)
new_envelope = envelope1 + (t * envelope2)
return self.compose(new_envelope, other.apply_transform(t))
================================================
FILE: chalk/core.py
================================================
from __future__ import annotations
import os
import tempfile
from dataclasses import dataclass
from typing import Any, List, Optional, TypeVar
import chalk.align
import chalk.arrow
import chalk.backend.cairo
import chalk.backend.svg
import chalk.backend.tikz
import chalk.combinators
import chalk.model
import chalk.subdiagram
import chalk.trace
import chalk.types
from chalk import backend
from chalk.envelope import Envelope
from chalk.style import Style
from chalk.subdiagram import Name
from chalk.transform import Affine, unit_x
from chalk.types import Diagram, Shape
from chalk.utils import imgen
from chalk.visitor import DiagramVisitor
Trail = Any
Ident = Affine.identity()
A = TypeVar("A", bound=chalk.monoid.Monoid)
SVG_HEIGHT = 200
SVG_DRAW_HEIGHT = None
def set_svg_height(height: int) -> None:
"Globally set the svg preview height for notebooks."
global SVG_HEIGHT
SVG_HEIGHT = height
def set_svg_draw_height(height: int) -> None:
"Globally set the svg preview height for notebooks."
global SVG_DRAW_HEIGHT
SVG_DRAW_HEIGHT = height
@dataclass
class BaseDiagram(chalk.types.Diagram):
"""Diagram class."""
# Monoid
__add__ = chalk.combinators.atop
@classmethod
def empty(cls) -> Diagram: # type: ignore
return Empty()
# Tranformable
def apply_transform(self, t: Affine) -> Diagram: # type: ignore
return ApplyTransform(t, self)
# Stylable
def apply_style(self, style: Style) -> Diagram: # type: ignore
return ApplyStyle(style, self)
def _style(self, style: Style) -> Diagram:
return self.apply_style(style)
def compose(
self, envelope: Envelope, other: Optional[Diagram] = None
) -> Diagram:
other = other if other is not None else Empty()
if isinstance(self, Compose) and isinstance(other, Compose):
return Compose(envelope, self.diagrams + other.diagrams)
elif isinstance(self, Compose):
return Compose(envelope, self.diagrams + [other])
elif isinstance(other, Compose):
return Compose(envelope, [self] + other.diagrams)
else:
return Compose(envelope, [self, other])
def named(self, name: Name) -> Diagram:
"""Add a name (or a sequence of names) to a diagram."""
return ApplyName(name, self)
# Combinators
with_envelope = chalk.combinators.with_envelope
juxtapose = chalk.combinators.juxtapose
juxtapose_snug = chalk.combinators.juxtapose_snug
beside_snug = chalk.combinators.beside_snug
above = chalk.combinators.above
atop = chalk.combinators.atop
beside = chalk.combinators.beside
above = chalk.combinators.above
# Align
align = chalk.align.align_to
align_t = chalk.align.align_t
align_b = chalk.align.align_b
align_l = chalk.align.align_l
align_r = chalk.align.align_r
align_tr = chalk.align.align_tr
align_tl = chalk.align.align_tl
align_bl = chalk.align.align_bl
align_br = chalk.align.align_br
center_xy = chalk.align.center_xy
center = chalk.align.center
scale_uniform_to_y = chalk.align.scale_uniform_to_y
scale_uniform_to_x = chalk.align.scale_uniform_to_x
# Arrows
connect = chalk.arrow.connect
connect_outside = chalk.arrow.connect_outside
connect_perim = chalk.arrow.connect_perim
# Model
show_origin = chalk.model.show_origin
show_envelope = chalk.model.show_envelope
show_beside = chalk.model.show_beside
show_labels = chalk.model.show_labels
# Combinators
frame = chalk.combinators.frame
pad = chalk.combinators.pad
# Infix
def __or__(self, d: Diagram) -> Diagram:
return chalk.combinators.beside(self, d, unit_x)
__truediv__ = chalk.combinators.above
__floordiv__ = chalk.combinators.above2
def display(
self, height: int = 256, verbose: bool = True, **kwargs: Any
) -> None:
"""Display the diagram using the default renderer.
Note: see ``chalk.utils.imgen`` for details on the keyword arguments.
"""
# update kwargs with defaults and user-specified values
kwargs.update({"height": height})
kwargs.update({"verbose": verbose})
kwargs.update({"dirpath": None})
kwargs.update({"wait": kwargs.get("wait", 1)})
# render and display the diagram
imgen(self, **kwargs)
# Rendering
render = chalk.backend.cairo.render
render_png = chalk.backend.cairo.render
render_svg = chalk.backend.svg.render
render_pdf = chalk.backend.tikz.render
to_svg = backend.svg.to_svg
to_tikz = backend.tikz.to_tikz
def _repr_svg_(self) -> str:
global SVG_HEIGHT
f = tempfile.NamedTemporaryFile(delete=False)
self.render_svg(f.name, height=SVG_HEIGHT, draw_height=SVG_DRAW_HEIGHT)
f.close()
svg = open(f.name).read()
os.unlink(f.name)
return svg
def _repr_html_(self) -> str | tuple[str, Any]:
"""Returns a rich HTML representation of an object."""
return self._repr_svg_()
# Getters
get_envelope = chalk.envelope.get_envelope
get_trace = chalk.trace.get_trace
get_subdiagram = chalk.subdiagram.get_subdiagram
get_sub_map = chalk.subdiagram.get_sub_map
with_names = chalk.subdiagram.with_names
def qualify(self, name: Name) -> Diagram:
"""Prefix names in the diagram by a given name or sequence of names."""
return self.accept(Qualify(name), None)
def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
raise NotImplementedError
@dataclass
class Primitive(BaseDiagram):
"""Primitive class.
This is derived from a ``chalk.core.Diagram`` class.
[TODO]: explain what Primitive class is for.
"""
shape: Shape
style: Style
transform: Affine
@classmethod
def from_shape(cls, shape: Shape) -> Primitive:
"""Creates a primitive from a shape using the default style (only line
stroke, no fill) and the identity transformation.
Args:
shape (Shape): A shape object.
Returns:
Primitive: A diagram object.
"""
return cls(shape, Style.empty(), Ident)
def apply_transform(self, t: Affine) -> Primitive:
"""Applies a transform and returns a primitive.
Args:
t (Transform): A transform object.
Returns:
Primitive
"""
new_transform = t * self.transform
return Primitive(self.shape, self.style, new_transform)
def apply_style(self, other_style: Style) -> Primitive:
"""Applies a style and returns a primitive.
Args:
other_style (Style): A style object.
Returns:
Primitive
"""
return Primitive(
self.shape, self.style.merge(other_style), self.transform
)
def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
return visitor.visit_primitive(self, args)
@dataclass
class Empty(BaseDiagram):
"""An Empty diagram class."""
def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
return visitor.visit_empty(self, args)
@dataclass
class Compose(BaseDiagram):
"""Compose class."""
envelope: Envelope
diagrams: List[Diagram]
def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
return visitor.visit_compose(self, args)
@dataclass
class ApplyTransform(BaseDiagram):
"""ApplyTransform class."""
transform: Affine
diagram: Diagram
def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
return visitor.visit_apply_transform(self, args)
@dataclass
class ApplyStyle(BaseDiagram):
"""ApplyStyle class."""
style: Style
diagram: Diagram
def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
return visitor.visit_apply_style(self, args)
@dataclass
class ApplyName(BaseDiagram):
"""ApplyName class."""
dname: Name
diagram: Diagram
def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
return visitor.visit_apply_name(self, args)
class Qualify(DiagramVisitor[Diagram, None]):
A_type = Diagram
def __init__(self, name: Name):
self.name = name
def visit_primitive(self, diagram: Primitive, args: None) -> Diagram:
return diagram
def visit_compose(self, diagram: Compose, args: None) -> Diagram:
return Compose(
diagram.envelope, [d.accept(self, None) for d in diagram.diagrams]
)
def visit_apply_transform(
self, diagram: ApplyTransform, args: None
) -> Diagram:
return ApplyTransform(
diagram.transform,
diagram.diagram.accept(self, None),
)
def visit_apply_style(self, diagram: ApplyStyle, args: None) -> Diagram:
return ApplyStyle(
diagram.style,
diagram.diagram.accept(self, None),
)
def visit_apply_name(self, diagram: ApplyName, args: None) -> Diagram:
return ApplyName(
self.name + diagram.dname, diagram.diagram.accept(self, None)
)
================================================
FILE: chalk/envelope.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING, Callable, Iterable, Tuple
from chalk.monoid import Monoid
from chalk.transform import (
P2,
V2,
Affine,
BoundingBox,
Transformable,
apply_affine,
origin,
remove_translation,
transpose_translation,
unit_x,
unit_y,
)
from chalk.visitor import DiagramVisitor
if TYPE_CHECKING:
from chalk.core import ApplyTransform, Compose, Primitive
from chalk.types import Diagram
SignedDistance = float
Ident = Affine.identity()
class Envelope(Transformable, Monoid):
def __init__(
self, f: Callable[[V2], SignedDistance], is_empty: bool = False
):
self.f = f
self.is_empty = is_empty
def __call__(self, direction: V2) -> SignedDistance:
assert not self.is_empty
return self.f(direction)
# Monoid
@staticmethod
def empty() -> Envelope:
return Envelope(lambda v: 0, is_empty=True)
def __add__(self, other: Envelope) -> Envelope:
if self.is_empty:
return other
if other.is_empty:
return self
return Envelope(
lambda direction: max(self(direction), other(direction))
)
@property
def center(self) -> P2:
if self.is_empty:
return origin
return P2(
(-self(-unit_x) + self(unit_x)) / 2,
(-self(-unit_y) + self(unit_y)) / 2,
)
@property
def width(self) -> float:
assert not self.is_empty
return self(unit_x) + self(-unit_x)
@property
def height(self) -> float:
assert not self.is_empty
return self(unit_y) + self(-unit_y)
def apply_transform(self, t: Affine) -> Envelope:
if self.is_empty:
return self
rt = remove_translation(t)
inv_t = ~rt
trans_t = transpose_translation(rt)
_, _, c, _, _, f = t[:6]
u: V2 = -V2(c, f)
def wrapped(v: V2) -> SignedDistance:
# Linear
vi = apply_affine(inv_t, v)
v_prim = apply_affine(trans_t, v).normalized()
inner: float = self(v_prim)
d: float = v_prim.dot(vi)
after_linear = inner / d
# Translation
diff: float = (u / (v.dot(v))).dot(v)
return after_linear - diff
return Envelope(wrapped)
def envelope_v(self, v: V2) -> V2:
if self.is_empty:
return V2(0, 0)
v = v.scaled_to(1)
d: float = self(v)
return v * d
@staticmethod
def from_bounding_box(box: BoundingBox) -> Envelope:
def wrapped(d: V2) -> SignedDistance:
v: float = apply_affine(
Affine.rotation(d.angle), box
).bounding_box.max_point.x
return v / d.length
return Envelope(wrapped)
@staticmethod
def from_circle(radius: float) -> Envelope:
def wrapped(d: V2) -> SignedDistance:
return radius / d.length
return Envelope(wrapped)
def to_path(self, angle: int = 45) -> Iterable[P2]:
"Draws an envelope by sampling every 10 degrees."
pts = []
for i in range(0, 361, angle):
v = V2.polar(i)
pts.append(self(v) * v)
return pts
def to_segments(self, angle: int = 45) -> Iterable[Tuple[P2, P2]]:
"Draws an envelope by sampling every 10 degrees."
segments = []
for i in range(0, 361, angle):
v = V2.polar(i)
segments.append((origin, self(v) * v))
return segments
class GetEnvelope(DiagramVisitor[Envelope, Affine]):
A_type = Envelope
def visit_primitive(
self, diagram: Primitive, t: Affine = Ident
) -> Envelope:
new_transform = t * diagram.transform
return diagram.shape.get_envelope().apply_transform(new_transform)
def visit_compose(self, diagram: Compose, t: Affine = Ident) -> Envelope:
return diagram.envelope.apply_transform(t)
def visit_apply_transform(
self, diagram: ApplyTransform, t: Affine = Ident
) -> Envelope:
n = t * diagram.transform
return diagram.diagram.accept(self, n)
def get_envelope(self: Diagram, t: Affine = Ident) -> Envelope:
return self.accept(GetEnvelope(), t)
================================================
FILE: chalk/model.py
================================================
from colour import Color
from chalk.combinators import concat
from chalk.shapes import Path, circle, text
from chalk.shapes.segment import seg
from chalk.transform import V2, origin
from chalk.types import Diagram
RED = Color("red")
BLUE = Color("blue")
def show_origin(self: Diagram) -> Diagram:
"Add a red dot at the origin of a diagram for debugging."
envelope = self.get_envelope()
if envelope.is_empty:
return self
origin_size = max(0.1, min(envelope.height, envelope.width) / 50)
origin = circle(origin_size).line_color(RED)
return self + origin
def show_envelope(
self: Diagram, phantom: bool = False, angle: int = 45
) -> Diagram:
"""Add red envelope to diagram for debugging.
Args:
self (Diagram) : Diagram
phantom (bool): Don't include debugging in the envelope
angle (int): Angle increment to show debugging lines.
Returns:
Diagram
"""
self.show_origin()
envelope = self.get_envelope()
if envelope.is_empty:
return self
outer: Diagram = (
Path.from_points(list(envelope.to_path(angle)))
.stroke()
.fill_opacity(0)
.line_color(RED)
)
outer += (
concat([seg(y).stroke() for (x, y) in envelope.to_segments(angle)])
.line_color(RED)
.dashing([0.01, 0.01], 0)
)
new = self + outer
if phantom:
new = new.with_envelope(self)
return new
def show_beside(self: Diagram, other: Diagram, direction: V2) -> Diagram:
"Add blue normal line to show placement of combination."
envelope1 = self.get_envelope()
envelope2 = other.get_envelope()
v1 = envelope1.envelope_v(direction)
one: Diagram = (
Path.from_points([origin, v1])
.stroke()
.line_color(RED)
.dashing([0.01, 0.01], 0)
.line_width(0.01)
)
v2 = envelope2.envelope_v(-direction)
two: Diagram = (
Path.from_points([origin, v2])
.stroke()
.line_color(RED)
.dashing([0.01, 0.01], 0)
.line_width(0.01)
)
split: Diagram = (
Path.from_points(
[
v1 + direction.perpendicular(),
v1 - direction.perpendicular(),
]
)
.stroke()
.line_color(BLUE)
.line_width(0.02)
)
one = (self.show_origin() + one + split).with_envelope(self)
two = (other.show_origin() + two).with_envelope(other)
return one.beside(two, direction)
def show_labels(self: Diagram, font_size: int = 1) -> Diagram:
"""Shows the labels of all named subdiagrams of a diagram at their
corresponding origin."""
for name, subs in self.get_sub_map().items():
for sub in subs:
n = str(name)
p = sub.get_location()
self = self + text(n, font_size).fill_color(RED).translate_by(p)
return self
================================================
FILE: chalk/monoid.py
================================================
from __future__ import annotations
from dataclasses import dataclass
from typing import (
Callable,
Generic,
Iterable,
Iterator,
List,
Optional,
TypeVar,
)
from typing_extensions import Self
o = TypeVar("o")
def associative_reduce(
fn: Callable[[o, o], o], iter: Iterable[o], initial: o
) -> o:
"Reduce for associative operations."
ls = list(iter)
if len(ls) == 0:
return initial
if len(ls) == 1:
return ls[0]
off = len(ls) % 2
v = associative_reduce(
fn, [fn(ls[i], ls[i + 1]) for i in range(0, len(ls) - off, 2)], initial
)
if off:
v = fn(v, ls[-1])
return v
class Monoid:
@classmethod
def empty(cls) -> Self:
raise NotImplementedError()
def __add__(self, other: Self) -> Self:
raise NotImplementedError()
@classmethod
def concat(cls, elems: Iterable[Self]) -> Self:
return associative_reduce(cls.__add__, elems, cls.empty())
A = TypeVar("A")
@dataclass
class Maybe(Generic[A], Monoid):
data: Optional[A]
@classmethod
def empty(cls) -> Maybe[A]:
return Maybe(None)
def __add__(self, other: Maybe[A]) -> Maybe[A]:
if self.data is None:
return other
return self
@dataclass
class MList(Generic[A], Monoid):
data: List[A]
@classmethod
def empty(cls) -> MList[A]:
return MList([])
def __add__(self, other: MList[A]) -> MList[A]:
return MList(self.data + other.data)
def __iter__(self) -> Iterator[A]:
return self.data.__iter__()
================================================
FILE: chalk/py.typed
================================================
================================================
FILE: chalk/shapes/__init__.py
================================================
from typing import Optional, Tuple, Union
from chalk.shapes.arc import ArcSegment, arc_seg, arc_seg_angle # noqa: F401
from chalk.shapes.arrowheads import ArrowHead, dart # noqa: F401
from chalk.shapes.image import Image, from_pil, image # noqa: F401
from chalk.shapes.latex import Latex, latex # noqa: F401
from chalk.shapes.path import Path, make_path # noqa: F401
from chalk.shapes.segment import Segment, seg # noqa: F401
from chalk.shapes.shape import Shape, Spacer # noqa: F401
from chalk.shapes.text import Text, text # noqa: F401
from chalk.trail import SegmentLike, Trail # noqa: F401
from chalk.transform import P2, V2
from chalk.types import Diagram
# Functions mirroring Diagrams.2d.Shapes
def hrule(length: float) -> Diagram:
return Trail.hrule(length).stroke().center_xy()
def vrule(length: float) -> Diagram:
return Trail.vrule(length).stroke().center_xy()
# def polygon(sides: int, radius: float, rotation: float = 0) -> Diagram:
# """
# Draw a polygon.
# Args:
# sides (int): Number of sides.
# radius (float): Internal radius.
# rotation: (int): Rotation in degrees
# Returns:
# Diagram
# """
# return Trail.polygon(sides, radius, to_radians(rotation)).stroke()
def regular_polygon(sides: int, side_length: float) -> Diagram:
"""Draws a regular polygon with given number of sides and given side
length. The polygon is oriented with one edge parallel to the x-axis."""
return Trail.regular_polygon(sides, side_length).centered().stroke()
def triangle(width: float) -> Diagram:
"""Draws an equilateral triangle with the side length specified by
the ``width`` argument. The origin is the traingle's centroid."""
return regular_polygon(3, width)
def rectangle(
width: float, height: float, radius: Optional[float] = None
) -> Diagram:
"""
Draws a rectangle.
Args:
width (float): Width
height (float): Height
radius (Optional[float]): Radius for rounded corners.
Returns:
Diagrams
"""
if radius is None:
return Trail.rectangle(width, height).stroke().center_xy()
else:
return (
Trail.rounded_rectangle(width, height, radius).stroke().center_xy()
)
def square(side: float) -> Diagram:
"""Draws a square with the specified side length. The origin is the
center of the square."""
return rectangle(side, side)
def circle(radius: float) -> Diagram:
"Draws a circle with the specified ``radius``."
return Trail.circle().stroke().center_xy().scale(radius)
def arc(radius: float, angle0: float, angle1: float) -> Diagram:
"""
Draws an arc.
Args:
radius (float): Circle radius.
angle0 (float): Starting cutoff in degrees.
angle1 (float): Finishing cutoff in degrees.
Returns:
Diagram
"""
return (
ArcSegment(angle0, angle1 - angle0)
.at(V2.polar(angle0, 1))
.stroke()
.scale(radius)
)
def arc_between(
point1: Union[P2, Tuple[float, float]],
point2: Union[P2, Tuple[float, float]],
height: float,
) -> Diagram:
"""Makes an arc starting at point1 and ending at point2, with the midpoint
at a distance of abs(height) away from the straight line from point1 to
point2. A positive value of height results in an arc to the left of the
line from point1 to point2; a negative value yields one to the right.
The implementation is based on the the function arcBetween from Haskell's
diagrams:
https://hackage.haskell.org/package/diagrams-lib-1.4.5.1/docs/src/Diagrams.TwoD.Arc.html#arcBetween
"""
p = P2(*point1)
q = P2(*point2)
return arc_seg(q - p, height).at(p).stroke()
ignore = [Optional]
__all__ = [
"Segment",
"seg",
"Shape",
"Spacer",
"Text",
"text",
"SegmentLike",
"Trail",
"P2",
"V2",
"Diagram",
"hrule",
"vrule",
"regular_polygon",
"triangle",
"rectangle",
"square",
"circle",
"arc",
"arc_between",
"Latex",
"Trail",
"Path",
"Image",
"ArrowHead",
"arc_seg",
"dart",
"ArcSegment",
"from_pil",
"make_path",
"arc_seg_angle",
]
================================================
FILE: chalk/shapes/arc.py
================================================
"""
Contains arithmetic for arc calculations.
"""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Union
from planar.py import Ray
import chalk.transform as tx
from chalk.envelope import Envelope
from chalk.shapes.segment import LocatedSegment, ray_circle_intersection
from chalk.trace import Trace
from chalk.transform import P2, V2, from_radians, unit_x, unit_y
from chalk.types import Enveloped, Traceable, TrailLike
if TYPE_CHECKING:
from chalk.trail import Trail
Ident = tx.Affine.identity()
ORIGIN = P2(0, 0)
Degrees = float
def is_in_mod_360(x: Degrees, a: Degrees, b: Degrees) -> bool:
"""Checks if x ∈ [a, b] mod 360. See the following link for an
explanation:
https://fgiesen.wordpress.com/2015/09/24/intervals-in-modular-arithmetic/
"""
return (x - a) % 360 <= (b - a) % 360
@dataclass
class LocatedArcSegment(Traceable, Enveloped, tx.Transformable):
"A ellipse arc represented with the cetner parameterization"
angle: float
dangle: float
# Ellipse is closed under affine.
t: tx.Affine = tx.Affine.identity()
# Parts to be careful about translation-invariance
@property
def p(self) -> P2:
"Real start"
return tx.apply_p2_affine(self.t, P2.polar(self.angle, 1))
@property
def q(self) -> P2:
"Real end"
return tx.apply_p2_affine(
self.t, P2.polar(self.angle + self.dangle, 1)
)
@property
def q_angle(self) -> float:
return (self.q - self.center).angle
@property
def center(self) -> P2:
"Real center"
return tx.apply_p2_affine(self.t, P2(0, 0))
@property
def r_x(self) -> float:
t2 = tx.remove_translation(self.t)
return tx.apply_p2_affine(t2, unit_x).length
@property
def r_y(self) -> float:
t2 = tx.remove_translation(self.t)
return tx.apply_p2_affine(t2, unit_y).length
@property
def rot(self) -> float:
t2 = tx.remove_translation(self.t)
return tx.apply_p2_affine(t2, unit_x).angle
def get_trace(self, t: tx.Affine = Ident) -> Trace:
"Trace is done as simple arc and transformed"
angle0_deg = self.angle
angle1_deg = self.angle + self.dangle
def f(p: P2, v: V2) -> List[float]:
ray = Ray(p, v)
return sorted(
[
d / v.length
for d in ray_circle_intersection(ray, 1)
if is_in_mod_360(
((d * v) + P2.polar(self.angle, 1)).angle,
min(angle0_deg, angle1_deg),
max(angle0_deg, angle1_deg),
)
]
)
return Trace(f).apply_transform(self.t)
def get_envelope(self, t: tx.Affine = Ident) -> Envelope:
"Trace is done as simple arc and transformed"
angle0_deg = self.angle
angle1_deg = self.angle + self.dangle
v1 = V2.polar(angle0_deg, 1)
v2 = V2.polar(angle1_deg, 1)
def wrapped(d: V2) -> float:
is_circle = abs(angle0_deg - angle1_deg) >= 360
if is_circle or is_in_mod_360(
d.angle,
min(angle0_deg, angle1_deg),
max(angle0_deg, angle1_deg),
):
# Case 1: P2 at arc
return 1 / d.length
else:
# Case 2: P2 outside of arc
x: float = max(d.dot(v1), d.dot(v2))
return x
return Envelope(wrapped).apply_transform(self.t)
@staticmethod
def arc_between(
p: P2, q: P2, height: float
) -> Union[LocatedSegment, LocatedArcSegment]:
h = abs(height)
if h < 1e-3:
return LocatedSegment(q - p, p)
d = (q - p).length
# Determine the arc's angle θ and its radius r
θ = math.acos((d**2 - 4.0 * h**2) / (d**2 + 4.0 * h**2))
r = d / (2 * math.sin(θ))
if height > 0:
# bend left
φ = +math.pi / 2
dy = r - h
flip = 1
else:
# bend right
φ = -math.pi / 2
dy = h - r
flip = -1
diff = q - p
ret = (
ArcSegment(flip * -from_radians(θ), flip * 2 * from_radians(θ))
.scale(r)
.rotate_rad(φ)
.translate(d / 2, dy)
)
ret = ret.rotate(-diff.angle).translate_by(p)
return ret
@dataclass
class ArcSegment(LocatedArcSegment, TrailLike):
"A translation invariant version of Arc"
def __post_init__(self) -> None:
self.t = tx.Affine.translation(-self.p) * self.t
assert self.p.x == 0, self.p
assert self.p.y == 0, self.p
def apply_transform(self, t: tx.Affine) -> ArcSegment:
t = tx.remove_translation(t)
return ArcSegment(self.angle, self.dangle, t * self.t)
def to_trail(self) -> Trail:
from chalk.trail import Trail
return Trail([self])
@staticmethod
def arc_between_trail(q: P2, height: float) -> Trail:
segment = LocatedArcSegment.arc_between(P2(0, 0), q, height)
if isinstance(segment, LocatedArcSegment):
return ArcSegment(
segment.angle, segment.dangle, segment.t
).to_trail()
else:
return segment.to_trail()
def reverse(self) -> ArcSegment:
angle = self.angle + self.dangle
dangle = -self.dangle
return ArcSegment(angle, dangle).apply_transform(self.t)
def arc_seg(q: V2, height: float) -> Trail:
return ArcSegment.arc_between_trail(q, height)
def arc_seg_angle(angle: float, dangle: float) -> Trail:
return ArcSegment(angle, dangle).to_trail()
# def arc_between(p:P2, q: P2, height: float) -> Path:
# return ArcSegment.arc_between_trail(q - p, height).at(p)
================================================
FILE: chalk/shapes/arrowheads.py
================================================
from dataclasses import dataclass
from typing import Any
from colour import Color
from chalk.shapes.path import Path
from chalk.shapes.shape import Shape
from chalk.transform import P2, BoundingBox, origin
from chalk.types import Diagram
from chalk.visitor import A, ShapeVisitor
black = Color("black")
def tri() -> Diagram:
return (
Path.from_list_of_tuples(
[(1.0, 0), (0.0, -1.0), (-1.0, 0), (1.0, 0)], closed=True
)
.stroke()
.rotate_by(-0.25)
.fill_color(Color("black"))
.center_xy()
.align_r()
.line_width(0)
)
def dart(cut: float = 0.2) -> Diagram:
return (
Path.from_list_of_tuples(
[
(0, -cut),
(1.0, cut),
(0.0, -1.0 - cut),
(-1.0, +cut),
(0, -cut),
],
closed=True,
)
.stroke()
.rotate_by(-0.25)
.fill_color(Color("black"))
.center_xy()
.align_r()
.line_width(0)
)
@dataclass
class ArrowHead(Shape):
"""Arrow Head."""
arrow_shape: Diagram
def get_bounding_box(self) -> BoundingBox:
# Arrow head don't have a bounding box since we can't accurately know
# the size until rendering
eps = 1e-4
self.bb = BoundingBox([origin, origin + P2(eps, eps)])
return self.bb
def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
return visitor.visit_arrowhead(self, **kwargs)
================================================
FILE: chalk/shapes/image.py
================================================
from dataclasses import dataclass
from io import BytesIO
from typing import Any, Optional
import PIL
from PIL import Image as Im
from chalk.shapes.shape import Shape
from chalk.transform import P2, BoundingBox, origin
from chalk.types import Diagram
def from_pil(
im: Im.Image,
alpha: float = 1.0,
) -> Any:
import cairo
format: cairo.Format = cairo.FORMAT_ARGB32
if "A" not in im.getbands():
im.putalpha(int(alpha * 256.0)) # type: ignore
arr = bytearray(im.tobytes("raw", "BGRa"))
surface = cairo.ImageSurface.create_for_data(
arr, format, im.width, im.height # type: ignore
)
return surface
@dataclass
class Image(Shape):
"""Image class."""
local_path: str
url_path: Optional[str]
def __post_init__(self) -> None:
if self.local_path.endswith("svg"):
import cairosvg
out = BytesIO()
cairosvg.svg2png(url=self.local_path, write_to=out)
else:
out = open(self.local_path, "rb") # type:ignore
self.im = PIL.Image.open(out)
self.height = self.im.height
self.width = self.im.width
def get_bounding_box(self) -> BoundingBox:
left = origin.x - self.width / 2
top = origin.y - self.height / 2
tl = P2(left, top)
br = P2(left + self.width, top + self.height)
return BoundingBox([tl, br])
def image(local_path: str, url_path: Optional[str]) -> Diagram:
from chalk.core import Primitive
return Primitive.from_shape(Image(local_path, url_path))
================================================
FILE: chalk/shapes/latex.py
================================================
from dataclasses import dataclass
from typing import Any
from chalk.shapes.shape import Shape
from chalk.transform import P2, BoundingBox, origin
from chalk.types import Diagram
from chalk.visitor import A, ShapeVisitor
@dataclass
class Latex(Shape):
"""Latex class."""
text: str
def __post_init__(self) -> None:
# Need to install latextools for this to run.
import latextools
# Border ensures no cropping.
latex_eq = latextools.render_snippet(
f"{self.text}",
commands=[latextools.cmd.all_math],
config=latextools.DocumentConfig(
"standalone", {"crop=true,border=0.1cm"}
),
)
self.eq = latex_eq.as_svg()
eq_lines = self.eq.content.split("\n")
c = "<g>\n" + "\n".join(eq_lines[2:-2]) + "\n</g>"
# Undo scaling done by latextools
# https://github.com/cduck/latextools/blob/caa15da02d88e5a4c82eb06f8fadbe48abd7ad2f/latextools/convert.py#L131
self.width = self.eq.width * 3 / 4
self.height = self.eq.height * 3 / 4
self.content = c
# From latextools Ensures no clash between multiple math statements
id_prefix = f"embed-{hash(self.content)}-"
self.content = (
self.content.replace('id="', f'id="{id_prefix}')
.replace('="url(#', f'="url(#{id_prefix}')
.replace('xlink:href="#', f'href="#{id_prefix}')
)
def get_bounding_box(self) -> BoundingBox:
eps = 1e-4
self.bb = BoundingBox([origin, origin + P2(eps, eps)])
return self.bb
def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
return visitor.visit_latex(self, **kwargs)
def latex(t: str) -> Diagram:
from chalk.core import Primitive
return Primitive.from_shape(Latex(t))
================================================
FILE: chalk/shapes/path.py
================================================
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Iterable, List, Tuple
from chalk import transform as tx
from chalk.envelope import Envelope
from chalk.shapes.shape import Shape
from chalk.trace import Trace
from chalk.trail import Located, Trail
from chalk.transform import P2, Transformable
from chalk.types import Diagram, Enveloped, Traceable
from chalk.visitor import A, ShapeVisitor
def make_path(
segments: List[Tuple[float, float]], closed: bool = False
) -> Diagram:
return Path.from_list_of_tuples(segments, closed).stroke()
@dataclass
class Path(Shape, Enveloped, Traceable, Transformable):
"""Path class."""
loc_trails: List[Located]
# Monoid - compose
@staticmethod
def empty() -> Path:
return Path([])
def __add__(self, other: Path) -> Path:
return Path(self.loc_trails + other.loc_trails)
def apply_transform(self, t: tx.Affine) -> Path:
return Path(
[loc_trail.apply_transform(t) for loc_trail in self.loc_trails]
)
def points(self) -> Iterable[P2]:
for loc_trails in self.loc_trails:
for pt in loc_trails.points():
yield pt
def get_envelope(self) -> Envelope:
return Envelope.concat((loc.get_envelope() for loc in self.loc_trails))
def get_trace(self) -> Trace:
return Trace.concat((loc.get_trace() for loc in self.loc_trails))
def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
return visitor.visit_path(self, **kwargs)
# Constructors
@staticmethod
def from_points(points: List[P2], closed: bool = False) -> Path:
if not points:
return Path.empty()
start = points[0]
trail = Trail.from_offsets(
[pt2 - pt1 for pt1, pt2 in zip(points, points[1:])], closed
)
return Path([trail.at(start)])
@staticmethod
def from_point(point: P2) -> Path:
return Path.from_points([point])
@staticmethod
def from_pairs(segs: List[Tuple[P2, P2]], closed: bool = False) -> Path:
if not segs:
return Path.empty()
ls = [segs[0][0]]
for seg in segs:
assert seg[0] == ls[-1]
ls.append(seg[1])
return Path.from_points(ls, closed)
@staticmethod
def from_list_of_tuples(
coords: List[Tuple[float, float]], closed: bool = False
) -> Path:
points = list([P2(x, y) for x, y in coords])
return Path.from_points(points, closed)
================================================
FILE: chalk/shapes/segment.py
================================================
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, List, Optional, Tuple
from planar.py import Ray
import chalk.transform as tx
from chalk.envelope import Envelope
from chalk.trace import Trace
from chalk.transform import P2, V2
from chalk.types import Enveloped, Traceable, TrailLike
if TYPE_CHECKING:
from chalk.trail import Trail
SignedDistance = float
Ident = tx.Affine.identity()
ORIGIN = P2(0, 0)
@dataclass
class LocatedSegment(Traceable, Enveloped, tx.Transformable, TrailLike):
offset: V2
origin: P2 = ORIGIN
@property
def p(self) -> P2:
return self.origin
@staticmethod
def from_points(p: P2, q: P2) -> LocatedSegment:
return LocatedSegment(q - p, p)
def apply_transform(self, t: tx.Affine) -> LocatedSegment:
return LocatedSegment(
tx.apply_affine(t, self.offset), tx.apply_affine(t, self.origin)
)
@property
def q(self) -> P2:
return self.p + self.offset
def get_trace(self, t: tx.Affine = Ident) -> Trace:
def f(point: P2, direction: V2) -> List[float]:
ray = Ray(point, direction)
inter = sorted(line_segment(ray, self))
return [r / direction.length for r in inter]
return Trace(f)
def get_envelope(self, t: tx.Affine = Ident) -> Envelope:
def f(d: V2) -> SignedDistance:
x: float = max(d.dot(self.q), d.dot(self.p))
return x
return Envelope(f)
@property
def length(self) -> Any:
return self.offset.length
def to_ray(self) -> "Ray":
return Ray(self.p, self.q - self.p)
def to_trail(self) -> Trail:
from chalk.trail import Trail
return Trail([Segment(self.offset)])
@dataclass
class Segment(LocatedSegment, TrailLike):
@property
def p(self) -> P2:
return ORIGIN
def apply_transform(self, t: tx.Affine) -> Segment:
return Segment(tx.apply_affine(tx.remove_translation(t), self.offset))
def reverse(self): # type: ignore
return self.scale(-1)
def seg(offset: V2) -> Trail:
return Segment(offset).to_trail()
def ray_ray_intersection(
ray1: Ray, ray2: Ray
) -> Optional[Tuple[float, float]]:
"""Given two rays
ray₁ = λ t . p₁ + t v₁
ray₂ = λ t . p₂ + t v₂
the function returns the parameters t₁ and t₂ at which the two rays meet,
that is:
ray₁ t₁ = ray₂ t₂
"""
u = ray2.anchor - ray1.anchor
x1 = ray1.direction.cross(ray2.direction)
x2 = u.cross(ray1.direction)
x3 = u.cross(ray2.direction)
if x1 == 0 and x2 != 0:
# parallel
return None
elif x1 == 0 and x2 == 0:
return 0.0, 0.0
else:
# intersecting or collinear
return x3 / x1, x2 / x1
def line_segment(ray: Ray, segment: LocatedSegment) -> List[float]:
"""Given a ray and a segment, return the parameter `t` for which the ray
meets the segment, that is:
ray t₁ = segment.to_ray t₂, with t₂ ∈ [0, segment.length]
Note: We need to consider the segment's length separately since `Ray`
normalizes the direction to unit and hences looses this information. The
length is important to determine whether the intersection point falls
within the given segment.
See also: https://github.com/danoneata/chalk/issues/91
"""
ray_s = segment.to_ray()
t = ray_ray_intersection(ray, ray_s)
if not t:
return []
else:
t1, t2 = t
# the intersection point is given by any of the two expressions:
# ray.anchor + t1 * ray.direction
# ray_s.anchor + t2 * ray_s.direction
if 0 <= t2 <= segment.length:
# intersection point is in segment
return [t1]
else:
# intersection point outside
return []
def ray_circle_intersection(ray: Ray, circle_radius: float) -> List[float]:
"""Given a ray and a circle centered at the origin, return the parameter t
where the ray meets the circle, that is:
ray t = circle θ
The above equation is solved as follows:
x + t v_x = r sin θ
y + t v_y = r cos θ
By squaring the equations and adding them we get
(x + t v_x)² + (y + t v_y)² = r²,
which is equivalent to the following equation:
(v_x² + v_y²) t² + 2 (x v_x + y v_y) t + (x² + y² - r²) = 0
This is a quadratic equation, whose solutions are well known.
"""
p = ray.anchor
a = ray.direction.length2
b = 2 * (p.dot(ray.direction))
c = p.length2 - circle_radius**2
Δ = b**2 - 4 * a * c
eps = 1e-6 # rounding error tolerance
if Δ < -eps:
# no intersection
return []
elif -eps <= Δ < eps:
# tangent
return [-b / (2 * a)]
else:
# the ray intersects at two points
return [
(-b - math.sqrt(Δ)) / (2 * a),
(-b + math.sqrt(Δ)) / (2 * a),
]
================================================
FILE: chalk/shapes/shape.py
================================================
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from chalk.envelope import Envelope
from chalk.trace import Trace
from chalk.trail import Trail
from chalk.transform import P2, BoundingBox, origin
from chalk.types import Diagram
from chalk.visitor import A, ShapeVisitor
@dataclass
class Shape:
"""Shape class."""
def get_bounding_box(self) -> BoundingBox:
raise NotImplementedError
def get_envelope(self) -> Envelope:
return Envelope.from_bounding_box(self.get_bounding_box())
def get_trace(self) -> Trace:
box = self.get_bounding_box()
return (
Trail.rectangle(box.width, box.height)
.stroke()
.center_xy()
.get_trace()
)
def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
raise NotImplementedError
def stroke(self) -> Diagram:
"""Returns a primitive (shape) with strokes
Returns:
Diagram: A diagram.
"""
from chalk.core import Primitive
return Primitive.from_shape(self)
@dataclass
class Spacer(Shape):
"""Spacer class."""
width: float
height: float
def get_bounding_box(self) -> BoundingBox:
left = origin.x - self.width / 2
top = origin.y - self.height / 2
tl = P2(left, top)
br = P2(left + self.width, top + self.height)
return BoundingBox([tl, br])
def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
return visitor.visit_spacer(self, **kwargs)
================================================
FILE: chalk/shapes/text.py
================================================
from dataclasses import dataclass
from typing import Any, Optional
from chalk.shapes.shape import Shape
from chalk.transform import P2, BoundingBox, origin
from chalk.types import Diagram
from chalk.visitor import A, ShapeVisitor
@dataclass
class Text(Shape):
"""Text class."""
text: str
font_size: Optional[float]
def get_bounding_box(self) -> BoundingBox:
# Text doesn't have a bounding box since we can't accurately know
# its size for all backends.
eps = 1e-4
self.bb = BoundingBox([origin, origin + P2(eps, eps)])
return self.bb
def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
return visitor.visit_text(self, **kwargs)
def text(t: str, size: Optional[float]) -> Diagram:
"""
Draw some text.
Args:
t (str): The text string.
size (Optional[float]): Size of the text.
Returns:
Diagram
"""
from chalk.core import Primitive
return Primitive.from_shape(Text(t, font_size=size))
================================================
FILE: chalk/style.py
================================================
from __future__ import annotations
from dataclasses import dataclass, fields
from enum import Enum, auto
from typing import Any, Dict, List, Optional, Tuple
from colour import Color
from typing_extensions import Self
PyCairoContext = Any
PyLatex = Any
class Stylable:
def line_width(self, width: float) -> Self:
return self.apply_style(
Style(line_width_=(WidthType.NORMALIZED, width))
)
def line_width_local(self, width: float) -> Self:
return self.apply_style(Style(line_width_=(WidthType.LOCAL, width)))
def line_color(self, color: Color) -> Self:
return self.apply_style(Style(line_color_=color))
def fill_color(self, color: Color) -> Self:
return self.apply_style(Style(fill_color_=color))
def fill_opacity(self, opacity: float) -> Self:
return self.apply_style(Style(fill_opacity_=opacity))
def dashing(self, dashing_strokes: List[float], offset: float) -> Self:
return self.apply_style(Style(dashing_=(dashing_strokes, offset)))
def apply_style(self: Self, style: Style) -> Self:
raise NotImplementedError("Abstract")
def m(a: Optional[Any], b: Optional[Any]) -> Optional[Any]:
return a if a is not None else b
class WidthType(Enum):
LOCAL = auto()
NORMALIZED = auto()
LC = Color("black")
LW = 0.1
@dataclass
class Style(Stylable):
"""Style class."""
line_width_: Optional[Tuple[WidthType, float]] = None
line_color_: Optional[Color] = None
fill_color_: Optional[Color] = None
fill_opacity_: Optional[float] = None
dashing_: Optional[Tuple[List[float], float]] = None
output_size: Optional[float] = None
@classmethod
def empty(cls) -> Style:
return cls()
@classmethod
def root(cls, output_size: float) -> Style:
return cls(output_size=output_size)
def apply_style(self, other: Style) -> Style:
return self.merge(other)
def merge(self, other: Style) -> Style:
"""Merges two styles and returns the merged style.
Args:
other (Style): Another style object.
Returns:
Style: A style object.
"""
return Style(
*(
m(getattr(other, dim.name), getattr(self, dim.name))
for dim in fields(self)
)
)
def render(self, ctx: PyCairoContext) -> None:
"""Renders the style object.
Args:
ctx (PyCairoContext): A context.
"""
if self.fill_color_:
if self.fill_opacity_ is None:
op = 1.0
else:
op = self.fill_opacity_
ctx.set_source_rgba(*self.fill_color_.rgb, op)
ctx.fill_preserve()
# set default values if they are not provided
if self.line_color_ is None:
lc = LC
else:
lc = self.line_color_
# Set by observation
assert self.output_size is not None
normalizer = self.output_size * (15 / 500)
if self.line_width_ is None:
lw = LW * normalizer
else:
lwt, lw = self.line_width_
if lwt == WidthType.NORMALIZED:
lw = lw * normalizer
elif lwt == WidthType.LOCAL:
lw = lw
ctx.set_source_rgb(*lc.rgb)
ctx.set_line_width(lw)
if self.dashing_ is not None:
ctx.set_dash(self.dashing_[0], self.dashing_[1])
def to_svg(self) -> str:
"""Converts to SVG.
Returns:
str: A string notation of the SVG.
"""
style = ""
if self.fill_color_ is not None:
style += f"fill: {self.fill_color_.hex_l};"
if self.line_color_ is not None:
style += f"stroke: {self.line_color_.hex_l};"
else:
style += "stroke: black;"
# Set by observation
assert self.output_size is not None
normalizer = self.output_size * (15 / 500)
if self.line_width_ is not None:
lwt, lw = self.line_width_
if lwt == WidthType.NORMALIZED:
lw = lw * normalizer
elif lwt == WidthType.LOCAL:
lw = lw
else:
lw = LW * normalizer
style += f"stroke-width: {lw};"
if self.fill_opacity_ is not None:
style += f"fill-opacity: {self.fill_opacity_};"
if self.dashing_ is not None:
style += (
f"stroke-dasharray: {' '.join(map(str, self.dashing_[0]))};"
)
return style
def to_tikz(self, pylatex: PyLatex) -> Dict[str, str]:
"""Converts to dictionary of tikz options."""
style = {}
def tikz_color(color: Color) -> str:
r, g, b = color.rgb
return f"{{rgb,1:red,{r}; green,{g}; blue,{b}}}"
if self.fill_color_ is not None:
style["fill"] = tikz_color(self.fill_color_)
if self.line_color_ is not None:
style["draw"] = tikz_color(self.line_color_)
# This constant was set based on observing TikZ output
assert self.output_size is not None
normalizer = self.output_size * (175 / 500)
if self.line_width_ is not None:
lwt, lw = self.line_width_
if lwt == WidthType.NORMALIZED:
lw = lw * normalizer
elif lwt == WidthType.LOCAL:
lw = lw
else:
lw = normalizer * LW
style["line width"] = f"{lw}pt"
if self.fill_opacity_ is not None:
style["fill opacity"] = f"{self.fill_opacity_}"
if self.dashing_ is not None:
style["dash pattern"] = (
f"{{on {self.dashing_[0][0]}pt off {self.dashing_[0][0]}pt}}"
)
return style
================================================
FILE: chalk/subdiagram.py
================================================
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple
from chalk.envelope import Envelope
from chalk.monoid import Maybe, Monoid
from chalk.trace import Trace
from chalk.transform import P2, V2, Affine, apply_p2_affine, origin
from chalk.types import Diagram
from chalk.visitor import DiagramVisitor
if TYPE_CHECKING:
from chalk.core import ApplyName, ApplyTransform, Compose
Ident = Affine.identity()
AtomicName = Any
@dataclass
class Name:
atomic_names: Tuple[AtomicName, ...]
def __init__(self, atomic_name: AtomicName):
self.atomic_names = (atomic_name,)
def __hash__(self) -> int:
return hash(self.atomic_names)
def __str__(self) -> str:
return "·".join(map(str, self.atomic_names))
def __add__(self, other: Name) -> Name:
new_name = Name(None)
new_name.atomic_names = self.atomic_names + other.atomic_names
return new_name
def qualify(self, name: Name) -> Name:
return name + self
@dataclass
class Subdiagram(Monoid):
diagram: Diagram
transform: Affine
# style: Style
def get_location(self) -> P2:
return apply_p2_affine(self.transform, origin)
def get_envelope(self) -> Envelope:
return self.diagram.get_envelope().apply_transform(self.transform)
def get_trace(self) -> Trace:
return self.diagram.get_trace().apply_transform(self.transform)
def boundary_from(self, v: V2) -> P2:
"""Returns the furthest point on the boundary of the subdiagram,
starting from the local origin of the subdiagram and going in the
direction of the given vector `v`.
"""
o = self.get_location()
p = self.get_trace().trace_p(o, -v)
if not p:
return origin
else:
return p
class GetSubdiagram(DiagramVisitor[Maybe[Subdiagram], Affine]):
A_type = Maybe[Subdiagram]
def __init__(self, name: Name, t: Affine = Ident):
self.name = name
def visit_compose(
self,
diagram: Compose,
t: Affine = Ident,
) -> Maybe[Subdiagram]:
for d in diagram.diagrams:
bb = d.accept(self, t)
if bb.data is not None:
return bb
return Maybe.empty()
def visit_apply_transform(
self,
diagram: ApplyTransform,
t: Affine = Ident,
) -> Maybe[Subdiagram]:
return diagram.diagram.accept(self, t * diagram.transform)
def visit_apply_name(
self,
diagram: ApplyName,
t: Affine = Ident,
) -> Maybe[Subdiagram]:
if self.name == diagram.dname:
return Maybe(Subdiagram(diagram.diagram, t))
else:
return diagram.diagram.accept(self, t)
def get_subdiagram(self: Diagram, name: Name) -> Optional[Subdiagram]:
return self.accept(GetSubdiagram(name), Ident).data
def with_names(
self: Diagram,
names: List[Name],
f: Callable[[List[Subdiagram], Diagram], Diagram],
) -> Diagram:
# NOTE Instead of performing a pass of the AST for each `name` in `names`,
# it might be more efficient to retrieve all named subdiagrams using the
# `get_sub_map` function and then filter the subdiagrams specified by
# `names`.
subs = [self.get_subdiagram(name) for name in names]
if any(sub is None for sub in subs):
# return self
raise LookupError("One of the names is missing from the diagram")
else:
# NOTE Unfortunately, mypy is not narrowing the type when using the
# `any` or `all` functions.
# https://github.com/python/mypy/issues/13069
# Hopefully this bug will be fixed at some point in the future.
return f(subs, self) # type: ignore
@dataclass
class SubMap(Monoid):
data: Dict[Name, List[Subdiagram]]
def __add__(self, other: SubMap) -> SubMap:
d1 = self.data
d2 = other.data
return SubMap(
{k: d1.get(k, []) + d2.get(k, []) for k in set(d1) | set(d2)}
)
@classmethod
def empty(cls) -> SubMap:
return SubMap({})
class GetSubMap(DiagramVisitor[SubMap, Affine]):
A_type = SubMap
def visit_apply_transform(
self,
diagram: ApplyTransform,
t: Affine = Ident,
) -> SubMap:
return diagram.diagram.accept(self, t * diagram.transform)
def visit_apply_name(
self,
diagram: ApplyName,
t: Affine = Ident,
) -> SubMap:
d1 = SubMap({diagram.dname: [Subdiagram(diagram.diagram, t)]})
d2 = diagram.diagram.accept(self, t)
return d1 + d2
def get_sub_map(
self: Diagram, t: Affine = Ident
) -> Dict[Name, List[Subdiagram]]:
"""Retrieves all named subdiagrams in the given diagram and accumulates
them in a dictionary (map) indexed by their name.
"""
return self.accept(GetSubMap(), t).data
================================================
FILE: chalk/trace.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING, Callable, List, Optional
from chalk.monoid import Monoid
from chalk.transform import (
P2,
V2,
Affine,
Transformable,
apply_affine,
remove_translation,
)
from chalk.visitor import DiagramVisitor
if TYPE_CHECKING:
from chalk.core import ApplyTransform, Primitive
from chalk.types import Diagram
SignedDistance = float
Ident = Affine.identity()
class Trace(Monoid, Transformable):
def __init__(self, f: Callable[[P2, V2], List[SignedDistance]]) -> None:
self.f = f
def __call__(self, point: P2, direction: V2) -> List[SignedDistance]:
return self.f(point, direction)
# Monoid
@classmethod
def empty(cls) -> Trace:
return cls(lambda point, direction: [])
def __add__(self, other: Trace) -> Trace:
return Trace(
lambda point, direction: self(point, direction)
+ other(point, direction)
)
# Transformable
def apply_transform(self, t: Affine) -> Trace:
def wrapped(p: P2, d: V2) -> List[SignedDistance]:
t1 = ~t
return self(
apply_affine(t1, p), apply_affine(remove_translation(t1), d)
)
return Trace(wrapped)
def trace_v(self, p: P2, v: V2) -> Optional[V2]:
v = v.scaled_to(1)
dists = self(p, v)
if dists:
s, *_ = sorted(dists)
return s * v
else:
return None
def trace_p(self, p: P2, v: V2) -> Optional[P2]:
u = self.trace_v(p, v)
return p + u if u else None
def max_trace_v(self, p: P2, v: V2) -> Optional[V2]:
return self.trace_v(p, -v)
def max_trace_p(self, p: P2, v: V2) -> Optional[P2]:
u = self.max_trace_v(p, v)
return p + u if u else None
class GetTrace(DiagramVisitor[Trace, Affine]):
A_type = Trace
def visit_primitive(self, diagram: Primitive, t: Affine = Ident) -> Trace:
new_transform = t * diagram.transform
return diagram.shape.get_trace().apply_transform(new_transform)
def visit_apply_transform(
self, diagram: ApplyTransform, t: Affine = Ident
) -> Trace:
return diagram.diagram.accept(self, t * diagram.transform)
def get_trace(self: Diagram, t: Affine = Ident) -> Trace:
return self.accept(GetTrace(), t)
================================================
FILE: chalk/trail.py
================================================
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import TYPE_CHECKING, Iterable, List, Tuple, Union
from chalk.envelope import Envelope
from chalk.monoid import Monoid
from chalk.shapes.arc import ArcSegment, arc_seg, arc_seg_angle
from chalk.shapes.segment import Segment, seg
from chalk.trace import Trace
from chalk.transform import (
P2,
V2,
Affine,
Transformable,
apply_affine,
remove_translation,
unit_x,
unit_y,
)
from chalk.types import Diagram, Enveloped, Traceable, TrailLike
if TYPE_CHECKING:
from chalk.shapes.path import Path
SegmentLike = Union[Segment, ArcSegment]
@dataclass
class Located(Enveloped, Traceable, Transformable):
trail: Trail
location: P2
def located_segments(self) -> Iterable[Tuple[SegmentLike, P2]]:
return zip(self.trail.segments, self.points())
def points(self) -> Iterable[P2]:
return (pt + self.location for pt in self.trail.points())
def get_envelope(self) -> Envelope:
return Envelope.concat(
segment.get_envelope().translate_by(location)
for segment, location in self.located_segments()
)
def get_trace(self) -> Trace:
return Trace.concat(
segment.get_trace().translate_by(location)
for segment, location in self.located_segments()
)
def stroke(self) -> Diagram:
return self.to_path().stroke()
def apply_transform(self, t: Affine) -> Located:
return Located(
apply_affine(t, self.trail), apply_affine(t, self.location)
)
def to_path(self) -> Path:
from chalk.shapes.path import Path
return Path([self])
@dataclass
class Trail(Monoid, Transformable, TrailLike):
segments: List[SegmentLike]
closed: bool = False
# Monoid
@staticmethod
def empty() -> Trail:
return Trail([], False)
def __add__(self, other: Trail) -> Trail:
assert not (self.closed or other.closed), "Cannot add closed trails"
return Trail(self.segments + other.segments, False)
# Transformable
def apply_transform(self, t: Affine) -> Trail:
t = remove_translation(t)
return Trail(
[seg.apply_transform(t) for seg in self.segments], self.closed
)
# Trail-like
def to_trail(self) -> Trail:
return self
def close(self) -> Trail:
return Trail(self.segments, True)
def points(self) -> Iterable[P2]:
cur = P2(0, 0)
pts = [cur]
for segment in self.segments:
cur += segment.q
pts.append(cur)
return pts
def at(self, p: P2) -> Located:
return Located(self, p)
def reverse(self) -> Trail:
return Trail(
[seg.reverse() for seg in reversed(self.segments)],
self.closed,
)
def centered(self) -> Located:
return self.at(-sum(self.points(), P2(0, 0)) / len(self.segments))
# Misc. Constructor
@staticmethod
def from_offsets(offsets: List[V2], closed: bool = False) -> Trail:
return Trail([Segment(off) for off in offsets], closed)
@staticmethod
def hrule(length: float) -> Trail:
return seg(length * unit_x)
@staticmethod
def vrule(length: float) -> Trail:
return seg(length * unit_y)
@staticmethod
def rectangle(width: float, height: float) -> Trail:
t = seg(unit_x * width) + seg(unit_y * height)
return (t + t.rotate_by(0.5)).close()
@staticmethod
def rounded_rectangle(width: float, height: float, radius: float) -> Trail:
r = radius
edge1 = math.sqrt(2 * r * r) / 2
edge3 = math.sqrt(r * r - edge1 * edge1)
corner = arc_seg(V2(r, r), -(r - edge3))
b = [height - r, width - r, height - r, width - r]
trail = Trail.concat(
(seg(b[i] * unit_y) + corner).rotate_by(i / 4) for i in range(4)
) + seg(0.01 * unit_y)
return trail.close()
@staticmethod
def circle(radius: float = 1.0, clockwise: bool = True) -> Trail:
sides = 4
dangle = -90
rotate_by = 1
if not clockwise:
dangle = 90
rotate_by *= -1
return (
Trail.concat(
[
arc_seg_angle(0, dangle).rotate_by(rotate_by * i / sides)
for i in range(sides)
]
)
.close()
.scale(radius)
)
@staticmethod
def regular_polygon(sides: int, side_length: float) -> Trail:
edge = Trail.hrule(side_length)
return Trail.concat(
edge.rotate_by(i / sides) for i in range(sides)
).close()
================================================
FILE: chalk/transform.py
================================================
import math
from typing import Any
from planar import Affine as Affine
from planar import BoundingBox, Point, Polygon, Ray, Vec2, Vec2Array
from typing_extensions import Self
def from_radians(θ: float) -> float:
t = (θ / math.pi) * 180
return t
def to_radians(θ: float) -> float:
t = (θ / 180) * math.pi
return t
def remove_translation(aff: Affine) -> Affine:
a, b, c, d, e, f = aff[:6]
return Affine(a, b, 0, d, e, 0)
def remove_linear(aff: Affine) -> Affine:
a, b, c, d, e, f = aff[:6]
return Affine(1, 0, c, 0, 1, f)
def transpose_translation(aff: Affine) -> Affine:
a, b, c, d, e, f = aff[:6]
return Affine(a, d, 0, b, e, 0)
def apply_affine(aff: Affine, x: Any) -> Any:
return aff * x
class Transformable:
"""Transformable class."""
def apply_transform(self, t: Affine) -> Self: # type: ignore[empty-body]
pass
def __rmul__(self, t: Affine) -> Self:
return self._app(t)
def _app(self, t: Affine) -> Self:
return self.apply_transform(t)
def scale(self, α: float) -> Self:
return self._app(Affine.scale(Vec2(α, α)))
def scale_x(self, α: float) -> Self:
return self._app(Affine.scale(Vec2(α, 1)))
def scale_y(self, α: float) -> Self:
return self._app(Affine.scale(Vec2(1, α)))
def rotate(self, θ: float) -> Self:
"Rotate by θ degrees counterclockwise"
return self._app(Affine.rotation(θ))
def rotate_rad(self, θ: float) -> Self:
"Rotate by θ radians counterclockwise"
return self._app(Affine.rotation(from_radians(θ)))
def rotate_by(self, turns: float) -> Self:
"Rotate by fractions of a circle (turn)."
θ = 2 * math.pi * turns
return self._app(Affine.rotation(from_radians(θ)))
def reflect_x(self) -> Self:
return self._app(Affine.scale(Vec2(-1, +1)))
def reflect_y(self) -> Self:
return self._app(Affine.scale(Vec2(+1, -1)))
def shear_y(self, λ: float) -> Self:
return self._app(Affine(1.0, 0.0, 0.0, λ, 1.0, 0.0))
def shear_x(self, λ: float) -> Self:
return self._app(Affine(1.0, λ, 0.0, 0.0, 1.0, 0.0))
def translate(self, dx: float, dy: float) -> Self:
return self._app(Affine.translation(Vec2(dx, dy)))
def translate_by(self, vector) -> Self: # type: ignore
return self._app(Affine.translation(vector))
# Below here are a collection of hacks to ensure that planar objects
# behave like the rest of the Chalk library. We do this by monkey
# patching in methods to Vec2 and by fixing a bug in the Affine
# transformation. This is not great, but necessary to keep the
# Object oriented api of Chalk.
Vec2._app = lambda x, y: y * x # type: ignore
Vec2.shear_x = Transformable.shear_x # type: ignore
Vec2.shear_y = Transformable.shear_y # type: ignore
Vec2.scale = Transformable.scale # type: ignore
Vec2.scale_x = Transformable.scale_x # type: ignore
Vec2.scale_y = Transformable.scale_y # type: ignore
Vec2.rotate = Transformable.rotate # type: ignore
Vec2.rotate_by = Transformable.rotate_by # type: ignore
Vec2.reflect_x = Transformable.reflect_x # type: ignore
Vec2.reflect_y = Transformable.reflect_y # type: ignore
V2 = Vec2
Vec2.translate = Transformable.translate # type: ignore
Vec2.translate_by = Transformable.translate_by # type: ignore
P2 = Point
origin = P2(0, 0)
unit_x = V2(1, 0)
unit_y = V2(0, 1)
def apply_p2_affine(aff: Affine, x: Point) -> Point:
y: Point = aff * x
return y
def affine(affine: Affine, other: Any) -> Any:
sa, sb, sc, sd, se, sf, _, _, _ = affine[:]
if isinstance(other, Affine):
oa, ob, oc, od, oe, of, _, _, _ = other
return tuple.__new__(
Affine,
(
sa * oa + sb * od,
sa * ob + sb * oe,
sa * oc + sb * of + sc,
sd * oa + se * od,
sd * ob + se * oe,
sd * oc + se * of + sf,
0.0,
0.0,
1.0,
),
)
elif hasattr(other, "from_points"):
# Point/vector array
points = getattr(other, "points", other)
try:
return other.from_points(
Point(px * sa + py * sb + sc, px * sd + py * se + sf)
for px, py in points
)
except TypeError:
return NotImplemented
else:
try:
vx, vy = other
except Exception:
return NotImplemented
return Vec2(vx * sa + vy * sb + sc, vx * sd + vy * se + sf)
Affine.__mul__ = affine # type: ignore
Affine.remove_translation = remove_translation # type: ignore
Affine.remove_linear = remove_linear # type: ignore
Affine.transpose_translation = transpose_translation # type: ignore
# Explicit rexport
__all__ = ["BoundingBox", "Polygon", "Vec2Array", "Ray"]
Affine
================================================
FILE: chalk/types.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Protocol
import chalk.transform as tx
from chalk.envelope import Envelope
from chalk.monoid import Monoid
from chalk.style import Stylable, Style
from chalk.trace import Trace
from chalk.transform import P2, V2
if TYPE_CHECKING:
from chalk.path import Path
from chalk.subdiagram import Name, Subdiagram
from chalk.trail import Located, Trail
from chalk.visitor import A, DiagramVisitor, ShapeVisitor
Ident = tx.Affine.identity()
class Enveloped(Protocol):
def get_envelope(self) -> Envelope: ...
class Traceable(Protocol):
def get_trace(self) -> Trace: ...
class Shape(Enveloped, Traceable, Protocol):
def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A: ...
class TrailLike(Protocol):
def to_trail(self) -> Trail: ...
def to_path(self, location: P2 = P2(0, 0)) -> Path:
return self.at(location).to_path()
def at(self, location: P2) -> Located:
return self.to_trail().at(location)
def stroke(self) -> Diagram:
return self.at(P2(0, 0)).stroke()
class Diagram(Enveloped, Traceable, Stylable, tx.Transformable, Monoid):
def apply_transform(self, t: tx.Affine) -> Diagram: # type: ignore[empty-body]
...
def __add__(self: Diagram, other: Diagram) -> Diagram: # type: ignore[empty-body]
...
def __or__(self, d: Diagram) -> Diagram: # type: ignore[empty-body]
...
def __truediv__(self, d: Diagram) -> Diagram: # type: ignore[empty-body]
...
def __floordiv__(self, d: Diagram) -> Diagram: # type: ignore[empty-body]
...
def juxtapose_snug( # type: ignore[empty-body]
self: Diagram, other: Diagram, direction: V2
) -> Diagram: ...
def beside_snug( # type: ignore[empty-body]
self: Diagram, other: Diagram, direction: V2
) -> Diagram: ...
def juxtapose( # type: ignore[empty-body]
self: Diagram, other: Diagram, direction: V2
) -> Diagram: ...
def atop(self: Diagram, other: Diagram) -> Diagram: # type: ignore[empty-body]
...
def above(self: Diagram, other: Diagram) -> Diagram: # type: ignore[empty-body]
...
def beside( # type: ignore[empty-body]
self: Diagram, other: Diagram, direction: V2
) -> Diagram: ...
def frame(self, extra: float) -> Diagram: # type: ignore[empty-body]
...
def pad(self, extra: float) -> Diagram: # type: ignore[empty-body]
...
def scale_uniform_to_x(self, x: float) -> Diagram: # type: ignore[empty-body]
...
def scale_uniform_to_y(self, y: float) -> Diagram: # type: ignore[empty-body]
...
def align(self: Diagram, v: V2) -> Diagram: # type: ignore[empty-body]
...
def align_t(self: Diagram) -> Diagram: # type: ignore[empty-body]
...
def align_b(self: Diagram) -> Diagram: # type: ignore[empty-body]
...
def align_l(self: Diagram) -> Diagram: # type: ignore[empty-body]
...
def align_r(self: Diagram) -> Diagram: # type: ignore[empty-body]
...
def align_tl(self: Diagram) -> Diagram: # type: ignore[empty-body]
...
def align_tr(self: Diagram) -> Diagram: # type: ignore[empty-body]
...
def align_bl(self: Diagram) -> Diagram: # type: ignore[empty-body]
...
def align_br(self: Diagram) -> Diagram: # type: ignore[empty-body]
...
def snug(self: Diagram, v: V2) -> Diagram: # type: ignore[empty-body]
...
def center_xy(self: Diagram) -> Diagram: # type: ignore[empty-body]
...
def get_subdiagram(self, name: Name) -> Optional[Subdiagram]: ...
def get_sub_map( # type: ignore[empty-body]
self, t: tx.Affine = Ident
) -> Dict[Name, List[Subdiagram]]: ...
def with_names( # type: ignore[empty-body]
self,
names: List[Name],
f: Callable[[List[Subdiagram], Diagram], Diagram],
) -> Diagram: ...
def _style(self, style: Style) -> Diagram: # type: ignore[empty-body]
...
def with_envelope(self, other: Diagram) -> Diagram: # type: ignore[empty-body]
...
def show_origin(self) -> Diagram: # type: ignore[empty-body]
...
def show_envelope( # type: ignore[empty-body]
self, phantom: bool = False, angle: int = 45
) -> Diagram: ...
def compose( # type: ignore[empty-body]
self, envelope: Envelope, other: Optional[Diagram] = None
) -> Diagram: ...
def to_list( # type: ignore[empty-body]
self, t: tx.Affine = Ident
) -> List[Diagram]: ...
def accept( # type: ignore[empty-body]
self, visitor: DiagramVisitor[A, Any], args: Any
) -> A: ...
================================================
FILE: chalk/utils.py
================================================
"""
The ``chalk.utils`` module is meant to provide various
utility-oriented functionalities.
Importing:
```python
# method-1
from chalk import utils as U
# method-2
import chalk.utils
# method-3
from chalk.utils import <some_function>
```
"""
import os
import sys
import tempfile
import time
from typing import Any, Optional, Tuple, TypeVar, Union
from colour import Color
from PIL import Image as PILImage
_HERE = os.path.dirname(__file__)
try:
from loguru import logger
logger.remove()
logger.add(
sys.stdout,
colorize=True,
format="<light-red>{time:HH:mm:ss}</light-red> <level>{message}</level>", # noqa: E501
level="INFO",
) # noqa: E124
prnt_success = logger.success
prnt_warning = logger.warning
except ImportError:
prnt_success = print # type: ignore
prnt_warning = print # type: ignore
Diagram = TypeVar("Diagram")
def show(filepath: str) -> None:
"""Show image from filepath.
Args:
filepath (str): Filepath of the image.
example: "examples/output/intro-01-a.png"
Usage:
```python
from chalk.utils import show
show("examples/output/intro-01.png")
```
"""
PILImage.open(filepath).show()
def imgen(
d: Diagram,
temporary: bool = True,
dirpath: Optional[str] = "examples/output",
prefix: str = "trial_",
suffix: str = "_image.png",
height: int = 64,
wait: int = 5,
verbose: bool = True,
) -> None:
"""Render a ``chalk`` diagram and visualize.
Args:
d (Diagram): A chalk diagram object (``chalk.Diagram``).
temporary (bool, optional): Whether to use a temporary file or not.
Defaults to True.
dirpath (Optional[str], optional): Directory to save the temporary
file in. If does not exist, creates a temporary directory
and destroys it afterwards. Defaults to "examples/output".
prefix (str, optional): Prefix for the generated image file.
Defaults to "trial_".
suffix (str, optional): Suffix for the generated image file.
Defaults to "_image.png".
height (int, optional): Height of the diagram, rendered as an image.
Defaults to 64.
wait (int, optional): The time (in seconds) to wait until destroying
the temporary image file. Defaults to 5.
verbose (bool): Set verbosity. Defaults to True.
Raises:
NotImplementedError: For non temporary file (``temporary=False``),
raises an error, as it has not been
implemented yet.
Usage:
```python
from colour import Color
from chalk import circle
from chalk.utils import imgen
papaya = Color("#ff9700")
d = circle(0.5).fill_color(papaya)
# Minimal example
imgen(d, temporary=True)
# Temporary file is created in current directory
imgen(d, temporary=True, dirpath=None)
# Folder path must exist; otherwise temporary folder is used
imgen(d, temporary=True, dirpath="examples/output")
# Display and delete the temporary file after 10 seconds
imgen(d, temporary=True, wait=10)
```
"""
make_tempdir = False
dp = None
if temporary:
if (dirpath is not None) and (not os.path.isdir(dirpath)):
make_tempdir = True
dp = tempfile.TemporaryDirectory(
dir=".", prefix=prefix, suffix=suffix
)
dirpath = dp.name
with tempfile.NamedTemporaryFile(
dir=dirpath, prefix=prefix, suffix=suffix
) as fp:
if verbose:
prnt_success(
f" ✅ 1. Created temporary file: \n\t\t{os.path.relpath(fp.name)}" # noqa: E501
) # noqa: E501
d.render(fp.name, height=height) # type: ignore
if verbose:
prnt_success(" ✅ 2. Saved rendered image to temporary file.")
fp.seek(0)
if verbose:
prnt_success(" ✅ 3. Displaying image from temporary file.")
show(fp.name)
time.sleep(wait)
if verbose:
prnt_success(" ✅ 4. Removed temporary image file!")
if make_tempdir and dp:
# Cleanup temporary directory
dp.cleanup()
else:
raise NotImplementedError(
"Only temporary file creation + load + display is supported."
)
def create_sample_diagram(
option: Optional[str] = "a|b",
) -> Union[Diagram, Tuple[Diagram, Diagram]]:
"""Creates a sample diagram.
Args:
option (Optional[str], optional): A string denoting what
kind of sample diagram(s) to return.
💡 Defaults to ``"a|b"``.
Choose ``option`` from for the following.
Click to expand:
| Option | Meaning | Output |
|:-----------:|:------------------|:---------------:|
| ``"a+b"`` | ``a.atop(b)`` | Single Diagram |
| ``"b+a"`` | ``b.atop(a)`` | Single Diagram |
| ``"a|b"`` | ``a.beside(b)`` | Single Diagram |
| ``"b|a"`` | ``b.beside(a)`` | Single Diagram |
| ``"a/b"`` | ``a.above(b)`` | Single Diagram |
| ``"b/a"`` | ``b.above(a)`` | Single Diagram |
| ``"a//b"`` | ``a.above2(b)`` | Single Diagram |
| ``"b//a"`` | ``b.above2(a)`` | Single Diagram |
| ``"a,b"`` | ``(a, b)`` | Two Diagrams |
Returns:
Diagram: Returns a sample diagram.
Usage:
```python
from chalk.utils import create_sample_diagram
# create a diagram composed of two diagrams: a|b
d = create_sample_diagram(option="a|b")
# create a diagram composed of two diagrams: b|a
d = create_sample_diagram(option="b|a")
# create a diagram composed of two diagrams: a+b
d = create_sample_diagram(option="a+b")
# create a diagram composed of two diagrams: a/b
d = create_sample_diagram(option="a/b")
# create a diagram composed of two diagrams: a//b
d = create_sample_diagram(option="a//b")
# create two diagrams: (a,b)
a, b = create_sample_diagram(option="a,b")
```
"""
from chalk import circle, square
papaya = Color("#ff9700")
blue = Color("#005FDB")
a = circle(0.5).fill_color(papaya)
b = square(1).fill_color(blue)
if option is None:
d = a | b # a|b
else:
option = "".join(option.split())
# handle specific cases
if option == "a+b":
d = a + b # a.atop(b)
if option == "b+a":
d = b + a # b.atop(a)
elif option == "a|b":
d = a | b # a.beside(b)
elif option == "a|b":
d = b | a # b.beside(a)
elif option == "a/b":
d = a / b # a.above(b)
elif option == "b/a":
d = b / a # b.above(a)
elif option == "a//b":
d = a // b # a.above2(b)
elif option == "b//a":
d = b // a # b.above2(a)
elif option == "a,b":
d = (a, b) # type: ignore
elif option == "b,a":
d = (b, a) # type: ignore
return d # type: ignore
def create_double_diagrams() -> Tuple[Diagram, Diagram]:
"""Creates a pair of sample diagrams (a circle and a square).
Returns:
Diagram: Returns a sample diagram.
"""
a, b = create_sample_diagram(option="a,b") # type: ignore
return (a, b)
def quick_probe(
d: Optional[Diagram] = None,
dirpath: Optional[str] = None,
verbose: bool = True,
**kwargs: Any,
) -> None:
"""Render diagram and generate an image tempfile (``.png``)
This utility is made to quickly create a sample diagram and display it,
without saving any permanent image file on disk. If a diagram is not
provided, a sample diagram is generated. If a diagram is provided, it
is displayed.
Args:
d (Optional[Diagram], optional): A chalk diagram object
(``chalk.Diagram``). Defaults to None.
dirpath (Optional[str], optional): Directory to save the temporary
file in. For example, you could use "examples/output" with
respect to the location of running a script.
Defaults to None.
verbose (bool, optional): Set verbosity. Defaults to True.
**kwargs (Any, optional): See the keyword arguments of
[``imgen()``][chalk.utils.imgen].
Usage:
```python
from chalk.utils import quick_probe
quick_probe(verbose=True, wait=2)
```
"""
# if verbose:
# prnt_warning(f"{chalk.__name__} version: v{chalk.__version__}")
if d is None:
d = create_sample_diagram() # type: ignore
if dirpath is None:
dirpath = os.path.join(_HERE, "../examples/output")
# render diagram and generate an image tempfile (.png)
imgen(d, dirpath=dirpath, verbose=verbose, **kwargs)
if __name__ == "__main__":
# determine initial directory
root = os.path.abspath(os.curdir)
# update sys-path
sys.path.append(_HERE)
os.chdir(_HERE) # change directory
quick_probe(verbose=True) # generate diagram
os.chdir(root) # switch back to initial directory
================================================
FILE: chalk/visitor.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING, Generic, TypeVar
if TYPE_CHECKING:
from chalk.ArrowHead import ArrowHead
from chalk.core import (
ApplyName,
ApplyStyle,
ApplyTransform,
Compose,
Empty,
Primitive,
)
from chalk.monoid import Monoid
from chalk.Path import Path
from chalk.shapes import Image, Latex, Spacer, Text
A = TypeVar("A", bound=Monoid)
else:
A = TypeVar("A")
B = TypeVar("B")
class DiagramVisitor(Generic[A, B]):
A_type: type[A]
def visit_primitive(self, diagram: Primitive, arg: B) -> A:
"Primitive defaults to empty"
return self.A_type.empty()
def visit_empty(self, diagram: Empty, arg: B) -> A:
"Empty defaults to empty"
return self.A_type.empty()
def visit_compose(self, diagram: Compose, arg: B) -> A:
"Compose defaults to monoid over children"
return self.A_type.concat(
[d.accept(self, arg) for d in diagram.diagrams]
)
def visit_apply_transform(self, diagram: ApplyTransform, arg: B) -> A:
"Defaults to pass over"
return diagram.diagram.accept(self, arg)
def visit_apply_style(self, diagram: ApplyStyle, arg: B) -> A:
"Defaults to pass over"
return diagram.diagram.accept(self, arg)
def visit_apply_name(self, diagram: ApplyName, arg: B) -> A:
"Defaults to pass over"
return diagram.diagram.accept(self, arg)
C = TypeVar("C")
class ShapeVisitor(Generic[C]):
def visit_path(self, shape: Path) -> C:
raise NotImplementedError
def visit_latex(self, shape: Latex) -> C:
raise NotImplementedError
def visit_text(self, shape: Text) -> C:
raise NotImplementedError
def visit_spacer(self, shape: Spacer) -> C:
raise NotImplementedError
def visit_arrowhead(self, shape: ArrowHead) -> C:
raise NotImplementedError
def visit_image(self, shape: Image) -> C:
raise NotImplementedError
================================================
FILE: doc/crop-images.sh
================================================
for img in squares hanoi escher-square-limit lenet logo hilbert koch tensor hex-variation tree lattice; do
# convert examples/output/${img}.png -gravity center -crop 200x200+0+0 +repage doc/imgs/${img}.png
convert -define png:size=200x200 examples/output/${img}.png -thumbnail 200x200^ -gravity center -extent 200x200 doc/imgs/${img}.png
done
================================================
FILE: docs/about/index.md
================================================
# About
{{ config.site_description }}
<sup>Copyright (c) 2022 {{ config.site_author }}.</sup>
================================================
FILE: docs/about/license.md
================================================
# License
--8<-- "LICENSE"
================================================
FILE: docs/api/alignment.py
================================================
# + tags=["hide_inp"]
from chalk.core import BaseDiagram
from chalk import *
def help(f):
import pydoc
from IPython.display import HTML
return HTML(pydoc.HTMLDoc().docroutine(f))
# -
# Each diagram has an origin and an envelope.
# Manipulating the position of the diagram with respect to its origin and envelope allows for precise control of the layout.
# Note that the Chalk API is immutable and always returns a new ``Diagram`` object.
# ### Diagram.show_origin
# + tags=["hide_inp"]
help(BaseDiagram.show_origin)
# -
#
triangle(1).show_origin()
# ### Diagram.show_envelope
# + tags=["hide_inp"]
help(BaseDiagram.show_envelope)
# -
rectangle(1, 1).show_envelope()
triangle(1).show_envelope()
rectangle(1, 1).show_beside(triangle(1), unit_x)
(rectangle(1, 1) | triangle(1)).pad(1.4)
arc(1, 0, math.pi).show_origin().show_envelope(angle=10)
# ### Diagram.align_*
# + tags=["hide_inp"]
help(BaseDiagram.align_t)
# -
#
triangle(1).align_t().show_envelope()
triangle(1).align_t().show_beside(rectangle(1, 1).align_b(), unit_x)
# + tags=["hide_inp"]
help(BaseDiagram.align_r)
# -
#
triangle(1).align_r().show_envelope().show_origin()
# ### Diagram.center_xy
# + tags=["hide_inp"]
help(BaseDiagram.center_xy)
# -
#
triangle(1).center_xy().show_envelope().show_origin()
# ### Diagram.pad_*
# + tags=["hide_inp"]
help(BaseDiagram.pad)
# -
#
triangle(1).pad(1.5).show_envelope().show_origin()
# ### Diagram.with_envelope
# + tags=["hide_inp"]
help(BaseDiagram.with_envelope)
# -
#
from colour import Color
(rectangle(1, 1) + triangle(0.5)) | rectangle(1, 1)
(rectangle(1, 1) + triangle(0.5)).with_envelope(triangle(0.5)) | rectangle(1, 1).fill_color(Color("red"))
================================================
FILE: docs/api/combinators.py
================================================
# + tags=["hide_inp"]
import math
from planar import Vec2
from chalk import *
def help(f):
import pydoc
from IPython.display import HTML
return HTML(pydoc.HTMLDoc().docroutine(f))
# -
# Complex diagrams can be created by combining simpler diagrams
# through placement combinators. These place diagrams above, atop or
# besides other diagrams. Relative location is determined by the envelope
# and origins of the diagrams.
# ### beside
# + tags=["hide_inp"]
help(beside)
# -
#
diagram = triangle(1).beside(square(1), unit_x)
diagram
#
triangle(1).show_beside(square(1), unit_x)
triangle(1).show_beside(triangle(1).rotate_by(1 / 6), Vec2.polar(-45))
#
triangle(1).show_beside(triangle(1).rotate_by(1 / 6), Vec2.polar(-30))
# ### above
# + tags=["hide_inp"]
help(above)
# -
#
diagram = triangle(1) / square(1)
diagram
#
diagram.show_envelope().show_origin()
# ### atop
# + tags=["hide_inp"]
help(atop)
# -
# Example 1 - Atop at origin
diagram = square(1) + triangle(1)
diagram
#
diagram.show_envelope().show_origin()
# Example 2 - Align then atop origin
s = square(1).align_r().align_b().show_origin()
t = triangle(1).align_l().align_t().show_origin()
s
#
t
#
s + t
# ### vcat
# + tags=["hide_inp"]
help(vcat)
# -
#
vcat([triangle(1), square(1), triangle(1)], 0.2)
# ### concat
# + tags=["hide_inp"]
help(concat)
# -
#
concat([triangle(1), square(1), triangle(1)])
# ### hcat
# + tags=["hide_inp"]
help(hcat)
# -
#
hcat([triangle(1), square(1), triangle(1)], 0.2)
# ### place_on_path
# + tags=["hide_inp"]
help(place_on_path)
# -
place_on_path(
[circle(0.25) for _ in range(6)],
Trail.regular_polygon(6, 1).to_path(),
)
================================================
FILE: docs/api/internals.md
================================================
---
title: Chalk internals
summary: A look inside Chalk
date: 2022-08-21
---
This document presents information on the internal implementation of the Chalk library.
This information should not be needed by the casual user of the library, but it can certainly be useful to developers and also provides a record of the major design decisions.
While much of the functionality of the Chalk library resembles the [Haskell `diagrams` library](https://diagrams.github.io/),
there are important distinctions in the implementation due to the differences of the two languages (Python and Haskell).
## Core data types
Chalk is an embedded domain specific language (EDSL).
The two extremes of language design are shallow and deep EDSLs.
Loosely put, a shallow EDSL specifies the language through a set of functions,
while a deep EDSL specifies the syntax of the language using a data type (the abstract syntax tree; AST), which is then interpreted using given evaluator functions.
Chalk uses a hybrid approach which defines an intermediate core data structure (in our case, the `Diagram` type) and a suite of functions that operate on this type.
(For more information on the concepts of deep and shallow EDSLs, see [the paper of Gibbons and Wu from ICFP'14](http://www.cs.ox.ac.uk/jeremy.gibbons/publications/embedding.pdf)).
The `Diagram` type (implemented as `BaseDiagram` in `chalk/core.py`) can be thought as an [algebraic data type](https://en.wikipedia.org/wiki/Algebraic_data_type) with the following six variants:
`Empty`, `Primitive`, `Compose`, `ApplyTransform`, `ApplyStyle`, `ApplyName`.
Each of the variants may hold additional information, as follows:
```python
class Empty(BaseDiagram):
pass
class Primitive(BaseDiagram):
shape: Shape
style: Style
transform: Affine
class Compose(BaseDiagram):
envelope: Envelope
diagram1: Diagram
diagram2: Diagram
class ApplyTransform(BaseDiagram):
transform: Affine
diagram: Diagram
class ApplyStyle(BaseDiagram):
style: Style
diagram: Diagram
class ApplyName(BaseDiagram):
dname: str
diagram: Diagram
```
Instances of `BaseDiagram` can be constructed and modified using the functions provided by the library.
For example,
```python
circle(1)
```
generates the following AST:
```python
Primitive(shape=Path(...), style=Style(...), transform=Affine(...))
```
and
```python
circle(1) | circle(1)
```
generates the following AST:
```python
Compose(
envelope=...,
diagram1=Primitive(shape=Path(...), style=Style(...), transform=Affine(...)),
diagram2=Primitive(shape=Path(...), style=Style(...), transform=Affine(...)),
)
```
A `Diagram` AST can be interpreted in multiple ways, arguably the most obvious being through the rendering functions (see the `chalk/backend` submodule);
other interpretations are flattening the `Diagram` AST to a list of `Primitive`s or extracting the `Envelope` or `Trace` of a `Diagram`.
All these functions are implemented using the [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern#Python_example), which is the object-oriented correspondent of pattern matching and folding encountered in functional programming.
(Jeremy Gibbons provides a nice exposition on the relationship between object-oriented design patterns and their functional counterparts in his paper [Design Patterns as Higher-Order Datatype-Generic Programs](http://www.cs.ox.ac.uk/jeremy.gibbons/publications/hodgp.pdf)).
## Support for functional and object-oriented use
Internally the library is implemented in a functional style, through functions that operate on the `Diagram` AST.
For example, for composition we have a combinator function `beside` (in `chalk/combinators.py`) that takes two `Diagram`s and returns a new `Diagram` corresponding to their composition.
The main benefit of using functions is that the library can be easily split into self-contained submodules, each pertaining to a certain type of functionality (shapes, alignment, transformations, and so on).
In contrast, using an object-oriented style would bundle the entire functionality as methods inside the class
and would only allow to separate the variants (e.g., `Empty`, `Primitive`, `Compose`) across files (see the ["expression problem"](https://en.wikipedia.org/wiki/Expression_problem) for the trade-offs between the functional and object-oriented style).
However, allowing to write code in an object-oriented style (using dot notation) provides an arguably more convenient and idiomatic style in Python.
For this reason, we attach all the functions as methods in the `chalk/core.py` file; for example:
```python
class BaseDiagram:
beside = chalk.combinators.beside
```
This implementation allows writing
```python
circle(1).beside(circle(1), unit_x)
```
and even more succinctly
```python
circle(1) | circle(1)
```
as we define ["dunder" methods](https://docs.python.org/3/reference/datamodel.html#special-method-names) for common combinators (`__or__` in this case).
The challenge of this implementation decision is that it complicates the [type checking](https://mypy.readthedocs.io/en/stable/index.html#) due to circular imports.
We solve this problem by introducing a `Diagram` [protocol](https://mypy.readthedocs.io/en/stable/protocols.html) (in `chalk/types.py`), which specifies type signatures for all the methods that are to be implemented later on.
The `Diagram` type is used throughout the code for type hinting, for example:
```python
def juxtapose(self: Diagram, other: Diagram, direction: V2) -> Diagram:
# We can use `get_envelope` here since the `Diagram` protocol promises
# that such a method will be implemented.
```
The concrete implementation of the `Diagram` protocol is provided by the `BaseDiagram` in `chalk/core.py`.
## Trail-like data types
Apart from the main `Diagram` data type, there are several other related types (`Trail`, `Located`, `Path`) that encode a trail-like drawing and can be "lifted" to a `Diagram` using the `stroke` method.
These trail-like structures encode different types of information and, as a consequence, have different combination semantics:
- `Trail` corresponds to a list of vectors (translation-invariant offsets, which can be either straight or bendy—implemented as arcs).
A `Trail` is `Transformable` (by transforming each of the vectors), but since vectors are translation-invariant, applying `translate` leaves a `Trail` unchanged.
The monoid composition corresponds to the list monoid: it _extends_ the first trail with the second one.
A `Trail` can be closed which means that it is a loop and it will be able to hold a color when filled in.
- `Located` is a `Trail` paired with a `location` origin point.
A `Trail` can be turned into `Located` using the `at` method which specifies the origin location.
`Located` instances do not form a monoid.
- `Path` is a list of `Located` instances.
Having more than one `Located` instance is important, since it allows to easily draw objects with holes in them (such as rings).
The monoid composition also corresponds to the list monoid, but the effects is different from `Trail`: it _overlays_ the `Located` subpaths.
Below we present an example (inspired from the `diagrams` library) that showcases the distinction of the combination semantics (the `concat` function) for the `Diagram`, `Path`, `Trail` types.
```python
from colour import Color
from toolz import iterate, take
from chalk import *
red = Color("red")
t = Trail.regular_polygon(3, 1)
t_loc = t.centered()
# Diagram
dia1 = concat(take(3, iterate(lambda d: d.rotate_by(1 / 9), t_loc.stroke()))).fill_color(red)
# Path
dia2 = Path.concat(take(3, iterate(lambda d: d.rotate_by(1 / 9), t_loc.to_path()))).stroke().fill_color(red)
# Trail
dia3 = Trail.concat(take(3, iterate(lambda d: d.rotate_by(1 / 9), t))).stroke().center_xy()
hcat([dia1, dia2, dia3], sep=0.2)
```
<img src="https://user-images.githubusercontent.com/819256/184515950-b3ce4245-19ee-4357-bc3a-f32f993b04ef.png">
================================================
FILE: docs/api/names.py
================================================
# + tags=["hide_inp"]
from colour import Color
from chalk.core import BaseDiagram
from chalk import *
def help(f):
import pydoc
from IPython.display import HTML
return HTML(pydoc.HTMLDoc().docroutine(f))
set_svg_height(100)
# -
# Chalk supports basic methods for complex connected layouts and diagrams.
# Individual elements can be assigned names, and then be referenced in their subdiagram locations.
# As we will see, names are particularly useful for connecting different parts of the diagram with arrows.
# Our implementation follows the API of the Haskell [diagrams](https://diagrams.github.io/doc/manual.html#named-subdiagrams) library,
# but named nodes are also common in TikZ.
#
# ### Diagram.named
# + tags=["hide_inp"]
help(BaseDiagram.named)
# -
diagram = circle(0.5).named("x") | square(1)
diagram
# ### Diagram.get_subdiagram
# + tags=["hide_inp"]
help(BaseDiagram.get_subdiagram)
# -
# A `Subdiagram` is a `Diagram` paired with its enclosing context (a `Transformation` for the moment; but `Style` should also be added at some point).
# It has the following methods:
# - `get_envelope`, which returns the corresponding `Envelope`
# - `get_trace`, which returns the corresponding `Trace`
# - `get_location`, which returns the local origin of the `Subdiagram`
# - `boundary_from`, which return the furthest point on the boundary of the `Subdiagram`, starting from the local origin of the `Subdigram` and going in the direction of a given vector.
#
diagram = circle(0.5).named("x") | square(1)
sub = diagram.get_subdiagram("x")
diagram + circle(0.2).translate_by(sub.get_location())
# ### Diagram.with_names
# + tags=["hide_inp"]
help(BaseDiagram.with_names)
# -
root = circle(1).named("root")
leaves = hcat([circle(1).named(c) for c in "abcde"], sep=0.5).center()
def connect(subs, nodes):
root, leaf = subs
pp = tuple(root.boundary_from(unit_y))
pc = tuple(leaf.boundary_from(-unit_y))
return nodes + make_path([pp, pc])
nodes = root / vstrut(2) / leaves
for c in "abcde":
nodes = nodes.with_names(["root", c], connect)
nodes
# ### Diagram.qualify
# + tags=["hide_inp"]
help(BaseDiagram.qualify)
# -
red = Color("red")
def attach(subs, dia):
sub1, sub2 = subs
p1 = tuple(sub1.get_location())
p2 = tuple(sub2.get_location())
return dia + make_path([p1, p2]).line_color(red)
def squares():
s = square(1)
return (
(s.named("NW") | s.named("NE")) /
(s.named("SW") | s.named("SE")))
dia = hcat([squares().qualify(str(i)) for i in range(5)], sep=0.5)
pairs = [
(("0", "NE"), ("2", "SW")),
(("1", "SE"), ("4", "NE")),
(("3", "NW"), ("3", "SE")),
(("0", "SE"), ("1", "NW")),
]
dia
for pair in pairs:
dia = dia.with_names(pair, attach)
dia
# ### Diagram.show_labels
# + tags=["hide_inp"]
help(BaseDiagram.show_labels)
# -
dia.show_labels(font_size=0.2)
# ### Diagram.connect
# + tags=["hide_inp"]
help(BaseDiagram.connect)
# -
diagram = circle(0.5).named("x") | hstrut(1) | square(1).named("y")
diagram.connect("x", "y")
# ### Diagram.connect_outside
# + tags=["hide_inp"]
help(BaseDiagram.connect_outside)
# -
diagram = circle(0.5).named("x") | hstrut(1) | square(1).named("y")
diagram.connect_outside("x", "y")
================================================
FILE: docs/api/rendering.py
================================================
# + tags=["hide"]
import math
from chalk.core import BaseDiagram
from chalk import *
def help(f):
import pydoc
from IPython.display import HTML
return HTML(pydoc.HTMLDoc().docroutine(f))
# -
# Chalk supports three back-ends (Cairo, SVG, TikZ),
# which allow the created `Diagram`s to be rendered as PNG, SVG, PDF files, respectively.
# The three corresponding methods for rendering are: `render`, `render_svg`, `render_pdf`;
# these are documented below.
#
# ### Diagram.render
# + tags=["hide_inp"]
help(BaseDiagram.render)
# -
circle(1).render("circle.png")
from IPython.display import Image
Image("circle.png")
# ### Diagram.render_svg
# + tags=["hide_inp"]
help(BaseDiagram.render_svg)
# -
# ### Diagram.render_pdf
# + tags=["hide_inp"]
help(BaseDiagram.render_pdf)
# -
# ### ``Diagram``s in IPython notebooks
# When a ``Diagram`` is used in an IPython notebook, it is automatically displayed as an SVG.
# To adjust the height of the generated image, one can use the `set_svg_height` function:
# + tags=["hide_inp"]
help(set_svg_height)
# -
# This function is particularly useful for showing tall drawings:
set_svg_height(500)
vcat([circle(1) for _ in range(5)])
================================================
FILE: docs/api/shapes.py
================================================
# + tags=["hide"]
from chalk.core import BaseDiagram
from chalk import *
def help(f):
import pydoc
from IPython.display import HTML
return HTML(pydoc.HTMLDoc().docroutine(f))
# -
# Elementary diagrams can be created using shapes: polygons, circle-like shapes, text and paths.
# ## Polygons
# ### Triangle
# + tags=["hide_inp"]
help(triangle)
# -
triangle(1)
# ### Square and rectangle
# + tags=["hide_inp"]
help(square)
# -
square(1)
# + tags=["hide_inp"]
help(rectangle)
# -
rectangle(3, 1, 0.0)
# ### Regular polygon
# + tags=["hide_inp"]
help(regular_polygon)
# -
hcat(
[
regular_polygon(5, 1),
regular_polygon(6, 1),
regular_polygon(7, 1),
],
sep=0.5,
)
# ## Circle-like shapes
# ### Circle
# + tags=["hide_inp"]
help(circle)
# -
circle(1)
# ### Arc
# Arcs can be specified either using angles (see ``arc``) or points (see ``arc_between``).
# + tags=["hide_inp"]
help(arc)
# -
quarter = 90
arc(1, 0, quarter)
arc(1, 0, quarter) + arc(1, 2 * quarter, 3 * quarter)
# + tags=["hide_inp"]
help(arc_between)
# -
arc_between((0, 0), (1, 0), 1)
# ## Text
# + tags=["hide_inp"]
help(text)
# -
# Note that unlike other shapes, ``text`` has an empty envelope, so we need to explicitly specify it in order to get a non-empty rendering.
text("hello", 1).with_envelope(rectangle(2.5, 1))
# ## Paths
# + tags=["hide_inp"]
help(make_path)
# -
make_path([(0, 0), (0, 1), (1, 1), (1, 2)])
================================================
FILE: docs/api/style.py
================================================
# + tags=["hide_inp"]
from chalk.core import BaseDiagram
from chalk import *
def help(f):
import pydoc
from IPython.display import HTML
return HTML(pydoc.HTMLDoc().docroutine(f))
# -
# Diagrams can be styled using standard vector graphic style
# primitives. Colors use the Python [colour](https://github.com/vaab/colour) library.
from colour import Color
blue = Color("blue")
orange = Color("orange")
# ### Diagram.fill_color
# + tags=["hide_inp"]
help(BaseDiagram.fill_color)
# -
#
triangle(1).fill_color(blue)
# ### Diagram.fill_opacity
# + tags=["hide_inp"]
help(BaseDiagram.fill_opacity)
# -
#
triangle(1).fill_color(blue).fill_opacity(0.2)
# ### Diagram.line_color
# + tags=["hide_inp"]
help(BaseDiagram.line_color)
# -
#
triangle(1).line_color(blue)
# ### Diagram.line_width
# + tags=["hide_inp"]
help(BaseDiagram.line_width)
# -
#
triangle(1).line_width(0.05)
# ### Diagram.dashing
# + tags=["hide_inp"]
help(BaseDiagram.dashing)
# -
#
triangle(1).dashing([0.2, 0.1], 0)
# ### Advanced Example
# Example: Outer styles override inner styles
(triangle(1).fill_color(orange) | square(2)).fill_color(blue)
================================================
FILE: docs/api/trails.py
================================================
# + tags=["hide_inp"]
import math
from chalk import *
from planar import Vec2Array
def help(f):
import pydoc
from IPython.display import HTML
return HTML(pydoc.HTMLDoc().docroutine(f))
# -
# Chalk also includes a ``Trail`` primitive which allows for creating new ``Diagram``s and more complex shapes.
# ``Trail``s are specified by position-invariant offsets and can be rendered as ``Path``s.
# See the [Koch](../examples/koch/) example for a use case.
# ### Constructors
# ``Trail``s are a sequence of vectors and can be constructed using the ``from_offsets`` method:
trail = Trail.from_offsets([V2(1, 0), V2(1, 1), V2(0, 1)])
# ### Converting to ``Diagram``
# In order to render, `Trail`s have to be turned into ``Diagram``s, which can be achieved using the `stroke` method.
# + tags=["hide_inp"]
help(Trail.stroke)
# -
trail.stroke()
# ### Transformations
# `Trail`s can be transformed using the usual geometric transformations, which are also applied to the ``Diagram`` object.
# For example, ``Trail``s can be rotated:
# + tags=["hide_inp"]
help(Trail.rotate_by)
# -
trail2 = trail.rotate_by(0.2)
trail2.stroke()
# However, since `Trail`s are translation invariant, applying the `translate` method leaves the `Trail` instance unchanged.
# ### Composition
# ``Trail``s form a monoid with addition given by list concatenation and identity given by the empty list.
# Intuitively, adding two ``Trails`` appends the two sequences of offsets.
# + tags=["hide_inp"]
help(Trail.__add__)
# -
(trail + trail2).stroke()
================================================
FILE: docs/api/transformations.py
================================================
# + tags=["hide_inp"]
from chalk.core import BaseDiagram
from chalk import *
def help(f):
import pydoc
from IPython.display import HTML
return HTML(pydoc.HTMLDoc().docroutine(f))
# -
# Any Diagram (or other object in Chalk) can be transformed by affine transformation.
# These produce a new diagram in the standard manner.
# ### scale
# + tags=["hide_inp"]
help(BaseDiagram.scale)
# -
#
triangle(1) | triangle(1).scale(2)
# Transformations apply to the whole diagram.
(triangle(1) | triangle(1)).scale(2)
# ### translate
# + tags=["hide_inp"]
help(BaseDiagram.translate)
# -
#
triangle(1).translate(1, 1).show_envelope().show_origin()
#
triangle(1) + triangle(1).translate(1, 1)
# ### shear_x
# + tags=["hide_inp"]
help(BaseDiagram.shear_x)
# -
#
square(1).shear_x(0.25).show_envelope()
#
square(1) | square(1).shear_x(0.25)
# ### rotate
# + tags=["hide_inp"]
help(BaseDiagram.rotate)
# -
#
triangle(1) | triangle(1).rotate(90)
# ### rotate_by
# + tags=["hide_inp"]
help(BaseDiagram.rotate_by)
# -
#
triangle(1) | triangle(1).rotate_by(0.2)
================================================
FILE: docs/api/utils.md
================================================
---
title: chalk.utils
---
# **`{{ title }}`**
👍
::: {{ title }}
================================================
FILE: docs/assets/css-js/as-fastapi/chat.js
================================================
((window.gitter = {}).chat = {}).options = {
room: 'tiangolo/fastapi'
};
================================================
FILE: docs/assets/css-js/as-fastapi/custom.css
================================================
/* source: https: //github.com/tiangolo/fastapi/blob/1876ebc77949a9a254909ec61ea0c09365169ec2/docs/en/docs/css/custom.css */
.termynal-comment {
color: #4a968f;
font-style: italic;
display: block;
}
.termy [data-termynal] {
white-space: pre-wrap;
}
a.external-link::after {
/* \00A0 is a non-breaking space
to make the mark be on the same line as the link
*/
content: "\00A0[↪]";
}
a.internal-link::after {
/* \00A0 is a non-breaking space
to make the mark be on the same line as the link
*/
content: "\00A0↪";
}
.shadow {
box-shadow: 5px 5px 10px #999;
}
/* Give space to lower icons so Gitter chat doesn't get on top of them */
.md-footer-meta {
padding-bottom: 2em;
}
.user-list {
display: flex;
flex-wrap: wrap;
margin-bottom: 2rem;
}
.user-list-center {
justify-content: space-evenly;
}
.user {
margin: 1em;
min-width: 7em;
}
.user .avatar-wrapper {
width: 80px;
height: 80px;
margin: 10px auto;
overflow: hidden;
border-radius: 50%;
position: relative;
}
.user .avatar-wrapper img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.user .title {
text-align: center;
}
.user .count {
font-size: 80%;
text-align: center;
}
a.announce-link:link,
a.announce-link:visited {
color: #fff;
}
a.announce-link:hover {
color: var(--md-accent-fg-color);
}
.announce-wrapper {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
align-items: center;
}
.announce-wrapper div.item {
display: none;
}
.announce-wrapper .sponsor-badge {
display: block;
position: absolute;
top: -5px;
right: 0;
font-size: 0.5rem;
color: #999;
background-color: #666;
border-radius: 10px;
padding: 0 10px;
z-index: 10;
}
.announce-wrapper .sponsor-image {
display: block;
border-radius: 20px;
}
.announce-wrapper>div {
min-height: 40px;
display: flex;
align-items: center;
}
.twitter {
color: #00acee;
}
================================================
FILE: docs/assets/css-js/as-fastapi/custom.js
================================================
// source: https://github.com/tiangolo/fastapi/blob/1876ebc77949a9a254909ec61ea0c09365169ec2/docs/en/docs/js/custom.js
const div = document.querySelector('.github-topic-projects')
async function getDataBatch(page) {
const response = await fetch(`https://api.github.com/search/repositories?q=topic:fastapi&per_page=100&page=${page}`, { headers: { Accept: 'application/vnd.github.mercy-preview+json' } })
const data = await response.json()
return data
}
async function getData() {
let page = 1
let data = []
let dataBatch = await getDataBatch(page)
data = data.concat(dataBatch.items)
const totalCount = dataBatch.total_count
while (data.length < totalCount) {
page += 1
dataBatch = await getDataBatch(page)
data = data.concat(dataBatch.items)
}
return data
}
function setupTermynal() {
document.querySelectorAll(".use-termynal").forEach(node => {
node.style.display = "block";
new Termynal(node, {
lineDelay: 500
});
});
const progressLiteralStart = "---> 100%";
const promptLiteralStart = "$ ";
const customPromptLiteralStart = "# ";
const termynalActivateClass = "termy";
let termynals = [];
function createTermynals() {
document
.querySelectorAll(`.${termynalActivateClass} .highlight`)
.forEach(node => {
const text = node.textContent;
const lines = text.split("\n");
const useLines = [];
let buffer = [];
function saveBuffer() {
if (buffer.length) {
let isBlankSpace = true;
buffer.forEach(line => {
if (line) {
isBlankSpace = false;
}
});
dataValue = {};
if (isBlankSpace) {
dataValue["delay"] = 0;
}
if (buffer[buffer.length - 1] === "") {
// A last single <br> won't have effect
// so put an additional one
buffer.push("");
}
const bufferValue = buffer.join("<br>");
dataValue["value"] = bufferValue;
useLines.push(dataValue);
buffer = [];
}
}
for (let line of lines) {
if (line === progressLiteralStart) {
saveBuffer();
useLines.push({
type: "progress"
});
} else if (line.startsWith(promptLiteralStart)) {
saveBuffer();
const value = line.replace(promptLiteralStart, "").trimEnd();
useLines.push({
type: "input",
value: value
});
} else if (line.startsWith("// ")) {
saveBuffer();
const value = "💬 " + line.replace("// ", "").trimEnd();
useLines.push({
value: value,
class: "termynal-comment",
delay: 0
});
} else if (line.startsWith(customPromptLiteralStart)) {
saveBuffer();
const promptStart = line.indexOf(promptLiteralStart);
if (promptStart === -1) {
console.error("Custom prompt found but no end delimiter", line)
}
const prompt = line.slice(0, promptStart).replace(customPromptLiteralStart, "")
let value = line.slice(promptStart + promptLiteralStart.length);
useLines.push({
type: "input",
value: value,
prompt: prompt
});
} else {
buffer.push(line);
}
}
saveBuffer();
const div = document.createElement("div");
node.replaceWith(div);
const termynal = new Termynal(div, {
lineData: useLines,
noInit: true,
lineDelay: 500
});
termynals.push(termynal);
});
}
function loadVisibleTermynals() {
termynals = termynals.filter(termynal => {
if (termynal.container.getBoundingClientRect().top - innerHeight <= 0) {
termynal.init();
return false;
}
return true;
});
}
window.addEventListener("scroll", loadVisibleTermynals);
createTermynals();
loadVisibleTermynals();
}
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
async function showRandomAnnouncement(groupId, timeInterval) {
const announceFastAPI = document.getElementById(groupId);
if (announceFastAPI) {
let children = [].slice.call(announceFastAPI.children);
children = shuffle(children)
let index = 0
const announceRandom = () => {
children.forEach((el, i) => { el.style.display = "none" });
children[index].style.display = "block"
index = (index + 1) % children.length
}
announceRandom()
setInterval(announceRandom, timeInterval
)
}
}
async function main() {
if (div) {
data = await getData()
div.innerHTML = '<ul></ul>'
const ul = document.querySelector('.github-topic-projects ul')
data.forEach(v => {
if (v.full_name === 'tiangolo/fastapi') {
return
}
const li = document.createElement('li')
li.innerHTML = `<a href="${v.html_url}" target="_blank">★ ${v.stargazers_count} - ${v.full_name}</a> by <a href="${v.owner.html_url}" target="_blank">@${v.owner.login}</a>`
ul.append(li)
})
}
setupTermynal();
showRandomAnnouncement('announce-left', 5000)
showRandomAnnouncement('announce-right', 10000)
}
main()
================================================
FILE: docs/assets/css-js/as-fastapi/termynal.css
================================================
/**
* termynal.js
*
* @author Ines Montani <ines@ines.io>
* @version 0.0.1
* @license MIT
*/
:root {
--color-bg: #252a33;
--color-text: #eee;
--color-text-subtle: #a2a2a2;
}
[data-termynal] {
width: 750px;
max-width: 100%;
background: var(--color-bg);
color: var(--color-text);
/* font-size: 18px; */
font-size: 15px;
/* font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; */
font-family: 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace;
border-radius: 4px;
padding: 75px 45px 35px;
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
[data-termynal]:before {
content: '';
position: absolute;
top: 15px;
left: 15px;
display: inline-block;
width: 15px;
height: 15px;
border-radius: 50%;
/* A little hack to display the window buttons in one pseudo element. */
background: #d9515d;
-webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
}
[data-termynal]:after {
content: 'bash';
position: absolute;
color: var(--color-text-subtle);
top: 5px;
left: 0;
width: 100%;
text-align: center;
}
a[data-terminal-control] {
text-align: right;
display: block;
color: #aebbff;
}
[data-ty] {
display: block;
line-height: 2;
}
[data-ty]:before {
/* Set up defaults and ensure empty lines are displayed. */
content: '';
display: inline-block;
vertical-align: middle;
}
[data-ty="input"]:before,
[data-ty-prompt]:before {
margin-right: 0.75em;
color: var(--color-text-subtle);
}
[data-ty="input"]:before {
content: '$';
}
[data-ty][data-ty-prompt]:before {
content: attr(data-ty-prompt);
}
[data-ty-cursor]:after {
content: attr(data-ty-cursor);
font-family: monospace;
margin-left: 0.5em;
-webkit-animation: blink 1s infinite;
animation: blink 1s infinite;
}
/* Cursor animation */
@-webkit-keyframes blink {
50% {
opacity: 0;
}
}
@keyframes blink {
50% {
opacity: 0;
}
}
================================================
FILE: docs/assets/css-js/as-fastapi/termynal.js
================================================
/**
* termynal.js
* A lightweight, modern and extensible animated terminal window, using
* async/await.
*
* @author Ines Montani <ines@ines.io>
* @version 0.0.1
* @license MIT
*/
'use strict';
/** Generate a terminal widget. */
class Termynal {
/**
* Construct the widget's settings.
* @param {(string|Node)=} container - Query selector or container element.
* @param {Object=} options - Custom settings.
* @param {string} options.prefix - Prefix to use for data attributes.
* @param {number} options.startDelay - Delay before animation, in ms.
* @param {number} options.typeDelay - Delay between each typed character, in ms.
* @param {number} options.lineDelay - Delay between each line, in ms.
* @param {number} options.progressLength - Number of characters displayed as progress bar.
* @param {string} options.progressChar – Character to use for progress bar, defaults to █.
* @param {number} options.progressPercent - Max percent of progress.
* @param {string} options.cursor – Character to use for cursor, defaults to ▋.
* @param {Object[]} lineData - Dynamically loaded line data objects.
* @param {boolean} options.noInit - Don't initialise the animation.
*/
constructor(container = '#termynal', options = {}) {
this.container = (typeof container === 'string') ? document.querySelector(container) : container;
this.pfx = `data-${options.prefix || 'ty'}`;
this.originalStartDelay = this.startDelay = options.startDelay
|| parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600;
this.originalTypeDelay = this.typeDelay = options.typeDelay
|| parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90;
this.originalLineDelay = this.lineDelay = options.lineDelay
|| parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500;
this.progressLength = options.progressLength
|| parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40;
this.progressChar = options.progressChar
|| this.container.getAttribute(`${this.pfx}-progressChar`) || '█';
this.progressPercent = options.progressPercent
|| parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100;
this.cursor = options.cursor
|| this.container.getAttribute(`${this.pfx}-cursor`) || '▋';
this.lineData = this.lineDataToElements(options.lineData || []);
this.loadLines()
if (!options.noInit) this.init()
}
loadLines() {
// Load all the lines and create the container so that the size is fixed
// Otherwise it would be changing and the user viewport would be constantly
// moving as she/he scrolls
const finish = this.generateFinish()
finish.style.visibility = 'hidden'
this.container.appendChild(finish)
// Appends dynamically loaded lines to existing line elements.
this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData);
for (let line of this.lines) {
line.style.visibility = 'hidden'
this.container.appendChild(line)
}
const restart = this.generateRestart()
restart.style.visibility = 'hidden'
this.container.appendChild(restart)
this.container.setAttribute('data-termynal', '');
}
/**
* Initialise the widget, get lines, clear container and start animation.
*/
init() {
/**
* Calculates width and height of Termynal container.
* If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS.
*/
const containerStyle = getComputedStyle(this.container);
this.container.style.width = containerStyle.width !== '0px' ?
containerStyle.width : undefined;
this.container.style.minHeight = containerStyle.height !== '0px' ?
containerStyle.height : undefined;
this.container.setAttribute('data-termynal', '');
this.container.innerHTML = '';
for (let line of this.lines) {
line.style.visibility = 'visible'
}
this.start();
}
/**
* Start the animation and rener the lines depending on their data attributes.
*/
async start() {
this.addFinish()
await this._wait(this.startDelay);
for (let line of this.lines) {
const type = line.getAttribute(this.pfx);
const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay;
if (type == 'input') {
line.setAttribute(`${this.pfx}-cursor`, this.cursor);
await this.type(line);
await this._wait(delay);
}
else if (type == 'progress') {
await this.progress(line);
await this._wait(delay);
}
else {
this.container.appendChild(line);
await this._wait(delay);
}
line.removeAttribute(`${this.pfx}-cursor`);
}
this.addRestart()
this.finishElement.style.visibility = 'hidden'
this.lineDelay = this.originalLineDelay
this.typeDelay = this.originalTypeDelay
this.startDelay = this.originalStartDelay
}
generateRestart() {
const restart = document.createElement('a')
restart.onclick = (e) => {
e.preventDefault()
this.container.innerHTML = ''
this.init()
}
restart.href = '#'
restart.setAttribute('data-terminal-control', '')
restart.innerHTML = "restart ↻"
return restart
}
generateFinish() {
const finish = document.createElement('a')
finish.onclick = (e) => {
e.preventDefault()
this.lineDelay = 0
this.typeDelay = 0
this.startDelay = 0
}
finish.href = '#'
finish.setAttribute('data-terminal-control', '')
finish.innerHTML = "fast →"
this.finishElement = finish
return finish
}
addRestart() {
const restart = this.generateRestart()
this.container.appendChild(restart)
}
addFinish() {
const finish = this.generateFinish()
this.container.appendChild(finish)
}
/**
* Animate a typed line.
* @param {Node} line - The line element to render.
*/
async type(line) {
const chars = [...line.textContent];
line.textContent = '';
this.container.appendChild(line);
for (let char of chars) {
const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay;
await this._wait(delay);
line.textContent += char;
}
}
/**
* Animate a progress bar.
* @param {Node} line - The line element to render.
*/
async progress(line) {
const progressLength = line.getAttribute(`${this.pfx}-progressLength`)
|| this.progressLength;
const progressChar = line.getAttribute(`${this.pfx}-progressChar`)
|| this.progressChar;
const chars = progressChar.repeat(progressLength);
const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`)
|| this.progressPercent;
line.textContent = '';
this.container.appendChild(line);
for (let i = 1; i < chars.length + 1; i++) {
await this._wait(this.typeDelay);
const percent = Math.round(i / chars.length * 100);
line.textContent = `${chars.slice(0, i)} ${percent}%`;
if (percent > progressPercent) {
break;
}
}
}
/**
* Helper function for animation delays, called with `await`.
* @param {number} time - Timeout, in ms.
*/
_wait(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
/**
* Converts line data objects into line elements.
*
* @param {Object[]} lineData - Dynamically loaded lines.
* @param {Object} line - Line data object.
* @returns {Element[]} - Array of line elements.
*/
lineDataToElements(lineData) {
return lineData.map(line => {
let div = document.createElement('div');
div.innerHTML = `<span ${this._attributes(line)}>${line.value || ''}</span>`;
return div.firstElementChild;
});
}
/**
* Helper function for generating attributes string.
*
* @param {Object} line - Line data object.
* @returns {string} - String of attributes.
*/
_attributes(line) {
let attrs = '';
for (let prop in line) {
// Custom add class
if (prop === 'class') {
attrs += ` class=${line[prop]} `
continue
}
if (prop === 'type') {
attrs += `${this.pfx}="${line[prop]}" `
} else if (prop !== 'value') {
attrs += `${this.pfx}-${prop}="${line[prop]}" `
}
}
return attrs;
}
}
/**
* HTML API: If current script has container(s) specified, initialise Termynal.
*/
if (document.currentScript.hasAttribute('data-termynal-container')) {
const containers = document.currentScript.getAttribute('data-termynal-container');
containers.split('|')
.forEach(container => new Termynal(container))
}
================================================
FILE: docs/assets/css-js/general/css/progressbar.css
================================================
/*
* Source: https://facelessuser.github.io/pymdown-extensions/extensions/progressbar/
*
* Note: Look under "CSS Setup".
*
*/
.progress-label {
position: absolute;
text-align: center;
font-weight: 700;
width: 100%;
margin: 0;
line-height: 1.2rem;
white-space: nowrap;
overflow: hidden;
}
.progress-bar {
height: 1.2rem;
float: left;
background-color: #2979ff;
}
.progress {
display: block;
width: 100%;
margin: 0.5rem 0;
height: 1.2rem;
background-color: #eeeeee;
position: relative;
}
.progress.thin {
margin-top: 0.9rem;
height: 0.4rem;
}
.progress.thin .progress-label {
margin-top: -0.4rem;
}
.progress.thin .progress-bar {
height: 0.4rem;
}
/* ColorPalette Source: https://colorbrewer2.org/#type=diverging&scheme=RdYlGn&n=11 */
.progress-0plus .progress-bar {
background-color: #a50026;
}
.progress-10plus .progress-bar {
background-color: #d73027;
}
.progress-20plus .progress-bar {
background-color: #f46d43;
}
.progress-30plus .progress-bar {
background-color: #fdae61;
}
.progress-40plus .progress-bar {
background-color: #fee08b;
}
.progress-50plus .progress-bar {
background-color: #ffffbf;
}
.progress-60plus .progress-bar {
background-color: #d9ef8b;
}
.progress-70plus .progress-bar {
background-color: #a6d96a;
}
.progress-80plus .progress-bar {
background-color: #66bd63;
}
.progress-90plus .progress-bar {
background-color: #1a9850;
}
.progress-100plus .progress-bar {
background-color: #006837;
}
================================================
FILE: docs/assets/css-js/general/css/social-color-stryle.css
================================================
/* # cspell: disable */
/* Icon Fill Color Styles
*
* Source: https://squidfunk.github.io/mkdocs-material/reference/icons-emojis/#with-colors
*
*/
.devdotto {
color: #0A0A0A;
src: "https://simpleicons.org/icons/devdotto.svg";
}
.facebook {
color: #4267B2;
src: "https://simpleicons.org/icons/facebook.svg";
}
.github {
color: #181717;
src: "https://simpleicons.org/icons/github.svg";
/* icon: "https://simpleicons.org/icons/github.svg"; */
}
.linkedin {
color: #0A66C2;
src: "https://simpleicons.org/icons/linkedin.svg";
/* icon: "https://simpleicons.org/icons/linkedin.svg"; */
}
.medium {
color: #00AB6C;
src: "https://simpleicons.org/icons/medium.svg";
/* icon: "https://simpleicons.org/icons/medium.svg"; */
}
.stackoverflow {
color: #F58025;
src: "https://simpleicons.org/icons/stackoverflow.svg";
/* icon: "https://simpleicons.org/icons/stackoverflow.svg"; */
}
.twitter {
color: #1DA1F2;
src: "https://simpleicons.org/icons/twitter.svg";
/* icon: "https://simpleicons.org/icons/twitter.svg"; */
}
.youtube {
color: #FF0000;
src: "https://simpleicons.org/icons/youtube.svg";
/* icon: "https://simpleicons.org/icons/youtube.svg"; */
}
================================================
FILE: docs/assets/css-js/general/js/highlight-config.js
================================================
// source: https://squidfunk.github.io/mkdocs-material/reference/code-blocks/#highlight
document$.subscribe(() => {
hljs.highlightAll()
})
================================================
FILE: docs/assets/css-js/general/js/material-extra/extra-uml.js
================================================
/*
* Source: https://github.com/facelessuser/pymdown-extensions/blob/main/docs/src/js/extra-uml.js
*/
import uml from "./uml"
(() => {
const onReady = function (fn) {
document.addEventListener("DOMContentLoaded", fn)
document.addEventListener("DOMContentSwitch", fn)
}
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === "attributes") {
let scheme = mutation.target.getAttribute("data-md-color-scheme")
if (!scheme) {
scheme = "default"
}
localStorage.setItem("data-md-color-scheme", scheme)
if (typeof mermaid !== "undefined") {
uml("mermaid")
}
}
})
})
onReady(() => {
observer.observe(document.querySelector("body"), { attributeFilter: ["data-md-color-scheme"] })
if (typeof mermaid !== "undefined") {
uml("mermaid")
}
})
})()
================================================
FILE: docs/assets/css-js/general/js/material-extra/material-extra-theme.js
================================================
/*
* Source: https://github.com/facelessuser/pymdown-extensions/blob/main/docs/src/js/material-extra-theme.js
*/
(() => {
const preferToggle = function (e) {
if (localStorage.getItem("data-md-prefers-color-scheme") === "true") {
document.querySelector("body").setAttribute("data-md-color-scheme", (e.matches) ? "dracula" : "default")
}
}
const setupTheme = function (body) {
const preferSupported = window.matchMedia("(prefers-color-scheme)").media !== "not all"
let scheme = localStorage.getItem("data-md-color-scheme")
let prefers = localStorage.getItem("data-md-prefers-color-scheme")
if (!scheme) {
scheme = "dracula"
}
if (!prefers) {
prefers = "false"
}
if (prefers === "true" && preferSupported) {
scheme = (window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dracula" : "default"
} else {
prefers = "false"
}
body.setAttribute("data-md-prefers-color-scheme", prefers)
body.setAttribute("data-md-color-scheme", scheme)
if (preferSupported) {
const matchListener = window.matchMedia("(prefers-color-scheme: dark)")
matchListener.addListener(preferToggle)
}
}
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === "childList") {
if (mutation.addedNodes.length) {
for (let i = 0; i < mutation.addedNodes.length; i++) {
const el = mutation.addedNodes[i]
if (el.nodeType === 1 && el.tagName.toLowerCase() === "body") {
setupTheme(el)
break
}
}
}
}
})
})
observer.observe(document.querySelector("html"), { childList: true })
})()
window.toggleScheme = function () {
const body = document.querySelector("body")
const preferSupported = window.matchMedia("(prefers-color-scheme)").media !== "not all"
let scheme = body.getAttribute("data-md-color-scheme"
gitextract_pp7itskp/
├── .github/
│ └── workflows/
│ ├── checks.yml
│ ├── mkdocs-publish-ghpages.yml
│ └── selfassign.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── chalk/
│ ├── __init__.py
│ ├── align.py
│ ├── arrow.py
│ ├── backend/
│ │ ├── __init__.py
│ │ ├── cairo.py
│ │ ├── svg.py
│ │ └── tikz.py
│ ├── combinators.py
│ ├── core.py
│ ├── envelope.py
│ ├── model.py
│ ├── monoid.py
│ ├── py.typed
│ ├── shapes/
│ │ ├── __init__.py
│ │ ├── arc.py
│ │ ├── arrowheads.py
│ │ ├── image.py
│ │ ├── latex.py
│ │ ├── path.py
│ │ ├── segment.py
│ │ ├── shape.py
│ │ └── text.py
│ ├── style.py
│ ├── subdiagram.py
│ ├── trace.py
│ ├── trail.py
│ ├── transform.py
│ ├── types.py
│ ├── utils.py
│ └── visitor.py
├── doc/
│ └── crop-images.sh
├── docs/
│ ├── about/
│ │ ├── index.md
│ │ └── license.md
│ ├── api/
│ │ ├── alignment.py
│ │ ├── combinators.py
│ │ ├── internals.md
│ │ ├── names.py
│ │ ├── rendering.py
│ │ ├── shapes.py
│ │ ├── style.py
│ │ ├── trails.py
│ │ ├── transformations.py
│ │ └── utils.md
│ ├── assets/
│ │ ├── css-js/
│ │ │ ├── as-fastapi/
│ │ │ │ ├── chat.js
│ │ │ │ ├── custom.css
│ │ │ │ ├── custom.js
│ │ │ │ ├── termynal.css
│ │ │ │ └── termynal.js
│ │ │ ├── general/
│ │ │ │ ├── css/
│ │ │ │ │ ├── progressbar.css
│ │ │ │ │ └── social-color-stryle.css
│ │ │ │ └── js/
│ │ │ │ ├── highlight-config.js
│ │ │ │ ├── material-extra/
│ │ │ │ │ ├── extra-uml.js
│ │ │ │ │ ├── material-extra-theme.js
│ │ │ │ │ └── uml.js
│ │ │ │ └── tables.js
│ │ │ ├── mkdocs-tooltips/
│ │ │ │ └── css/
│ │ │ │ └── custom.css
│ │ │ ├── pymdownx-extras/
│ │ │ │ ├── css/
│ │ │ │ │ └── extra.css
│ │ │ │ └── js/
│ │ │ │ └── extra-uml.js
│ │ │ └── termynal/
│ │ │ ├── css/
│ │ │ │ ├── custom.css
│ │ │ │ └── termynal.css
│ │ │ ├── js/
│ │ │ │ ├── custom.js
│ │ │ │ └── termynal.js
│ │ │ └── readme.md
│ │ └── snippets/
│ │ ├── macros/
│ │ │ └── .gitkeep
│ │ └── notifications/
│ │ └── videos/
│ │ └── disclaimer.md
│ ├── examples/
│ │ ├── hanoi.py
│ │ ├── koch.py
│ │ ├── lenet.py
│ │ ├── squares.py
│ │ └── tensor.py
│ ├── index.md
│ ├── overrides/
│ │ └── main.html
│ └── quickstart/
│ └── .gitkeep
├── examples/
│ ├── arrows.py
│ ├── bigben.py
│ ├── comparison.tex
│ ├── escher_square_limit.py
│ ├── hanoi.py
│ ├── hex_variation.py
│ ├── hilbert.py
│ ├── intro.py
│ ├── isometric.py
│ ├── koch.py
│ ├── latex.py
│ ├── lattice.py
│ ├── lenet.py
│ ├── logo.py
│ ├── normalized.py
│ ├── output/
│ │ ├── index.html
│ │ ├── path.tex
│ │ └── path.tex.tex
│ ├── path.py
│ ├── rectangle_parade.py
│ ├── squares.py
│ ├── subdiagrams.py
│ ├── tensor.py
│ ├── tests/
│ │ └── index.tpl.html
│ ├── tournament-network.py
│ └── tree.py
├── mkdocs.yml
├── pyproject.toml
├── requirements/
│ ├── dev.txt
│ └── docs.txt
├── requirements.txt
├── setup.cfg
├── setup.py
└── tests/
├── test_envelope.py
└── test_reverse_trail.py
SYMBOL INDEX (595 symbols across 65 files)
FILE: chalk/align.py
function align_to (line 7) | def align_to(self: Diagram, v: V2) -> Diagram:
function snug (line 13) | def snug(self: Diagram, v: V2) -> Diagram:
function center (line 22) | def center(self: Diagram) -> Diagram:
function align_t (line 29) | def align_t(self: Diagram) -> Diagram:
function align_b (line 33) | def align_b(self: Diagram) -> Diagram:
function align_r (line 37) | def align_r(self: Diagram) -> Diagram:
function align_l (line 41) | def align_l(self: Diagram) -> Diagram:
function align_tl (line 45) | def align_tl(self: Diagram) -> Diagram:
function align_br (line 49) | def align_br(self: Diagram) -> Diagram:
function align_tr (line 53) | def align_tr(self: Diagram) -> Diagram:
function align_bl (line 57) | def align_bl(self: Diagram) -> Diagram:
function center_xy (line 61) | def center_xy(self: Diagram) -> Diagram:
function scale_uniform_to_x (line 69) | def scale_uniform_to_x(self: Diagram, x: float) -> Diagram:
function scale_uniform_to_y (line 86) | def scale_uniform_to_y(self: Diagram, y: float) -> Diagram:
FILE: chalk/arrow.py
class ArrowOpts (line 18) | class ArrowOpts:
function connect (line 31) | def connect(
function connect_outside (line 45) | def connect_outside(
function connect_perim (line 71) | def connect_perim(
function arrow (line 102) | def arrow(length: float, style: ArrowOpts = ArrowOpts()) -> Diagram:
function arrow_v (line 133) | def arrow_v(vec: V2, style: ArrowOpts = ArrowOpts()) -> Diagram:
function arrow_at (line 138) | def arrow_at(base: P2, vec: V2, style: ArrowOpts = ArrowOpts()) -> Diagram:
function arrow_between (line 142) | def arrow_between(
FILE: chalk/backend/cairo.py
function tx_to_cairo (line 32) | def tx_to_cairo(affine: Affine) -> Any:
class ToList (line 41) | class ToList(DiagramVisitor[MList[Any], Affine]):
method visit_primitive (line 48) | def visit_primitive(
method visit_apply_transform (line 53) | def visit_apply_transform(
method visit_apply_style (line 64) | def visit_apply_style(
method visit_apply_name (line 74) | def visit_apply_name(
class ToCairoShape (line 80) | class ToCairoShape(ShapeVisitor[None]):
method render_segment (line 82) | def render_segment(
method visit_path (line 101) | def visit_path(
method visit_latex (line 117) | def visit_latex(
method visit_text (line 125) | def visit_text(
method visit_spacer (line 139) | def visit_spacer(
method visit_arrowhead (line 147) | def visit_arrowhead(
method visit_image (line 157) | def visit_image(
function render_cairo_prims (line 170) | def render_cairo_prims(
function render (line 188) | def render(
FILE: chalk/backend/svg.py
function tx_to_svg (line 42) | def tx_to_svg(affine: tx.Affine) -> str:
class Raw (line 51) | class Raw(Rect): # type: ignore
method __init__ (line 57) | def __init__(self, st: str):
method get_xml (line 61) | def get_xml(self) -> ET.Element:
class ToSVG (line 65) | class ToSVG(DiagramVisitor[BaseElement, Style]):
method __init__ (line 68) | def __init__(self, dwg: Drawing):
method visit_primitive (line 72) | def visit_primitive(
method visit_empty (line 88) | def visit_empty(
method visit_compose (line 93) | def visit_compose(
method visit_apply_transform (line 102) | def visit_apply_transform(
method visit_apply_style (line 109) | def visit_apply_style(
method visit_apply_name (line 114) | def visit_apply_name(
class ToSVGShape (line 122) | class ToSVGShape(ShapeVisitor[BaseElement]):
method __init__ (line 123) | def __init__(self, dwg: Drawing):
method render_segment (line 126) | def render_segment(self, seg: SegmentLike, p: P2) -> str:
method visit_path (line 137) | def visit_path(
method visit_latex (line 155) | def visit_latex(
method visit_text (line 163) | def visit_text(
method visit_spacer (line 178) | def visit_spacer(
method visit_arrowhead (line 183) | def visit_arrowhead(
method visit_image (line 190) | def visit_image(
function to_svg (line 200) | def to_svg(self: Diagram, dwg: Drawing, style: Style) -> BaseElement:
function render (line 204) | def render(
FILE: chalk/backend/tikz.py
function tx_to_tikz (line 33) | def tx_to_tikz(affine: tx.Affine) -> str:
class ToTikZ (line 42) | class ToTikZ(DiagramVisitor[MList[PyLatexElement], Style]):
method __init__ (line 45) | def __init__(self, pylatex: PyLatex):
method visit_primitive (line 49) | def visit_primitive(
method visit_apply_transform (line 65) | def visit_apply_transform(
method visit_apply_style (line 74) | def visit_apply_style(
class ToTikZShape (line 81) | class ToTikZShape(ShapeVisitor[PyLatexElement]):
method __init__ (line 82) | def __init__(self, pylatex: PyLatex):
method render_segment (line 85) | def render_segment(
method visit_path (line 109) | def visit_path(
method visit_latex (line 129) | def visit_latex(
method visit_text (line 134) | def visit_text(
method visit_spacer (line 152) | def visit_spacer(
method visit_arrowhead (line 167) | def visit_arrowhead(
method visit_image (line 179) | def visit_image(
function to_tikz (line 185) | def to_tikz(
function render (line 191) | def render(self: Diagram, path: str, height: int = 128) -> None:
FILE: chalk/combinators.py
function with_envelope (line 12) | def with_envelope(self: Diagram, other: Diagram) -> Diagram:
function strut (line 19) | def strut(width: float, height: float) -> Diagram:
function pad (line 25) | def pad(self: Diagram, extra: float) -> Diagram:
function frame (line 47) | def frame(self: Diagram, extra: float) -> Diagram:
function atop (line 71) | def atop(self: Diagram, other: Diagram) -> Diagram:
function above (line 81) | def above(self: Diagram, other: Diagram) -> Diagram:
function beside (line 88) | def beside(self: Diagram, other: Diagram, direction: V2) -> Diagram:
function place_at (line 92) | def place_at(
function place_on_path (line 98) | def place_on_path(diagrams: Iterable[Diagram], path: Path) -> Diagram:
function cat (line 105) | def cat(
function concat (line 121) | def concat(diagrams: Iterable[Diagram]) -> Diagram:
function empty (line 137) | def empty() -> Diagram:
function hstrut (line 149) | def hstrut(width: Optional[float]) -> Diagram:
function vstrut (line 157) | def vstrut(height: Optional[float]) -> Diagram:
function hcat (line 165) | def hcat(diagrams: Iterable[Diagram], sep: Optional[float] = None) -> Di...
function vcat (line 180) | def vcat(diagrams: Iterable[Diagram], sep: Optional[float] = None) -> Di...
function above2 (line 198) | def above2(self: Diagram, other: Diagram) -> Diagram:
function juxtapose_snug (line 215) | def juxtapose_snug(self: Diagram, other: Diagram, direction: V2) -> Diag...
function beside_snug (line 226) | def beside_snug(self: Diagram, other: Diagram, direction: V2) -> Diagram:
function juxtapose (line 230) | def juxtapose(self: Diagram, other: Diagram, direction: V2) -> Diagram:
function at_center (line 249) | def at_center(self: Diagram, other: Diagram) -> Diagram:
FILE: chalk/core.py
function set_svg_height (line 35) | def set_svg_height(height: int) -> None:
function set_svg_draw_height (line 41) | def set_svg_draw_height(height: int) -> None:
class BaseDiagram (line 48) | class BaseDiagram(chalk.types.Diagram):
method empty (line 55) | def empty(cls) -> Diagram: # type: ignore
method apply_transform (line 59) | def apply_transform(self, t: Affine) -> Diagram: # type: ignore
method apply_style (line 63) | def apply_style(self, style: Style) -> Diagram: # type: ignore
method _style (line 66) | def _style(self, style: Style) -> Diagram:
method compose (line 69) | def compose(
method named (line 82) | def named(self, name: Name) -> Diagram:
method __or__ (line 127) | def __or__(self, d: Diagram) -> Diagram:
method display (line 133) | def display(
method _repr_svg_ (line 157) | def _repr_svg_(self) -> str:
method _repr_html_ (line 166) | def _repr_html_(self) -> str | tuple[str, Any]:
method qualify (line 178) | def qualify(self, name: Name) -> Diagram:
method accept (line 182) | def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
class Primitive (line 187) | class Primitive(BaseDiagram):
method from_shape (line 200) | def from_shape(cls, shape: Shape) -> Primitive:
method apply_transform (line 212) | def apply_transform(self, t: Affine) -> Primitive:
method apply_style (line 224) | def apply_style(self, other_style: Style) -> Primitive:
method accept (line 237) | def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
class Empty (line 242) | class Empty(BaseDiagram):
method accept (line 245) | def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
class Compose (line 250) | class Compose(BaseDiagram):
method accept (line 256) | def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
class ApplyTransform (line 261) | class ApplyTransform(BaseDiagram):
method accept (line 267) | def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
class ApplyStyle (line 272) | class ApplyStyle(BaseDiagram):
method accept (line 278) | def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
class ApplyName (line 283) | class ApplyName(BaseDiagram):
method accept (line 289) | def accept(self, visitor: DiagramVisitor[A, Any], args: Any) -> A:
class Qualify (line 293) | class Qualify(DiagramVisitor[Diagram, None]):
method __init__ (line 296) | def __init__(self, name: Name):
method visit_primitive (line 299) | def visit_primitive(self, diagram: Primitive, args: None) -> Diagram:
method visit_compose (line 302) | def visit_compose(self, diagram: Compose, args: None) -> Diagram:
method visit_apply_transform (line 307) | def visit_apply_transform(
method visit_apply_style (line 315) | def visit_apply_style(self, diagram: ApplyStyle, args: None) -> Diagram:
method visit_apply_name (line 321) | def visit_apply_name(self, diagram: ApplyName, args: None) -> Diagram:
FILE: chalk/envelope.py
class Envelope (line 30) | class Envelope(Transformable, Monoid):
method __init__ (line 31) | def __init__(
method __call__ (line 37) | def __call__(self, direction: V2) -> SignedDistance:
method empty (line 43) | def empty() -> Envelope:
method __add__ (line 46) | def __add__(self, other: Envelope) -> Envelope:
method center (line 56) | def center(self) -> P2:
method width (line 65) | def width(self) -> float:
method height (line 70) | def height(self) -> float:
method apply_transform (line 74) | def apply_transform(self, t: Affine) -> Envelope:
method envelope_v (line 97) | def envelope_v(self, v: V2) -> V2:
method from_bounding_box (line 105) | def from_bounding_box(box: BoundingBox) -> Envelope:
method from_circle (line 115) | def from_circle(radius: float) -> Envelope:
method to_path (line 121) | def to_path(self, angle: int = 45) -> Iterable[P2]:
method to_segments (line 129) | def to_segments(self, angle: int = 45) -> Iterable[Tuple[P2, P2]]:
class GetEnvelope (line 138) | class GetEnvelope(DiagramVisitor[Envelope, Affine]):
method visit_primitive (line 141) | def visit_primitive(
method visit_compose (line 147) | def visit_compose(self, diagram: Compose, t: Affine = Ident) -> Envelope:
method visit_apply_transform (line 150) | def visit_apply_transform(
function get_envelope (line 157) | def get_envelope(self: Diagram, t: Affine = Ident) -> Envelope:
FILE: chalk/model.py
function show_origin (line 13) | def show_origin(self: Diagram) -> Diagram:
function show_envelope (line 24) | def show_envelope(
function show_beside (line 60) | def show_beside(self: Diagram, other: Diagram, direction: V2) -> Diagram:
function show_labels (line 97) | def show_labels(self: Diagram, font_size: int = 1) -> Diagram:
FILE: chalk/monoid.py
function associative_reduce (line 19) | def associative_reduce(
class Monoid (line 37) | class Monoid:
method empty (line 39) | def empty(cls) -> Self:
method __add__ (line 42) | def __add__(self, other: Self) -> Self:
method concat (line 46) | def concat(cls, elems: Iterable[Self]) -> Self:
class Maybe (line 54) | class Maybe(Generic[A], Monoid):
method empty (line 58) | def empty(cls) -> Maybe[A]:
method __add__ (line 61) | def __add__(self, other: Maybe[A]) -> Maybe[A]:
class MList (line 68) | class MList(Generic[A], Monoid):
method empty (line 72) | def empty(cls) -> MList[A]:
method __add__ (line 75) | def __add__(self, other: MList[A]) -> MList[A]:
method __iter__ (line 78) | def __iter__(self) -> Iterator[A]:
FILE: chalk/shapes/__init__.py
function hrule (line 18) | def hrule(length: float) -> Diagram:
function vrule (line 22) | def vrule(length: float) -> Diagram:
function regular_polygon (line 41) | def regular_polygon(sides: int, side_length: float) -> Diagram:
function triangle (line 47) | def triangle(width: float) -> Diagram:
function rectangle (line 53) | def rectangle(
function square (line 75) | def square(side: float) -> Diagram:
function circle (line 81) | def circle(radius: float) -> Diagram:
function arc (line 86) | def arc(radius: float, angle0: float, angle1: float) -> Diagram:
function arc_between (line 107) | def arc_between(
FILE: chalk/shapes/arc.py
function is_in_mod_360 (line 29) | def is_in_mod_360(x: Degrees, a: Degrees, b: Degrees) -> bool:
class LocatedArcSegment (line 38) | class LocatedArcSegment(Traceable, Enveloped, tx.Transformable):
method p (line 48) | def p(self) -> P2:
method q (line 53) | def q(self) -> P2:
method q_angle (line 60) | def q_angle(self) -> float:
method center (line 64) | def center(self) -> P2:
method r_x (line 69) | def r_x(self) -> float:
method r_y (line 74) | def r_y(self) -> float:
method rot (line 79) | def rot(self) -> float:
method get_trace (line 83) | def get_trace(self, t: tx.Affine = Ident) -> Trace:
method get_envelope (line 104) | def get_envelope(self, t: tx.Affine = Ident) -> Envelope:
method arc_between (line 129) | def arc_between(
class ArcSegment (line 165) | class ArcSegment(LocatedArcSegment, TrailLike):
method __post_init__ (line 168) | def __post_init__(self) -> None:
method apply_transform (line 173) | def apply_transform(self, t: tx.Affine) -> ArcSegment:
method to_trail (line 177) | def to_trail(self) -> Trail:
method arc_between_trail (line 183) | def arc_between_trail(q: P2, height: float) -> Trail:
method reverse (line 192) | def reverse(self) -> ArcSegment:
function arc_seg (line 198) | def arc_seg(q: V2, height: float) -> Trail:
function arc_seg_angle (line 202) | def arc_seg_angle(angle: float, dangle: float) -> Trail:
FILE: chalk/shapes/arrowheads.py
function tri (line 15) | def tri() -> Diagram:
function dart (line 29) | def dart(cut: float = 0.2) -> Diagram:
class ArrowHead (line 51) | class ArrowHead(Shape):
method get_bounding_box (line 56) | def get_bounding_box(self) -> BoundingBox:
method accept (line 63) | def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
FILE: chalk/shapes/image.py
function from_pil (line 13) | def from_pil(
class Image (line 30) | class Image(Shape):
method __post_init__ (line 36) | def __post_init__(self) -> None:
method get_bounding_box (line 49) | def get_bounding_box(self) -> BoundingBox:
function image (line 57) | def image(local_path: str, url_path: Optional[str]) -> Diagram:
FILE: chalk/shapes/latex.py
class Latex (line 11) | class Latex(Shape):
method __post_init__ (line 16) | def __post_init__(self) -> None:
method get_bounding_box (line 46) | def get_bounding_box(self) -> BoundingBox:
method accept (line 51) | def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
function latex (line 55) | def latex(t: str) -> Diagram:
FILE: chalk/shapes/path.py
function make_path (line 16) | def make_path(
class Path (line 23) | class Path(Shape, Enveloped, Traceable, Transformable):
method empty (line 30) | def empty() -> Path:
method __add__ (line 33) | def __add__(self, other: Path) -> Path:
method apply_transform (line 36) | def apply_transform(self, t: tx.Affine) -> Path:
method points (line 41) | def points(self) -> Iterable[P2]:
method get_envelope (line 46) | def get_envelope(self) -> Envelope:
method get_trace (line 49) | def get_trace(self) -> Trace:
method accept (line 52) | def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
method from_points (line 57) | def from_points(points: List[P2], closed: bool = False) -> Path:
method from_point (line 67) | def from_point(point: P2) -> Path:
method from_pairs (line 71) | def from_pairs(segs: List[Tuple[P2, P2]], closed: bool = False) -> Path:
method from_list_of_tuples (line 81) | def from_list_of_tuples(
FILE: chalk/shapes/segment.py
class LocatedSegment (line 25) | class LocatedSegment(Traceable, Enveloped, tx.Transformable, TrailLike):
method p (line 30) | def p(self) -> P2:
method from_points (line 34) | def from_points(p: P2, q: P2) -> LocatedSegment:
method apply_transform (line 37) | def apply_transform(self, t: tx.Affine) -> LocatedSegment:
method q (line 43) | def q(self) -> P2:
method get_trace (line 46) | def get_trace(self, t: tx.Affine = Ident) -> Trace:
method get_envelope (line 54) | def get_envelope(self, t: tx.Affine = Ident) -> Envelope:
method length (line 62) | def length(self) -> Any:
method to_ray (line 65) | def to_ray(self) -> "Ray":
method to_trail (line 68) | def to_trail(self) -> Trail:
class Segment (line 75) | class Segment(LocatedSegment, TrailLike):
method p (line 77) | def p(self) -> P2:
method apply_transform (line 80) | def apply_transform(self, t: tx.Affine) -> Segment:
method reverse (line 83) | def reverse(self): # type: ignore
function seg (line 87) | def seg(offset: V2) -> Trail:
function ray_ray_intersection (line 91) | def ray_ray_intersection(
function line_segment (line 119) | def line_segment(ray: Ray, segment: LocatedSegment) -> List[float]:
function ray_circle_intersection (line 150) | def ray_circle_intersection(ray: Ray, circle_radius: float) -> List[float]:
FILE: chalk/shapes/shape.py
class Shape (line 15) | class Shape:
method get_bounding_box (line 18) | def get_bounding_box(self) -> BoundingBox:
method get_envelope (line 21) | def get_envelope(self) -> Envelope:
method get_trace (line 24) | def get_trace(self) -> Trace:
method accept (line 33) | def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
method stroke (line 36) | def stroke(self) -> Diagram:
class Spacer (line 48) | class Spacer(Shape):
method get_bounding_box (line 54) | def get_bounding_box(self) -> BoundingBox:
method accept (line 61) | def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
FILE: chalk/shapes/text.py
class Text (line 11) | class Text(Shape):
method get_bounding_box (line 17) | def get_bounding_box(self) -> BoundingBox:
method accept (line 24) | def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A:
function text (line 28) | def text(t: str, size: Optional[float]) -> Diagram:
FILE: chalk/style.py
class Stylable (line 14) | class Stylable:
method line_width (line 15) | def line_width(self, width: float) -> Self:
method line_width_local (line 20) | def line_width_local(self, width: float) -> Self:
method line_color (line 23) | def line_color(self, color: Color) -> Self:
method fill_color (line 26) | def fill_color(self, color: Color) -> Self:
method fill_opacity (line 29) | def fill_opacity(self, opacity: float) -> Self:
method dashing (line 32) | def dashing(self, dashing_strokes: List[float], offset: float) -> Self:
method apply_style (line 35) | def apply_style(self: Self, style: Style) -> Self:
function m (line 39) | def m(a: Optional[Any], b: Optional[Any]) -> Optional[Any]:
class WidthType (line 43) | class WidthType(Enum):
class Style (line 53) | class Style(Stylable):
method empty (line 64) | def empty(cls) -> Style:
method root (line 68) | def root(cls, output_size: float) -> Style:
method apply_style (line 71) | def apply_style(self, other: Style) -> Style:
method merge (line 74) | def merge(self, other: Style) -> Style:
method render (line 90) | def render(self, ctx: PyCairoContext) -> None:
method to_svg (line 128) | def to_svg(self) -> str:
method to_tikz (line 166) | def to_tikz(self, pylatex: PyLatex) -> Dict[str, str]:
FILE: chalk/subdiagram.py
class Name (line 22) | class Name:
method __init__ (line 25) | def __init__(self, atomic_name: AtomicName):
method __hash__ (line 28) | def __hash__(self) -> int:
method __str__ (line 31) | def __str__(self) -> str:
method __add__ (line 34) | def __add__(self, other: Name) -> Name:
method qualify (line 39) | def qualify(self, name: Name) -> Name:
class Subdiagram (line 44) | class Subdiagram(Monoid):
method get_location (line 49) | def get_location(self) -> P2:
method get_envelope (line 52) | def get_envelope(self) -> Envelope:
method get_trace (line 55) | def get_trace(self) -> Trace:
method boundary_from (line 58) | def boundary_from(self, v: V2) -> P2:
class GetSubdiagram (line 71) | class GetSubdiagram(DiagramVisitor[Maybe[Subdiagram], Affine]):
method __init__ (line 74) | def __init__(self, name: Name, t: Affine = Ident):
method visit_compose (line 77) | def visit_compose(
method visit_apply_transform (line 88) | def visit_apply_transform(
method visit_apply_name (line 95) | def visit_apply_name(
function get_subdiagram (line 106) | def get_subdiagram(self: Diagram, name: Name) -> Optional[Subdiagram]:
function with_names (line 110) | def with_names(
class SubMap (line 132) | class SubMap(Monoid):
method __add__ (line 135) | def __add__(self, other: SubMap) -> SubMap:
method empty (line 143) | def empty(cls) -> SubMap:
class GetSubMap (line 147) | class GetSubMap(DiagramVisitor[SubMap, Affine]):
method visit_apply_transform (line 150) | def visit_apply_transform(
method visit_apply_name (line 157) | def visit_apply_name(
function get_sub_map (line 167) | def get_sub_map(
FILE: chalk/trace.py
class Trace (line 25) | class Trace(Monoid, Transformable):
method __init__ (line 26) | def __init__(self, f: Callable[[P2, V2], List[SignedDistance]]) -> None:
method __call__ (line 29) | def __call__(self, point: P2, direction: V2) -> List[SignedDistance]:
method empty (line 34) | def empty(cls) -> Trace:
method __add__ (line 37) | def __add__(self, other: Trace) -> Trace:
method apply_transform (line 44) | def apply_transform(self, t: Affine) -> Trace:
method trace_v (line 53) | def trace_v(self, p: P2, v: V2) -> Optional[V2]:
method trace_p (line 62) | def trace_p(self, p: P2, v: V2) -> Optional[P2]:
method max_trace_v (line 66) | def max_trace_v(self, p: P2, v: V2) -> Optional[V2]:
method max_trace_p (line 69) | def max_trace_p(self, p: P2, v: V2) -> Optional[P2]:
class GetTrace (line 74) | class GetTrace(DiagramVisitor[Trace, Affine]):
method visit_primitive (line 77) | def visit_primitive(self, diagram: Primitive, t: Affine = Ident) -> Tr...
method visit_apply_transform (line 81) | def visit_apply_transform(
function get_trace (line 87) | def get_trace(self: Diagram, t: Affine = Ident) -> Trace:
FILE: chalk/trail.py
class Located (line 31) | class Located(Enveloped, Traceable, Transformable):
method located_segments (line 35) | def located_segments(self) -> Iterable[Tuple[SegmentLike, P2]]:
method points (line 38) | def points(self) -> Iterable[P2]:
method get_envelope (line 41) | def get_envelope(self) -> Envelope:
method get_trace (line 47) | def get_trace(self) -> Trace:
method stroke (line 53) | def stroke(self) -> Diagram:
method apply_transform (line 56) | def apply_transform(self, t: Affine) -> Located:
method to_path (line 61) | def to_path(self) -> Path:
class Trail (line 68) | class Trail(Monoid, Transformable, TrailLike):
method empty (line 74) | def empty() -> Trail:
method __add__ (line 77) | def __add__(self, other: Trail) -> Trail:
method apply_transform (line 82) | def apply_transform(self, t: Affine) -> Trail:
method to_trail (line 89) | def to_trail(self) -> Trail:
method close (line 92) | def close(self) -> Trail:
method points (line 95) | def points(self) -> Iterable[P2]:
method at (line 103) | def at(self, p: P2) -> Located:
method reverse (line 106) | def reverse(self) -> Trail:
method centered (line 112) | def centered(self) -> Located:
method from_offsets (line 117) | def from_offsets(offsets: List[V2], closed: bool = False) -> Trail:
method hrule (line 121) | def hrule(length: float) -> Trail:
method vrule (line 125) | def vrule(length: float) -> Trail:
method rectangle (line 129) | def rectangle(width: float, height: float) -> Trail:
method rounded_rectangle (line 134) | def rounded_rectangle(width: float, height: float, radius: float) -> T...
method circle (line 146) | def circle(radius: float = 1.0, clockwise: bool = True) -> Trail:
method regular_polygon (line 165) | def regular_polygon(sides: int, side_length: float) -> Trail:
FILE: chalk/transform.py
function from_radians (line 9) | def from_radians(θ: float) -> float:
function to_radians (line 14) | def to_radians(θ: float) -> float:
function remove_translation (line 19) | def remove_translation(aff: Affine) -> Affine:
function remove_linear (line 24) | def remove_linear(aff: Affine) -> Affine:
function transpose_translation (line 29) | def transpose_translation(aff: Affine) -> Affine:
function apply_affine (line 34) | def apply_affine(aff: Affine, x: Any) -> Any:
class Transformable (line 38) | class Transformable:
method apply_transform (line 41) | def apply_transform(self, t: Affine) -> Self: # type: ignore[empty-body]
method __rmul__ (line 44) | def __rmul__(self, t: Affine) -> Self:
method _app (line 47) | def _app(self, t: Affine) -> Self:
method scale (line 50) | def scale(self, α: float) -> Self:
method scale_x (line 53) | def scale_x(self, α: float) -> Self:
method scale_y (line 56) | def scale_y(self, α: float) -> Self:
method rotate (line 59) | def rotate(self, θ: float) -> Self:
method rotate_rad (line 63) | def rotate_rad(self, θ: float) -> Self:
method rotate_by (line 67) | def rotate_by(self, turns: float) -> Self:
method reflect_x (line 72) | def reflect_x(self) -> Self:
method reflect_y (line 75) | def reflect_y(self) -> Self:
method shear_y (line 78) | def shear_y(self, λ: float) -> Self:
method shear_x (line 81) | def shear_x(self, λ: float) -> Self:
method translate (line 84) | def translate(self, dx: float, dy: float) -> Self:
method translate_by (line 87) | def translate_by(self, vector) -> Self: # type: ignore
function apply_p2_affine (line 119) | def apply_p2_affine(aff: Affine, x: Point) -> Point:
function affine (line 124) | def affine(affine: Affine, other: Any) -> Any:
FILE: chalk/types.py
class Enveloped (line 21) | class Enveloped(Protocol):
method get_envelope (line 22) | def get_envelope(self) -> Envelope: ...
class Traceable (line 25) | class Traceable(Protocol):
method get_trace (line 26) | def get_trace(self) -> Trace: ...
class Shape (line 29) | class Shape(Enveloped, Traceable, Protocol):
method accept (line 30) | def accept(self, visitor: ShapeVisitor[A], **kwargs: Any) -> A: ...
class TrailLike (line 33) | class TrailLike(Protocol):
method to_trail (line 34) | def to_trail(self) -> Trail: ...
method to_path (line 36) | def to_path(self, location: P2 = P2(0, 0)) -> Path:
method at (line 39) | def at(self, location: P2) -> Located:
method stroke (line 42) | def stroke(self) -> Diagram:
class Diagram (line 46) | class Diagram(Enveloped, Traceable, Stylable, tx.Transformable, Monoid):
method apply_transform (line 47) | def apply_transform(self, t: tx.Affine) -> Diagram: # type: ignore[em...
method __add__ (line 50) | def __add__(self: Diagram, other: Diagram) -> Diagram: # type: ignore...
method __or__ (line 53) | def __or__(self, d: Diagram) -> Diagram: # type: ignore[empty-body]
method __truediv__ (line 56) | def __truediv__(self, d: Diagram) -> Diagram: # type: ignore[empty-body]
method __floordiv__ (line 59) | def __floordiv__(self, d: Diagram) -> Diagram: # type: ignore[empty-b...
method juxtapose_snug (line 62) | def juxtapose_snug( # type: ignore[empty-body]
method beside_snug (line 66) | def beside_snug( # type: ignore[empty-body]
method juxtapose (line 70) | def juxtapose( # type: ignore[empty-body]
method atop (line 74) | def atop(self: Diagram, other: Diagram) -> Diagram: # type: ignore[em...
method above (line 77) | def above(self: Diagram, other: Diagram) -> Diagram: # type: ignore[e...
method beside (line 80) | def beside( # type: ignore[empty-body]
method frame (line 84) | def frame(self, extra: float) -> Diagram: # type: ignore[empty-body]
method pad (line 87) | def pad(self, extra: float) -> Diagram: # type: ignore[empty-body]
method scale_uniform_to_x (line 90) | def scale_uniform_to_x(self, x: float) -> Diagram: # type: ignore[emp...
method scale_uniform_to_y (line 93) | def scale_uniform_to_y(self, y: float) -> Diagram: # type: ignore[emp...
method align (line 96) | def align(self: Diagram, v: V2) -> Diagram: # type: ignore[empty-body]
method align_t (line 99) | def align_t(self: Diagram) -> Diagram: # type: ignore[empty-body]
method align_b (line 102) | def align_b(self: Diagram) -> Diagram: # type: ignore[empty-body]
method align_l (line 105) | def align_l(self: Diagram) -> Diagram: # type: ignore[empty-body]
method align_r (line 108) | def align_r(self: Diagram) -> Diagram: # type: ignore[empty-body]
method align_tl (line 111) | def align_tl(self: Diagram) -> Diagram: # type: ignore[empty-body]
method align_tr (line 114) | def align_tr(self: Diagram) -> Diagram: # type: ignore[empty-body]
method align_bl (line 117) | def align_bl(self: Diagram) -> Diagram: # type: ignore[empty-body]
method align_br (line 120) | def align_br(self: Diagram) -> Diagram: # type: ignore[empty-body]
method snug (line 123) | def snug(self: Diagram, v: V2) -> Diagram: # type: ignore[empty-body]
method center_xy (line 126) | def center_xy(self: Diagram) -> Diagram: # type: ignore[empty-body]
method get_subdiagram (line 129) | def get_subdiagram(self, name: Name) -> Optional[Subdiagram]: ...
method get_sub_map (line 131) | def get_sub_map( # type: ignore[empty-body]
method with_names (line 135) | def with_names( # type: ignore[empty-body]
method _style (line 141) | def _style(self, style: Style) -> Diagram: # type: ignore[empty-body]
method with_envelope (line 144) | def with_envelope(self, other: Diagram) -> Diagram: # type: ignore[em...
method show_origin (line 147) | def show_origin(self) -> Diagram: # type: ignore[empty-body]
method show_envelope (line 150) | def show_envelope( # type: ignore[empty-body]
method compose (line 154) | def compose( # type: ignore[empty-body]
method to_list (line 158) | def to_list( # type: ignore[empty-body]
method accept (line 162) | def accept( # type: ignore[empty-body]
FILE: chalk/utils.py
function show (line 50) | def show(filepath: str) -> None:
function imgen (line 67) | def imgen(
function create_sample_diagram (line 161) | def create_sample_diagram(
function create_double_diagrams (line 249) | def create_double_diagrams() -> Tuple[Diagram, Diagram]:
function quick_probe (line 260) | def quick_probe(
FILE: chalk/visitor.py
class DiagramVisitor (line 26) | class DiagramVisitor(Generic[A, B]):
method visit_primitive (line 29) | def visit_primitive(self, diagram: Primitive, arg: B) -> A:
method visit_empty (line 33) | def visit_empty(self, diagram: Empty, arg: B) -> A:
method visit_compose (line 37) | def visit_compose(self, diagram: Compose, arg: B) -> A:
method visit_apply_transform (line 43) | def visit_apply_transform(self, diagram: ApplyTransform, arg: B) -> A:
method visit_apply_style (line 47) | def visit_apply_style(self, diagram: ApplyStyle, arg: B) -> A:
method visit_apply_name (line 51) | def visit_apply_name(self, diagram: ApplyName, arg: B) -> A:
class ShapeVisitor (line 59) | class ShapeVisitor(Generic[C]):
method visit_path (line 60) | def visit_path(self, shape: Path) -> C:
method visit_latex (line 63) | def visit_latex(self, shape: Latex) -> C:
method visit_text (line 66) | def visit_text(self, shape: Text) -> C:
method visit_spacer (line 69) | def visit_spacer(self, shape: Spacer) -> C:
method visit_arrowhead (line 72) | def visit_arrowhead(self, shape: ArrowHead) -> C:
method visit_image (line 75) | def visit_image(self, shape: Image) -> C:
FILE: docs/api/alignment.py
function help (line 5) | def help(f):
FILE: docs/api/combinators.py
function help (line 5) | def help(f):
FILE: docs/api/names.py
function help (line 6) | def help(f):
function connect (line 57) | def connect(subs, nodes):
function attach (line 77) | def attach(subs, dia):
function squares (line 84) | def squares():
FILE: docs/api/rendering.py
function help (line 7) | def help(f):
FILE: docs/api/shapes.py
function help (line 6) | def help(f):
FILE: docs/api/style.py
function help (line 5) | def help(f):
FILE: docs/api/trails.py
function help (line 5) | def help(f):
FILE: docs/api/transformations.py
function help (line 5) | def help(f):
FILE: docs/assets/css-js/as-fastapi/custom.js
function getDataBatch (line 5) | async function getDataBatch(page) {
function getData (line 11) | async function getData() {
function setupTermynal (line 25) | function setupTermynal() {
function shuffle (line 133) | function shuffle(array) {
function showRandomAnnouncement (line 145) | async function showRandomAnnouncement(groupId, timeInterval) {
function main (line 162) | async function main() {
FILE: docs/assets/css-js/as-fastapi/termynal.js
class Termynal (line 14) | class Termynal {
method constructor (line 30) | constructor(container = '#termynal', options = {}) {
method loadLines (line 52) | loadLines() {
method init (line 74) | init() {
method start (line 96) | async start() {
method generateRestart (line 129) | generateRestart() {
method generateFinish (line 142) | generateFinish() {
method addRestart (line 157) | addRestart() {
method addFinish (line 162) | addFinish() {
method type (line 171) | async type(line) {
method progress (line 187) | async progress(line) {
method _wait (line 212) | _wait(time) {
method lineDataToElements (line 223) | lineDataToElements(lineData) {
method _attributes (line 238) | _attributes(line) {
FILE: docs/assets/css-js/general/js/material-extra/uml.js
class MermaidDiv (line 21) | class MermaidDiv extends HTMLElement {
method constructor (line 28) | constructor() {
FILE: docs/assets/css-js/pymdownx-extras/js/extra-uml.js
function _typeof (line 1) | function _typeof(e) { return (_typeof = "function" == typeof Symbol && "...
function e (line 1) | function e(t) { return (e = Object.setPrototypeOf ? Object.getPrototypeO...
function t (line 1) | function t(e, n) { return (t = Object.setPrototypeOf || function (e, t) ...
function n (line 1) | function n() { if ("undefined" == typeof Reflect || !Reflect.construct) ...
function o (line 1) | function o(e, r, i) { return (o = n() ? Reflect.construct : function (e,...
function r (line 1) | function r(n) { var i = "function" == typeof Map ? new Map : void 0; ret...
function i (line 1) | function i(e, t) { if (t && ("object" === _typeof(t) || "function" == ty...
function u (line 1) | function u() { var e; !function (e, t) { if (!(e instanceof t)) throw ne...
FILE: docs/assets/css-js/termynal/js/custom.js
function setupTermynal (line 1) | function setupTermynal() {
function main (line 109) | async function main() {
FILE: docs/assets/css-js/termynal/js/termynal.js
class Termynal (line 14) | class Termynal {
method constructor (line 30) | constructor(container = '#termynal', options = {}) {
method loadLines (line 52) | loadLines() {
method init (line 74) | init() {
method start (line 96) | async start() {
method generateRestart (line 129) | generateRestart() {
method generateFinish (line 142) | generateFinish() {
method addRestart (line 157) | addRestart() {
method addFinish (line 162) | addFinish() {
method type (line 171) | async type(line) {
method progress (line 187) | async progress(line) {
method _wait (line 212) | _wait(time) {
method lineDataToElements (line 223) | lineDataToElements(lineData) {
method _attributes (line 238) | _attributes(line) {
FILE: docs/examples/hanoi.py
function draw_disk (line 24) | def draw_disk(n: Disk) -> Diagram:
function draw_stack (line 34) | def draw_stack(s: Stack) -> Diagram:
function draw_hanoi (line 41) | def draw_hanoi(state: Hanoi) -> Diagram:
function solve_hanoi (line 48) | def solve_hanoi(num_disks: int) -> List[Move]:
function do_move (line 62) | def do_move(move: Move, state: Hanoi) -> Hanoi:
function state_sequence (line 79) | def state_sequence(num_disks: int) -> List[Hanoi]:
function draw_state_sequence (line 88) | def draw_state_sequence(seq: List[Hanoi]) -> Diagram:
FILE: docs/examples/koch.py
function koch (line 11) | def koch(n):
FILE: docs/examples/lenet.py
function label (line 16) | def label(te):
function cover (line 20) | def cover(d, a, b):
function tile (line 28) | def tile(d, m, n, name = ""):
function connect_all (line 32) | def connect_all(d, a, b):
function cell (line 42) | def cell():
function matrix (line 45) | def matrix(n, r, c):
function back (line 48) | def back(r, n):
function stack (line 54) | def stack(n, size, l, top, bot):
function network (line 61) | def network(n, size, top, bot):
FILE: docs/examples/squares.py
function make_square (line 12) | def make_square():
function make_group (line 29) | def make_group(num_squares=4):
FILE: docs/examples/tensor.py
function draw_cube (line 11) | def draw_cube():
function draw_tensor (line 21) | def draw_tensor(depth, rows, columns):
function t (line 36) | def t(d, r, c):
function label (line 39) | def label(te, s=1.5):
FILE: examples/bigben.py
function rot_cycle (line 162) | def rot_cycle(d: Diagram, times: int) -> Diagram:
function fit_in (line 268) | def fit_in(b: Diagram, s: Diagram, frame=0.1) -> Diagram:
function thin_line (line 284) | def thin_line(h):
FILE: examples/escher_square_limit.py
function normalize (line 90) | def normalize(coords):
function make_tile (line 97) | def make_tile(name):
function quartet (line 100) | def quartet(tl, tr, bl, br):
function cycle (line 105) | def cycle(diagram):
FILE: examples/hanoi.py
function draw_disk (line 24) | def draw_disk(n: Disk) -> Diagram:
function draw_stack (line 34) | def draw_stack(s: Stack) -> Diagram:
function draw_hanoi (line 41) | def draw_hanoi(state: Hanoi) -> Diagram:
function solve_hanoi (line 48) | def solve_hanoi(num_disks: int) -> List[Move]:
function do_move (line 62) | def do_move(move: Move, state: Hanoi) -> Hanoi:
function state_sequence (line 79) | def state_sequence(num_disks: int) -> List[Hanoi]:
function draw_state_sequence (line 88) | def draw_state_sequence(seq: List[Hanoi]) -> Diagram:
FILE: examples/hex_variation.py
function hexagon_tile (line 15) | def hexagon_tile():
function rotated_hexagon_tile (line 24) | def rotated_hexagon_tile(n):
function center_position (line 27) | def center_position(x, y):
function hex_variation (line 34) | def hex_variation(num_tiles):
FILE: examples/hilbert.py
function hilbert (line 12) | def hilbert(n):
FILE: examples/intro.py
function sierpinski (line 47) | def sierpinski(n: int, size: int) -> Diagram:
FILE: examples/isometric.py
function lookAt (line 14) | def lookAt(eye: ArrayLike, center: ArrayLike, up: ArrayLike):
function scale3 (line 22) | def scale3(x, y, z):
class D3 (line 27) | class D3:
method to_np (line 32) | def to_np(self):
function homogenous (line 39) | def homogenous(trails: List[List[D3]]):
function cube (line 44) | def cube():
function to_trail (line 58) | def to_trail(trail: ArrayLike, locations: ArrayLike):
function project (line 70) | def project(projection, shape3, positions):
FILE: examples/koch.py
function koch (line 11) | def koch(n):
FILE: examples/latex.py
function box (line 9) | def box(t):
function label (line 12) | def label(text):
function arrow (line 15) | def arrow(text, d=True):
FILE: examples/lattice.py
function node (line 13) | def node(i, j):
FILE: examples/lenet.py
function label (line 16) | def label(te):
function cover (line 20) | def cover(d, a, b, n):
function tile (line 28) | def tile(d, m, n, name=""):
function connect_all (line 32) | def connect_all(d, a, b):
function cell (line 41) | def cell():
function matrix (line 44) | def matrix(n, r, c):
function back (line 47) | def back(r, n):
function stack (line 53) | def stack(n, size, l, top, bot):
function network (line 61) | def network(n, size, top, bot):
FILE: examples/logo.py
function coord (line 11) | def coord(m):
function from_polar (line 14) | def from_polar(r, theta):
function mkCoords (line 17) | def mkCoords(n):
function floret (line 20) | def floret(r):
function florets (line 32) | def florets(m):
function sunflower (line 35) | def sunflower(n):
FILE: examples/squares.py
function make_square (line 12) | def make_square():
function make_group (line 29) | def make_group(num_squares=4):
FILE: examples/subdiagrams.py
function example1 (line 6) | def example1():
function example2 (line 17) | def example2():
function example3 (line 49) | def example3():
FILE: examples/tensor.py
function draw_cube (line 11) | def draw_cube():
function draw_tensor (line 21) | def draw_tensor(depth, rows, columns):
function t (line 36) | def t(d, r, c):
function label (line 39) | def label(te, s=1.5):
FILE: examples/tournament-network.py
function make_node (line 13) | def make_node(n):
FILE: examples/tree.py
function flip (line 13) | def flip(p=0.4):
function sample_tree (line 16) | def sample_tree(n=1):
function draw_tree (line 26) | def draw_tree(tree, name="", ysep=5, xsep=1):
FILE: tests/test_envelope.py
function vectors (line 31) | def vectors(draw: DrawFn) -> V2:
function trails (line 41) | def trails(draw: DrawFn) -> Trail:
function paths (line 47) | def paths(draw: DrawFn) -> Diagram:
function circles (line 52) | def circles(draw: DrawFn) -> Diagram:
function rects (line 57) | def rects(draw: DrawFn) -> Diagram:
function shapes (line 62) | def shapes(draw: DrawFn) -> Diagram:
function diagrams (line 67) | def diagrams(draw: DrawFn) -> Diagram:
function transforms (line 76) | def transforms(draw: DrawFn) -> chalk.transform.Affine:
function test_envelope_trail (line 90) | def test_envelope_trail(diagram: Diagram, vec: V2) -> None:
function test_pad (line 101) | def test_pad(diagram: Diagram, vec: V2) -> None:
function test_square (line 112) | def test_square() -> None:
function test_circle (line 123) | def test_circle() -> None:
function test_circle_trace (line 132) | def test_circle_trace() -> None:
function test_path_trace (line 141) | def test_path_trace() -> None:
function test_transform (line 150) | def test_transform() -> None:
FILE: tests/test_reverse_trail.py
function vectors (line 17) | def vectors(draw: DrawFn) -> V2:
function transforms (line 24) | def transforms(draw: DrawFn) -> chalk.transform.Affine:
function segment (line 39) | def segment(draw: DrawFn) -> Trail:
function arc (line 46) | def arc(draw: DrawFn) -> Trail:
function trails (line 56) | def trails(draw: DrawFn) -> Trail:
function test_involution (line 65) | def test_involution(t: Trail) -> None:
function test_order (line 71) | def test_order(t1: Trail, t2: Trail) -> None:
Condensed preview — 117 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (447K chars).
[
{
"path": ".github/workflows/checks.yml",
"chars": 1834,
"preview": "name: checks.yml\n\non: [push, pull_request]\n\njobs:\n build:\n runs-on: ubuntu-latest\n strategy:\n matrix:\n "
},
{
"path": ".github/workflows/mkdocs-publish-ghpages.yml",
"chars": 1839,
"preview": "name: \"MkDocs Publish Docs on GitHub Pages CI\"\non:\n # Manually trigger workflow\n workflow_dispatch:\n inputs:\n "
},
{
"path": ".github/workflows/selfassign.yml",
"chars": 890,
"preview": "# Allow users to automatically tag themselves to issues\n#\n# Usage:\n# - a github user (a member of the repo) needs to co"
},
{
"path": ".gitignore",
"chars": 1859,
"preview": "# User Defined\n.vscode\n\n# Temporary files\n*.swp\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.cla"
},
{
"path": "CHANGELOG.md",
"chars": 563,
"preview": "## `v0.2.2` – 2024-06-14\n\nAdded:\n\n- Isometric example (#130)\n- Associative reduce (#129)\n- Subdiagram (#116)\n- Property-"
},
{
"path": "LICENSE",
"chars": 1086,
"preview": "MIT License\n\nCopyright (c) 2022 Dan Oneață and Alexander Rush\n\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "MANIFEST.in",
"chars": 606,
"preview": "include README.md\ninclude chalk/py.typed # marker file for PEP 561\n\ninclude CHANGELOG.md\ninclude changelog.md\ninclude L"
},
{
"path": "Makefile",
"chars": 9783,
"preview": "\n# maintenance\n.PHONY: isort flake black test type interrogate darglint \\\n\t\tclean cleanall style docs check\n\n# installat"
},
{
"path": "README.md",
"chars": 6743,
"preview": "<p align=\"center\"><img src=\"https://raw.githubusercontent.com/chalk-diagrams/chalk/master/examples/output/logo-sm.png\" w"
},
{
"path": "chalk/__init__.py",
"chars": 1085,
"preview": "import math\nimport sys\nfrom typing import TYPE_CHECKING, Iterable, List, Optional, Tuple, Union\n\nif sys.version_info >= "
},
{
"path": "chalk/align.py",
"chars": 2261,
"preview": "from chalk.transform import V2, Affine, origin, unit_x, unit_y\nfrom chalk.types import Diagram\n\n# Functions mirroring Di"
},
{
"path": "chalk/arrow.py",
"chars": 3920,
"preview": "from dataclasses import dataclass, field\nfrom typing import List, Optional\n\nfrom colour import Color\n\nfrom chalk.shapes "
},
{
"path": "chalk/backend/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "chalk/backend/cairo.py",
"chars": 6467,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, Optional\n\nfrom chalk.monoid import MList\nfrom"
},
{
"path": "chalk/backend/svg.py",
"chars": 7717,
"preview": "from __future__ import annotations\n\nimport xml.etree.ElementTree as ET\nfrom typing import TYPE_CHECKING, Optional\n\nimpor"
},
{
"path": "chalk/backend/tikz.py",
"chars": 7562,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, List\n\nfrom chalk import transform as tx\nfrom "
},
{
"path": "chalk/combinators.py",
"chars": 6777,
"preview": "from typing import Iterable, List, Optional, Tuple\n\nfrom chalk.envelope import Envelope\nfrom chalk.monoid import associa"
},
{
"path": "chalk/core.py",
"chars": 9198,
"preview": "from __future__ import annotations\n\nimport os\nimport tempfile\nfrom dataclasses import dataclass\nfrom typing import Any, "
},
{
"path": "chalk/envelope.py",
"chars": 4319,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Callable, Iterable, Tuple\n\nfrom chalk.monoid impor"
},
{
"path": "chalk/model.py",
"chars": 2894,
"preview": "from colour import Color\n\nfrom chalk.combinators import concat\nfrom chalk.shapes import Path, circle, text\nfrom chalk.sh"
},
{
"path": "chalk/monoid.py",
"chars": 1586,
"preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import (\n Callable,\n Generic,\n "
},
{
"path": "chalk/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "chalk/shapes/__init__.py",
"chars": 4239,
"preview": "from typing import Optional, Tuple, Union\n\nfrom chalk.shapes.arc import ArcSegment, arc_seg, arc_seg_angle # noqa: F401"
},
{
"path": "chalk/shapes/arc.py",
"chars": 5949,
"preview": "\"\"\"\nContains arithmetic for arc calculations.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport math\nfrom dataclasses impo"
},
{
"path": "chalk/shapes/arrowheads.py",
"chars": 1529,
"preview": "from dataclasses import dataclass\nfrom typing import Any\n\nfrom colour import Color\n\nfrom chalk.shapes.path import Path\nf"
},
{
"path": "chalk/shapes/image.py",
"chars": 1560,
"preview": "from dataclasses import dataclass\nfrom io import BytesIO\nfrom typing import Any, Optional\n\nimport PIL\nfrom PIL import Im"
},
{
"path": "chalk/shapes/latex.py",
"chars": 1841,
"preview": "from dataclasses import dataclass\nfrom typing import Any\n\nfrom chalk.shapes.shape import Shape\nfrom chalk.transform impo"
},
{
"path": "chalk/shapes/path.py",
"chars": 2547,
"preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import Any, Iterable, List, Tuple\n\nfro"
},
{
"path": "chalk/shapes/segment.py",
"chars": 4978,
"preview": "from __future__ import annotations\n\nimport math\nfrom dataclasses import dataclass\nfrom typing import TYPE_CHECKING, Any,"
},
{
"path": "chalk/shapes/shape.py",
"chars": 1576,
"preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import Any\n\nfrom chalk.envelope import"
},
{
"path": "chalk/shapes/text.py",
"chars": 1022,
"preview": "from dataclasses import dataclass\nfrom typing import Any, Optional\n\nfrom chalk.shapes.shape import Shape\nfrom chalk.tran"
},
{
"path": "chalk/style.py",
"chars": 5824,
"preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass, fields\nfrom enum import Enum, auto\nfrom typing im"
},
{
"path": "chalk/subdiagram.py",
"chars": 4960,
"preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import TYPE_CHECKING, Any, Callable, D"
},
{
"path": "chalk/trace.py",
"chars": 2383,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Callable, List, Optional\n\nfrom chalk.monoid import"
},
{
"path": "chalk/trail.py",
"chars": 4757,
"preview": "from __future__ import annotations\n\nimport math\nfrom dataclasses import dataclass\nfrom typing import TYPE_CHECKING, Iter"
},
{
"path": "chalk/transform.py",
"chars": 4911,
"preview": "import math\nfrom typing import Any\n\nfrom planar import Affine as Affine\nfrom planar import BoundingBox, Point, Polygon, "
},
{
"path": "chalk/types.py",
"chars": 4800,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Protocol\n\nimp"
},
{
"path": "chalk/utils.py",
"chars": 9532,
"preview": "\"\"\"\nThe ``chalk.utils`` module is meant to provide various\nutility-oriented functionalities.\n\nImporting:\n\n ```python\n"
},
{
"path": "chalk/visitor.py",
"chars": 2039,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Generic, TypeVar\n\nif TYPE_CHECKING:\n from chalk"
},
{
"path": "doc/crop-images.sh",
"chars": 353,
"preview": "for img in squares hanoi escher-square-limit lenet logo hilbert koch tensor hex-variation tree lattice; do\n # convert"
},
{
"path": "docs/about/index.md",
"chars": 96,
"preview": "# About\n\n{{ config.site_description }}\n\n<sup>Copyright (c) 2022 {{ config.site_author }}.</sup>\n"
},
{
"path": "docs/about/license.md",
"chars": 28,
"preview": "# License\n\n--8<-- \"LICENSE\"\n"
},
{
"path": "docs/api/alignment.py",
"chars": 1715,
"preview": "# + tags=[\"hide_inp\"]\nfrom chalk.core import BaseDiagram\nfrom chalk import *\n\ndef help(f):\n import pydoc\n from IPy"
},
{
"path": "docs/api/combinators.py",
"chars": 1686,
"preview": "# + tags=[\"hide_inp\"]\nimport math\nfrom planar import Vec2\nfrom chalk import *\ndef help(f):\n import pydoc\n from IPy"
},
{
"path": "docs/api/internals.md",
"chars": 7978,
"preview": "---\ntitle: Chalk internals\nsummary: A look inside Chalk\ndate: 2022-08-21\n---\n\nThis document presents information on the "
},
{
"path": "docs/api/names.py",
"chars": 3246,
"preview": "# + tags=[\"hide_inp\"]\nfrom colour import Color\nfrom chalk.core import BaseDiagram\nfrom chalk import *\n\ndef help(f):\n "
},
{
"path": "docs/api/rendering.py",
"chars": 1193,
"preview": "# + tags=[\"hide\"]\nimport math\n\nfrom chalk.core import BaseDiagram\nfrom chalk import *\n\ndef help(f):\n import pydoc\n "
},
{
"path": "docs/api/shapes.py",
"chars": 1459,
"preview": "# + tags=[\"hide\"]\nfrom chalk.core import BaseDiagram\nfrom chalk import *\n\n\ndef help(f):\n import pydoc\n from IPytho"
},
{
"path": "docs/api/style.py",
"chars": 1153,
"preview": "# + tags=[\"hide_inp\"]\nfrom chalk.core import BaseDiagram\nfrom chalk import *\n\ndef help(f):\n import pydoc\n from IPy"
},
{
"path": "docs/api/trails.py",
"chars": 1543,
"preview": "# + tags=[\"hide_inp\"]\nimport math\nfrom chalk import *\nfrom planar import Vec2Array\ndef help(f):\n import pydoc\n fro"
},
{
"path": "docs/api/transformations.py",
"chars": 1083,
"preview": "# + tags=[\"hide_inp\"]\nfrom chalk.core import BaseDiagram\nfrom chalk import *\n\ndef help(f):\n import pydoc\n from IPy"
},
{
"path": "docs/api/utils.md",
"chars": 67,
"preview": "---\ntitle: chalk.utils\n---\n# **`{{ title }}`**\n\n👍\n\n::: {{ title }}\n"
},
{
"path": "docs/assets/css-js/as-fastapi/chat.js",
"chars": 77,
"preview": "((window.gitter = {}).chat = {}).options = {\n room: 'tiangolo/fastapi'\n};\n"
},
{
"path": "docs/assets/css-js/as-fastapi/custom.css",
"chars": 2069,
"preview": "/* source: https: //github.com/tiangolo/fastapi/blob/1876ebc77949a9a254909ec61ea0c09365169ec2/docs/en/docs/css/custom.cs"
},
{
"path": "docs/assets/css-js/as-fastapi/custom.js",
"chars": 6851,
"preview": "// source: https://github.com/tiangolo/fastapi/blob/1876ebc77949a9a254909ec61ea0c09365169ec2/docs/en/docs/js/custom.js\n\n"
},
{
"path": "docs/assets/css-js/as-fastapi/termynal.css",
"chars": 2168,
"preview": "/**\n * termynal.js\n *\n * @author Ines Montani <ines@ines.io>\n * @version 0.0.1\n * @license MIT\n */\n\n:root {\n --color-"
},
{
"path": "docs/assets/css-js/as-fastapi/termynal.js",
"chars": 9580,
"preview": "/**\n * termynal.js\n * A lightweight, modern and extensible animated terminal window, using\n * async/await.\n *\n * @author"
},
{
"path": "docs/assets/css-js/general/css/progressbar.css",
"chars": 1503,
"preview": "/*\n * Source: https://facelessuser.github.io/pymdown-extensions/extensions/progressbar/\n *\n * Note: Look under \"CSS Setu"
},
{
"path": "docs/assets/css-js/general/css/social-color-stryle.css",
"chars": 1241,
"preview": "/* # cspell: disable */\n\n/* Icon Fill Color Styles\n *\n * Source: https://squidfunk.github.io/mkdocs-material/reference/i"
},
{
"path": "docs/assets/css-js/general/js/highlight-config.js",
"chars": 143,
"preview": "// source: https://squidfunk.github.io/mkdocs-material/reference/code-blocks/#highlight\ndocument$.subscribe(() => {\n "
},
{
"path": "docs/assets/css-js/general/js/material-extra/extra-uml.js",
"chars": 1044,
"preview": "/*\n * Source: https://github.com/facelessuser/pymdown-extensions/blob/main/docs/src/js/extra-uml.js\n */\n\nimport uml from"
},
{
"path": "docs/assets/css-js/general/js/material-extra/material-extra-theme.js",
"chars": 2939,
"preview": "/*\n * Source: https://github.com/facelessuser/pymdown-extensions/blob/main/docs/src/js/material-extra-theme.js\n */\n\n(() "
},
{
"path": "docs/assets/css-js/general/js/material-extra/uml.js",
"chars": 5390,
"preview": "/* Notes (as of Mermaid 8.7.0):\n * - Gantt: width is always relative to the parent, if you have a small parent, the char"
},
{
"path": "docs/assets/css-js/general/js/tables.js",
"chars": 171,
"preview": "document$.subscribe(function () {\n var tables = document.querySelectorAll(\"article table\")\n tables.forEach(functio"
},
{
"path": "docs/assets/css-js/mkdocs-tooltips/css/custom.css",
"chars": 648,
"preview": ".tooltip {\n cursor:default;\n}\n\n.icons {\n margin: 0 auto !important;\n}\n\n.icons.position {\n width: 240px; /* 3 * (10 + "
},
{
"path": "docs/assets/css-js/pymdownx-extras/css/extra.css",
"chars": 74002,
"preview": "@charset \"UTF-8\";\n\n:root>* {\n\t--md-code-link-bg-color: hsla(0, 0%, 96%, 1);\n\t--md-code-link-accent-bg-color: var(--md-co"
},
{
"path": "docs/assets/css-js/pymdownx-extras/js/extra-uml.js",
"chars": 5512,
"preview": "function _typeof(e) { return (_typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (e)"
},
{
"path": "docs/assets/css-js/termynal/css/custom.css",
"chars": 495,
"preview": ".termynal-comment {\n color: #4a968f;\n font-style: italic;\n display: block;\n}\n\n.termy [data-termynal] {\n whit"
},
{
"path": "docs/assets/css-js/termynal/css/termynal.css",
"chars": 2192,
"preview": "/**\n * termynal.js\n *\n * @author Ines Montani <ines@ines.io>\n * @version 0.0.1\n * @license MIT\n */\n\n:root {\n --color-"
},
{
"path": "docs/assets/css-js/termynal/js/custom.js",
"chars": 4390,
"preview": "function setupTermynal() {\n document.querySelectorAll(\".use-termynal\").forEach(node => {\n node.style.display ="
},
{
"path": "docs/assets/css-js/termynal/js/termynal.js",
"chars": 9579,
"preview": "/**\n * termynal.js\n * A lightweight, modern and extensible animated terminal window, using\n * async/await.\n *\n * @author"
},
{
"path": "docs/assets/css-js/termynal/readme.md",
"chars": 369,
"preview": "# **Termynal**: Terminal Animation\n\nJust copy and paste the `terminal` folder in a `docs/assests`\nfolder and then add th"
},
{
"path": "docs/assets/snippets/macros/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "docs/assets/snippets/notifications/videos/disclaimer.md",
"chars": 129,
"preview": "!!! warning \"Disclaimer\"\n\n This library is currently under rapid initial development. Breaking changes may happen fre"
},
{
"path": "docs/examples/hanoi.py",
"chars": 2704,
"preview": "# Based on the following example from Diagrams\n# https://archives.haskell.org/projects.haskell.org/diagrams/gallery/Hano"
},
{
"path": "docs/examples/koch.py",
"chars": 764,
"preview": "# Based on the following example from Diagrams\n# https://archives.haskell.org/projects.haskell.org/diagrams/gallery/Koch"
},
{
"path": "docs/examples/lenet.py",
"chars": 3055,
"preview": "from PIL import Image as PILImage\nfrom chalk import *\nfrom colour import Color\nfrom chalk import BoundingBox\n\n# Colors\np"
},
{
"path": "docs/examples/squares.py",
"chars": 829,
"preview": "from PIL import Image as PILImage\nimport math\nimport random\n\nfrom itertools import product\n\nfrom colour import Color\nfro"
},
{
"path": "docs/examples/tensor.py",
"chars": 1325,
"preview": "from PIL import Image as PILImage\nfrom chalk import *\nfrom colour import Color\n\nh = hstrut(2.5)\npapaya = Color(\"#ff9700\""
},
{
"path": "docs/index.md",
"chars": 56,
"preview": "---\nhide:\n - navigation\n - toc\n---\n--8<-- \"README.md\"\n"
},
{
"path": "docs/overrides/main.html",
"chars": 755,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n{{ super() }}\n\n<style>\n// Do whatever changes you need here\n\n.jp-Rendered"
},
{
"path": "docs/quickstart/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "examples/arrows.py",
"chars": 1339,
"preview": "from chalk import *\nfrom chalk.trail import Trail\nfrom colour import Color\nfrom PIL import Image as PILImage\n\ngrey = Col"
},
{
"path": "examples/bigben.py",
"chars": 12205,
"preview": "# # Big Ben\n\n# *A literate notebook by [Sasha Rush](http://www.rush-nlp.com)*\n\n# [Big Ben](https://en.wikipedia.org/wiki"
},
{
"path": "examples/comparison.tex",
"chars": 1885,
"preview": "\\documentclass{article}%\n\\usepackage[T1]{fontenc}%\n\\usepackage[utf8]{inputenc}%\n\\usepackage{lmodern}%\n\\usepackage{fullpa"
},
{
"path": "examples/escher_square_limit.py",
"chars": 4046,
"preview": "# This example is based on a corresponding example from Lisp by Frank Buss:\n# https://frank-buss.de/lisp/functional.html"
},
{
"path": "examples/hanoi.py",
"chars": 2868,
"preview": "# Based on the following example from Diagrams\n# https://archives.haskell.org/projects.haskell.org/diagrams/gallery/Hano"
},
{
"path": "examples/hex_variation.py",
"chars": 1285,
"preview": "import math\nimport random\n\nfrom itertools import product\n\nfrom chalk import *\n\nrandom.seed(1337)\n\n\nh = math.sqrt(3) / 2\n"
},
{
"path": "examples/hilbert.py",
"chars": 901,
"preview": "# Based on the following example from Diagrams\n# https://archives.haskell.org/projects.haskell.org/diagrams/gallery/Hilb"
},
{
"path": "examples/intro.py",
"chars": 1443,
"preview": "from colour import Color\nfrom chalk import *\n\n# define some colors\npapaya = Color(\"#ff9700\")\nblue = Color(\"#005FDB\")\n\n\np"
},
{
"path": "examples/isometric.py",
"chars": 2767,
"preview": "from dataclasses import dataclass\nfrom PIL import Image as PILImage\nfrom chalk import *\nfrom colour import Color\nimport "
},
{
"path": "examples/koch.py",
"chars": 893,
"preview": "# Based on the following example from Diagrams\n# https://archives.haskell.org/projects.haskell.org/diagrams/gallery/Koch"
},
{
"path": "examples/latex.py",
"chars": 569,
"preview": "from chalk import *\nfrom colour import Color\n\n\ngrey = Color(\"#bbbbbb\")\npapaya = Color(\"#ff9700\")\n\nleft_arrow = make_path"
},
{
"path": "examples/lattice.py",
"chars": 727,
"preview": "import sys\n\nfrom chalk import *\nfrom colour import Color\n\nsys.setrecursionlimit(100_000)\n\nBLACK = Color(\"black\")\nSTEPS ="
},
{
"path": "examples/lenet.py",
"chars": 3318,
"preview": "from PIL import Image as PILImage\nfrom chalk import *\nfrom colour import Color\nfrom chalk import BoundingBox\n\n# Colors\np"
},
{
"path": "examples/logo.py",
"chars": 1605,
"preview": "from colour import Color\nfrom chalk import *\n\n# This code is for a Vogel subflower, ported from:\n# https://diagrams.gith"
},
{
"path": "examples/normalized.py",
"chars": 340,
"preview": "from chalk import *\n\n\nd = triangle(1).line_width(0.1) / triangle(0.5).line_width(0.1).scale(2) / triangle(0.5).line_widt"
},
{
"path": "examples/output/index.html",
"chars": 6908,
"preview": "<html>\n \n <div>\n <h2> intro-01 </h2>\n <div> <h3>SVG</h3>\n <object data=\"intro-01.svg\" type=\"image/svg+xml\">"
},
{
"path": "examples/output/path.tex",
"chars": 20178,
"preview": "\\documentclass{standalone}%\n\\usepackage[T1]{fontenc}%\n\\usepackage[utf8]{inputenc}%\n\\usepackage{lmodern}%\n\\usepackage{tex"
},
{
"path": "examples/output/path.tex.tex",
"chars": 20178,
"preview": "\\documentclass{standalone}%\n\\usepackage[T1]{fontenc}%\n\\usepackage[utf8]{inputenc}%\n\\usepackage{lmodern}%\n\\usepackage{tex"
},
{
"path": "examples/path.py",
"chars": 1918,
"preview": "from chalk import *\nfrom chalk.shapes.segment import Segment\nfrom chalk.shapes.arc import ArcSegment\nfrom colour import "
},
{
"path": "examples/rectangle_parade.py",
"chars": 313,
"preview": "import random\nfrom colour import Color\nfrom chalk import *\nblue = Color(\"#005FDB\")\n\npath = \"examples/output/rectangle-pa"
},
{
"path": "examples/squares.py",
"chars": 1153,
"preview": "from PIL import Image as PILImage\nimport math\nimport random\n\nfrom itertools import product\n\nfrom colour import Color\nfro"
},
{
"path": "examples/subdiagrams.py",
"chars": 1882,
"preview": "import pdb\nfrom colour import Color\nfrom chalk import *\n\n\ndef example1():\n dia1 = square(1).translate(0, -4).named(Na"
},
{
"path": "examples/tensor.py",
"chars": 1515,
"preview": "from PIL import Image as PILImage\nfrom chalk import *\nfrom colour import Color\n\nh = hstrut(2.5)\npapaya = Color(\"#ff9700\""
},
{
"path": "examples/tests/index.tpl.html",
"chars": 777,
"preview": "<html>\n {% for x, h in [(\"intro-01\", 64), (\"intro-02\", 64), (\"intro-03\", 64), (\"intro-04\", 256), (\"normalized\", 100),"
},
{
"path": "examples/tournament-network.py",
"chars": 784,
"preview": "# Example taken from\n# https://diagrams.github.io/doc/quickstart.html\n\nfrom colour import Color\nfrom chalk import *\n\n\nwh"
},
{
"path": "examples/tree.py",
"chars": 1534,
"preview": "from PIL import Image as PILImage\nfrom chalk import *\nfrom chalk.transform import *\nimport random\n\nfrom chalk import tra"
},
{
"path": "mkdocs.yml",
"chars": 11068,
"preview": "site_name: chalk-diagrams\nsite_url: https://chalk-diagrams.github.io\nsite_description: Chalk Diagrams - A declarative dr"
},
{
"path": "pyproject.toml",
"chars": 101,
"preview": "[build-system]\nrequires = [\n \"setuptools\",\n \"wheel\"\n]\n\nbuild-backend = \"setuptools.build_meta\"\n"
},
{
"path": "requirements/dev.txt",
"chars": 153,
"preview": "flake8>=3.6.0\nblack>=19.3b0\nmypy>=0.95\ninterrogate>=1.5.0\nisort>=5.10.0\ndarglint>=1.8.0\npre-commit>=2.2.0\n# flake8-print"
},
{
"path": "requirements/docs.txt",
"chars": 2183,
"preview": "mkdocs\nmkdocs-material==8.1.3\nmkdocs-material-extensions>=1.0.3\npymdown-extensions>=9.0\nmdx-include>=1.4.1\nmarkdown-incl"
},
{
"path": "requirements.txt",
"chars": 138,
"preview": "toolz\ncolour\nsvgwrite\ncairosvg\nPillow\ngit+https://github.com/chalk-diagrams/planar\nlatextools\nloguru\ntyping-extensions\ni"
},
{
"path": "setup.cfg",
"chars": 3154,
"preview": "[bdist_wheel]\n# what is this for?\n# - https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptoo"
},
{
"path": "setup.py",
"chars": 2234,
"preview": "import pathlib\n\nfrom setuptools import find_packages, setup\n\nLICENSE: str = \"MIT\"\nREADME: str = pathlib.Path(\"README.md\""
},
{
"path": "tests/test_envelope.py",
"chars": 3907,
"preview": "import math\n\nimport pytest\nfrom hypothesis import given\nfrom hypothesis.strategies import (\n DrawFn,\n composite,\n "
},
{
"path": "tests/test_reverse_trail.py",
"chars": 1789,
"preview": "from hypothesis import given\nfrom hypothesis.strategies import (\n DrawFn,\n composite,\n integers,\n one_of,\n "
}
]
About this extraction
This page contains the full source code of the chalk-diagrams/chalk GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 117 files (413.2 KB), approximately 127.6k tokens, and a symbol index with 595 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.