Module generativepy.graph
Classes
class Axes (ctx, position, width, height, appearance=None)
-
Controls the range and appearance of a set of Cartesian axes
Expand source code
class Axes: ''' Controls the range and appearance of a set of Cartesian axes ''' def __init__(self, ctx, position, width, height, appearance=None): self.ctx = ctx self.position = position self.width = width self.height = height self.appearance = dataclasses.replace(appearance) if appearance is not None else AxesAppearance() def of_start(self, start): ''' Sets the start value of the axes Args: start: (x, y) value of bottom left corner of axes Returns: self ''' self.appearance.start = start return self def of_extent(self, extent): ''' Sets the range of the axes Args: extent: (x, y) range of axes Returns: self ''' self.appearance.extent = extent return self def with_feature_scale(self, scale): ''' Sets the scale of the features. For example a value of 2 will make all the gridlines and label text on the axes twice as big. This is a quick way of resizing everything in one go. Args: scale: scale factor Returns: self ''' self.appearance.featurescale = scale return self def with_divisions(self, divisions): ''' Set divisions spacing Args: divisions: (x, y) spacing divisions in each direction Returns: self ''' self.appearance.divisions = divisions return self def with_axis_positions(self, x_axis_pos, y_axis_pos): ''' Set axis positions x and y axes can be individually positioned at zero, at the minimum edge, at the maximum edge, or not shows at all Args: x_axis_pos: Position of x axis y_axis_pos: Position of y axis Returns: self ''' self.appearance.x_axis_pos = x_axis_pos self.appearance.y_axis_pos = y_axis_pos return self def with_division_formatters(self, x_div_formatter=None, y_div_formatter=None): self.appearance.x_div_formatter = x_div_formatter self.appearance.y_div_formatter = y_div_formatter return self def with_subdivisions(self, factor): ''' Draw subdivision lines on graph Args: factor: (x, y) Number of subdivisions per division in each direction Returns: self ''' self.appearance.subdivisions = True self.appearance.subdivisionfactor = factor return self def with_border(self, has_border): ''' Draw border around graph, The border will be drawn with the style of the axis lines. Args: has_border: Boolean determines if border is drawn around axis. Returns: self ''' self.appearance.border = has_border return self def with_ticklabeloffset(self, ticklabeloffset): ''' Use the tick label offset. The tick text is offset from the tick position on the axis by a fixed amount in the x amd y directions. This offset is equal to the text height of the labels divided by the ticklabeloffset. The ticklabeloffset defaults to 1.1, which usually works well provided the divisions are reasonably far apart. If the divisions are quite close, or ifthe label font is unusually large or small, try adjsuting this value. The larger tha value, the closer the text will be to the axis. Args: ticklabeloffset: Number, the offset Returns: self ''' self.appearance.ticklabeloffset = ticklabeloffset return self def background(self, pattern): ''' Sets the entire graph background Args: pattern: color or fill pattern Returns: self ''' self.appearance.background = FillParameters(pattern) return self def text_color(self, pattern): ''' Sets the color of the axes text Args: pattern: color or pattern Returns: self ''' self.appearance.textcolor = pattern return self def text_style(self, font="arial", weight=FONT_WEIGHT_BOLD, slant=FONT_SLANT_NORMAL, size=15): ''' Set the style of the axis text Args: font: Font name weight: Font weight slant: Font slant size: Font size in units. This will be multiplied by the featurescale value. Returns: self ''' self.appearance.fontparams = FontParameters(font, weight, slant, size) return self def axis_linestyle(self, pattern=Color(0), line_width=None, dash=None, cap=None, join=None, miter_limit=None): ''' Sets the style of the axis lines Args: pattern: the fill pattern or color to use for the outline, None for default line_width: width of stroke line, None for default dash: dash patter of line, as for Pycairo, None for default cap: line end style, None for default join: line join style, None for default miter_limit: mitre limit, None for default Returns: self ''' self.appearance.axislines = StrokeParameters(pattern, line_width, dash, cap, join, miter_limit) return self def division_linestyle(self, pattern=Color(0), line_width=None, dash=None, cap=None, join=None, miter_limit=None): ''' Sets the style of the division lines Args: pattern: the fill pattern or color to use for the outline, None for default line_width: width of stroke line, None for default dash: dash patter of line, as for Pycairo, None for default cap: line end style, None for default join: line join style, None for default miter_limit: mitre limit, None for default Returns: self ''' self.appearance.divlines = StrokeParameters(pattern, line_width, dash, cap, join, miter_limit) return self def subdivision_linestyle(self, pattern=Color(0), line_width=None, dash=None, cap=None, join=None, miter_limit=None): ''' Sets the style of the subdivision lines Args: pattern: the fill pattern or color to use for the outline, None for default line_width: width of stroke line, None for default dash: dash patter of line, as for Pycairo, None for default cap: line end style, None for default join: line join style, None for default miter_limit: mitre limit, None for default Returns: self ''' self.appearance.subdivlines = StrokeParameters(pattern, line_width, dash, cap, join, miter_limit) return self def draw(self): ''' Draw the axes ''' self.ctx.new_path() # Get the text height using the selected font. This is used to control text offset and other sizes. _, self.appearance.text_height = Text(self.ctx).of('0', (0, 0)) \ .font(self.appearance.fontparams.font, self.appearance.fontparams.weight, self.appearance.fontparams.slant) \ .size(self.appearance.fontparams.size * self.appearance.featurescale) \ .get_size() self.clip() self._draw_background() if self.appearance.subdivisions: self._draw_subdivlines() self._draw_divlines() self.unclip() self._draw_axes() if self.appearance.border: self._draw_border() def _draw_background(self): self.appearance.background.apply(self.ctx) self.ctx.rectangle(self.position[0], self.position[1], self.width, self.height) self.ctx.fill() def _draw_border(self): params = copy.copy(self.appearance.axislines) params.line_width *= self.appearance.featurescale params.apply(self.ctx) self.ctx.rectangle(self.position[0], self.position[1], self.width, self.height) self.ctx.stroke() def _draw_divlines(self): params = copy.copy(self.appearance.divlines) params.line_width *= self.appearance.featurescale params.apply(self.ctx) for p in self._get_divs(self.appearance.start [0], self.appearance.extent[0], self.appearance.divisions[0]): self.ctx.move_to(*self.transform_from_graph((p, self.appearance.start [1]))) self.ctx.line_to(*self.transform_from_graph((p, self.appearance.start [1] + self.appearance.extent[1]))) for p in self._get_divs(self.appearance.start [1], self.appearance.extent[1], self.appearance.divisions[1]): self.ctx.move_to(*self.transform_from_graph((self.appearance.start [0], p))) self.ctx.line_to(*self.transform_from_graph((self.appearance.start [0] + self.appearance.extent[0], p))) self.ctx.stroke() def _draw_subdivlines(self): params = copy.copy(self.appearance.subdivlines) params.line_width *= self.appearance.featurescale params.apply(self.ctx) for p in self._get_subdivs(self.appearance.start [0], self.appearance.extent[0], self.appearance.divisions[0], self.appearance.subdivisionfactor[0]): self.ctx.move_to(*self.transform_from_graph((p, self.appearance.start [1]))) self.ctx.line_to(*self.transform_from_graph((p, self.appearance.start [1] + self.appearance.extent[1]))) for p in self._get_subdivs(self.appearance.start [1], self.appearance.extent[1], self.appearance.divisions[1], self.appearance.subdivisionfactor[1]): self.ctx.move_to(*self.transform_from_graph((self.appearance.start [0], p))) self.ctx.line_to(*self.transform_from_graph((self.appearance.start [0] + self.appearance.extent[0], p))) self.ctx.stroke() def _draw_axes(self): line_params = copy.copy(self.appearance.axislines) line_params.line_width *= self.appearance.featurescale xoffset = self.appearance.text_height / self.appearance.ticklabeloffset yoffset = self.appearance.text_height / self.appearance.ticklabeloffset has_origin_marker = self.appearance.x_axis_pos == AXIS_ZERO and self.appearance.y_axis_pos == AXIS_ZERO self.clip_x() tick_direction = -1 if self.appearance.x_axis_pos == AXIS_MAX else 1 tick_align = (drawing.RIGHT, drawing.BOTTOM) if self.appearance.x_axis_pos == AXIS_MAX else (drawing.RIGHT, drawing.TOP) if self.appearance.x_axis_pos != AXIS_NONE: axis_line_pos = 0 if self.appearance.x_axis_pos == AXIS_ZERO else self.appearance.start[1] if self.appearance.x_axis_pos == AXIS_MIN else self.appearance.start[1] + self.appearance.extent[1] line_params.apply(self.ctx) self.ctx.move_to(*self.transform_from_graph((self.appearance.start[0], axis_line_pos))) self.ctx.line_to(*self.transform_from_graph((self.appearance.start[0] + self.appearance.extent[0], axis_line_pos))) self.ctx.stroke() for p in self._get_divs(self.appearance.start [0], self.appearance.extent[0], self.appearance.divisions[0]): if abs(p)>0.001 or not has_origin_marker: position = self.transform_from_graph((p, axis_line_pos)) pstr = self._format_div(p, self.appearance.divisions[0], self.appearance.x_div_formatter) Text(self.ctx).of(pstr, (position[0] - xoffset, position[1] + yoffset*tick_direction)) \ .font(self.appearance.fontparams.font, self.appearance.fontparams.weight, self.appearance.fontparams.slant) \ .size(self.appearance.fontparams.size*self.appearance.featurescale) \ .align(*tick_align).fill(self.appearance.textcolor) line_params.apply(self.ctx) self.ctx.new_path() self.ctx.move_to(position[0], position[1]) self.ctx.line_to(position[0], position[1] + yoffset*tick_direction) self.ctx.stroke() self.unclip() self.clip_y() tick_direction = -1 if self.appearance.y_axis_pos == AXIS_MAX else 1 tick_align = (drawing.LEFT, drawing.TOP) if self.appearance.y_axis_pos == AXIS_MAX else (drawing.RIGHT, drawing.TOP) if self.appearance.y_axis_pos != AXIS_NONE: axis_line_pos = 0 if self.appearance.y_axis_pos == AXIS_ZERO else self.appearance.start[0] if self.appearance.y_axis_pos == AXIS_MIN else self.appearance.start[0] + self.appearance.extent[0] line_params.apply(self.ctx) self.ctx.move_to(*self.transform_from_graph((axis_line_pos, self.appearance.start[1]))) self.ctx.line_to(*self.transform_from_graph((axis_line_pos, self.appearance.start[1] + self.appearance.extent[1]))) self.ctx.stroke() for p in self._get_divs(self.appearance.start [1], self.appearance.extent[1], self.appearance.divisions[1]): if abs(p)>0.001 or not has_origin_marker: position = self.transform_from_graph((axis_line_pos, p)) pstr = self._format_div(p, self.appearance.divisions[1], self.appearance.y_div_formatter) Text(self.ctx).of(pstr, (position[0] - xoffset*tick_direction, position[1] + yoffset)) \ .font(self.appearance.fontparams.font, self.appearance.fontparams.weight, self.appearance.fontparams.slant) \ .size(self.appearance.fontparams.size*self.appearance.featurescale) \ .align(*tick_align).fill(self.appearance.textcolor) line_params.apply(self.ctx) self.ctx.new_path() self.ctx.move_to(position[0], position[1]) self.ctx.line_to(position[0] - xoffset*tick_direction, position[1]) self.ctx.stroke() self.unclip() if has_origin_marker: line_params.apply(self.ctx) self.ctx.new_path() self.ctx.arc(*self.transform_from_graph((0, 0)), self.appearance.text_height/1.1, 0, 2 * math.pi) self.ctx.stroke() def clip_x(self): ''' Set the clip region to width of the axes area. The height clip allows a region above and below the graph to be painted ''' self.ctx.rectangle(self.position[0], self.position[1] - self.height, self.width, 3*self.height) self.ctx.save() self.ctx.clip() def clip_y(self): ''' Set the clip region to height of the axes area. The width clip allows a region toe the left and right of the graph to be painted ''' self.ctx.rectangle(self.position[0] - self.width, self.position[1], 3*self.width, self.height) self.ctx.save() self.ctx.clip() def clip(self): ''' Set the clip region to the axes area. ''' self.ctx.rectangle(*self.position, self.width, self.height) self.ctx.save() self.ctx.clip() def unclip(self): ''' Undo a previous clip() ''' self.ctx.restore() def _get_divs(self, start, extent, div): divs = [] n = math.ceil(start/div)*div while n <= start + extent: divs.append(n) n += div return divs def _contains(self, values, value, tolerance): ''' Return true if the sequence values contains the value to within a given tolerance Args: values value tolerance Returns: Result ''' for v in values: if abs(value - v) < tolerance: return True return False def _get_subdivs(self, start, extent, div, factor): subdiv = div/factor divs = self._get_divs(start, extent, div) subdivs = [] n = math.ceil(start/subdiv)*subdiv while n <= start + extent: if not self._contains(divs, subdiv, extent/100000): subdivs.append(n) n += subdiv return subdivs def _format_div(self, value, div, formatter): """ Formats a division value into a string. If the division spacing is an integer, the string will be an integer (no dp). If the division spacing is float, the string will be a float with a suitable number of decimal places Args: value: value to be formatted div: division spacing formatter: formatting function, accepts vale and div, returns a formatted value string Returns: String representation of the value """ if formatter: return formatter(value, div) if isinstance(value, int): return str(value) return str(round(value*1000)/1000) def transform_from_graph(self, point): ''' Coverts a point/list of points defined in axes coordinates to the equivalent vector(s) in user space Args: point: Either a single point (a sequence of 2 numbers), or a sequence of points Returns: User space vector ''' def _transform_point(point): # Transform a single point if not (hasattr(point, "__getitem__") and hasattr(point, "__iter__") and hasattr(point, "__len__")): raise TypeError("point must be a List, Tuple or Vector") if len(point) != 2: raise ValueError("point must have 2 elements") x = ((point[0] - self.appearance.start [0]) * self.width / self.appearance.extent[0]) + self.position[0] y = self.height + self.position[1] - ((point[1] - self.appearance.start [1]) * self.height / self.appearance.extent[1]) return V(x, y) if not (hasattr(point, "__getitem__") and hasattr(point, "__iter__") and hasattr(point, "__len__")): raise TypeError("point must be a List, Tuple or Vector") if len(point) > 0 and isinstance(point[0], (int, float)): # If there is at least one element and it is a number, assume it is a single point return _transform_point(point) else: # All other cases assume it is a list of (zero or more) points return [_transform_point(p) for p in point]
Methods
def axis_linestyle(self, pattern=<generativepy.color.Color object>, line_width=None, dash=None, cap=None, join=None, miter_limit=None)
-
Sets the style of the axis lines
Args
pattern
- the fill pattern or color to use for the outline, None for default
line_width
- width of stroke line, None for default
dash
- dash patter of line, as for Pycairo, None for default
cap
- line end style, None for default
join
- line join style, None for default
miter_limit
- mitre limit, None for default
Returns
self
def background(self, pattern)
-
Sets the entire graph background
Args
pattern
- color or fill pattern
Returns
self
def clip(self)
-
Set the clip region to the axes area.
def clip_x(self)
-
Set the clip region to width of the axes area. The height clip allows a region above and below the graph to be painted
def clip_y(self)
-
Set the clip region to height of the axes area. The width clip allows a region toe the left and right of the graph to be painted
def division_linestyle(self, pattern=<generativepy.color.Color object>, line_width=None, dash=None, cap=None, join=None, miter_limit=None)
-
Sets the style of the division lines
Args
pattern
- the fill pattern or color to use for the outline, None for default
line_width
- width of stroke line, None for default
dash
- dash patter of line, as for Pycairo, None for default
cap
- line end style, None for default
join
- line join style, None for default
miter_limit
- mitre limit, None for default
Returns
self
def draw(self)
-
Draw the axes
def of_extent(self, extent)
-
Sets the range of the axes
Args
extent
- (x, y) range of axes
Returns
self
def of_start(self, start)
-
Sets the start value of the axes
Args
start
- (x, y) value of bottom left corner of axes
Returns
self
def subdivision_linestyle(self, pattern=<generativepy.color.Color object>, line_width=None, dash=None, cap=None, join=None, miter_limit=None)
-
Sets the style of the subdivision lines
Args
pattern
- the fill pattern or color to use for the outline, None for default
line_width
- width of stroke line, None for default
dash
- dash patter of line, as for Pycairo, None for default
cap
- line end style, None for default
join
- line join style, None for default
miter_limit
- mitre limit, None for default
Returns
self
def text_color(self, pattern)
-
Sets the color of the axes text
Args
pattern
- color or pattern
Returns
self
def text_style(self, font='arial', weight=1, slant=0, size=15)
-
Set the style of the axis text
Args
font
- Font name
weight
- Font weight
slant
- Font slant
size
- Font size in units. This will be multiplied by the featurescale value.
Returns
self
def transform_from_graph(self, point)
-
Coverts a point/list of points defined in axes coordinates to the equivalent vector(s) in user space
Args
point
- Either a single point (a sequence of 2 numbers), or a sequence of points
Returns
User space vector
def unclip(self)
-
Undo a previous clip()
def with_axis_positions(self, x_axis_pos, y_axis_pos)
-
Set axis positions
x and y axes can be individually positioned at zero, at the minimum edge, at the maximum edge, or not shows at all
Args
x_axis_pos
- Position of x axis
y_axis_pos
- Position of y axis
Returns
self
def with_border(self, has_border)
-
Draw border around graph, The border will be drawn with the style of the axis lines.
Args
has_border
- Boolean determines if border is drawn around axis.
Returns
self
def with_division_formatters(self, x_div_formatter=None, y_div_formatter=None)
def with_divisions(self, divisions)
-
Set divisions spacing
Args
divisions
- (x, y) spacing divisions in each direction
Returns
self
def with_feature_scale(self, scale)
-
Sets the scale of the features. For example a value of 2 will make all the gridlines and label text on the axes twice as big. This is a quick way of resizing everything in one go.
Args
scale
- scale factor
Returns
self
def with_subdivisions(self, factor)
-
Draw subdivision lines on graph
Args
factor
- (x, y) Number of subdivisions per division in each direction
Returns
self
def with_ticklabeloffset(self, ticklabeloffset)
-
Use the tick label offset. The tick text is offset from the tick position on the axis by a fixed amount in the x amd y directions.
This offset is equal to the text height of the labels divided by the ticklabeloffset. The ticklabeloffset defaults to 1.1, which usually works well provided the divisions are reasonably far apart. If the divisions are quite close, or ifthe label font is unusually large or small, try adjsuting this value. The larger tha value, the closer the text will be to the axis.
Args
ticklabeloffset
- Number, the offset
Returns
self
class AxesAppearance (background:
= <factory>, textcolor: = <generativepy.color.Color object>, fontparams: = <factory>, divlines: = <factory>, subdivlines: = <factory>, axislines: = <factory>, featurescale: = 1, divisions: = (1, 1), subdivisions: = False, subdivisionfactor: = (1, 1), text_height: = 0, x_div_formatter: = None, y_div_formatter: = None, start: = (0, 0), extent: = (10, 10), border: = False, x_axis_pos: int = 1, y_axis_pos: int = 1, ticklabeloffset: = 1.1) -
Parameters that control the appearance and position of the axes (colours, line styles).
Expand source code
@dataclass class AxesAppearance: ''' Parameters that control the appearance and position of the axes (colours, line styles). ''' background: any = dataclasses.field(default_factory=lambda: FillParameters(Color(1))) textcolor: any = Color(0.2) fontparams: any = dataclasses.field(default_factory=lambda: FontParameters('arial', size=15, weight=FONT_WEIGHT_BOLD)) divlines: any = dataclasses.field(default_factory=lambda: StrokeParameters(Color(0.8, 0.8, 1), line_width=2, cap=BUTT)) subdivlines: any = dataclasses.field(default_factory=lambda: StrokeParameters(Color(0.9, 0.9, 1), line_width=2, cap=BUTT)) axislines: any = dataclasses.field(default_factory=lambda: StrokeParameters(Color(0.2), line_width=2, cap=BUTT)) featurescale: any = 1 divisions: any = (1, 1) subdivisions: any = False subdivisionfactor: any = (1, 1) text_height: any = 0 x_div_formatter: any = None y_div_formatter: any = None start: any = (0, 0) extent: any = (10, 10) border: any = False x_axis_pos: int = AXIS_ZERO y_axis_pos: int = AXIS_ZERO # x and y offset of tick labels. The actual offset is: # text height (height of 0 character using fontparams) DIVIDED by ticklabeloffset ticklabeloffset: any = 1.1
Class variables
var axislines :
var background :
var border :
var divisions :
var divlines :
var extent :
var featurescale :
var fontparams :
var start :
var subdivisionfactor :
var subdivisions :
var subdivlines :
var text_height :
var textcolor :
var ticklabeloffset :
var x_axis_pos : int
var x_div_formatter :
var y_axis_pos : int
var y_div_formatter :
class Plot (axes)
-
Plot a function in a set of axes.
Args
ctx
- Pycairo drawing context - The context to draw on.
Returns
self
Expand source code
class Plot(Shape): ''' Plot a function in a set of axes. ''' def __init__(self, axes): super().__init__(axes.ctx) self.axes = axes self.points = [] self.closed = False def add(self): self._do_path_() first = True for p in self.points: if first: if not self.extend: self.ctx.move_to(*p) first = False else: self.ctx.line_to(*p) if self.closed or self.final_close: self.ctx.close_path() return self def stroke(self, pattern=None, line_width=2, dash=None, cap=None, join=None, miter_limit=None): ''' Stroke overrides the Shape stroke() method. It clips the stroke to the area of the axes. This ensures that if the curve goes out of range it will not interfere with other parts of the image. Args: pattern line_width dash cap join miter_limit Returns: self ''' super().stroke(pattern, line_width, dash, cap, join, miter_limit) def of_function(self, fn, extent=None, precision=100, close=()): ''' Plot a function y = fn(x) Args: fn: the function to plot. It must take a single argument extent: the range of x values to plot. If not supplied, the plot will use the full range of the axes. precision: number of points to plot. Defaults to 100. This can be increased if needed for hi res plots close: sequence of (x, y) points. One or more additional points, defined in axes coordinates, that will be added to the plot path to create a polygon. The polygon will also be closed. This allows an area under the curve to be filled. Returns: self ''' self.points = [] start = self.axes.appearance.start[0] end = self.axes.appearance.start[0] + self.axes.appearance.extent[0] if extent: start = max(start, extent[0]) end = min(end, extent[1]) self.points += [self.axes.transform_from_graph((x, fn(x))) for x in np.linspace(start, end, precision)] if close: self.points += [self.axes.transform_from_graph(p) for p in close] self.closed = True return self def of_xy_function(self, fn, extent=None, precision=100, close=()): ''' Plot a function x = fn(y) Args: fn: the function to plot. It must take a single argument extent: the range of y values to plot. If not supplied, the plot will use the full range of the axes. precision: number of points to plot. Defaults to 100. This can be increased if needed for hi res plots close: sequence of (x, y) points. One or more additional points, defined in axes coordinates, that will be added to the plot path to create a polygon. The polygon will also be closed. This allows an area under the curve to be filled. Returns: self ''' self.points = [] start = self.axes.appearance.start[1] end = self.axes.appearance.start[1] + self.axes.appearance.extent[1] if extent: start = max(start, extent[0]) end = min(end, extent[1]) self.points += [self.axes.transform_from_graph((fn(y), y)) for y in np.linspace(start, end, precision)] if close: self.points += [self.axes.transform_from_graph(p) for p in close] self.closed = True return self def of_polar_function(self, fn, extent=(0, 2*math.pi), precision=100, close=()): ''' Plot a polar function r = fn(theta). theta is measured in radians Args: fn: the function to plot. It must take a single argument extent: the range of theta values to plot. If not supplied, the plot will use the range 0 to 2*pi. precision: number of points to plot. Defaults to 100. This can be increased if needed for hi res plots close: sequence of (x, y) points. One or more additional points, defined in axes coordinates, that will be added to the plot path to create a polygon. The polygon will also be closed. This allows an area under the curve to be filled. Returns: self ''' self.points = [] for theta in np.linspace(extent[0], extent[1], precision): r = fn(theta) self.points.append(self.axes.transform_from_graph((r*math.cos(theta), r*math.sin(theta)))) if close: self.points += [self.axes.transform_from_graph(p) for p in close] self.closed = True return self def of_parametric_function(self, fx, fy, extent=(0, 1), precision=100, close=()): ''' Plot a parametric function x = fx(t), y = ft(t). Args: fx: x as a function of t. It must take a single argument fy: y as a function of t. It must take a single argument extent: the range of t values to plot. If not supplied the range 0 to 1 is used. precision: number of points to plot. Defaults to 100. This can be increased if needed for hi res plots close: sequence of (x, y) points. One or more additional points, defined in axes coordinates, that will be added to the plot path to create a polygon. The polygon will also be closed. This allows an area under the curve to be filled. Returns: self ''' self.points = [] for t in np.linspace(extent[0], extent[1], precision): x = fx(t) y = fy(t) self.points.append(self.axes.transform_from_graph((x, y))) if close: self.points += [self.axes.transform_from_graph(p) for p in close] self.closed = True return self
Ancestors
Methods
def of_function(self, fn, extent=None, precision=100, close=())
-
Plot a function y = fn(x)
Args
fn
- the function to plot. It must take a single argument
extent
- the range of x values to plot. If not supplied, the plot will use the full range of the axes.
precision
- number of points to plot. Defaults to 100. This can be increased if needed for hi res plots
close
- sequence of (x, y) points. One or more additional points, defined in axes coordinates, that will be added to the plot path to create a polygon. The polygon will also be closed. This allows an area under the curve to be filled.
Returns
self
def of_parametric_function(self, fx, fy, extent=(0, 1), precision=100, close=())
-
Plot a parametric function x = fx(t), y = ft(t).
Args
fx
- x as a function of t. It must take a single argument
fy
- y as a function of t. It must take a single argument
extent
- the range of t values to plot. If not supplied the range 0 to 1 is used.
precision
- number of points to plot. Defaults to 100. This can be increased if needed for hi res plots
close
- sequence of (x, y) points. One or more additional points, defined in axes coordinates, that will be added to the plot path to create a polygon. The polygon will also be closed. This allows an area under the curve to be filled.
Returns
self
def of_polar_function(self, fn, extent=(0, 6.283185307179586), precision=100, close=())
-
Plot a polar function r = fn(theta). theta is measured in radians
Args
fn
- the function to plot. It must take a single argument
extent
- the range of theta values to plot. If not supplied, the plot will use the range 0 to 2*pi.
precision
- number of points to plot. Defaults to 100. This can be increased if needed for hi res plots
close
- sequence of (x, y) points. One or more additional points, defined in axes coordinates, that will be added to the plot path to create a polygon. The polygon will also be closed. This allows an area under the curve to be filled.
Returns
self
def of_xy_function(self, fn, extent=None, precision=100, close=())
-
Plot a function x = fn(y)
Args
fn
- the function to plot. It must take a single argument
extent
- the range of y values to plot. If not supplied, the plot will use the full range of the axes.
precision
- number of points to plot. Defaults to 100. This can be increased if needed for hi res plots
close
- sequence of (x, y) points. One or more additional points, defined in axes coordinates, that will be added to the plot path to create a polygon. The polygon will also be closed. This allows an area under the curve to be filled.
Returns
self
def stroke(self, pattern=None, line_width=2, dash=None, cap=None, join=None, miter_limit=None)
-
Stroke overrides the Shape stroke() method. It clips the stroke to the area of the axes. This ensures that if the curve goes out of range it will not interfere with other parts of the image.
Args
pattern line_width dash cap join miter_limit
Returns
self
Inherited members
class Scatter (axes)
-
Plot a scatter chart in a set of axes. Note that a Scatter plot is not a
Shape
object. It simply draws a scatter plot on the supplied axes.Expand source code
class Scatter: ''' Plot a scatter chart in a set of axes. Note that a Scatter plot is not a `Shape` object. It simply draws a scatter plot on the supplied axes. ''' def __init__(self, axes): self.axes = axes self.ctx = axes.ctx self.stroke_params = StrokeParameters() self.fill = FillParameters() self.point_style = POINT_CIRCLE self.point_size = 4 self.line_style = SCATTER_NO_LINE def with_line_style(self, style=SCATTER_NO_LINE, pattern=Color(0), line_width=1, dash=None, cap=SQUARE, join=MITER, miter_limit=None): """ Outline the shape. This draws the shape to the supplied context. Parameters are as described for `StrokeParameters`. Args: style: SCATTERXXX constant - the style of the plot. Default no line. pattern: the fill `Pattern` or `Color` to use for the outline, None for default line_width: width of stroke line. None for default dash: sequence, dash patter of line. None for default cap: line end style, None for default. join: line join style, None for default. miter_limit: mitre limit, number, None for default Returns: self """ self.line_style = style self.stroke_params = StrokeParameters(pattern, line_width, dash, cap, join, miter_limit) return self def with_point_style(self, size, style=POINT_CIRCLE, pattern=Color(0), fill_rule=WINDING): """ Sets the style of the points Args: size: number - the size of the point in user space. style: POINTXXX constant - the style of the point. Default circular. pattern: the fill `Pattern` or `Color` to use for the points. fill_rule: the fill rule to use for the points Returns: self """ self.point_style = style self.point_size = size self.fill = FillParameters(pattern, fill_rule) return self def plot(self, x_values, y_values): ''' Plot a scatter chart of the sample values Args: x_values: sequence of numbers - the x values for each sample. y_values: sequence of numbers - the y values for each sample. The number of x and y values should be equal. If not, the minimum count will be used. Eg if there are 10 x values and 8 y values, only 8 points will be plotted. Returns: self ''' points = [self.axes.transform_from_graph((x, y)) for x, y in zip(x_values, y_values)] if self.line_style == SCATTER_CONNECTED: Polygon(self.ctx).of_points(points).open().stroke(self.stroke_params) if self.line_style == SCATTER_STALK: bases = [self.axes.transform_from_graph((x, 0)) for x in x_values] for p, b in zip(points, bases): Line(self.ctx).of_start_end(b, p).stroke(self.stroke_params) for p in points: Circle(self.ctx).of_center_radius(p, self.point_size).fill(self.fill.pattern, self.fill.fill_rule) return self
Methods
def plot(self, x_values, y_values)
-
Plot a scatter chart of the sample values
Args
x_values
- sequence of numbers - the x values for each sample.
y_values
- sequence of numbers - the y values for each sample. The number of x and y values should be equal. If not,
the minimum count will be used. Eg if there are 10 x values and 8 y values, only 8 points will be plotted.
Returns
self
def with_line_style(self, style=0, pattern=<generativepy.color.Color object>, line_width=1, dash=None, cap=4, join=0, miter_limit=None)
-
Outline the shape. This draws the shape to the supplied context.
Parameters are as described for
StrokeParameters
.Args
style
- SCATTERXXX constant - the style of the plot. Default no line.
pattern
- the fill
Pattern
orColor
to use for the outline, None for default line_width
- width of stroke line. None for default
dash
- sequence, dash patter of line. None for default
cap
- line end style, None for default.
join
- line join style, None for default.
miter_limit
- mitre limit, number, None for default
Returns
self
def with_point_style(self, size, style=0, pattern=<generativepy.color.Color object>, fill_rule=1)
-
Sets the style of the points
Args
size
- number - the size of the point in user space.
style
- POINTXXX constant - the style of the point. Default circular.
pattern
- the fill
Pattern
orColor
to use for the points. fill_rule
- the fill rule to use for the points
Returns
self