:py:mod:`cotengra.schematic` ============================ .. py:module:: cotengra.schematic .. autoapi-nested-parse:: Draw psuedo-3D diagrams using matplotlib. Module Contents --------------- Classes ~~~~~~~ .. autoapisummary:: cotengra.schematic.Drawing Functions ~~~~~~~~~ .. autoapisummary:: cotengra.schematic.parse_style_preset cotengra.schematic.axonometric_project cotengra.schematic.coo_to_zorder cotengra.schematic.get_wong_color cotengra.schematic.mod_sat cotengra.schematic.auto_colors cotengra.schematic.darken_color cotengra.schematic.average_color cotengra.schematic.jitter_color cotengra.schematic.set_coloring_seed cotengra.schematic.hash_to_nvalues cotengra.schematic.hash_to_color cotengra.schematic.mean cotengra.schematic.distance cotengra.schematic.get_angle cotengra.schematic.get_rotator_and_inverse cotengra.schematic.get_control_points cotengra.schematic.gen_points_around Attributes ~~~~~~~~~~ .. autoapisummary:: cotengra.schematic._COLORS_DEFAULT cotengra.schematic._COLORS_SORTED cotengra.schematic.COLORING_SEED .. py:class:: Drawing(background=(0, 0, 0, 0), drawcolor=(0.14, 0.15, 0.16, 1.0), shapecolor=(0.45, 0.5, 0.55, 1.0), a=50, b=12, xscale=1, yscale=1, zscale=1, presets=None, ax=None, **kwargs) Draw 2D or pseudo-3D diagrams using matplotlib. This handles the axonometric projection and the z-ordering of the elements, as well as named preset styles for repeated elements, and the automatic adjustment of the figure limits. It also has basic support for drawing smooth curves and shaded areas around certain elements automatically. :param background: The background color of the figure, defaults to transparent. :type background: color, optional :param drawcolor: The default color to draw lines and text in. :type drawcolor: color, optional :param shapecolor: The default color to fill shapes with. :type shapecolor: color, optional :param a: The axonometric angle of the x-axis in degrees. :type a: float :param b: The axonometric angle of the y-axis in degrees. :type b: float :param xscale: A factor to scale the x-axis by. :type xscale: float :param yscale: A factor to scale the y-axis by. :type yscale: float :param zscale: A factor to scale the z-axis by. :type zscale: float :param presets: A dictionary of named style presets. When you add an element to the drawing, you can specify a preset name to use as default styling. :type presets: dict :param ax: The axes to draw on. If None, a new figure is created. :type ax: matplotlib.axes.Axes :param kwargs: Passed to ``plt.figure`` if ``ax`` is None. .. py:method:: _adjust_lims(x, y) .. py:method:: text(coo, text, preset=None, **kwargs) Place text at the specified coordinate. :param coo: The 2D or 3D coordinate of the text. If 3D, the coordinate will be projected onto the 2D plane, and a z-order will be assigned. :type coo: tuple[int, int] or tuple[int, int, int] :param text: The text to place. :type text: str :param preset: A preset style to use for the text. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.axes.Axes.text``. .. py:method:: text_between(cooa, coob, text, preset=None, **kwargs) Place text between two coordinates. :param cooa: The 2D or 3D coordinates of the text endpoints. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type cooa: tuple[int, int] or tuple[int, int, int] :param coob: The 2D or 3D coordinates of the text endpoints. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type coob: tuple[int, int] or tuple[int, int, int] :param text: The text to place. :type text: str :param center: The position of the text along the line, where 0.0 is the start and 1.0 is the end. Default is 0.5. :type center: float, optional :param preset: A preset style to use for the text. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.axes.Axes.text``. .. py:method:: label_ax(x, y, text, preset=None, **kwargs) Place text at the specified location, using the axis coordinates rather than 2D or 3D data coordinates. :param x: The x and y positions of the text, relative to the axis. :type x: float :param y: The x and y positions of the text, relative to the axis. :type y: float :param text: The text to place. :type text: str :param preset: A preset style to use for the text. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.axes.Axes.text``. .. py:method:: label_fig(x, y, text, preset=None, **kwargs) Place text at the specified location, using the figure coordinates rather than 2D or 3D data coordinates. :param x: The x and y positions of the text, relative to the figure. :type x: float :param y: The x and y positions of the text, relative to the figure. :type y: float :param text: The text to place. :type text: str :param preset: A preset style to use for the text. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.axes.Axes.text``. .. py:method:: _parse_style_for_marker(coo, preset=None, **kwargs) .. py:method:: _adjust_lims_for_marker(x, y, r) .. py:method:: circle(coo, preset=None, **kwargs) Draw a circle at the specified coordinate. :param coo: The 2D or 3D coordinate of the circle. If 3D, the coordinate will be projected onto the 2D plane, and a z-order will be assigned. :type coo: tuple[int, int] or tuple[int, int, int] :param preset: A preset style to use for the circle. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.patches.Circle``. .. py:method:: wedge(coo, theta1, theta2, preset=None, **kwargs) Draw a wedge at the specified coordinate. :param coo: The 2D or 3D coordinate of the wedge. If 3D, the coordinate will be projected onto the 2D plane, and a z-order will be assigned. :type coo: tuple[int, int] or tuple[int, int, int] :param theta1: The angle in degrees of the first edge of the wedge. :type theta1: float :param theta2: The angle in degrees of the second edge of the wedge. :type theta2: float :param preset: A preset style to use for the wedge. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.patches.Wedge``. .. py:method:: dot(coo, preset=None, **kwargs) Draw a small circle with no border. Alias for circle with defaults `radius=0.1` and `linewidth=0.0`. :param coo: The 2D or 3D coordinate of the dot. If 3D, the coordinate will be projected onto the 2D plane, and a z-order will be assigned. :type coo: tuple[int, int] or tuple[int, int, int] :param preset: A preset style to use for the dot. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.patches.Circle``. .. py:method:: regular_polygon(coo, preset=None, **kwargs) Draw a regular polygon at the specified coordinate. :param coo: The 2D or 3D coordinate of the polygon. If 3D, the coordinate will be projected onto the 2D plane, and a z-order will be assigned. :type coo: tuple[int, int] or tuple[int, int, int] :param n: The number of sides of the polygon. :type n: int :param orientation: The orientation of the polygon in radians. Default is 0.0. :type orientation: float, optional :param preset: A preset style to use for the polygon. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.patches.Polygon``. .. py:method:: marker(coo, preset=None, **kwargs) Draw a 'marker' at the specified coordinate. This is a shorthand for creating polygons with shape specified by a single character. :param coo: The 2D or 3D coordinate of the marker. If 3D, the coordinate will be projected onto the 2D plane, and a z-order will be assigned. :type coo: tuple[int, int] or tuple[int, int, int] :param marker: The marker shape to draw. One of ``"o.v^<>sDphH8"``. :type marker: str, optional :param preset: A preset style to use for the marker. :type preset: str, optional :param kwargs: Specific style options. .. py:method:: cube(coo, preset=None, **kwargs) Draw a cube at the specified coordinate, which must be 3D. :param coo: The 3D coordinate of the cube. The coordinate will be projected onto the 2D plane, and a z-order will be assigned. :type coo: tuple[int, int, int] :param preset: A preset style to use for the cube. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.patches.Polygon``. .. py:method:: line(cooa, coob, preset=None, **kwargs) Draw a line between two coordinates. :param cooa: The 2D or 3D coordinates of the line endpoints. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type cooa: tuple[int, int] or tuple[int, int, int] :param coob: The 2D or 3D coordinates of the line endpoints. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type coob: tuple[int, int] or tuple[int, int, int] :param stretch: Stretch the line by this factor. 1.0 is no stretch, 0.5 is half length, 2.0 is double length. Default is 1.0. :type stretch: float :param arrowhead: Draw an arrowhead at the end of the line. Default is False. If a dict, it is passed as keyword arguments to the arrowhead method. :type arrowhead: bool or dict, optional :param text_between: Add text along the line. :type text_between: str, optional :param preset: A preset style to use for the line. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.lines.Line2D``. .. seealso:: :obj:`Drawing.arrowhead` .. py:method:: line_offset(cooa, coob, offset, midlength=0.5, relative=True, preset=None, **kwargs) Draw a line between two coordinates, but curving out by a given offset perpendicular to the line. :param cooa: The 2D or 3D coordinates of the line endpoints. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type cooa: tuple[int, int] or tuple[int, int, int] :param coob: The 2D or 3D coordinates of the line endpoints. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type coob: tuple[int, int] or tuple[int, int, int] :param offset: The offset of the curve from the line, as a fraction of the total line length. This is always processed in the 2D projected plane. :type offset: float :param midlength: The length of the middle straight section, as a fraction of the total line length. Default is 0.5. :type midlength: float :param arrowhead: Draw an arrowhead at the end of the line. Default is False. If a dict, it is passed as keyword arguments to the arrowhead method. :type arrowhead: bool or dict, optional :param text_between: Add text along the line. :type text_between: str, optional :param relative: If ``True`` (the default), then ``offset`` is taken as a fraction of the line length, else in absolute units. :type relative: bool, optional :param preset: A preset style to use for the line. :type preset: str, optional :param kwargs: Specific style options passed to ``curve``. .. py:method:: arrowhead(cooa, coob, preset=None, **kwargs) Draw just a arrowhead on the line between ``cooa`` and ``coob``. :param cooa: The coordinates of the start and end of the line. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type cooa: tuple[int, int] or tuple[int, int, int] :param coob: The coordinates of the start and end of the line. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type coob: tuple[int, int] or tuple[int, int, int] :param reverse: Reverse the direction by switching ``cooa`` and ``coob``. If ``"both"``, draw an arrowhead in both directions. Default is False. :type reverse: bool or "both", optional :param center: The position of the arrowhead along the line, where 0 is the start and 1 is the end. Default is 0.5. :type center: float, optional :param width: The width of the arrowhead. Default is 0.05. :type width: float, optional :param length: The length of the arrowhead. Default is 0.1. :type length: float, optional :param preset: A preset style to use for the arrowhead, including the above options. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.lines.Line2D``. .. py:method:: curve(coos, preset=None, **kwargs) Draw a smooth line through the given coordinates. :param coos: The 2D or 3D coordinates of the line. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type coos: Sequence[tuple[int, int]] or Sequence[tuple[int, int, int]] :param smoothing: The amount of smoothing to apply to the curve. 0.0 is no smoothing, 1.0 is maximum smoothing. Default is 0.5. :type smoothing: float, optional :param preset: A preset style to use for the curve. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.lines.Line2D``. .. py:method:: shape(coos, preset=None, **kwargs) Draw a closed shape with (sharp) corners at the given coordinates. :param coos: The coordinates of the corners' of the shape. :type coos: sequence of coordinates :param preset: A preset style to use for the shape. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.patches.PathPatch``. .. seealso:: :obj:`Drawing.patch` .. py:method:: patch(coos, preset=None, **kwargs) Draw a closed smooth patch through given coordinates. :param coos: The coordinates of the 'corners' of the patch, the outline is guaranteed to pass through these points. :type coos: sequence of coordinates :param smoothing: The smoothing factor, the higher the smoother. The default is 0.5. :type smoothing: float :param preset: A preset style to use for the patch. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.patches.PathPatch``. .. seealso:: :obj:`Drawing.shape`, :obj:`Drawing.curve` .. py:method:: patch_around(coos, radius=0.0, resolution=12, preset=None, **kwargs) Draw a patch around the given coordinates, by contructing a convex hull around the points, optionally including an extra uniform or per coordinate radius. :param coos: The coordinates of the points to draw the patch around. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on *min* z-order of the endpoints. :type coos: sequence[tuple[int, int]] or sequence[tuple[int, int, int]] :param radius: The radius of the patch around each point. If a sequence, must be the same length as ``coos``. Default is 0.0. :type radius: float or sequence[float], optional :param resolution: The number of points to use pad around each point. Default is 12. :type resolution: int, optional :param preset: A preset style to use for the patch. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.patches.PathPatch``. .. py:method:: patch_around_circles(cooa, ra, coob, rb, padding=0.2, pinch=True, preset=None, **kwargs) Draw a smooth patch around two circles. :param cooa: The coordinates of the center of the first circle. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type cooa: tuple[int, int] or tuple[int, int, int] :param ra: The radius of the first circle. :type ra: int :param coob: The coordinates of the center of the second circle. If 3D, the coordinates will be projected onto the 2D plane, and a z-order will be assigned based on average z-order of the endpoints. :type coob: tuple[int, int] or tuple[int, int, int] :param rb: The radius of the second circle. :type rb: int :param padding: The amount of padding to add around the circles. Default is 0.2. :type padding: float, optional :param pinch: If or how much to pinch the patch in between the circles. Default is to match the padding. :type pinch: bool or float, optional :param preset: A preset style to use for the patch. :type preset: str, optional :param kwargs: Specific style options passed to ``matplotlib.patches.PathPatch``. .. seealso:: :obj:`Drawing.patch` .. py:function:: parse_style_preset(presets, preset, **kwargs) Parse a one or more style presets plus manual kwargs. :param presets: The dictionary of presets. :type presets: dict :param preset: The name of the preset(s) to use. If multiple, later presets take precedence. :type preset: str or sequence of str :param kwargs: Any additional manual keyword arguments are added to the style and override the presets. :returns: **style** :rtype: dict .. py:function:: axonometric_project(i, j, k, a=50, b=12, xscale=1, yscale=1, zscale=1) Project the 3D location ``(i, j, k)`` onto the 2D plane, using the axonometric projection with the given angles ``a`` and ``b``. The ``xscale``, ``yscale`` and ``zscale`` parameters can be used to scale the axes, including flipping them. :param i: The 3D coordinates of the point to project. :type i: float :param j: The 3D coordinates of the point to project. :type j: float :param k: The 3D coordinates of the point to project. :type k: float :param a: The left and right angles to displace x and y axes, from horizontal, in degrees. :type a: float :param b: The left and right angles to displace x and y axes, from horizontal, in degrees. :type b: float :param xscale: The scaling factor for the x, y and z axes. If negative, the axis is flipped. :type xscale: float :param yscale: The scaling factor for the x, y and z axes. If negative, the axis is flipped. :type yscale: float :param zscale: The scaling factor for the x, y and z axes. If negative, the axis is flipped. :type zscale: float :returns: **x, y** -- The 2D coordinates of the projected point. :rtype: float .. py:function:: coo_to_zorder(i, j, k, xscale=1, yscale=1, zscale=1) Given the coordinates of a point in 3D space, return a z-order value that can be used to draw it on top of other elements in the diagram. Take into account the scaling of the axes, so that the z-ordering is correct even if the axes flipped. .. py:data:: _COLORS_DEFAULT .. py:function:: get_wong_color(which, alpha=None, hue_factor=0.0, sat_factor=1.0, val_factor=1.0) Get a color by name, optionally modifying its alpha, hue, saturation or value. These colorblind friendly colors were ppularized in an article by Wong (https://www.nature.com/articles/nmeth.1618) but originally come from Okabe & Ito (https://jfly.uni-koeln.de/color/). :param which: The name of the color to get. :type which: {'blue', 'orange', 'green', 'red', 'yellow', 'pink', 'bluedark'} :param alpha: The alpha channel value to set for the color. Default is 1.0. :type alpha: float, optional :param hue_factor: The amount to shift the hue of the color. Default is 0.0. :type hue_factor: float, optional :param sat_factor: The amount to scale the saturation of the color. Default is 1.0. :type sat_factor: float, optional :param val_factor: The amount to scale the value of the color. Default is 1.0. :type val_factor: float, optional :returns: **color** -- The RGBA color as a tuple of floats. :rtype: tuple[float, float, float, float] .. py:data:: _COLORS_SORTED .. py:function:: mod_sat(c, mod=None, alpha=None) Modify the luminosity of color ``c``, optionally set the ``alpha`` channel, and return the final color as a RGBA tuple. .. py:function:: auto_colors(nc, alpha=None, default_sequence=False) Generate a nice sequence of ``nc`` colors. By default this uses an interpolation between the colorblind friendly colors of Okabe & Ito in hue sorted order, with luminosity moderated by a sine function to increase local distinguishability. :param nc: The number of colors to generate. :type nc: int :param alpha: The alpha channel value to set for all colors. Default is 1.0. :type alpha: float, optional :param default_sequence: If ``True``, take from the default sequence of 7 colors, un-sorted and un-modulated. :type default_sequence: bool, optional :returns: **colors** :rtype: list[tuple[float, float, float, float]] .. py:function:: darken_color(color, factor=2 / 3) Take ``color`` and darken it by ``factor``. .. py:function:: average_color(colors) Take a sequence of colors and return the RMS average in RGB space. .. py:function:: jitter_color(color, factor=0.05) Take ``color`` and add a random offset to each of its components. .. py:data:: COLORING_SEED :value: 8 .. py:function:: set_coloring_seed(seed) Set the seed for the random color generator. :param seed: The seed to use. :type seed: int .. py:function:: hash_to_nvalues(s, nval, seed=None) Hash the string ``s`` to ``nval`` different floats in the range [0, 1]. .. py:function:: hash_to_color(s, hmin=0.0, hmax=1.0, smin=0.3, smax=0.8, vmin=0.8, vmax=0.9) Generate a random color for a string ``s``. :param s: The string to generate a color for. :type s: str :param hmin: The minimum hue value. :type hmin: float, optional :param hmax: The maximum hue value. :type hmax: float, optional :param smin: The minimum saturation value. :type smin: float, optional :param smax: The maximum saturation value. :type smax: float, optional :param vmin: The minimum value value. :type vmin: float, optional :param vmax: The maximum value value. :type vmax: float, optional :returns: **color** -- A tuple of floats in the range [0, 1] representing the RGB color. :rtype: tuple .. py:function:: mean(xs) Get the mean of a list of numbers. .. py:function:: distance(pa, pb) Get the distance between two points, in arbtirary dimensions. .. py:function:: get_angle(pa, pb) Get the angle between the line from p1 to p2 and the x-axis. .. py:function:: get_rotator_and_inverse(pa, pb) Get a rotation matrix that rotates points by theta radians about the origin and then translates them by offset. .. py:function:: get_control_points(pa, pb, pc, spacing=1 / 3) Get two points that can be used to construct a bezier curve that passes smoothly through the angle `pa`, `pb`, `pc`. .. py:function:: gen_points_around(coo, radius=1, resolution=12) Generate points around a circle.