This release is a pre-release and may not be stable for production use.
A Python toolkit for producing publication-quality microeconomics diagrams. Define utility functions declaratively, solve for consumer equilibria, and export figures as PNG, PDF, SVG, or pure TikZ — all in a few lines of code.
Installation
pip install utility-viz
Requires Python 3.10 or later.
Quick Start
from utility_viz import Canvas, levels, solve
from utility_viz.models import CobbDouglas
model = CobbDouglas(alpha=0.5, beta=0.5)
eq = solve(model, px=2.0, py=3.0, income=30.0)
lvls = levels.around(eq.utility, n=5)
cvs = Canvas(x_max=20, y_max=15, x_label="x", y_label="y", title="Cobb-Douglas $x^{0.5} y^{0.5}$")
cvs.add_utility(model, levels=lvls)
cvs.add_budget(2.0, 3.0, 30.0, fill=True)
cvs.add_equilibrium(eq, show_ray=True)
cvs.save("cobb_douglas.png")
TikZ export writes a standalone LaTeX document with only TikZ drawing commands:
cvs.save("cobb_douglas.tex", tikz_scale=0.0125)
The default TikZ scale maps a 6 inch wide Matplotlib figure to about 7.5 cm.
Notebook
The project ships with an interactive playground notebook:
notebook/econ-viz Playground.ipynb
Download it and open it in Jupyter, VS Code, or Colab. The first code cell upgrades utility-viz from PyPI for fresh runtimes.
Highlights
- Built-in models: Cobb-Douglas, Leontief, Perfect Substitutes, CES, Satiation, Quasi-Linear, Stone-Geary, and Translog
- Solver support for interior, kink, boundary, and corner solutions
- Closed-form demand helpers with
solution_tex(...) - Comparative tools including
comparative_statics(...)andslutsky_matrix(...) - Multi-panel
Figurelayouts,PricePath/IncomePath, and linkedDemandDiagram - CLI support for plotting and closed-form demand output
- Color-blind-friendly default palette (
themes.COLORBLIND_CYCLE_RGB) sourced from thriveth/8560036, with related citation at DOI:10.1080/00220485.1996.10844911
Additional Tools
Axis labels can be placed around their arrowheads, and each axis can use its own arrowhead style and line style (solid, dashed, dotted, or dashdot):
from utility_viz import ArrowStyle, Canvas, LabelPosition, LineStyle
canvas = Canvas(
x_label_pos=LabelPosition.TOP,
y_label_pos=LabelPosition.RIGHT,
x_arrow_style=ArrowStyle.SIMPLE,
y_arrow_style=ArrowStyle.WEDGE,
x_line_style=LineStyle.DASHED,
)
Every line can be restyled with a Stroke: width, line style, colour, and an
arrowhead at its end. Fields you leave out keep the theme default (see the
*_stroke defaults on Theme, such as theme.budget_stroke):
from utility_viz import ArrowStyle, Stroke
canvas = Canvas(axis_stroke=Stroke(width=1.4, arrow=ArrowStyle.SIMPLE))
canvas.add_budget(2, 3, 30, stroke=Stroke(width=3, style="dashed"))
canvas.add_equilibrium(eq, drop_stroke=Stroke(style="dashdot"))
canvas.add_ray(0.5, stroke=Stroke(arrow=ArrowStyle.TRIANGLE))
add_utility, add_path, add_decomposition, DemandDiagram, and
EdgeworthBox take one *_stroke argument per kind of line they draw.
Stroke is the preferred way to style lines; the separate color,
linewidth, and linestyle arguments still work as shorthand and draw the
same thing.
Point markers work the same way with Marker (colour, size, and shape);
fields you leave out keep the theme default, such as theme.eq_marker:
from utility_viz import Marker
canvas.add_equilibrium(eq, marker=Marker(shape="s", size=8))
canvas.add_point(12, 2, label="A", marker=Marker(color="black", shape="D"))
canvas.add_decomposition(dec, point_marker=Marker(shape="^"))
Point labels take a Label (text, position, offset, colour, size, and
visibility) wherever a plain string worked. A label follows its point's
Marker colour unless it sets its own:
from utility_viz import Label
canvas.add_equilibrium(eq, label=Label(position="bottom-left", offset=8))
canvas.add_point(12, 2, label=Label(text="A", position="left", fontsize=14))
canvas.add_utility(u, levels=3, ic_label=Label(text="U={:.1f}", position="top"))
canvas.add_decomposition(dec, point_label=Label(visible=False)) # hide A, B, C
The same Label styles every other piece of text: axis labels, the origin
0, titles, effect labels, and the Edgeworth box's good names and origins:
canvas = Canvas(
title=Label(text="Hicks decomposition", fontsize=13),
x_axis=Axis(label=Label(text="x_1", fontsize=16)),
origin_label=Label(visible=False),
)
canvas.add_decomposition(dec, substitution=Effect(label=Label(text="SE", fontsize=12)))
Shade the budget set with fill=True, or pass a Fill for a colour and
opacity of its own (default theme.budget_fill, coloured like the line):
from utility_viz import Fill
canvas.add_budget(2, 3, 30, color="black", fill=Fill(color="lightgrey", opacity=0.4))
Every style object takes an opacity from 0 to 1, for example to show the
original budget line faintly:
canvas.add_budget(2, 3, 30, stroke=Stroke(opacity=0.35))
canvas.add_decomposition(dec, income=Effect(opacity=0.5), legend=Legend(opacity=0.8))
Each axis's label, label position, and stroke fit in one Axis, accepted by
Canvas, Figure, DemandDiagram, and EdgeworthBox. x_label,
x_label_pos, and x_axis_stroke stay as shorthand; an Axis field wins
when both are set:
from utility_viz import Axis, Stroke
canvas = Canvas(
x_axis=Axis(label="x_1", label_position="bottom", stroke=Stroke(width=1.2)),
y_axis=Axis(label="x_2"),
)
Legends go where they cover the least of the diagram by default, moving
outside the plot area when every corner is taken. Pass a Legend to choose
an inside corner ("upper left", …) or a side outside ("top", "bottom",
"left", "right"), or to change its font size, frame, and columns:
from utility_viz import Legend
canvas.add_decomposition(dec, legend=Legend(position="bottom"))
canvas.show_legend(legend=Legend(position="upper left", fontsize=10))
Set a font for one canvas or a whole multi-panel figure without touching
Matplotlib's global settings. Pass a family name, a generic family such as
"serif", or a fallback list:
from utility_viz import Figure, Layout
canvas = Canvas(font=["Times New Roman", "serif"], math_font="stix")
figure = Figure(Layout.SIDE_BY_SIDE, font="serif", math_font="stix")
font applies to titles, axis labels, annotations, curve labels, and legends.
Math text, including the default axis labels, uses math_font: "stix"
(Times-like), "cm" (Computer Modern), "dejavuserif", "dejavusans", or
"stixsans". An unavailable font raises
InvalidParameterError. TikZ output uses the LaTeX document's fonts, so only
generic families are mapped (serif → \rmfamily, monospace → \ttfamily).
Closed-form Marshallian demand in TeX:
from utility_viz import solution_tex
from utility_viz.models import CobbDouglas
tex = solution_tex(CobbDouglas(alpha=0.4, beta=0.6))
Slutsky matrix:
from utility_viz import slutsky_matrix
from utility_viz.models import CobbDouglas
S = slutsky_matrix(CobbDouglas(alpha=0.4, beta=0.6), px=2.0, py=3.0, income=60.0)
# S.s_xx, S.s_xy, S.s_yx, S.s_yy
Settings file
Keep your style in an utility-viz.toml and load it once. Section names match
Theme properties ([stroke.budget] is theme.budget_stroke), fields match the
style objects, and anything left out keeps the default:
[color]
ic = "#2E86AB"
[stroke.budget]
width = 1.5
[label.point]
fontsize = 12
[legend]
position = "bottom"
from utility_viz import Config
Config.load("utility-viz.toml").use() # diagrams created from now on use it
utility-viz init writes a commented template, and utility-viz plot --config utility-viz.toml ... uses the same file. Arguments passed to a method still win
over the file.
CLI
utility-viz --version
utility-viz help
utility-viz models
utility-viz solve-tex --model cobb-douglas --symbolic-params
Plotting example:
utility-viz plot --model cobb-douglas --alpha 0.5 --beta 0.5 \
--px 2 --py 3 --income 30 \
--fill --show-ray \
--output cobb_douglas.png
Migrating from econ-viz
econ-viz was renamed to utility-viz in 2.0.0.
| 1.x | 2.x | |
|---|---|---|
| Distribution | pip install econ-viz |
pip install utility-viz |
| Import | import econ_viz |
import utility_viz |
| CLI | econ-viz |
utility-viz |
| Config file | econ-viz.toml |
utility-viz.toml (section names unchanged) |
Which package to install. utility-viz ships only utility_viz and the utility-viz command: it has no
econ_viz package and no econ-viz command. econ-viz 2.x (same version number) is a thin compatibility
distribution: pip install econ-viz installs utility-viz of the same version plus the econ_viz package and
the econ-viz command, which warn that they are deprecated. Upgrading an existing 1.x installation with
pip install --upgrade econ-viz therefore keeps working and moves you onto 2.x. Pre-releases need --pre
(for example pip install --pre --upgrade econ-viz). Switch to pip install utility-viz when you are ready
to drop the compatibility layer.
Compatibility layer. Throughout 2.x the econ-viz distribution provides an
econ_viz package and an econ-viz command so documented 1.x code keeps working:
import econ_vizemits one deprecation warning per process.from econ_viz import ...and the documented sub-modules (econ_viz.models,econ_viz.optimizer,econ_viz.themes, ...) resolve to theirutility_vizequivalents. Names that did not change are the very same objects.- Constructing
econ_viz.Canvas,econ_viz.Figureorecon_viz.animation.Animator, or accessingecon_viz.Layout, emits autility_viz.UtilityVizDeprecationWarning(aFutureWarning) stating "deprecated since 2.0.0, removed in 3.0.0" and the replacement.Figure,LayoutandAnimatormap to their current 2.x equivalents; their declarative replacements (CanvasGrid,Animation) are planned and named in the message as such. - Config lookup: explicit path, then
utility-viz.toml, then legacyecon-viz.toml(with a warning), then defaults. If both files exist the new one wins and the legacy one is ignored with a warning.Config.load()with no argument follows the file order and raises if neither file exists (as in 1.x);Config.discover()falls back to defaults;Config.load("file.toml")reads exactly that file.utility-viz plotapplies the same lookup in the current directory when--configis not given. - The
econ-vizcommand prints a deprecation warning and forwards toutility-viz. utility-viz init --migratewritesutility-viz.tomlfromecon-viz.tomland keeps the old file.
Removal boundary (3.0.0). The econ_viz package, the econ-viz command and econ-viz.toml lookup are
removed in 3.0.0, not 2.0.0. Only the documented 1.x public API is covered; undocumented deep module paths
(for example econ_viz.canvas.renderers.*) resolve on a best-effort basis and may disappear at any time.
Internal utility_viz.core.* modules are advanced APIs and not part of the compatibility contract.
Documentation
Full documentation lives at econ-viz.org.
License
MIT © Pin Yue Sung
Metadata
Release files for utility-viz 2.0.0b1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| utility_viz-2.0.0b1.tar.gz | 126.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| utility_viz-2.0.0b1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 314.8 kB
Release files / utility_viz-2.0.0b1.tar.gz
| Download URL | utility_viz-2.0.0b1.tar.gz |
|---|---|
| Size | 126.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9bfca43fb4fc68d0bca6fdd3575e8998213b605a5dff3c11b0ac753dee84139e
|
|
BLAKE2b-256 checksum How to use checksums |
5125f017ce18c3a35dca0bcd0948ad40cb17972627b6edb682ec4eedac869055
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / utility_viz-2.0.0b1-py3-none-any.whl
| Download URL | utility_viz-2.0.0b1-py3-none-any.whl |
|---|---|
| Size | 188.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6c3eb1a369fd331f6ea03e53e88e04307b410a0a27482a810232023486ee80dd
|
|
BLAKE2b-256 checksum How to use checksums |
f7fd835a308af073783d728cd1d15a4f25e46b0a9cb6217494cf32fb9f43cfaf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency log