Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Enable Documentation
kiva/quickref.rst
kiva/fonts.rst
kiva/compiled_path.rst
kiva/state.rst

credits.rst

Expand Down
4 changes: 2 additions & 2 deletions docs/source/kiva/compiled_path_ex.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from math import pi
from kiva.agg import GraphicsContextArray as GraphicsContext
from kiva.image import GraphicsContext

gc = GraphicsContext((600, 600))

Expand All @@ -17,4 +17,4 @@
gc.add_path(path)
gc.fill_path()

gc.save("example.png")
gc.save("compiled_path_ex.png")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the changes to this file seem unrelated to the main purpose of this PR, but even so the changes make sense as something we want to do

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it's not related. I had made a similar change in the new state example (which was copied from the compiled path example)

Binary file added docs/source/kiva/images/state_ex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 95 additions & 0 deletions docs/source/kiva/state.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
Kiva State
==========
Kiva is a "stateful" drawing API. What this means is that the graphics context
has a collection of state which affects the results of its drawing actions.
Furthermore, Kiva enables this state to be managed with a stack such that state
can be "pushed" onto the stack before making some temporary changes and then
"popped" off the stack to restore the state to a version which no longer
includes those changes.

State Components
----------------
Here is a list of all the pieces of state tracked by a Kiva graphics context,
along with the methods which operate on them:

* Affine transformation (:py:meth:`translate_ctm`, :py:meth:`rotate_ctm`,
:py:meth:`scale_ctm`, :py:meth:`concat_ctm`, :py:meth:`set_ctm`,
:py:meth:`get_ctm`)
* Clipping (:py:meth:`clip_to_rect`, :py:meth:`clip_to_rects`, :py:meth:`clip`,
:py:meth:`even_odd_clip`)
* Fill color (:py:meth:`set_fill_color`, :py:meth:`get_fill_color`,
:py:meth:`linear_gradient`, :py:meth:`radial_gradient`)
* Stroke color (:py:meth:`set_stroke_color`, :py:meth:`get_stroke_color`)
* Line width (:py:meth:`set_line_width`)
* Line join style (:py:meth:`set_line_join`)
* Line cap style (:py:meth:`set_line_cap`)
* Line dashing (:py:meth:`set_line_dash`)
* Global transparency (:py:meth:`set_alpha`, :py:meth:`get_alpha`)
* Anti-aliasing (:py:meth:`set_antialias`, :py:meth:`get_antialias`)
* Miter limit (:py:meth:`set_miter_limit`)
* Flatness (:py:meth:`set_flatness`)
* Image interpolation (:py:meth:`set_image_interpolation`, :py:meth:`get_image_interpolation`)
Comment thread
jwiggins marked this conversation as resolved.

State Stack Management
----------------------
Graphics context instances have two methods for saving and restoring the state,
:py:meth:`save_state` ("push") and :py:meth:`restore_state` ("pop"). That said,
it isn't recommended practice to call the methods directly. Instead, you can
treat the graphics context object as a
`context manager <https://docs.python.org/3/library/stdtypes.html#typecontextmanager>`_
and use the ``with`` keyword to create a block of code where the graphics state
is temporarily modified. Using the context manager approach provides safety from
"temporary" modifications becoming permanent if an uncaught exception is raised
while drawing.

In Enable and Chaco, it is frequently the case that a graphics context instance
will be passed into a method for the purpose of some drawing. Because it is not
reasonable to push the responsibility of state management "up" the call stack,
the onus is on the code making state modifications to do them safely so that
other changes don't leak into other code.

**Well behaved code should take care to only modify graphics state inside a**
``with`` **block**.

Example
-------
.. image:: images/state_ex.png
:width: 300
:height: 300

First, the whole example:

.. literalinclude:: state_ex.py
:linenos:

The first part sets up the default graphics state. Here, that includes a scale
of 2 in X and Y, a translation of (150, 150) which is affected by the
preceeding scale transformation, and some line properties: stroke color, width,
join, and cap:

.. literalinclude:: state_ex.py
:lines: 7-13
:linenos:
:lineno-match:

Then in a loop, we draw twice (the two :py:meth:`stroke_path` calls). The first
draw uses a ``with`` block to temporarily modify the drawing state. It adds more
affine transformations: a rotate and a translate. It also changes some line
properties: stroke color, width, and cap. A rectangle is then added to the
current path and stroked.

.. literalinclude:: state_ex.py
:lines: 17-24
:linenos:
:lineno-match:

After leaving the first ``with`` block, the state is now restored to its
default. A new ``with`` block is entered and the current transformation matrix
is modified with the same rotation as the first drawing block, but a
*different* translation is applied. The line properties are unchanged
and so use the defaults set at the top.

.. literalinclude:: state_ex.py
:lines: 26-31
:linenos:
:lineno-match:
Comment thread
jwiggins marked this conversation as resolved.
33 changes: 33 additions & 0 deletions docs/source/kiva/state_ex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import math
from kiva import CAP_ROUND, CAP_SQUARE, JOIN_ROUND
from kiva.image import GraphicsContext

gc = GraphicsContext((600, 600))

gc.scale_ctm(2, 2)
gc.translate_ctm(150, 150)

gc.set_stroke_color((0.66, 0.88, 0.66))
gc.set_line_width(7.0)
gc.set_line_join(JOIN_ROUND)
gc.set_line_cap(CAP_SQUARE)

for i in range(0, 12):
theta = i*2*math.pi / 12.0
with gc:
gc.rotate_ctm(theta)
gc.translate_ctm(105, 0)
gc.set_stroke_color((1 - (i / 12), math.fmod(i / 6, 1), i / 12))
gc.set_line_width(10.0)
gc.set_line_cap(CAP_ROUND)
gc.rect(0, 0, 25, 25)
gc.stroke_path()

with gc:
gc.rotate_ctm(theta)
gc.translate_ctm(20, 0)
gc.move_to(0, 0)
gc.line_to(80, 0)
gc.stroke_path()

gc.save("state_ex.png")