|
| 1 | +======================= |
| 2 | + MEP26: Artist styling |
| 3 | +======================= |
| 4 | + |
| 5 | +.. contents:: |
| 6 | + :local: |
| 7 | + |
| 8 | + |
| 9 | +Status |
| 10 | +====== |
| 11 | + |
| 12 | +**Proposed** |
| 13 | + |
| 14 | +Branches and Pull requests |
| 15 | +========================== |
| 16 | + |
| 17 | +Abstract |
| 18 | +======== |
| 19 | + |
| 20 | +This MEP proposes a new stylesheet implementation to allow more |
| 21 | +comprehensive and dynamic styling of artists. |
| 22 | + |
| 23 | +The current version of matplotlib (1.4.0) allows stylesheets based on |
| 24 | +the rcParams syntax to be applied before creation of a plot. The |
| 25 | +methodology below proposes a new syntax, based on CSS, which would |
| 26 | +allow styling of individual artists and properties, which can be |
| 27 | +applied dynamically to existing objects. |
| 28 | + |
| 29 | +This is related to (and makes steps toward) the overall goal of moving |
| 30 | +to a DOM/tree-like architecture. |
| 31 | + |
| 32 | + |
| 33 | +Detailed description |
| 34 | +==================== |
| 35 | + |
| 36 | +Currently, the look and appearance of existing artist objects (figure, |
| 37 | +axes, Line2D etc...) can only be updated via `set_` and `get_` methods |
| 38 | +on the artist object, which is quite laborious, especially if no |
| 39 | +reference to the artist(s) has been stored. The new style sheets |
| 40 | +introduced in 1.4 allow styling before a plot is created, but do not |
| 41 | +offer any means to dynamically update plots or distinguish between |
| 42 | +artists of the same type (i.e. to specifiy the `line color` and `line |
| 43 | +style` separately for differing `Line2D` objects). |
| 44 | + |
| 45 | +The initial development should concentrate on allowing styling of |
| 46 | +artist primitives (those `artists` that do not contain other |
| 47 | +`artists`), and further development could expand the CSS syntax rules |
| 48 | +and parser to allow more complex styling. See the appendix for a list |
| 49 | +of primitives. |
| 50 | + |
| 51 | +The new methodology would require development of a number of steps: |
| 52 | + |
| 53 | +- A new stylesheet syntax (likely based on CSS) to allow selection of |
| 54 | + artists by type, class, id etc... |
| 55 | +- A mechanism by which to parse a stylesheet into a tree |
| 56 | +- A mechanism by which to translate the parse-tree into something |
| 57 | + which can be used to update the properties of relevant |
| 58 | + artists. Ideally this would implement a method by which to traverse |
| 59 | + the artists in a tree-like structure. |
| 60 | +- A mechanism by which to generate a stylesheet from existing artist |
| 61 | + properties. This would be useful to allow a user to export a |
| 62 | + stylesheet from an existing figure (where the appearance may have |
| 63 | + been set using the matplotlib API)... |
| 64 | + |
| 65 | +Implementation |
| 66 | +============== |
| 67 | + |
| 68 | +It will be easiest to allow a '3rd party' to modify/set the style of |
| 69 | +an artist if the 'style' is created as a separate class and store |
| 70 | +against the artist as a property. The `GraphicsContext` class already |
| 71 | +provides a the basis of a `Style` class and an artists `draw` method can |
| 72 | +be refactored to use the `Style` class rather than setting up it's own |
| 73 | +`GraphicsContext` and transferring it's style-related properties to |
| 74 | +it. A minimal example of how this could be implemented is shown here: |
| 75 | +https://github.com/JamesRamm/mpl_experiment |
| 76 | + |
| 77 | +IMO, this will also make the API and code base much neater as |
| 78 | +individual get/set methods for artist style properties are now |
| 79 | +redundant... Indirectly related would be a general drive to replace |
| 80 | +get/set methods with properties. Implementing the style class with |
| 81 | +properties would be a big stride toward this... |
| 82 | + |
| 83 | +For initial development, I suggest developing a syntax based on a much |
| 84 | +(much much) simplified version of CSS. I am in favour of dubbing this |
| 85 | +Artist Style Sheets :+1: : |
| 86 | + |
| 87 | +BNF Grammar |
| 88 | +----------- |
| 89 | + |
| 90 | +I propose a very simple syntax to implement initially (like a proof of |
| 91 | +concept), which can be expanded upon in the future. The BNF form of |
| 92 | +the syntax is given below and then explained :: |
| 93 | + |
| 94 | + RuleSet ::= SelectorSequence "{"Declaration"}" |
| 95 | + |
| 96 | + SelectorSequence :: = Selector {"," Selector} |
| 97 | + |
| 98 | + Declaration ::= propName":" propValue";" |
| 99 | + |
| 100 | + Selector ::= ArtistIdent{"#"Ident} |
| 101 | + |
| 102 | + propName ::= Ident |
| 103 | + |
| 104 | + propValue ::= Ident | Number | Colour | "None" |
| 105 | + |
| 106 | +`ArtistIdent`, `Ident`, `Number` and `Colour` are tokens (the basic |
| 107 | +building blocks of the expression) which are defined by regular |
| 108 | +expressions. |
| 109 | + |
| 110 | +Syntax |
| 111 | +------ |
| 112 | + |
| 113 | +A CSS stylesheet consists of a series of **rule sets** in hierarchical |
| 114 | +order (rules are applied from top to bottom). Each rule follows the |
| 115 | +syntax :: |
| 116 | + |
| 117 | + selector {attribute: value;} |
| 118 | + |
| 119 | +Each rule can have any number of `attribute`:`value` pairs, and a |
| 120 | +stylesheet can have any number of rules. |
| 121 | + |
| 122 | +The initial syntax is designed only for `artist` primitives. It does |
| 123 | +not address the question of how to set properties on `container` types |
| 124 | +(whose properties may themselves be `artists` with settable |
| 125 | +properties), however, a future solution to this could simply be nested |
| 126 | +`RuleSet`s |
| 127 | + |
| 128 | +Selectors |
| 129 | +~~~~~~~~~ |
| 130 | + |
| 131 | + |
| 132 | +Selectors define the object to which the attribute updates should be |
| 133 | +applied. As a starting point, I propose just 2 selectors to use in |
| 134 | +initial development: |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | +Artist Type Selector |
| 139 | + |
| 140 | + |
| 141 | +Select an `artist` by it's type. E.g `Line2D` or `Text`:: |
| 142 | + |
| 143 | + Line2D {attribute: value} |
| 144 | + |
| 145 | +The regex for matching the artist type selector (`ArtistIdent` in the BNF grammar) would be:: |
| 146 | + |
| 147 | + ArtistIdent = r'(?P<ArtistIdent>\bLine2D\b|\bText\b|\bAxesImage\b|\bFigureImage\b|\bPatch\b)' |
| 148 | + |
| 149 | +GID selector |
| 150 | +~~~~~~~~~~~~ |
| 151 | + |
| 152 | +Select an `artist` by its `gid`:: |
| 153 | + |
| 154 | + Line2D#myGID {attribute: value} |
| 155 | + |
| 156 | +A `gid` can be any string, so the regex could be as follows:: |
| 157 | + |
| 158 | + Ident = r'(?P<Ident>[a-zA-Z_][a-zA-Z_0-9]*)' |
| 159 | + |
| 160 | + |
| 161 | +The above selectors roughly correspond to their CSS counterparts |
| 162 | +(http://www.w3.org/TR/CSS21/selector.html) |
| 163 | + |
| 164 | +Attributes and values |
| 165 | +~~~~~~~~~~~~~~~~~~~~~ |
| 166 | + |
| 167 | +- `Attributes` are any valid (settable) property for the `artist` in question. |
| 168 | +- `Values` are any valid value for the property (Usually a string, or number). |
| 169 | + |
| 170 | +Parsing |
| 171 | +------- |
| 172 | + |
| 173 | +Parsing would consist of breaking the stylesheet into tokens (the |
| 174 | +python cookbook gives a nice tokenizing recipe on page 66), applying |
| 175 | +the syntax rules and constructing a `Tree`. This requires defining the |
| 176 | +grammar of the stylesheet (again, we can borrow from CSS) and writing |
| 177 | +a parser. Happily, there is a recipe for this in the python cookbook |
| 178 | +aswell. |
| 179 | + |
| 180 | + |
| 181 | +Visitor pattern for matplotlib figure |
| 182 | +------------------------------------- |
| 183 | + |
| 184 | +In order to apply the stylesheet rules to the relevant artists, we |
| 185 | +need to 'visit' each artist in a figure and apply the relevant rule. |
| 186 | +Here is a visitor class (again, thanks to python cookbook), where each |
| 187 | +`node` would be an artist in the figure. A `visit_` method would need |
| 188 | +to be implemented for each mpl artist, to handle the different |
| 189 | +properties for each :: |
| 190 | + |
| 191 | + class Visitor: |
| 192 | + def visit(self, node): |
| 193 | + name = 'visit_' + type(node).__name__ |
| 194 | + meth = getattr(self, name, None) |
| 195 | + if meth is None: |
| 196 | + raise NotImplementedError |
| 197 | + return meth(node) |
| 198 | + |
| 199 | +An `evaluator` class would then take the stylesheet rules and |
| 200 | +implement the visitor on each one of them. |
| 201 | + |
| 202 | + |
| 203 | + |
| 204 | +Backward compatibility |
| 205 | +====================== |
| 206 | + |
| 207 | +Implementing a separate `Style` class would break backward |
| 208 | +compatibility as many get/set methods on an artist would become |
| 209 | +redundant. While it would be possible to alter these methods to hook |
| 210 | +into the `Style` class (stored as a property against the artist), I |
| 211 | +would be in favor of simply removing them to both neaten/simplify the |
| 212 | +codebase and to provide a simple, uncluttered API... |
| 213 | + |
| 214 | +Alternatives |
| 215 | +============ |
| 216 | + |
| 217 | +No alternatives, but some of the ground covered here overlaps with |
| 218 | +MEP25, which may assist in this development |
| 219 | + |
| 220 | +Appendix |
| 221 | +======== |
| 222 | + |
| 223 | +Matplotlib primitives |
| 224 | +~~~~~~~~~~~~~~~~~~~~~ |
| 225 | + |
| 226 | +This will form the initial selectors which stylesheets can use. |
| 227 | + |
| 228 | +* Line2D |
| 229 | +* Text |
| 230 | +* AxesImage |
| 231 | +* FigureImage |
| 232 | +* Patch |
0 commit comments