2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 from pyx
import attr
, deco
, style
, color
, unit
, canvas
, path
28 from pyx
import text
as textmodule
33 def setdatapattern(self
, graph
, columns
, pattern
):
34 for datakey
in columns
.keys():
35 match
= pattern
.match(datakey
)
37 # XXX match.groups()[0] must contain the full axisname
38 axisname
= match
.groups()[0]
39 index
= columns
[datakey
]
41 return graph
.axes
[axisname
], index
43 def key_pt(self
, c
, x_pt
, y_pt
, width_pt
, height_pt
, styledata
):
44 raise RuntimeError("style doesn't provide a key")
46 def adjustaxes(self
, points
, columns
, styledata
):
49 def setdata(self
, graph
, columns
, styledata
):
52 def selectstyle(self
, selectindex
, selecttotal
, styledata
):
55 def initdrawpoints(self
, graph
, styledata
):
58 def drawpoint(self
, graph
, styledata
):
61 def donedrawpoints(self
, graph
, styledata
):
65 class pointpos(_style
):
67 def __init__(self
, epsilon
=1e-10):
68 self
.epsilon
= epsilon
70 def setdata(self
, graph
, columns
, styledata
):
71 # analyse column information
72 styledata
.pointposaxisindex
= []
73 columns
= columns
.copy()
74 for axisname
in graph
.axisnames
:
75 pattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % axisname
)
76 styledata
.pointposaxisindex
.append(self
.setdatapattern(graph
, columns
, pattern
)) # TODO: append "needed=1"
79 def adjustaxes(self
, points
, columns
, styledata
):
80 for axis
, index
in styledata
.pointposaxisindex
:
81 axis
.adjustrange(points
, index
)
83 def drawpoint(self
, graph
, styledata
):
85 styledata
.validvpos
= 1 # valid position (but might be outside of the graph)
86 styledata
.drawsymbol
= 1 # valid position inside the graph
87 styledata
.vpos
= [axis
.convert(styledata
.point
[index
]) for axis
, index
in styledata
.pointposaxisindex
]
88 # for axisname in graph.axisnames:
90 # v = styledata.axes[axisname].convert(styledata.point[styledata.index[axisname]["x"]])
91 # except (ArithmeticError, KeyError, ValueError, TypeError):
92 # styledata.validvpos = 0
93 # styledata.drawsymbol = 0
94 # styledata.vpos.append(None)
96 # if v < - self.epsilon or v > 1 + self.epsilon:
97 # styledata.drawsymbol = 0
98 # styledata.vpos.append(v)
101 class rangepos(_style
):
103 def setdata(self
, graph
, columns
, styledata
):
105 - the instance should be considered read-only
106 (it might be shared between several data)
107 - styledata is the place where to store information
108 - returns the dictionary of columns not used by the style"""
110 # analyse column information
111 styledata
.index
= {} # a nested index dictionary containing
112 # column numbers, e.g. styledata.index["x"]["x"],
113 # styledata.index["y"]["dmin"] etc.; the first key is a axis
114 # name (without the axis number), the second is one of
115 # the datanames ["x", "min", "max", "d", "dmin", "dmax"]
116 styledata
.axes
= {} # mapping from axis name (without axis number) to the axis
118 columns
= columns
.copy()
119 for axisname
in graph
.axisnames
:
120 for dataname
, pattern
in [("x", re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % axisname
)),
121 ("min", re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % axisname
)),
122 ("max", re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % axisname
)),
123 ("d", re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % axisname
)),
124 ("dmin", re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % axisname
)),
125 ("dmax", re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % axisname
))]:
126 matchresult
= self
.setdatapattern(graph
, columns
, pattern
)
127 if matchresult
is not None:
128 axis
, index
= matchresult
129 if styledata
.axes
.has_key(axisname
):
130 if styledata
.axes
[axisname
] != axis
:
131 raise ValueError("axis mismatch for axis name '%s'" % axisname
)
132 styledata
.index
[axisname
][dataname
] = index
134 styledata
.index
[axisname
] = {dataname
: index
}
135 styledata
.axes
[axisname
] = axis
136 if not styledata
.axes
.has_key(axisname
):
137 raise ValueError("missing columns for axis name '%s'" % axisname
)
138 if ((styledata
.index
[axisname
].has_key("min") and styledata
.index
[axisname
].has_key("d")) or
139 (styledata
.index
[axisname
].has_key("min") and styledata
.index
[axisname
].has_key("dmin")) or
140 (styledata
.index
[axisname
].has_key("d") and styledata
.index
[axisname
].has_key("dmin")) or
141 (styledata
.index
[axisname
].has_key("max") and styledata
.index
[axisname
].has_key("d")) or
142 (styledata
.index
[axisname
].has_key("max") and styledata
.index
[axisname
].has_key("dmax")) or
143 (styledata
.index
[axisname
].has_key("d") and styledata
.index
[axisname
].has_key("dmax"))):
144 raise ValueError("multiple errorbar definition for axis name '%s'" % axisname
)
145 if (not styledata
.index
[axisname
].has_key("x") and
146 (styledata
.index
[axisname
].has_key("d") or
147 styledata
.index
[axisname
].has_key("dmin") or
148 styledata
.index
[axisname
].has_key("dmax"))):
149 raise ValueError("errorbar definition start value missing for axis name '%s'" % axisname
)
152 def adjustaxes(self
, points
, columns
, styledata
):
153 # reverse lookup for axisnames
154 # TODO: the reverse lookup is ugly
156 for column
in columns
:
157 for axisname
in styledata
.index
.keys():
158 for thiscolumn
in styledata
.index
[axisname
].values():
159 if thiscolumn
== column
and axisname
not in axisnames
:
160 axisnames
.append(axisname
)
161 # TODO: perform check to verify that all columns for a given axisname are available at the same time
162 for axisname
in axisnames
:
163 if styledata
.index
[axisname
].has_key("x"):
164 styledata
.axes
[axisname
].adjustrange(points
, styledata
.index
[axisname
]["x"])
165 if styledata
.index
[axisname
].has_key("min"):
166 styledata
.axes
[axisname
].adjustrange(points
, styledata
.index
[axisname
]["min"])
167 if styledata
.index
[axisname
].has_key("max"):
168 styledata
.axes
[axisname
].adjustrange(points
, styledata
.index
[axisname
]["max"])
169 if styledata
.index
[axisname
].has_key("d"):
170 styledata
.axes
[axisname
].adjustrange(points
, styledata
.index
[axisname
]["x"], deltaindex
=styledata
.index
[axisname
]["d"])
171 if styledata
.index
[axisname
].has_key("dmin"):
172 styledata
.axes
[axisname
].adjustrange(points
, styledata
.index
[axisname
]["x"], deltaminindex
=styledata
.index
[axisname
]["dmin"])
173 if styledata
.index
[axisname
].has_key("dmax"):
174 styledata
.axes
[axisname
].adjustrange(points
, styledata
.index
[axisname
]["x"], deltamaxindex
=styledata
.index
[axisname
]["dmax"])
176 def doerrorbars(self
, styledata
):
177 # errorbar loop over the different direction having errorbars
178 for erroraxisname
, erroraxisindex
in styledata
.errorlist
:
180 # check for validity of other point components
182 for v
in styledata
.vpos
:
183 if v
is None and i
!= erroraxisindex
:
187 # calculate min and max
188 errorindex
= styledata
.index
[erroraxisname
]
190 min = styledata
.point
[errorindex
["x"]] - styledata
.point
[errorindex
["d"]]
193 min = styledata
.point
[errorindex
["x"]] - styledata
.point
[errorindex
["dmin"]]
196 min = styledata
.point
[errorindex
["min"]]
200 max = styledata
.point
[errorindex
["x"]] + styledata
.point
[errorindex
["d"]]
203 max = styledata
.point
[errorindex
["x"]] + styledata
.point
[errorindex
["dmax"]]
206 max = styledata
.point
[errorindex
["max"]]
210 # calculate vmin and vmax
212 vmin
= styledata
.axes
[erroraxisname
].convert(min)
216 vmax
= styledata
.axes
[erroraxisname
].convert(max)
220 # create vminpos and vmaxpos
223 vminpos
= styledata
.vpos
[:]
224 if vmin
> - self
.epsilon
and vmin
< 1 + self
.epsilon
:
225 vminpos
[erroraxisindex
] = vmin
226 vcaps
.append(vminpos
)
228 vminpos
[erroraxisindex
] = 0
229 elif styledata
.vpos
[erroraxisindex
] is not None:
230 vminpos
= styledata
.vpos
234 vmaxpos
= styledata
.vpos
[:]
235 if vmax
> - self
.epsilon
and vmax
< 1 + self
.epsilon
:
236 vmaxpos
[erroraxisindex
] = vmax
237 vcaps
.append(vmaxpos
)
239 vmaxpos
[erroraxisindex
] = 1
240 elif styledata
.vpos
[erroraxisindex
] is not None:
241 vmaxpos
= styledata
.vpos
246 class symbol(_style
):
248 def cross(self
, x_pt
, y_pt
, size_pt
):
249 return (path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
250 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
251 path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
252 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
))
254 def plus(self
, x_pt
, y_pt
, size_pt
):
255 return (path
.moveto_pt(x_pt
-0.707106781*size_pt
, y_pt
),
256 path
.lineto_pt(x_pt
+0.707106781*size_pt
, y_pt
),
257 path
.moveto_pt(x_pt
, y_pt
-0.707106781*size_pt
),
258 path
.lineto_pt(x_pt
, y_pt
+0.707106781*size_pt
))
260 def square(self
, x_pt
, y_pt
, size_pt
):
261 return (path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
262 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
),
263 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
264 path
.lineto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
267 def triangle(self
, x_pt
, y_pt
, size_pt
):
268 return (path
.moveto_pt(x_pt
-0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
269 path
.lineto_pt(x_pt
+0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
270 path
.lineto_pt(x_pt
, y_pt
+0.877382675*size_pt
),
273 def circle(self
, x_pt
, y_pt
, size_pt
):
274 return (path
.arc_pt(x_pt
, y_pt
, 0.564189583*size_pt
, 0, 360),
277 def diamond(self
, x_pt
, y_pt
, size_pt
):
278 return (path
.moveto_pt(x_pt
-0.537284965*size_pt
, y_pt
),
279 path
.lineto_pt(x_pt
, y_pt
-0.930604859*size_pt
),
280 path
.lineto_pt(x_pt
+0.537284965*size_pt
, y_pt
),
281 path
.lineto_pt(x_pt
, y_pt
+0.930604859*size_pt
),
284 changecross
= attr
.changelist([cross
, plus
, square
, triangle
, circle
, diamond
])
285 changeplus
= attr
.changelist([plus
, square
, triangle
, circle
, diamond
, cross
])
286 changesquare
= attr
.changelist([square
, triangle
, circle
, diamond
, cross
, plus
])
287 changetriangle
= attr
.changelist([triangle
, circle
, diamond
, cross
, plus
, square
])
288 changecircle
= attr
.changelist([circle
, diamond
, cross
, plus
, square
, triangle
])
289 changediamond
= attr
.changelist([diamond
, cross
, plus
, square
, triangle
, circle
])
290 changesquaretwice
= attr
.changelist([square
, square
, triangle
, triangle
, circle
, circle
, diamond
, diamond
])
291 changetriangletwice
= attr
.changelist([triangle
, triangle
, circle
, circle
, diamond
, diamond
, square
, square
])
292 changecircletwice
= attr
.changelist([circle
, circle
, diamond
, diamond
, square
, square
, triangle
, triangle
])
293 changediamondtwice
= attr
.changelist([diamond
, diamond
, square
, square
, triangle
, triangle
, circle
, circle
])
295 changestrokedfilled
= attr
.changelist([deco
.stroked
, deco
.filled
])
296 changefilledstroked
= attr
.changelist([deco
.filled
, deco
.stroked
])
298 defaultsymbolattrs
= [deco
.stroked
]
300 def __init__(self
, symbol
=changecross
,
306 self
.symbolattrs
= symbolattrs
307 self
.epsilon
= epsilon
309 def selectstyle(self
, selectindex
, selecttotal
, styledata
):
310 styledata
.symbol
= attr
.selectattr(self
.symbol
, selectindex
, selecttotal
)
311 styledata
.size_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
312 if self
.symbolattrs
is not None:
313 styledata
.symbolattrs
= attr
.selectattrs(self
.defaultsymbolattrs
+ self
.symbolattrs
, selectindex
, selecttotal
)
315 styledata
.symbolattrs
= None
317 def drawsymbol_pt(self
, c
, x_pt
, y_pt
, styledata
, point
=None):
318 if styledata
.symbolattrs
is not None:
319 c
.draw(path
.path(*styledata
.symbol(self
, x_pt
, y_pt
, styledata
.size_pt
)), styledata
.symbolattrs
)
321 def drawpoint(self
, graph
, styledata
):
322 if styledata
.drawsymbol
:
323 styledata
.xpos
, styledata
.ypos
= graph
.vpos_pt(*styledata
.vpos
)
324 self
.drawsymbol_pt(graph
, styledata
.xpos
, styledata
.ypos
, styledata
, point
=styledata
.point
)
326 def key_pt(self
, c
, x_pt
, y_pt
, width_pt
, height_pt
, styledata
):
327 self
.drawsymbol_pt(c
, x_pt
+0.5*width_pt
, y_pt
+0.5*height_pt
, styledata
)
328 if styledata
.lineattrs
is not None:
329 c
.stroke(path
.line_pt(x_pt
, y_pt
+0.5*height_pt
, x_pt
+width_pt
, y_pt
+0.5*height_pt
), styledata
.lineattrs
)
334 changelinestyle
= attr
.changelist([style
.linestyle
.solid
,
335 style
.linestyle
.dashed
,
336 style
.linestyle
.dotted
,
337 style
.linestyle
.dashdotted
])
339 defaultlineattrs
= [changelinestyle
]
341 def __init__(self
, lineattrs
=[]):
342 self
.lineattrs
= lineattrs
344 def selectstyle(self
, selectindex
, selecttotal
, styledata
):
345 styledata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
347 def initdrawpoints(self
, graph
, styledata
):
348 styledata
.linecanvas
= graph
.insert(canvas
.canvas())
349 styledata
.path
= path
.path()
350 styledata
.linebasepoints
= []
351 styledata
.lastvpos
= None
353 def appendlinebasepoints(self
, graph
, styledata
):
354 # append linebasepoints
355 if styledata
.validvpos
:
356 if len(styledata
.linebasepoints
):
357 # the last point was inside the graph
358 if styledata
.drawsymbol
: # this is wrong
359 # we always need the following line here:
360 styledata
.xpos
, styledata
.ypos
= graph
.vpos_pt(*styledata
.vpos
)
361 styledata
.linebasepoints
.append((styledata
.xpos
, styledata
.ypos
))
365 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
368 # 1 = vstart + (vend - vstart) * cut
370 newcut
= (1 - vstart
)/(vend
- vstart
)
371 except ArithmeticError:
374 # 0 = vstart + (vend - vstart) * cut
376 newcut
= - vstart
/(vend
- vstart
)
377 except ArithmeticError:
379 if newcut
is not None and newcut
< cut
:
383 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
384 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
385 styledata
.linebasepoints
.append(styledata
.graph
.vpos_pt(*cutvpos
))
386 styledata
.validvpos
= 0 # clear linebasepoints below
388 # the last point was outside the graph
389 if styledata
.lastvpos
is not None:
390 if styledata
.drawsymbol
:
393 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
396 # 1 = vstart + (vend - vstart) * cut
398 newcut
= (1 - vstart
)/(vend
- vstart
)
399 except ArithmeticError:
402 # 0 = vstart + (vend - vstart) * cut
404 newcut
= - vstart
/(vend
- vstart
)
405 except ArithmeticError:
407 if newcut
is not None and newcut
> cut
:
411 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
412 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
413 styledata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
414 styledata
.linebasepoints
.append(graph
.vpos_pt(*styledata
.vpos
))
416 # sometimes cut beginning and end
419 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
424 # 1 = vstart + (vend - vstart) * cutfrom
426 newcutfrom
= (1 - vstart
)/(vend
- vstart
)
427 except ArithmeticError:
432 # 0 = vstart + (vend - vstart) * cutfrom
434 newcutfrom
= - vstart
/(vend
- vstart
)
435 except ArithmeticError:
437 if newcutfrom
is not None and newcutfrom
> cutfrom
:
441 # 1 = vstart + (vend - vstart) * cutto
443 newcutto
= (1 - vstart
)/(vend
- vstart
)
444 except ArithmeticError:
447 # 0 = vstart + (vend - vstart) * cutto
449 newcutto
= - vstart
/(vend
- vstart
)
450 except ArithmeticError:
452 if newcutto
is not None and newcutto
< cutto
:
458 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
459 cutfromvpos
.append(vstart
+ (vend
- vstart
) * cutfrom
)
460 cuttovpos
.append(vstart
+ (vend
- vstart
) * cutto
)
461 styledata
.linebasepoints
.append(styledata
.graph
.vpos_pt(*cutfromvpos
))
462 styledata
.linebasepoints
.append(styledata
.graph
.vpos_pt(*cuttovpos
))
463 styledata
.validvpos
= 0 # clear linebasepoints below
464 styledata
.lastvpos
= styledata
.vpos
466 styledata
.lastvpos
= None
468 def addpointstopath(self
, styledata
):
469 # add baselinepoints to styledata.path
470 if len(styledata
.linebasepoints
) > 1:
471 styledata
.path
.append(path
.moveto_pt(*styledata
.linebasepoints
[0]))
472 if len(styledata
.linebasepoints
) > 2:
473 styledata
.path
.append(path
.multilineto_pt(styledata
.linebasepoints
[1:]))
475 styledata
.path
.append(path
.lineto_pt(*styledata
.linebasepoints
[1]))
476 styledata
.linebasepoints
= []
478 def donedrawpoints(self
, graph
, styledata
):
479 self
.addpointstopath(styledata
)
480 if styledata
.lineattrs
is not None and len(styledata
.path
.path
):
481 styledata
.linecanvas
.stroke(styledata
.path
, styledata
.lineattrs
)
483 def drawpoint(self
, graph
, styledata
):
484 self
.appendlinebasepoints(graph
, styledata
)
485 if not styledata
.validvpos
:
486 self
.addpointstopath(styledata
)
489 class errorbars(_style
):
491 defaulterrorbarattrs
= []
493 def __init__(self
, size
=0.1*unit
.v_cm
,
497 self
.errorbarattrs
= errorbarattrs
498 self
.epsilon
= epsilon
500 def selectstyle(self
, selectindex
, selecttotal
, styledata
):
501 styledata
.errorsize_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
502 styledata
.errorbarattrs
= attr
.selectattrs(self
.defaulterrorbarattrs
+ self
.errorbarattrs
, selectindex
, selecttotal
)
504 def initdrawpoints(self
, graph
, styledata
):
505 styledata
.errorbarcanvas
= graph
.insert(canvas
.canvas())
506 styledata
.errorlist
= []
507 if styledata
.errorbarattrs
is not None:
509 for axisname
in graph
.axisnames
:
510 if styledata
.index
[axisname
].keys() != ["x"]:
511 styledata
.errorlist
.append((axisname
, axisindex
))
514 def doerrorbars(self
, styledata
):
515 # errorbar loop over the different direction having errorbars
516 for erroraxisname
, erroraxisindex
in styledata
.errorlist
:
517 # create path for errorbars
518 errorpath
= path
.path()
519 errorpath
+= styledata
.graph
.vgeodesic(*(vminpos
+ vmaxpos
))
521 for axisname
in styledata
.graph
.axisnames
:
522 if axisname
!= erroraxisname
:
523 errorpath
+= styledata
.graph
.vcap_pt(axisname
, styledata
.errorsize_pt
, *vcap
)
526 if len(errorpath
.path
):
527 styledata
.errorbarcanvas
.stroke(errorpath
, styledata
.errorbarattrs
)
529 def drawpoint(self
, graph
, styledata
):
530 self
.doerrorbars(styledata
)
535 defaulttextattrs
= [textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
]
537 def __init__(self
, textdx
=0*unit
.v_cm
, textdy
=0.3*unit
.v_cm
, textattrs
=[], **kwargs
):
540 self
.textattrs
= textattrs
541 symbol
.__init
__(self
, **kwargs
)
543 def setdata(self
, graph
, columns
, styledata
):
544 columns
= columns
.copy()
545 styledata
.textindex
= columns
["text"]
547 return symbol
.setdata(self
, graph
, columns
, styledata
)
549 def selectstyle(self
, selectindex
, selecttotal
, styledata
):
550 if self
.textattrs
is not None:
551 styledata
.textattrs
= attr
.selectattrs(self
.defaulttextattrs
+ self
.textattrs
, selectindex
, selecttotal
)
553 styledata
.textattrs
= None
554 symbol
.selectstyle(self
, selectindex
, selecttotal
, styledata
)
556 def drawsymbol_pt(self
, c
, x
, y
, styledata
, point
=None):
557 symbol
.drawsymbol_pt(self
, c
, x
, y
, styledata
, point
)
558 if None not in (x
, y
, point
[styledata
.textindex
]) and styledata
.textattrs
is not None:
559 c
.text_pt(x
+ styledata
.textdx_pt
, y
+ styledata
.textdy_pt
, str(point
[styledata
.textindex
]), styledata
.textattrs
)
561 def drawpoints(self
, points
, graph
, styledata
):
562 styledata
.textdx_pt
= unit
.topt(self
.textdx
)
563 styledata
.textdy_pt
= unit
.topt(self
.textdy
)
564 symbol
.drawpoints(self
, points
, graph
, styledata
)
569 defaultlineattrs
= []
570 defaultarrowattrs
= []
572 def __init__(self
, linelength
=0.25*unit
.v_cm
, arrowsize
=0.15*unit
.v_cm
, lineattrs
=[], arrowattrs
=[], epsilon
=1e-10):
573 self
.linelength
= linelength
574 self
.arrowsize
= arrowsize
575 self
.lineattrs
= lineattrs
576 self
.arrowattrs
= arrowattrs
577 self
.epsilon
= epsilon
579 def setdata(self
, graph
, columns
, styledata
):
580 if len(graph
.axisnames
) != 2:
581 raise TypeError("arrow style restricted on two-dimensional graphs")
582 columns
= columns
.copy()
583 styledata
.xaxis
, styledata
.xindex
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.axisnames
[0]))
584 styledata
.yaxis
, styledata
.yindex
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.axisnames
[1]))
585 styledata
.sizeindex
= columns
["size"]
587 styledata
.angleindex
= columns
["angle"]
591 def adjustaxes(self
, points
, columns
, styledata
):
592 if styledata
.xindex
in columns
:
593 styledata
.xaxis
.adjustrange(points
, styledata
.xindex
)
594 if styledata
.yindex
in columns
:
595 styledata
.yaxis
.adjustrange(points
, styledata
.yindex
)
597 def selectstyle(self
, selectindex
, selecttotal
, styledata
):
598 if self
.lineattrs
is not None:
599 styledata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
601 styledata
.lineattrs
= None
602 if self
.arrowattrs
is not None:
603 styledata
.arrowattrs
= attr
.selectattrs(self
.defaultarrowattrs
+ self
.arrowattrs
, selectindex
, selecttotal
)
605 styledata
.arrowattrs
= None
607 def drawpoints(self
, points
, graph
, styledata
):
608 if styledata
.lineattrs
is not None and styledata
.arrowattrs
is not None:
609 linelength_pt
= unit
.topt(self
.linelength
)
611 xpos
, ypos
= graph
.pos_pt(point
[styledata
.xindex
], point
[styledata
.yindex
], xaxis
=styledata
.xaxis
, yaxis
=styledata
.yaxis
)
612 if point
[styledata
.sizeindex
] > self
.epsilon
:
613 dx
= math
.cos(point
[styledata
.angleindex
]*math
.pi
/180)
614 dy
= math
.sin(point
[styledata
.angleindex
]*math
.pi
/180)
615 x1
= xpos
-0.5*dx
*linelength_pt
*point
[styledata
.sizeindex
]
616 y1
= ypos
-0.5*dy
*linelength_pt
*point
[styledata
.sizeindex
]
617 x2
= xpos
+0.5*dx
*linelength_pt
*point
[styledata
.sizeindex
]
618 y2
= ypos
+0.5*dy
*linelength_pt
*point
[styledata
.sizeindex
]
619 graph
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), styledata
.lineattrs
+
620 [deco
.earrow(styledata
.arrowattrs
, size
=self
.arrowsize
*point
[styledata
.sizeindex
])])
625 def __init__(self
, palette
=color
.palette
.Gray
):
626 self
.palette
= palette
628 def setdata(self
, graph
, columns
, styledata
):
629 if len(graph
.axisnames
) != 2:
630 raise TypeError("arrow style restricted on two-dimensional graphs")
631 columns
= columns
.copy()
632 styledata
.xaxis
, styledata
.xminindex
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.axisnames
[0]))
633 styledata
.yaxis
, styledata
.yminindex
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.axisnames
[1]))
634 xaxis
, styledata
.xmaxindex
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.axisnames
[0]))
635 yaxis
, styledata
.ymaxindex
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.axisnames
[1]))
636 if xaxis
!= styledata
.xaxis
or yaxis
!= styledata
.yaxis
:
637 raise ValueError("min/max values should use the same axes")
638 styledata
.colorindex
= columns
["color"]
642 def selectstyle(self
, selectindex
, selecttotal
, styledata
):
645 def adjustaxes(self
, points
, columns
, styledata
):
646 if styledata
.xminindex
in columns
:
647 styledata
.xaxis
.adjustrange(points
, styledata
.xminindex
)
648 if styledata
.xmaxindex
in columns
:
649 styledata
.xaxis
.adjustrange(points
, styledata
.xmaxindex
)
650 if styledata
.yminindex
in columns
:
651 styledata
.yaxis
.adjustrange(points
, styledata
.yminindex
)
652 if styledata
.ymaxindex
in columns
:
653 styledata
.yaxis
.adjustrange(points
, styledata
.ymaxindex
)
655 def drawpoints(self
, points
, graph
, styledata
):
656 # TODO: bbox shortcut
657 c
= graph
.insert(canvas
.canvas())
658 lastcolorvalue
= None
661 xvmin
= styledata
.xaxis
.convert(point
[styledata
.xminindex
])
662 xvmax
= styledata
.xaxis
.convert(point
[styledata
.xmaxindex
])
663 yvmin
= styledata
.yaxis
.convert(point
[styledata
.yminindex
])
664 yvmax
= styledata
.yaxis
.convert(point
[styledata
.ymaxindex
])
665 colorvalue
= point
[styledata
.colorindex
]
666 if colorvalue
!= lastcolorvalue
:
667 color
= self
.palette
.getcolor(point
[styledata
.colorindex
])
670 if ((xvmin
< 0 and xvmax
< 0) or (xvmin
> 1 and xvmax
> 1) or
671 (yvmin
< 0 and yvmax
< 0) or (yvmin
> 1 and yvmax
> 1)):
689 p
= graph
.vgeodesic(xvmin
, yvmin
, xvmax
, yvmin
)
690 p
.append(graph
.vgeodesic_el(xvmax
, yvmin
, xvmax
, yvmax
))
691 p
.append(graph
.vgeodesic_el(xvmax
, yvmax
, xvmin
, yvmax
))
692 p
.append(graph
.vgeodesic_el(xvmin
, yvmax
, xvmin
, yvmin
))
693 p
.append(path
.closepath())
694 if colorvalue
!= lastcolorvalue
:
700 defaultfrompathattrs
= []
701 defaultbarattrs
= [color
.palette
.Rainbow
, deco
.stroked([color
.gray
.black
])]
703 def __init__(self
, fromvalue
=None, frompathattrs
=[], barattrs
=[], subnames
=None, epsilon
=1e-10):
704 self
.fromvalue
= fromvalue
705 self
.frompathattrs
= frompathattrs
706 self
.barattrs
= barattrs
707 self
.subnames
= subnames
708 self
.epsilon
= epsilon
710 def setdata(self
, graph
, columns
, styledata
):
711 # TODO: remove limitation to 2d graphs
712 if len(graph
.axisnames
) != 2:
713 raise TypeError("arrow style currently restricted on two-dimensional graphs")
714 columns
= columns
.copy()
715 xvalue
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.axisnames
[0]))
716 yvalue
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.axisnames
[1]))
717 if (xvalue
is None and yvalue
is None) or (xvalue
is not None and yvalue
is not None):
718 raise TypeError("must specify exactly one value axis")
719 if xvalue
is not None:
720 styledata
.valuepos
= 0
721 styledata
.nameaxis
, styledata
.nameindex
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)name$" % graph
.axisnames
[1]))
722 styledata
.valueaxis
= xvalue
[0]
723 styledata
.valueindices
= [xvalue
[1]]
725 styledata
.valuepos
= 1
726 styledata
.nameaxis
, styledata
.nameindex
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)name$" % graph
.axisnames
[0]))
727 styledata
.valueaxis
= yvalue
[0]
728 styledata
.valueindices
= [yvalue
[1]]
732 valueaxis
, valueindex
= _style
.setdatapattern(self
, graph
, columns
, re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)stack%i$" % (graph
.axisnames
[styledata
.valuepos
], i
)))
735 if styledata
.valueaxis
!= valueaxis
:
736 raise ValueError("different value axes for stacked bars")
737 styledata
.valueindices
.append(valueindex
)
741 def selectstyle(self
, selectindex
, selecttotal
, styledata
):
743 styledata
.frompathattrs
= None
745 styledata
.frompathattrs
= self
.defaultfrompathattrs
+ self
.frompathattrs
747 if self
.barattrs
is not None:
748 styledata
.barattrs
= attr
.selectattrs(self
.defaultbarattrs
+ self
.barattrs
, selectindex
, selecttotal
)
750 styledata
.barattrs
= None
752 styledata
.barattrs
= self
.defaultbarattrs
+ self
.barattrs
753 styledata
.selectindex
= selectindex
754 styledata
.selecttotal
= selecttotal
755 if styledata
.selecttotal
!= 1 and self
.subnames
is not None:
756 raise ValueError("subnames not allowed when iterating over bars")
758 def adjustaxes(self
, points
, columns
, styledata
):
759 if styledata
.nameindex
in columns
:
760 if styledata
.selecttotal
== 1:
761 styledata
.nameaxis
.adjustrange(points
, styledata
.nameindex
, subnames
=self
.subnames
)
763 for i
in range(styledata
.selecttotal
):
764 styledata
.nameaxis
.adjustrange(points
, styledata
.nameindex
, subnames
=[i
])
765 for valueindex
in styledata
.valueindices
:
766 if valueindex
in columns
:
767 styledata
.valueaxis
.adjustrange(points
, valueindex
)
769 def drawpoints(self
, points
, graph
, styledata
):
770 if self
.fromvalue
is not None:
771 vfromvalue
= styledata
.valueaxis
.convert(self
.fromvalue
)
772 if vfromvalue
< -self
.epsilon
:
774 if vfromvalue
> 1 + self
.epsilon
:
776 if styledata
.frompathattrs
is not None and vfromvalue
> self
.epsilon
and vfromvalue
< 1 - self
.epsilon
:
777 if styledata
.valuepos
:
778 p
= graph
.vgeodesic(0, vfromvalue
, 1, vfromvalue
)
780 p
= graph
.vgeodesic(vfromvalue
, 0, vfromvalue
, 1)
781 graph
.stroke(p
, styledata
.frompathattrs
)
784 l
= len(styledata
.valueindices
)
788 barattrslist
.append(attr
.selectattrs(styledata
.barattrs
, i
, l
))
790 barattrslist
= [styledata
.barattrs
]
792 vvaluemax
= vfromvalue
793 for valueindex
, barattrs
in zip(styledata
.valueindices
, barattrslist
):
794 vvaluemin
= vvaluemax
796 vvaluemax
= styledata
.valueaxis
.convert(point
[valueindex
])
800 if styledata
.selecttotal
== 1:
802 vnamemin
= styledata
.nameaxis
.convert((point
[styledata
.nameindex
], 0))
806 vnamemax
= styledata
.nameaxis
.convert((point
[styledata
.nameindex
], 1))
811 vnamemin
= styledata
.nameaxis
.convert((point
[styledata
.nameindex
], styledata
.selectindex
, 0))
815 vnamemax
= styledata
.nameaxis
.convert((point
[styledata
.nameindex
], styledata
.selectindex
, 1))
819 if styledata
.valuepos
:
820 p
= graph
.vgeodesic(vnamemin
, vvaluemin
, vnamemin
, vvaluemax
)
821 p
.append(graph
.vgeodesic_el(vnamemin
, vvaluemax
, vnamemax
, vvaluemax
))
822 p
.append(graph
.vgeodesic_el(vnamemax
, vvaluemax
, vnamemax
, vvaluemin
))
823 p
.append(graph
.vgeodesic_el(vnamemax
, vvaluemin
, vnamemin
, vvaluemin
))
824 p
.append(path
.closepath())
826 p
= graph
.vgeodesic(vvaluemin
, vnamemin
, vvaluemin
, vnamemax
)
827 p
.append(graph
.vgeodesic_el(vvaluemin
, vnamemax
, vvaluemax
, vnamemax
))
828 p
.append(graph
.vgeodesic_el(vvaluemax
, vnamemax
, vvaluemax
, vnamemin
))
829 p
.append(graph
.vgeodesic_el(vvaluemax
, vnamemin
, vvaluemin
, vnamemin
))
830 p
.append(path
.closepath())
831 if barattrs
is not None:
832 graph
.fill(p
, barattrs
)
834 def key_pt(self
, c
, x_pt
, y_pt
, width_pt
, height_pt
, styledata
):
835 l
= len(styledata
.valueindices
)
838 c
.fill(path
.rect_pt(x_pt
+i
*width_pt
/l
, y_pt
, width_pt
/l
, height_pt
), attr
.selectattrs(styledata
.barattrs
, i
, l
))
840 c
.fill(path
.rect_pt(x_pt
, y_pt
, width_pt
, height_pt
), styledata
.barattrs
)