From 1c4e87ebe67fec54ac3ca5668533ccd1f0ade2aa Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 12:56:58 +0100 Subject: [PATCH 01/44] move old plot impl --- src/plot.typ | 553 ----------------------------------------------- src/plot.typ.old | 552 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 552 insertions(+), 553 deletions(-) create mode 100644 src/plot.typ.old diff --git a/src/plot.typ b/src/plot.typ index 05640d5..e69de29 100644 --- a/src/plot.typ +++ b/src/plot.typ @@ -1,553 +0,0 @@ -#import "/src/cetz.typ": util, draw, matrix, vector, styles, palette -#import util: bezier - -#import "/src/axes.typ" -#import "/src/plot/sample.typ": sample-fn, sample-fn2 -#import "/src/plot/line.typ": add, add-hline, add-vline, add-fill-between -#import "/src/plot/contour.typ": add-contour -#import "/src/plot/boxwhisker.typ": add-boxwhisker -#import "/src/plot/util.typ" as plot-util -#import "/src/plot/legend.typ" as plot-legend -#import "/src/plot/annotation.typ": annotate, calc-annotation-domain -#import "/src/plot/bar.typ": add-bar -#import "/src/plot/errorbar.typ": add-errorbar -#import "/src/plot/mark.typ" -#import "/src/plot/violin.typ": add-violin -#import "/src/plot/formats.typ" -#import plot-legend: add-legend - -#let default-colors = (blue, red, green, yellow, black) - -#let default-plot-style(i) = { - let color = default-colors.at(calc.rem(i, default-colors.len())) - return (stroke: color, - fill: color.lighten(75%)) -} - -#let default-mark-style(i) = { - return default-plot-style(i) -} - -/// Create a plot environment. Data to be plotted is given by passing it to the -/// `plot.add` or other plotting functions. The plot environment supports different -/// axis styles to draw, see its parameter `axis-style:`. -/// -/// #example(``` -/// plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { -/// plot.add(((0,0), (1,1), (2,.5), (4,3))) -/// }) -/// ```) -/// -/// To draw elements insides a plot, using the plots coordinate system, use -/// the `plot.annotate(..)` function. -/// -/// = parameters -/// -/// = Options -/// -/// You can use the following options to customize each axis of the plot. You must pass them as named arguments prefixed by the axis name followed by a dash (`-`) they should target. Example: `x-min: 0`, `y-ticks: (..)` or `x2-label: [..]`. -/// -/// #show-parameter-block("label", ("none", "content"), default: "none", [ -/// The axis' label. If and where the label is drawn depends on the `axis-style`.]) -/// #show-parameter-block("min", ("auto", "float"), default: "auto", [ -/// Axis lower domain value. If this is set greater than than `max`, the axis' direction is swapped]) -/// #show-parameter-block("max", ("auto", "float"), default: "auto", [ -/// Axis upper domain value. If this is set to a lower value than `min`, the axis' direction is swapped]) -/// #show-parameter-block("equal", ("string"), default: "none", [ -/// Set the axis domain to keep a fixed aspect ratio by multiplying the other axis domain by the plots aspect ratio, -/// depending on the other axis orientation (see `horizontal`). -/// This can be useful to force one axis to grow or shrink with another one. -/// You can only "lock" two axes of different orientations. -/// #example(``` -/// plot.plot(size: (2,1), x-tick-step: 1, y-tick-step: 1, -/// x-equal: "y", -/// { -/// plot.add(domain: (0, 2 * calc.pi), -/// t => (calc.cos(t), calc.sin(t))) -/// }) -/// ```) -/// ]) -/// #show-parameter-block("horizontal", ("bool"), default: "axis name dependant", [ -/// If true, the axis is considered an axis that gets drawn horizontally, vertically otherwise. -/// The default value depends on the axis name on axis creation. Axes which name start with `x` have this -/// set to `true`, all others have it set to `false`. Each plot has to use one horizontal and one -/// vertical axis for plotting, a combination of two y-axes will panic: ("y", "y2"). -/// ]) -/// #show-parameter-block("tick-step", ("none", "auto", "float"), default: "auto", [ -/// The increment between tick marks on the axis. If set to `auto`, an -/// increment is determined. When set to `none`, incrementing tick marks are disabled.]) -/// #show-parameter-block("minor-tick-step", ("none", "float"), default: "none", [ -/// Like `tick-step`, but for minor tick marks. In contrast to ticks, minor ticks do not have labels.]) -/// #show-parameter-block("ticks", ("none", "array"), default: "none", [ -/// A List of custom tick marks to additionally draw along the axis. They can be passed as -/// an array of `` values or an array of `(, )` tuples for -/// setting custom tick mark labels per mark. -/// -/// #example(``` -/// plot.plot(x-tick-step: none, y-tick-step: none, -/// x-min: 0, x-max: 4, -/// x-ticks: (1, 2, 3), -/// y-min: 1, y-max: 2, -/// y-ticks: ((1, [One]), (2, [Two])), -/// { -/// plot.add(((0,0),)) -/// }) -/// ```) -/// -/// Examples: `(1, 2, 3)` or `((1, [One]), (2, [Two]), (3, [Three]))`]) -/// #show-parameter-block("format", ("none", "string", "function"), default: "float", [ -/// How to format the tick label: You can give a function that takes a `` and return -/// `` to use as the tick label. You can also give one of the predefined options: -/// / float: Floating point formatting rounded to two digits after the point (see `decimals`) -/// / sci: Scientific formatting with $times 10^n$ used as exponet syntax -/// -/// #example(``` -/// let formatter(v) = if v != 0 {$ #{v/calc.pi} pi $} else {$ 0 $} -/// plot.plot(x-tick-step: calc.pi, y-tick-step: none, -/// x-min: 0, x-max: 2 * calc.pi, -/// x-format: formatter, -/// { -/// plot.add(((0,0),)) -/// }) -/// ```) -/// ]) -/// #show-parameter-block("decimals", ("int"), default: "2", [ -/// Number of decimals digits to display for tick labels, if the format is set -/// to `"float"`. -/// ]) -/// #show-parameter-block("unit", ("none", "content"), default: "none", [ -/// Suffix to append to all tick labels. -/// ]) -/// #show-parameter-block("mode", ("none", "string"), default: "none", [ -/// The scaling function of the axis. Takes `lin` (default) for linear scaling, -/// and `log` for logarithmic scaling.]) -/// #show-parameter-block("base", ("none", "number"), default: "none", [ -/// The base to be used when labeling axis ticks in logarithmic scaling]) -/// #show-parameter-block("grid", ("bool", "string"), default: "false", [ -/// If `true` or `"major"`, show grid lines for all major ticks. If set -/// to `"minor"`, show grid lines for minor ticks only. -/// The value `"both"` enables grid lines for both, major- and minor ticks. -/// -/// #example(``` -/// plot.plot(x-tick-step: 1, y-tick-step: 1, -/// y-minor-tick-step: .2, -/// x-min: 0, x-max: 2, x-grid: true, -/// y-min: 0, y-max: 2, y-grid: "both", { -/// plot.add(((0,0),)) -/// }) -/// ```) -/// ]) -/// #show-parameter-block("break", ("bool"), default: "false", [ -/// If true, add a "sawtooth" at the start or end of the axis line, depending -/// on the axis bounds. If the axis min. value is > 0, a sawtooth is added -/// to the start of the axes, if the axis max. value is < 0, a sawtooth is added -/// to its end.]) -/// -/// - body (body): Calls of `plot.add` or `plot.add-*` commands. Note that normal drawing -/// commands like `line` or `rect` are not allowed inside the plots body, instead wrap -/// them in `plot.annotate`, which lets you select the axes used for drawing. -/// - size (array): Plot size tuple of `(, )` in canvas units. -/// This is the plots inner plotting size without axes and labels. -/// - axis-style (none, string): How the axes should be styled: -/// / scientific: Frames plot area using a rectangle and draw axes `x` (bottom), `y` (left), `x2` (top), and `y2` (right) around it. -/// If `x2` or `y2` are unset, they mirror their opposing axis. -/// / scientific-auto: Draw set (used) axes `x` (bottom), `y` (left), `x2` (top) and `y2` (right) around -/// the plotting area, forming a rect if all axes are in use or a L-shape if only `x` and `y` are in use. -/// / school-book: Draw axes `x` (horizontal) and `y` (vertical) as arrows pointing to the right/top with both crossing at $(0, 0)$ -/// / left: Draw axes `x` and `y` as arrows, while the y axis stays on the left (at `x.min`) -/// and the x axis at the bottom (at `y.min`) -/// / `none`: Draw no axes (and no ticks). -/// -/// #example(``` -/// let opts = (x-tick-step: none, y-tick-step: none, size: (2,1)) -/// let data = cetz.plot.add(((-1,-1), (1,1),), mark: "o") -/// -/// for name in (none, "school-book", "left", "scientific") { -/// plot.plot(axis-style: name, ..opts, data, name: "plot") -/// content(((0,-1), "-|", "plot.south"), repr(name)) -/// set-origin((3.5,0)) -/// } -/// ```, vertical: true) -/// - plot-style (style,function): Styling to use for drawing plot graphs. -/// This style gets inherited by all plots and supports `palette` functions. -/// The following style keys are supported: -/// #show-parameter-block("stroke", ("none", "stroke"), default: 1pt, [ -/// Stroke style to use for stroking the graph. -/// ]) -/// #show-parameter-block("fill", ("none", "paint"), default: none, [ -/// Paint to use for filled graphs. Note that not all graphs may support filling and -/// that you may have to enable filling per graph, see `plot.add(fill: ..)`. -/// ]) -/// - mark-style (style,function): Styling to use for drawing plot marks. -/// This style gets inherited by all plots and supports `palette` functions. -/// The following style keys are supported: -/// #show-parameter-block("stroke", ("none", "stroke"), default: 1pt, [ -/// Stroke style to use for stroking the mark. -/// ]) -/// #show-parameter-block("fill", ("none", "paint"), default: none, [ -/// Paint to use for filling marks. -/// ]) -/// - fill-below (bool): If true, the filled shape of plots is drawn _below_ axes. -/// - name (string): The plots element name to be used when referring to anchors -/// - legend (none, auto, coordinate): The position the legend will be drawn at. See plot-legends for information about legends. If set to ``, the legend's "default-placement" styling will be used. If set to a ``, it will be taken as relative to the plot's origin. -/// - legend-anchor (auto, string): Anchor of the legend group to use as its origin. -/// If set to `auto` and `lengend` is one of the predefined legend anchors, the -/// opposite anchor to `legend` gets used. -/// - legend-style (style): Style key-value overwrites for the legend style with style root `legend`. -/// - ..options (any): Axis options, see _options_ below. -#let plot(body, - size: (1, 1), - axis-style: "scientific", - name: none, - plot-style: default-plot-style, - mark-style: default-mark-style, - fill-below: true, - legend: auto, - legend-anchor: auto, - legend-style: (:), - ..options - ) = draw.group(name: name, ctx => { - // TODO: Assert cetz min version here! - - // Create plot context object - let make-ctx(x, y, size) = { - assert(x != none, message: "X axis does not exist") - assert(y != none, message: "Y axis does not exist") - assert(size.at(0) > 0 and size.at(1) > 0, message: "Plot size must be > 0") - - let x-scale = ((x.max - x.min) / size.at(0)) - let y-scale = ((y.max - y.min) / size.at(1)) - - if y.horizontal { - (x-scale, y-scale) = (y-scale, x-scale) - } - - return (x: x, y: y, size: size, x-scale: x-scale, y-scale: y-scale) - } - - // Setup data viewport - let data-viewport(data, x, y, size, body, name: none) = { - if body == none or body == () { return } - - assert.ne(x.horizontal, y.horizontal, - message: "Data must use one horizontal and one vertical axis!") - - // If y is the horizontal axis, swap x and y - // coordinates by swapping the transformation - // matrix columns. - if y.horizontal { - (x, y) = (y, x) - body = draw.set-ctx(ctx => { - ctx.transform = matrix.swap-cols(ctx.transform, 0, 1) - return ctx - }) + body - } - - // Setup the viewport - axes.axis-viewport(size, x, y, none, body, name: name) - } - - let data = () - let anchors = () - let annotations = () - let body = if body != none { body } else { () } - - for cmd in body { - assert(type(cmd) == dictionary and "type" in cmd, - message: "Expected plot sub-command in plot body") - if cmd.type == "anchor" { - anchors.push(cmd) - } else if cmd.type == "annotation" { - annotations.push(cmd) - } else { data.push(cmd) } - } - - assert(axis-style in (none, "scientific", "scientific-auto", "school-book", "left"), - message: "Invalid plot style") - - // Create axes for data & annotations - let axis-dict = (:) - for d in data + annotations { - if "axes" not in d { continue } - - for (i, name) in d.axes.enumerate() { - if not name in axis-dict { - axis-dict.insert(name, axes.axis( - min: none, max: none)) - } - - let axis = axis-dict.at(name) - let domain = if i == 0 { - d.at("x-domain", default: (0, 0)) - } else { - d.at("y-domain", default: (0, 0)) - } - if domain != (none, none) { - axis.min = util.min(axis.min, ..domain) - axis.max = util.max(axis.max, ..domain) - } - - axis-dict.at(name) = axis - } - } - - // Create axes for anchors - for a in anchors { - for (i, name) in a.axes.enumerate() { - if not name in axis-dict { - axis-dict.insert(name, axes.axis(min: none, max: none)) - } - } - } - - // Adjust axis bounds for annotations - for a in annotations { - let (x, y) = a.axes.map(name => axis-dict.at(name)) - (x, y) = calc-annotation-domain(ctx, x, y, a) - axis-dict.at(a.axes.at(0)) = x - axis-dict.at(a.axes.at(1)) = y - } - - // Set axis options - axis-dict = plot-util.setup-axes(ctx, axis-dict, options.named(), size) - - // Prepare styles - for i in range(data.len()) { - if "style" not in data.at(i) { continue } - - let style-base = plot-style - if type(style-base) == function { - style-base = (style-base)(i) - } - assert.eq(type(style-base), dictionary, - message: "plot-style must be of type dictionary") - - if type(data.at(i).style) == function { - data.at(i).style = (data.at(i).style)(i) - } - assert.eq(type(style-base), dictionary, - message: "data plot-style must be of type dictionary") - - data.at(i).style = util.merge-dictionary( - style-base, data.at(i).style) - - if "mark-style" in data.at(i) { - let mark-style-base = mark-style - if type(mark-style-base) == function { - mark-style-base = (mark-style-base)(i) - } - assert.eq(type(mark-style-base), dictionary, - message: "mark-style must be of type dictionary") - - if type(data.at(i).mark-style) == function { - data.at(i).mark-style = (data.at(i).mark-style)(i) - } - - if type(data.at(i).mark-style) == dictionary { - data.at(i).mark-style = util.merge-dictionary( - mark-style-base, - data.at(i).mark-style - ) - } - } - } - - draw.group(name: "plot", { - draw.anchor("origin", (0, 0)) - - // Prepare - for i in range(data.len()) { - if "axes" not in data.at(i) { continue } - - let (x, y) = data.at(i).axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - if "plot-prepare" in data.at(i) { - data.at(i) = (data.at(i).plot-prepare)(data.at(i), plot-ctx) - assert(data.at(i) != none, - message: "Plot prepare(self, cxt) returned none!") - } - } - - // Background Annotations - for a in annotations.filter(a => a.background) { - let (x, y) = a.axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - data-viewport(a, x, y, size, { - draw.anchor("default", (0, 0)) - a.body - }) - } - - // Fill - if fill-below { - for d in data { - if "axes" not in d { continue } - - let (x, y) = d.axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - data-viewport(d, x, y, size, { - draw.anchor("default", (0, 0)) - draw.set-style(..d.style) - - if "plot-fill" in d { - (d.plot-fill)(d, plot-ctx) - } - }) - } - } - - if axis-style in ("scientific", "scientific-auto") { - let draw-unset = if axis-style == "scientific" { - true - } else { - false - } - - let mirror = if axis-style == "scientific" { - auto - } else { - none - } - - axes.scientific( - size: size, - draw-unset: draw-unset, - bottom: axis-dict.at("x", default: none), - top: axis-dict.at("x2", default: mirror), - left: axis-dict.at("y", default: none), - right: axis-dict.at("y2", default: mirror),) - } else if axis-style == "left" { - axes.school-book( - size: size, - axis-dict.x, - axis-dict.y, - x-position: axis-dict.y.min, - y-position: axis-dict.x.min) - } else if axis-style == "school-book" { - axes.school-book( - size: size, - axis-dict.x, - axis-dict.y,) - } - - // Stroke + Mark data - for d in data { - if "axes" not in d { continue } - - let (x, y) = d.axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - data-viewport(d, x, y, size, { - draw.anchor("default", (0, 0)) - draw.set-style(..d.style) - - if not fill-below and "plot-fill" in d { - (d.plot-fill)(d, plot-ctx) - } - if "plot-stroke" in d { - (d.plot-stroke)(d, plot-ctx) - } - }) - - if "mark" in d and d.mark != none { - draw.group({ - draw.set-style(..d.style, ..d.mark-style) - mark.draw-mark(d.data, x, y, d.mark, d.mark-size, size) - }) - } - } - - // Foreground Annotations - for a in annotations.filter(a => not a.background) { - let (x, y) = a.axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - data-viewport(a, x, y, size, { - draw.anchor("default", (0, 0)) - a.body - }) - } - - // Place anchors - for a in anchors { - let (x, y) = a.axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - let pt = a.position.enumerate().map(((i, v)) => { - if v == "min" { return axis-dict.at(a.axes.at(i)).min } - if v == "max" { return axis-dict.at(a.axes.at(i)).max } - return v - }) - pt = axes.transform-vec(size, x, y, none, pt) - if pt != none { - draw.anchor(a.name, pt) - } - } - }) - - // Draw the legend - if legend != none { - let items = data.filter(d => "label" in d and d.label != none) - if items.len() > 0 { - let legend-style = styles.resolve(ctx.style, - base: plot-legend.default-style, merge: legend-style, root: "legend") - - plot-legend.add-legend-anchors(legend-style, "plot", size) - plot-legend.legend(legend, anchor: legend-anchor, { - for item in items { - let preview = if "plot-legend-preview" in item { - _ => {(item.plot-legend-preview)(item) } - } else { - auto - } - - plot-legend.item(item.label, preview, - mark: item.at("mark", default: none), - mark-size: item.at("mark-size", default: none), - mark-style: item.at("mark-style", default: none), - ..item.at("style", default: (:))) - } - }, ..legend-style) - } - } - - draw.copy-anchors("plot") -}) - -/// Add an anchor to a plot environment -/// -/// This function is similar to `draw.anchor` but it takes an additional -/// axis tuple to specify which axis coordinate system to use. -/// -/// #example(``` -/// import cetz.plot -/// import cetz.draw: * -/// plot.plot(size: (2,2), name: "plot", -/// x-tick-step: none, y-tick-step: none, { -/// plot.add(((0,0), (1,1), (2,.5), (4,3))) -/// plot.add-anchor("pt", (1,1)) -/// }) -/// -/// line("plot.pt", ((), "|-", (0,1.5)), mark: (start: ">"), name: "line") -/// content("line.end", [Here], anchor: "south", padding: .1) -/// ```) -/// -/// - name (string): Anchor name -/// - position (tuple): Tuple of x and y values. -/// Both values can have the special values "min" and -/// "max", which resolve to the axis min/max value. -/// Position is in axis space defined by the axes passed to `axes`. -/// - axes (tuple): Name of the axes to use `("x", "y")` as coordinate -/// system for `position`. Note that both axes must be used, -/// as `add-anchors` does not create them on demand. -#let add-anchor(name, position, axes: ("x", "y")) = { - (( - type: "anchor", - name: name, - position: position, - axes: axes, - ),) -} diff --git a/src/plot.typ.old b/src/plot.typ.old new file mode 100644 index 0000000..04578fd --- /dev/null +++ b/src/plot.typ.old @@ -0,0 +1,552 @@ +#import "/src/cetz.typ": util, draw, matrix, vector, styles, palette +#import util: bezier + +#import "/src/axes.typ" +#import "/src/plot/sample.typ": sample-fn, sample-fn2 +#import "/src/plot/line.typ": add, add-hline, add-vline, add-fill-between +#import "/src/plot/contour.typ": add-contour +#import "/src/plot/boxwhisker.typ": add-boxwhisker +#import "/src/plot/util.typ" as plot-util +#import "/src/plot/legend.typ" as plot-legend +#import "/src/plot/annotation.typ": annotate, calc-annotation-domain +#import "/src/plot/bar.typ": add-bar +#import "/src/plot/errorbar.typ": add-errorbar +#import "/src/plot/mark.typ" +#import "/src/plot/formats.typ" +#import plot-legend: add-legend + +#let default-colors = (blue, red, green, yellow, black) + +#let default-plot-style(i) = { + let color = default-colors.at(calc.rem(i, default-colors.len())) + return (stroke: color, + fill: color.lighten(75%)) +} + +#let default-mark-style(i) = { + return default-plot-style(i) +} + +/// Create a plot environment. Data to be plotted is given by passing it to the +/// `plot.add` or other plotting functions. The plot environment supports different +/// axis styles to draw, see its parameter `axis-style:`. +/// +/// #example(``` +/// plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { +/// plot.add(((0,0), (1,1), (2,.5), (4,3))) +/// }) +/// ```) +/// +/// To draw elements insides a plot, using the plots coordinate system, use +/// the `plot.annotate(..)` function. +/// +/// = parameters +/// +/// = Options +/// +/// You can use the following options to customize each axis of the plot. You must pass them as named arguments prefixed by the axis name followed by a dash (`-`) they should target. Example: `x-min: 0`, `y-ticks: (..)` or `x2-label: [..]`. +/// +/// #show-parameter-block("label", ("none", "content"), default: "none", [ +/// The axis' label. If and where the label is drawn depends on the `axis-style`.]) +/// #show-parameter-block("min", ("auto", "float"), default: "auto", [ +/// Axis lower domain value. If this is set greater than than `max`, the axis' direction is swapped]) +/// #show-parameter-block("max", ("auto", "float"), default: "auto", [ +/// Axis upper domain value. If this is set to a lower value than `min`, the axis' direction is swapped]) +/// #show-parameter-block("equal", ("string"), default: "none", [ +/// Set the axis domain to keep a fixed aspect ratio by multiplying the other axis domain by the plots aspect ratio, +/// depending on the other axis orientation (see `horizontal`). +/// This can be useful to force one axis to grow or shrink with another one. +/// You can only "lock" two axes of different orientations. +/// #example(``` +/// plot.plot(size: (2,1), x-tick-step: 1, y-tick-step: 1, +/// x-equal: "y", +/// { +/// plot.add(domain: (0, 2 * calc.pi), +/// t => (calc.cos(t), calc.sin(t))) +/// }) +/// ```) +/// ]) +/// #show-parameter-block("horizontal", ("bool"), default: "axis name dependant", [ +/// If true, the axis is considered an axis that gets drawn horizontally, vertically otherwise. +/// The default value depends on the axis name on axis creation. Axes which name start with `x` have this +/// set to `true`, all others have it set to `false`. Each plot has to use one horizontal and one +/// vertical axis for plotting, a combination of two y-axes will panic: ("y", "y2"). +/// ]) +/// #show-parameter-block("tick-step", ("none", "auto", "float"), default: "auto", [ +/// The increment between tick marks on the axis. If set to `auto`, an +/// increment is determined. When set to `none`, incrementing tick marks are disabled.]) +/// #show-parameter-block("minor-tick-step", ("none", "float"), default: "none", [ +/// Like `tick-step`, but for minor tick marks. In contrast to ticks, minor ticks do not have labels.]) +/// #show-parameter-block("ticks", ("none", "array"), default: "none", [ +/// A List of custom tick marks to additionally draw along the axis. They can be passed as +/// an array of `` values or an array of `(, )` tuples for +/// setting custom tick mark labels per mark. +/// +/// #example(``` +/// plot.plot(x-tick-step: none, y-tick-step: none, +/// x-min: 0, x-max: 4, +/// x-ticks: (1, 2, 3), +/// y-min: 1, y-max: 2, +/// y-ticks: ((1, [One]), (2, [Two])), +/// { +/// plot.add(((0,0),)) +/// }) +/// ```) +/// +/// Examples: `(1, 2, 3)` or `((1, [One]), (2, [Two]), (3, [Three]))`]) +/// #show-parameter-block("format", ("none", "string", "function"), default: "float", [ +/// How to format the tick label: You can give a function that takes a `` and return +/// `` to use as the tick label. You can also give one of the predefined options: +/// / float: Floating point formatting rounded to two digits after the point (see `decimals`) +/// / sci: Scientific formatting with $times 10^n$ used as exponet syntax +/// +/// #example(``` +/// let formatter(v) = if v != 0 {$ #{v/calc.pi} pi $} else {$ 0 $} +/// plot.plot(x-tick-step: calc.pi, y-tick-step: none, +/// x-min: 0, x-max: 2 * calc.pi, +/// x-format: formatter, +/// { +/// plot.add(((0,0),)) +/// }) +/// ```) +/// ]) +/// #show-parameter-block("decimals", ("int"), default: "2", [ +/// Number of decimals digits to display for tick labels, if the format is set +/// to `"float"`. +/// ]) +/// #show-parameter-block("unit", ("none", "content"), default: "none", [ +/// Suffix to append to all tick labels. +/// ]) +/// #show-parameter-block("mode", ("none", "string"), default: "none", [ +/// The scaling function of the axis. Takes `lin` (default) for linear scaling, +/// and `log` for logarithmic scaling.]) +/// #show-parameter-block("base", ("none", "number"), default: "none", [ +/// The base to be used when labeling axis ticks in logarithmic scaling]) +/// #show-parameter-block("grid", ("bool", "string"), default: "false", [ +/// If `true` or `"major"`, show grid lines for all major ticks. If set +/// to `"minor"`, show grid lines for minor ticks only. +/// The value `"both"` enables grid lines for both, major- and minor ticks. +/// +/// #example(``` +/// plot.plot(x-tick-step: 1, y-tick-step: 1, +/// y-minor-tick-step: .2, +/// x-min: 0, x-max: 2, x-grid: true, +/// y-min: 0, y-max: 2, y-grid: "both", { +/// plot.add(((0,0),)) +/// }) +/// ```) +/// ]) +/// #show-parameter-block("break", ("bool"), default: "false", [ +/// If true, add a "sawtooth" at the start or end of the axis line, depending +/// on the axis bounds. If the axis min. value is > 0, a sawtooth is added +/// to the start of the axes, if the axis max. value is < 0, a sawtooth is added +/// to its end.]) +/// +/// - body (body): Calls of `plot.add` or `plot.add-*` commands. Note that normal drawing +/// commands like `line` or `rect` are not allowed inside the plots body, instead wrap +/// them in `plot.annotate`, which lets you select the axes used for drawing. +/// - size (array): Plot size tuple of `(, )` in canvas units. +/// This is the plots inner plotting size without axes and labels. +/// - axis-style (none, string): How the axes should be styled: +/// / scientific: Frames plot area using a rectangle and draw axes `x` (bottom), `y` (left), `x2` (top), and `y2` (right) around it. +/// If `x2` or `y2` are unset, they mirror their opposing axis. +/// / scientific-auto: Draw set (used) axes `x` (bottom), `y` (left), `x2` (top) and `y2` (right) around +/// the plotting area, forming a rect if all axes are in use or a L-shape if only `x` and `y` are in use. +/// / school-book: Draw axes `x` (horizontal) and `y` (vertical) as arrows pointing to the right/top with both crossing at $(0, 0)$ +/// / left: Draw axes `x` and `y` as arrows, while the y axis stays on the left (at `x.min`) +/// and the x axis at the bottom (at `y.min`) +/// / `none`: Draw no axes (and no ticks). +/// +/// #example(``` +/// let opts = (x-tick-step: none, y-tick-step: none, size: (2,1)) +/// let data = cetz.plot.add(((-1,-1), (1,1),), mark: "o") +/// +/// for name in (none, "school-book", "left", "scientific") { +/// plot.plot(axis-style: name, ..opts, data, name: "plot") +/// content(((0,-1), "-|", "plot.south"), repr(name)) +/// set-origin((3.5,0)) +/// } +/// ```, vertical: true) +/// - plot-style (style,function): Styling to use for drawing plot graphs. +/// This style gets inherited by all plots and supports `palette` functions. +/// The following style keys are supported: +/// #show-parameter-block("stroke", ("none", "stroke"), default: 1pt, [ +/// Stroke style to use for stroking the graph. +/// ]) +/// #show-parameter-block("fill", ("none", "paint"), default: none, [ +/// Paint to use for filled graphs. Note that not all graphs may support filling and +/// that you may have to enable filling per graph, see `plot.add(fill: ..)`. +/// ]) +/// - mark-style (style,function): Styling to use for drawing plot marks. +/// This style gets inherited by all plots and supports `palette` functions. +/// The following style keys are supported: +/// #show-parameter-block("stroke", ("none", "stroke"), default: 1pt, [ +/// Stroke style to use for stroking the mark. +/// ]) +/// #show-parameter-block("fill", ("none", "paint"), default: none, [ +/// Paint to use for filling marks. +/// ]) +/// - fill-below (bool): If true, the filled shape of plots is drawn _below_ axes. +/// - name (string): The plots element name to be used when referring to anchors +/// - legend (none, auto, coordinate): The position the legend will be drawn at. See plot-legends for information about legends. If set to ``, the legend's "default-placement" styling will be used. If set to a ``, it will be taken as relative to the plot's origin. +/// - legend-anchor (auto, string): Anchor of the legend group to use as its origin. +/// If set to `auto` and `lengend` is one of the predefined legend anchors, the +/// opposite anchor to `legend` gets used. +/// - legend-style (style): Style key-value overwrites for the legend style with style root `legend`. +/// - ..options (any): Axis options, see _options_ below. +#let plot(body, + size: (1, 1), + axis-style: "scientific", + name: none, + plot-style: default-plot-style, + mark-style: default-mark-style, + fill-below: true, + legend: auto, + legend-anchor: auto, + legend-style: (:), + ..options + ) = draw.group(name: name, ctx => { + // TODO: Assert cetz min version here! + + // Create plot context object + let make-ctx(x, y, size) = { + assert(x != none, message: "X axis does not exist") + assert(y != none, message: "Y axis does not exist") + assert(size.at(0) > 0 and size.at(1) > 0, message: "Plot size must be > 0") + + let x-scale = ((x.max - x.min) / size.at(0)) + let y-scale = ((y.max - y.min) / size.at(1)) + + if y.horizontal { + (x-scale, y-scale) = (y-scale, x-scale) + } + + return (x: x, y: y, size: size, x-scale: x-scale, y-scale: y-scale) + } + + // Setup data viewport + let data-viewport(data, x, y, size, body, name: none) = { + if body == none or body == () { return } + + assert.ne(x.horizontal, y.horizontal, + message: "Data must use one horizontal and one vertical axis!") + + // If y is the horizontal axis, swap x and y + // coordinates by swapping the transformation + // matrix columns. + if y.horizontal { + (x, y) = (y, x) + body = draw.set-ctx(ctx => { + ctx.transform = matrix.swap-cols(ctx.transform, 0, 1) + return ctx + }) + body + } + + // Setup the viewport + axes.axis-viewport(size, x, y, none, body, name: name) + } + + let data = () + let anchors = () + let annotations = () + let body = if body != none { body } else { () } + + for cmd in body { + assert(type(cmd) == dictionary and "type" in cmd, + message: "Expected plot sub-command in plot body") + if cmd.type == "anchor" { + anchors.push(cmd) + } else if cmd.type == "annotation" { + annotations.push(cmd) + } else { data.push(cmd) } + } + + assert(axis-style in (none, "scientific", "scientific-auto", "school-book", "left"), + message: "Invalid plot style") + + // Create axes for data & annotations + let axis-dict = (:) + for d in data + annotations { + if "axes" not in d { continue } + + for (i, name) in d.axes.enumerate() { + if not name in axis-dict { + axis-dict.insert(name, axes.axis( + min: none, max: none)) + } + + let axis = axis-dict.at(name) + let domain = if i == 0 { + d.at("x-domain", default: (0, 0)) + } else { + d.at("y-domain", default: (0, 0)) + } + if domain != (none, none) { + axis.min = util.min(axis.min, ..domain) + axis.max = util.max(axis.max, ..domain) + } + + axis-dict.at(name) = axis + } + } + + // Create axes for anchors + for a in anchors { + for (i, name) in a.axes.enumerate() { + if not name in axis-dict { + axis-dict.insert(name, axes.axis(min: none, max: none)) + } + } + } + + // Adjust axis bounds for annotations + for a in annotations { + let (x, y) = a.axes.map(name => axis-dict.at(name)) + (x, y) = calc-annotation-domain(ctx, x, y, a) + axis-dict.at(a.axes.at(0)) = x + axis-dict.at(a.axes.at(1)) = y + } + + // Set axis options + axis-dict = plot-util.setup-axes(ctx, axis-dict, options.named(), size) + + // Prepare styles + for i in range(data.len()) { + if "style" not in data.at(i) { continue } + + let style-base = plot-style + if type(style-base) == function { + style-base = (style-base)(i) + } + assert.eq(type(style-base), dictionary, + message: "plot-style must be of type dictionary") + + if type(data.at(i).style) == function { + data.at(i).style = (data.at(i).style)(i) + } + assert.eq(type(style-base), dictionary, + message: "data plot-style must be of type dictionary") + + data.at(i).style = util.merge-dictionary( + style-base, data.at(i).style) + + if "mark-style" in data.at(i) { + let mark-style-base = mark-style + if type(mark-style-base) == function { + mark-style-base = (mark-style-base)(i) + } + assert.eq(type(mark-style-base), dictionary, + message: "mark-style must be of type dictionary") + + if type(data.at(i).mark-style) == function { + data.at(i).mark-style = (data.at(i).mark-style)(i) + } + + if type(data.at(i).mark-style) == dictionary { + data.at(i).mark-style = util.merge-dictionary( + mark-style-base, + data.at(i).mark-style + ) + } + } + } + + draw.group(name: "plot", { + draw.anchor("origin", (0, 0)) + + // Prepare + for i in range(data.len()) { + if "axes" not in data.at(i) { continue } + + let (x, y) = data.at(i).axes.map(name => axis-dict.at(name)) + let plot-ctx = make-ctx(x, y, size) + + if "plot-prepare" in data.at(i) { + data.at(i) = (data.at(i).plot-prepare)(data.at(i), plot-ctx) + assert(data.at(i) != none, + message: "Plot prepare(self, cxt) returned none!") + } + } + + // Background Annotations + for a in annotations.filter(a => a.background) { + let (x, y) = a.axes.map(name => axis-dict.at(name)) + let plot-ctx = make-ctx(x, y, size) + + data-viewport(a, x, y, size, { + draw.anchor("default", (0, 0)) + a.body + }) + } + + // Fill + if fill-below { + for d in data { + if "axes" not in d { continue } + + let (x, y) = d.axes.map(name => axis-dict.at(name)) + let plot-ctx = make-ctx(x, y, size) + + data-viewport(d, x, y, size, { + draw.anchor("default", (0, 0)) + draw.set-style(..d.style) + + if "plot-fill" in d { + (d.plot-fill)(d, plot-ctx) + } + }) + } + } + + if axis-style in ("scientific", "scientific-auto") { + let draw-unset = if axis-style == "scientific" { + true + } else { + false + } + + let mirror = if axis-style == "scientific" { + auto + } else { + none + } + + axes.scientific( + size: size, + draw-unset: draw-unset, + bottom: axis-dict.at("x", default: none), + top: axis-dict.at("x2", default: mirror), + left: axis-dict.at("y", default: none), + right: axis-dict.at("y2", default: mirror),) + } else if axis-style == "left" { + axes.school-book( + size: size, + axis-dict.x, + axis-dict.y, + x-position: axis-dict.y.min, + y-position: axis-dict.x.min) + } else if axis-style == "school-book" { + axes.school-book( + size: size, + axis-dict.x, + axis-dict.y,) + } + + // Stroke + Mark data + for d in data { + if "axes" not in d { continue } + + let (x, y) = d.axes.map(name => axis-dict.at(name)) + let plot-ctx = make-ctx(x, y, size) + + data-viewport(d, x, y, size, { + draw.anchor("default", (0, 0)) + draw.set-style(..d.style) + + if not fill-below and "plot-fill" in d { + (d.plot-fill)(d, plot-ctx) + } + if "plot-stroke" in d { + (d.plot-stroke)(d, plot-ctx) + } + }) + + if "mark" in d and d.mark != none { + draw.group({ + draw.set-style(..d.style, ..d.mark-style) + mark.draw-mark(d.data, x, y, d.mark, d.mark-size, size) + }) + } + } + + // Foreground Annotations + for a in annotations.filter(a => not a.background) { + let (x, y) = a.axes.map(name => axis-dict.at(name)) + let plot-ctx = make-ctx(x, y, size) + + data-viewport(a, x, y, size, { + draw.anchor("default", (0, 0)) + a.body + }) + } + + // Place anchors + for a in anchors { + let (x, y) = a.axes.map(name => axis-dict.at(name)) + let plot-ctx = make-ctx(x, y, size) + + let pt = a.position.enumerate().map(((i, v)) => { + if v == "min" { return axis-dict.at(a.axes.at(i)).min } + if v == "max" { return axis-dict.at(a.axes.at(i)).max } + return v + }) + pt = axes.transform-vec(size, x, y, none, pt) + if pt != none { + draw.anchor(a.name, pt) + } + } + }) + + // Draw the legend + if legend != none { + let items = data.filter(d => "label" in d and d.label != none) + if items.len() > 0 { + let legend-style = styles.resolve(ctx.style, + base: plot-legend.default-style, merge: legend-style, root: "legend") + + plot-legend.add-legend-anchors(legend-style, "plot", size) + plot-legend.legend(legend, anchor: legend-anchor, { + for item in items { + let preview = if "plot-legend-preview" in item { + _ => {(item.plot-legend-preview)(item) } + } else { + auto + } + + plot-legend.item(item.label, preview, + mark: item.at("mark", default: none), + mark-size: item.at("mark-size", default: none), + mark-style: item.at("mark-style", default: none), + ..item.at("style", default: (:))) + } + }, ..legend-style) + } + } + + draw.copy-anchors("plot") +}) + +/// Add an anchor to a plot environment +/// +/// This function is similar to `draw.anchor` but it takes an additional +/// axis tuple to specify which axis coordinate system to use. +/// +/// #example(``` +/// import cetz.plot +/// import cetz.draw: * +/// plot.plot(size: (2,2), name: "plot", +/// x-tick-step: none, y-tick-step: none, { +/// plot.add(((0,0), (1,1), (2,.5), (4,3))) +/// plot.add-anchor("pt", (1,1)) +/// }) +/// +/// line("plot.pt", ((), "|-", (0,1.5)), mark: (start: ">"), name: "line") +/// content("line.end", [Here], anchor: "south", padding: .1) +/// ```) +/// +/// - name (string): Anchor name +/// - position (tuple): Tuple of x and y values. +/// Both values can have the special values "min" and +/// "max", which resolve to the axis min/max value. +/// Position is in axis space defined by the axes passed to `axes`. +/// - axes (tuple): Name of the axes to use `("x", "y")` as coordinate +/// system for `position`. Note that both axes must be used, +/// as `add-anchors` does not create them on demand. +#let add-anchor(name, position, axes: ("x", "y")) = { + (( + type: "anchor", + name: name, + position: position, + axes: axes, + ),) +} From b6d7781286ed8b008fbf4fe7d34cd709a4e7f087 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 12:57:11 +0100 Subject: [PATCH 02/44] start impl of scientific plot --- src/plots/2d-orthorect.typ | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/plots/2d-orthorect.typ diff --git a/src/plots/2d-orthorect.typ b/src/plots/2d-orthorect.typ new file mode 100644 index 0000000..d6bd744 --- /dev/null +++ b/src/plots/2d-orthorect.typ @@ -0,0 +1,36 @@ + +#let make-ctx(x, y, size) = { + assert(x != none, message: "X axis does not exist") + assert(y != none, message: "Y axis does not exist") + assert(size.at(0) > 0 and size.at(1) > 0, message: "Plot size must be > 0") + + let x-scale = ((x.max - x.min) / size.at(0)) + let y-scale = ((y.max - y.min) / size.at(1)) + + if y.horizontal { + (x-scale, y-scale) = (y-scale, x-scale) + } + + return (x: x, y: y, size: size, x-scale: x-scale, y-scale: y-scale) +} + +#let data-viewport(data, x, y, size, body, name: none) = { + if body == none or body == () { return } + + assert.ne(x.horizontal, y.horizontal, + message: "Data must use one horizontal and one vertical axis!") + + // If y is the horizontal axis, swap x and y + // coordinates by swapping the transformation + // matrix columns. + if y.horizontal { + (x, y) = (y, x) + body = draw.set-ctx(ctx => { + ctx.transform = matrix.swap-cols(ctx.transform, 0, 1) + return ctx + }) + body + } + + // Setup the viewport + axes.axis-viewport(size, x, y, none, body, name: name) +} \ No newline at end of file From ea0d40d04711cff7765670dd3db7c6bd65315ef0 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 12:57:21 +0100 Subject: [PATCH 03/44] start impl of ternary --- src/plots/2d-barycentric.typ | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/plots/2d-barycentric.typ diff --git a/src/plots/2d-barycentric.typ b/src/plots/2d-barycentric.typ new file mode 100644 index 0000000..e69de29 From 8a81f5ce6ad8f25461ef6a54e84228787e803c21 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 13:05:07 +0100 Subject: [PATCH 04/44] rename old plot directory --- src/{plot => plot-old}/annotation.typ | 0 src/{plot => plot-old}/bar.typ | 0 src/{plot => plot-old}/boxwhisker.typ | 0 src/{plot => plot-old}/contour.typ | 0 src/{plot => plot-old}/errorbar.typ | 0 src/{plot => plot-old}/formats.typ | 0 src/{plot => plot-old}/legend.typ | 0 src/{plot => plot-old}/line.typ | 0 src/{plot => plot-old}/mark.typ | 0 src/{plot => plot-old}/sample.typ | 0 src/{plot => plot-old}/util.typ | 0 src/{plot => plot-old}/violin.typ | 0 src/plot.typ | 54 +++++++++++++++++++ ...{2d-barycentric.typ => barycentric-2d.typ} | 0 .../{2d-orthorect.typ => orthorect-2d.typ} | 0 15 files changed, 54 insertions(+) rename src/{plot => plot-old}/annotation.typ (100%) rename src/{plot => plot-old}/bar.typ (100%) rename src/{plot => plot-old}/boxwhisker.typ (100%) rename src/{plot => plot-old}/contour.typ (100%) rename src/{plot => plot-old}/errorbar.typ (100%) rename src/{plot => plot-old}/formats.typ (100%) rename src/{plot => plot-old}/legend.typ (100%) rename src/{plot => plot-old}/line.typ (100%) rename src/{plot => plot-old}/mark.typ (100%) rename src/{plot => plot-old}/sample.typ (100%) rename src/{plot => plot-old}/util.typ (100%) rename src/{plot => plot-old}/violin.typ (100%) rename src/plots/{2d-barycentric.typ => barycentric-2d.typ} (100%) rename src/plots/{2d-orthorect.typ => orthorect-2d.typ} (100%) diff --git a/src/plot/annotation.typ b/src/plot-old/annotation.typ similarity index 100% rename from src/plot/annotation.typ rename to src/plot-old/annotation.typ diff --git a/src/plot/bar.typ b/src/plot-old/bar.typ similarity index 100% rename from src/plot/bar.typ rename to src/plot-old/bar.typ diff --git a/src/plot/boxwhisker.typ b/src/plot-old/boxwhisker.typ similarity index 100% rename from src/plot/boxwhisker.typ rename to src/plot-old/boxwhisker.typ diff --git a/src/plot/contour.typ b/src/plot-old/contour.typ similarity index 100% rename from src/plot/contour.typ rename to src/plot-old/contour.typ diff --git a/src/plot/errorbar.typ b/src/plot-old/errorbar.typ similarity index 100% rename from src/plot/errorbar.typ rename to src/plot-old/errorbar.typ diff --git a/src/plot/formats.typ b/src/plot-old/formats.typ similarity index 100% rename from src/plot/formats.typ rename to src/plot-old/formats.typ diff --git a/src/plot/legend.typ b/src/plot-old/legend.typ similarity index 100% rename from src/plot/legend.typ rename to src/plot-old/legend.typ diff --git a/src/plot/line.typ b/src/plot-old/line.typ similarity index 100% rename from src/plot/line.typ rename to src/plot-old/line.typ diff --git a/src/plot/mark.typ b/src/plot-old/mark.typ similarity index 100% rename from src/plot/mark.typ rename to src/plot-old/mark.typ diff --git a/src/plot/sample.typ b/src/plot-old/sample.typ similarity index 100% rename from src/plot/sample.typ rename to src/plot-old/sample.typ diff --git a/src/plot/util.typ b/src/plot-old/util.typ similarity index 100% rename from src/plot/util.typ rename to src/plot-old/util.typ diff --git a/src/plot/violin.typ b/src/plot-old/violin.typ similarity index 100% rename from src/plot/violin.typ rename to src/plot-old/violin.typ diff --git a/src/plot.typ b/src/plot.typ index e69de29..641eb1b 100644 --- a/src/plot.typ +++ b/src/plot.typ @@ -0,0 +1,54 @@ +#import "plots/orthorect-2d.typ" +#import "plots/barycentric-2d.typ" + +// TODO: Refactor this into a better way of providing palettes + +#let default-colors = (blue, red, green, yellow, black) + +#let default-plot-style(i) = { + let color = default-colors.at(calc.rem(i, default-colors.len())) + return (stroke: color, + fill: color.lighten(75%)) +} + +#let default-mark-style(i) = { + return default-plot-style(i) +} + +// Consider splitting into sevaral files +#let _create-axis-dict() + +#let plot( + body, + size: (1,1), + axis-style: orthorect-2d, + name: none, + plot-style: default-plot-style, + mark-style: default-mark-style, + legend: auto, + legend-anchor: auto, + legend-style: (:), + ..options +) = draw.group(name: name, ctx => { + + // TODO: Assert cetz min version here! + + let data = () + let anchors = () + let annotations = () + let body = if body != none { body } else { () } + + for cmd in body { + assert(type(cmd) == dictionary and "type" in cmd, + message: "Expected plot sub-command in plot body") + + if cmd.type == "anchor" { + anchors.push(cmd) + } else if cmd.type == "annotation" { + annotations.push(cmd) + } else { + data.push(cmd) + } + } + +}) \ No newline at end of file diff --git a/src/plots/2d-barycentric.typ b/src/plots/barycentric-2d.typ similarity index 100% rename from src/plots/2d-barycentric.typ rename to src/plots/barycentric-2d.typ diff --git a/src/plots/2d-orthorect.typ b/src/plots/orthorect-2d.typ similarity index 100% rename from src/plots/2d-orthorect.typ rename to src/plots/orthorect-2d.typ From eb5f2d5d749c074a1433c9b44c66de3999403800 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 14:22:35 +0100 Subject: [PATCH 05/44] move plot to old --- src/axes.typ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axes.typ b/src/axes.typ index cfd4669..3bd3b66 100644 --- a/src/axes.typ +++ b/src/axes.typ @@ -1,5 +1,5 @@ #import "/src/cetz.typ": util, draw, vector, matrix, styles, process, drawable, path-util, process -#import "/src/plot/formats.typ" +#import "/src/plot-old/formats.typ" #let typst-content = content From 6f455244523d28c80a12d3e0bcc173d9f3c8e054 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 14:22:44 +0100 Subject: [PATCH 06/44] explicitly named exports --- src/lib.typ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.typ b/src/lib.typ index 128ff08..2ec7c33 100644 --- a/src/lib.typ +++ b/src/lib.typ @@ -1,5 +1,5 @@ #let version = version(0,1,0) #import "/src/axes.typ" -#import "/src/plot.typ" +#import "/src/plot.typ": plot, orthorect-2d, barycentric-2d #import "/src/chart.typ" From d652d3d823f653b80a59065e4ff4412baa1a4c41 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 14:23:10 +0100 Subject: [PATCH 07/44] add anchor plot element --- src/elements/anchor.typ | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/elements/anchor.typ diff --git a/src/elements/anchor.typ b/src/elements/anchor.typ new file mode 100644 index 0000000..b5ef3f4 --- /dev/null +++ b/src/elements/anchor.typ @@ -0,0 +1,34 @@ +/// Add an anchor to a plot environment +/// +/// This function is similar to `draw.anchor` but it takes an additional +/// axis tuple to specify which axis coordinate system to use. +/// +/// #example(``` +/// import cetz.plot +/// import cetz.draw: * +/// plot.plot(size: (2,2), name: "plot", +/// x-tick-step: none, y-tick-step: none, { +/// plot.add(((0,0), (1,1), (2,.5), (4,3))) +/// plot.add-anchor("pt", (1,1)) +/// }) +/// +/// line("plot.pt", ((), "|-", (0,1.5)), mark: (start: ">"), name: "line") +/// content("line.end", [Here], anchor: "south", padding: .1) +/// ```) +/// +/// - name (string): Anchor name +/// - position (tuple): Tuple of x and y values. +/// Both values can have the special values "min" and +/// "max", which resolve to the axis min/max value. +/// Position is in axis space defined by the axes passed to `axes`. +/// - axes (tuple): Name of the axes to use `("x", "y")` as coordinate +/// system for `position`. Note that both axes must be used, +/// as `add-anchors` does not create them on demand. +#let add-anchor(name, position, axes: ("x", "y")) = { + (( + type: "anchor", + name: name, + position: position, + axes: axes, + ),) +} \ No newline at end of file From 6874579d5653bd9b029b9701063fb54fdba8554c Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 14:23:21 +0100 Subject: [PATCH 08/44] start on axis lib --- src/plot/axes.typ | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/plot/axes.typ diff --git a/src/plot/axes.typ b/src/plot/axes.typ new file mode 100644 index 0000000..e83a3e1 --- /dev/null +++ b/src/plot/axes.typ @@ -0,0 +1,37 @@ +// Construct Axis Object +// +// - min (number): Minimum value +// - max (number): Maximum value +// - ticks (dictionary): Tick settings: +// - step (number): Major tic step +// - minor-step (number): Minor tic step +// - unit (content): Tick label suffix +// - decimals (int): Tick float decimal length +// - label (content): Axis label +// - mode (string): Axis scaling function. Takes `lin` or `log` +// - base (number): Base for tick labels when logarithmically scaled. +#let axis( + min: -1, + max: 1, + label: none, + ticks: ( + step: auto, + minor-step: none, + unit: none, + decimals: 2, + grid: false, + format: "float" + ), + mode: + auto, + base: auto +) = ( + min: min, + max: max, + ticks: ticks, + label: label, + inset: (0, 0), + show-break: false, + mode: mode, + base: base +) \ No newline at end of file From 80359b93510c32398521b3bc726d3dc5b75558f2 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 14:23:33 +0100 Subject: [PATCH 09/44] copy over legend (TODO: refactor) --- src/plot/legend.typ | 236 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 src/plot/legend.typ diff --git a/src/plot/legend.typ b/src/plot/legend.typ new file mode 100644 index 0000000..f26a43b --- /dev/null +++ b/src/plot/legend.typ @@ -0,0 +1,236 @@ +#import "/src/cetz.typ" +#import cetz: draw, styles +#import draw: group + +#import "mark.typ": draw-mark-shape + +#let default-style = ( + orientation: ttb, + default-position: "north-east", + layer: 1, // Legend layer + fill: rgb(255,255,255,200), // Legend background + stroke: black, // Legend border + padding: .1, // Legend border padding + offset: (0, 0), // Legend displacement + spacing: .1, // Spacing between anchor and legend + item: ( + radius: 0, + spacing: .05, // Spacing between items + preview: ( + width: .75, // Preview width + height: .3, // Preview height + margin: .1 // Distance between preview and label + ) + ), + radius: 0, + scale: 100%, +) + +// Map position to legend group anchor +#let auto-group-anchor = ( + inner-north-west: "north-west", + inner-north: "north", + inner-north-east: "north-east", + inner-south-west: "south-west", + inner-south: "south", + inner-south-east: "south-east", + inner-west: "west", + inner-east: "east", + north-west: "north-east", + north: "south", + north-east: "north-west", + south-west: "south-east", + south: "north", + south-east: "south-west", + east: "west", + west: "east", +) + +// Generate legend positioning anchors +#let add-legend-anchors(style, element, size) = { + import draw: * + let (w, h) = size + let (xo, yo) = { + let spacing = style.at("spacing", default: (0, 0)) + if type(spacing) == array { + spacing + } else { + (spacing, spacing) + } + } + + anchor("north", (rel: (w / 2, yo), to: (element + ".north", "-|", element + ".origin"))) + anchor("south", (rel: (w / 2, -yo), to: (element + ".south", "-|", element + ".origin"))) + anchor("east", (rel: (xo, h / 2), to: (element + ".east", "|-", element + ".origin"))) + anchor("west", (rel: (-xo, h / 2), to: (element + ".west", "|-", element + ".origin"))) + anchor("north-east", (rel: (xo, h), to: (element + ".north-east", "|-", element + ".origin"))) + anchor("north-west", (rel: (-xo, h), to: (element + ".north-west", "|-", element + ".origin"))) + anchor("south-east", (rel: (xo, 0), to: (element + ".south-east", "|-", element + ".origin"))) + anchor("south-west", (rel: (-xo, 0), to: (element + ".south-west", "|-", element + ".origin"))) + anchor("inner-north", (rel: (w / 2, h - yo), to: element + ".origin")) + anchor("inner-north-east", (rel: (w - xo, h - yo), to: element + ".origin")) + anchor("inner-north-west", (rel: (yo, h - yo), to: element + ".origin")) + anchor("inner-south", (rel: (w / 2, yo), to: element + ".origin")) + anchor("inner-south-east", (rel: (w - xo, yo), to: element + ".origin")) + anchor("inner-south-west", (rel: (xo, yo), to: element + ".origin")) + anchor("inner-east", (rel: (w - xo, h / 2), to: element + ".origin")) + anchor("inner-west", (rel: (xo, h / 2), to: element + ".origin")) +} + +// Draw a generic item preview +#let draw-generic-preview(item) = { + import draw: * + + if item.at("fill", default: false) { + rect((0,0), (1,1), ..item.style) + } else { + line((0,.5), (1,.5), ..item.style) + } +} + +/// Construct a legend item for use with the `legend` function +/// +/// - label (none, auto, content): Legend label or auto to use the enumerated default label +/// - preview (auto, function): Legend preview icon function of the format `item => elements`. +/// Note that the canvas bounds for drawing the preview are (0,0) to (1,1). +/// - mark: (none,string): Legend mark symbol +/// - mark-style: (none,dictionary): Mark style +/// - mark-size: (number): Mark size +/// - style (styles): Style keys for the single item +#let item(label, preview, mark: none, mark-style: (:), mark-size: 1, ..style) = { + assert.eq(style.pos().len(), 0, + message: "Unexpected positional arguments") + return ((label: label, preview: preview, + mark: mark, mark-style: mark-style, mark-size: mark-size, + style: style.named()),) +} + +/// Draw a legend +#let legend(position, items, name: "legend", ..style) = group(name: name, ctx => { + draw.anchor("default", ()) + let items = if items != none { items.filter(v => v.label != none) } else { () } + if items == () { + return + } + + let style = styles.resolve( + ctx.style, merge: style.named(), base: default-style, root: "legend") + assert(style.orientation in (ttb, ltr), + message: "Unsupported legend orientation.") + + // Scaling + draw.scale(style.scale) + + // Position + let position = if position == auto { + style.default-position + } else { + position + } + + // Adjust anchor + if style.anchor == auto { + style.anchor = if type(position) == str { + auto-group-anchor.at(position, default: "north-west") + } else { + "north-west" + } + } + + // Apply offset + if style.offset not in (none, (0,0)) { + position = (rel: style.offset, to: position) + } + + // Draw items + draw.on-layer(style.layer, { + draw.group(name: "items", padding: style.padding, ctx => { + import draw: * + + set-origin(position) + anchor("default", (0,0)) + + let pt = (0, 0) + for (i, item) in items.enumerate() { + let (label, preview) = item + if label == none { + continue + } else if label == auto { + label = $ f_(#i) $ + } + + group({ + anchor("default", (0,0)) + + let row-height = style.item.preview.height + let preview-width = style.item.preview.width + let preview-a = (0, -row-height / 2) + let preview-b = (preview-width, +row-height / 2) + let label-west = (preview-width + style.item.preview.margin, 0) + + // Draw item preview + let draw-preview = if preview == auto { draw-generic-preview } else { preview } + group({ + set-viewport(preview-a, preview-b, bounds: (1, 1, 0)) + (draw-preview)(item) + }) + + // Draw mark preview + let mark = item.at("mark", default: none) + if mark != none { + draw-mark-shape((preview-a, 50%, preview-b), + calc.min(style.item.preview.width / 2, item.mark-size), + mark, + item.mark-style) + } + + // Draw label + content(label-west, + align(left + horizon, label), + name: "label", anchor: "west") + }, name: "item", anchor: if style.orientation == ltr { "west" } else { "north-west" }) + + if style.orientation == ttb { + set-origin((rel: (0, -style.item.spacing), + to: "item.south-west")) + } else if style.orientation == ltr { + set-origin((rel: (style.item.spacing, 0), + to: "item.east")) + } + } + }, anchor: style.anchor) + }) + + // Fill legend background + draw.on-layer(style.layer - .5, { + draw.rect("items.south-west", + "items.north-east", fill: style.fill, stroke: style.stroke, radius: style.radius) + }) +}) + +/// Function for manually adding a legend item from within +/// a plot environment +/// +/// - label (content): Legend label +/// - preview (auto,function): Legend preview function of the format `() => elements`. +/// The preview canvas bounds are between (0,0) and (1,1). +/// If set to `auto`, a straight line is drawn. +/// +/// ```example +/// add-legend([Custom item], preview _ => { +/// draw.rect((0,0), (1,1)) // Draw a rect as preview +/// }) +/// ``` +#let add-legend(label, preview: auto) = { + assert(preview == auto or type(preview) == function, + message: "Expected auto or function, got " + repr(type(preview))) + + return (( + type: "legend-item", + label: label, + style: (:), + axes: ("x", "y"), + ) + if preview != auto { + (plot-legend-preview: _ => { preview() }) + },) +} From e93c1d12ed683facf7f0d1ffe795e6d2ec7d7458 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 14:23:44 +0100 Subject: [PATCH 10/44] copy of mark (TODO: refactor) --- src/plot/mark.typ | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/plot/mark.typ diff --git a/src/plot/mark.typ b/src/plot/mark.typ new file mode 100644 index 0000000..9450d21 --- /dev/null +++ b/src/plot/mark.typ @@ -0,0 +1,45 @@ +#import "/src/cetz.typ": draw +#import "/src/axes.typ" + +// Draw mark at point with size +#let draw-mark-shape(pt, size, mark, style) = { + let sx = size + let sy = size + + let bl(pt) = (rel: (-sx/2, -sy/2), to: pt) + let br(pt) = (rel: (sx/2, -sy/2), to: pt) + let tl(pt) = (rel: (-sx/2, sy/2), to: pt) + let tr(pt) = (rel: (sx/2, sy/2), to: pt) + let ll(pt) = (rel: (-sx/2, 0), to: pt) + let rr(pt) = (rel: (sx/2, 0), to: pt) + let tt(pt) = (rel: (0, sy/2), to: pt) + let bb(pt) = (rel: (0, -sy/2), to: pt) + + if mark == "o" { + draw.circle(pt, radius: (sx/2, sy/2), ..style) + } else if mark == "square" { + draw.rect(bl(pt), tr(pt), ..style) + } else if mark == "triangle" { + draw.line(bl(pt), br(pt), tt(pt), close: true, ..style) + } else if mark == "*" or mark == "x" { + draw.line(bl(pt), tr(pt), ..style) + draw.line(tl(pt), br(pt), ..style) + } else if mark == "+" { + draw.line(ll(pt), rr(pt), ..style); + draw.line(tt(pt), bb(pt), ..style) + } else if mark == "-" { + draw.line(ll(pt), rr(pt), ..style) + } else if mark == "|" { + draw.line(tt(pt), bb(pt), ..style) + } +} + +#let draw-mark(pts, x, y, mark, mark-size, plot-size) = { + let pts = pts.map(pt => { + axes.transform-vec(plot-size, x, y, none, pt) + }).filter(pt => pt != none) + + for pt in pts { + draw-mark-shape(pt, mark-size, mark, (:)) + } +} From 9a42d92f6e73a6cca02c9363e5df0b86692a1548 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 14:24:01 +0100 Subject: [PATCH 11/44] begin work on orthorect-2d --- src/plots/orthorect-2d.typ | 111 ++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/src/plots/orthorect-2d.typ b/src/plots/orthorect-2d.typ index d6bd744..4cf9e23 100644 --- a/src/plots/orthorect-2d.typ +++ b/src/plots/orthorect-2d.typ @@ -1,5 +1,7 @@ +#import "/src/cetz.typ": draw, matrix, process, util -#let make-ctx(x, y, size) = { + +#let make-ctx((x, y), size) = { assert(x != none, message: "X axis does not exist") assert(y != none, message: "Y axis does not exist") assert(size.at(0) > 0 and size.at(1) > 0, message: "Plot size must be > 0") @@ -14,7 +16,72 @@ return (x: x, y: y, size: size, x-scale: x-scale, y-scale: y-scale) } -#let data-viewport(data, x, y, size, body, name: none) = { +// Transform a single vector along a x, y and z axis +// +// - size (vector): Coordinate system size +// - x-axis (axis): X axis +// - y-axis (axis): Y axis +// - z-axis (axis): Z axis +// - vec (vector): Input vector to transform +// -> vector +#let transform-vec(size, axes, vec) = { + + let (x,y,) = for (dim, axis) in axes.enumerate() { + + let s = size.at(dim) - axis.inset.sum() + let o = axis.inset.at(0) + + let transform-func(n) = if (axis.mode == "log") { + calc.log(calc.max(n, util.float-epsilon), base: axis.base) + } else {n} + + let range = transform-func(axis.max) - transform-func(axis.min) + + let f = s / range + ((transform-func(vec.at(dim)) - transform-func(axis.min)) * f + o,) + } + + return (x, y, 0) +} + +// Draw inside viewport coordinates of two axes +// +// - size (vector): Axis canvas size (relative to origin) +// - x (axis): Horizontal axis +// - y (axis): Vertical axis +// - z (axis): Z axis +// - name (string,none): Group name +#let axis-viewport(size,(x, y,), body, name: none) = { + draw.group(name: name, (ctx => { + let transform = ctx.transform + + ctx.transform = matrix.ident() + let (ctx, drawables, bounds) = process.many(ctx, util.resolve-body(ctx, body)) + + ctx.transform = transform + + drawables = drawables.map(d => { + if "segments" in d { + d.segments = d.segments.map(((kind, ..pts)) => { + (kind, ..pts.map(pt => { + transform-vec(size, (x, y, none), pt) + })) + }) + } + if "pos" in d { + d.pos = transform-vec(size, (x, y, none), d.pos) + } + return d + }) + + return ( + ctx: ctx, + drawables: drawable.apply-transform(ctx.transform, drawables) + ) + },)) +} + +#let data-viewport((x, y), size, body, name: none) = { if body == none or body == () { return } assert.ne(x.horizontal, y.horizontal, @@ -32,5 +99,43 @@ } // Setup the viewport - axes.axis-viewport(size, x, y, none, body, name: name) + axis-viewport(size, (x,y), body, name: name) +} + +#let draw-axes( + (w,h), + axis-dict, + name: none, + ..style +) = { + let bottom = axis-dict.at("x", default: none) + let top = axis-dict.at("x2", default: auto) + let left = axis-dict.at("y", default: none) + let right = axis-dict.at("y2", default: auto) + + if (top == auto){ + top = bottom + top.is-mirror = true + } + + if (right == auto){ + right = bottom + right.is-mirror = true + } + + draw.group(name: name, ctx => { + draw.anchor("origin", (0, 0)) + + // Handle style + let style = style.named() + style = styles.resolve(ctx.style, merge: style, root: "axes", + base: default-style-scientific) + style = _prepare-style(ctx, style) + + // Compute ticks + let x-ticks = compute-ticks(bottom, style) + let y-ticks = compute-ticks(left, style) + let x2-ticks = compute-ticks(top, style) + let y2-ticks = compute-ticks(right, style) + }) } \ No newline at end of file From a5012ab116d94b9ce4a35b0a8cda3090427314be Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 14:24:19 +0100 Subject: [PATCH 12/44] add in support functions (TODO: refactor) --- src/plot.typ | 363 +++++++++++++++++- src/plot/ticks.typ | 0 {tests => tests-old}/.gitignore | 0 {tests => tests-old}/axes/log-mode/ref/1.png | Bin {tests => tests-old}/axes/log-mode/test.typ | 0 {tests => tests-old}/axes/ref/1.png | Bin {tests => tests-old}/axes/test.typ | 0 .../chart/boxwhisker/ref/1.png | Bin .../chart/boxwhisker/test.typ | 0 {tests => tests-old}/chart/piechart/ref/1.png | Bin {tests => tests-old}/chart/piechart/test.typ | 0 {tests => tests-old}/chart/ref/1.png | Bin {tests => tests-old}/chart/test.typ | 0 tests-old/helper.typ | 31 ++ {tests => tests-old}/plot/annotation/ref.png | Bin .../plot/annotation/ref/1.png | Bin {tests => tests-old}/plot/annotation/test.typ | 0 {tests => tests-old}/plot/bar/ref/1.png | Bin {tests => tests-old}/plot/bar/test.typ | 0 .../plot/boxwhisker/ref/1.png | Bin {tests => tests-old}/plot/boxwhisker/test.typ | 0 .../plot/broken-axes/ref/1.png | Bin .../plot/broken-axes/test.typ | 0 {tests => tests-old}/plot/contour/ref/1.png | Bin {tests => tests-old}/plot/contour/test.typ | 0 .../plot/equal-axis/ref/1.png | Bin {tests => tests-old}/plot/equal-axis/test.typ | 0 {tests => tests-old}/plot/format/ref/1.png | Bin {tests => tests-old}/plot/format/test.typ | 0 {tests => tests-old}/plot/grid/ref/1.png | Bin {tests => tests-old}/plot/grid/test.typ | 0 {tests => tests-old}/plot/hvline/ref/1.png | Bin {tests => tests-old}/plot/hvline/test.typ | 0 {tests => tests-old}/plot/legend/ref/1.png | Bin {tests => tests-old}/plot/legend/test.typ | 0 .../plot/line/between/ref/1.png | Bin .../plot/line/between/test.typ | 0 {tests => tests-old}/plot/line/fill/ref/1.png | Bin {tests => tests-old}/plot/line/fill/test.typ | 0 .../plot/line/line-type/ref/1.png | Bin .../plot/line/line-type/test.typ | 0 .../plot/line/linearization/ref.png | Bin .../plot/line/linearization/ref/1.png | Bin .../plot/line/linearization/test.typ | 0 {tests => tests-old}/plot/line/mark/ref/1.png | Bin {tests => tests-old}/plot/line/mark/test.typ | 0 {tests => tests-old}/plot/line/spline/ref.png | Bin .../plot/line/spline/ref/1.png | Bin .../plot/line/spline/test.typ | 0 {tests => tests-old}/plot/marks/ref/1.png | Bin {tests => tests-old}/plot/marks/test.typ | 0 .../plot/mirror-axes/ref/1.png | Bin .../plot/mirror-axes/test.typ | 0 .../plot/parametric/ref/1.png | Bin {tests => tests-old}/plot/parametric/test.typ | 0 {tests => tests-old}/plot/ref.png | Bin {tests => tests-old}/plot/ref/1.png | Bin .../plot/reverse-axis/ref.png | Bin .../plot/reverse-axis/ref/1.png | Bin .../plot/reverse-axis/test.typ | 0 {tests => tests-old}/plot/sample/sample.typ | 0 {tests => tests-old}/plot/test.typ | 0 {tests => tests-old}/plot/vertical/ref/1.png | Bin {tests => tests-old}/plot/vertical/test.typ | 0 tests/plots/orthorect-2d/scatter/test.typ | 13 + 65 files changed, 393 insertions(+), 14 deletions(-) create mode 100644 src/plot/ticks.typ rename {tests => tests-old}/.gitignore (100%) rename {tests => tests-old}/axes/log-mode/ref/1.png (100%) rename {tests => tests-old}/axes/log-mode/test.typ (100%) rename {tests => tests-old}/axes/ref/1.png (100%) rename {tests => tests-old}/axes/test.typ (100%) rename {tests => tests-old}/chart/boxwhisker/ref/1.png (100%) rename {tests => tests-old}/chart/boxwhisker/test.typ (100%) rename {tests => tests-old}/chart/piechart/ref/1.png (100%) rename {tests => tests-old}/chart/piechart/test.typ (100%) rename {tests => tests-old}/chart/ref/1.png (100%) rename {tests => tests-old}/chart/test.typ (100%) create mode 100644 tests-old/helper.typ rename {tests => tests-old}/plot/annotation/ref.png (100%) rename {tests => tests-old}/plot/annotation/ref/1.png (100%) rename {tests => tests-old}/plot/annotation/test.typ (100%) rename {tests => tests-old}/plot/bar/ref/1.png (100%) rename {tests => tests-old}/plot/bar/test.typ (100%) rename {tests => tests-old}/plot/boxwhisker/ref/1.png (100%) rename {tests => tests-old}/plot/boxwhisker/test.typ (100%) rename {tests => tests-old}/plot/broken-axes/ref/1.png (100%) rename {tests => tests-old}/plot/broken-axes/test.typ (100%) rename {tests => tests-old}/plot/contour/ref/1.png (100%) rename {tests => tests-old}/plot/contour/test.typ (100%) rename {tests => tests-old}/plot/equal-axis/ref/1.png (100%) rename {tests => tests-old}/plot/equal-axis/test.typ (100%) rename {tests => tests-old}/plot/format/ref/1.png (100%) rename {tests => tests-old}/plot/format/test.typ (100%) rename {tests => tests-old}/plot/grid/ref/1.png (100%) rename {tests => tests-old}/plot/grid/test.typ (100%) rename {tests => tests-old}/plot/hvline/ref/1.png (100%) rename {tests => tests-old}/plot/hvline/test.typ (100%) rename {tests => tests-old}/plot/legend/ref/1.png (100%) rename {tests => tests-old}/plot/legend/test.typ (100%) rename {tests => tests-old}/plot/line/between/ref/1.png (100%) rename {tests => tests-old}/plot/line/between/test.typ (100%) rename {tests => tests-old}/plot/line/fill/ref/1.png (100%) rename {tests => tests-old}/plot/line/fill/test.typ (100%) rename {tests => tests-old}/plot/line/line-type/ref/1.png (100%) rename {tests => tests-old}/plot/line/line-type/test.typ (100%) rename {tests => tests-old}/plot/line/linearization/ref.png (100%) rename {tests => tests-old}/plot/line/linearization/ref/1.png (100%) rename {tests => tests-old}/plot/line/linearization/test.typ (100%) rename {tests => tests-old}/plot/line/mark/ref/1.png (100%) rename {tests => tests-old}/plot/line/mark/test.typ (100%) rename {tests => tests-old}/plot/line/spline/ref.png (100%) rename {tests => tests-old}/plot/line/spline/ref/1.png (100%) rename {tests => tests-old}/plot/line/spline/test.typ (100%) rename {tests => tests-old}/plot/marks/ref/1.png (100%) rename {tests => tests-old}/plot/marks/test.typ (100%) rename {tests => tests-old}/plot/mirror-axes/ref/1.png (100%) rename {tests => tests-old}/plot/mirror-axes/test.typ (100%) rename {tests => tests-old}/plot/parametric/ref/1.png (100%) rename {tests => tests-old}/plot/parametric/test.typ (100%) rename {tests => tests-old}/plot/ref.png (100%) rename {tests => tests-old}/plot/ref/1.png (100%) rename {tests => tests-old}/plot/reverse-axis/ref.png (100%) rename {tests => tests-old}/plot/reverse-axis/ref/1.png (100%) rename {tests => tests-old}/plot/reverse-axis/test.typ (100%) rename {tests => tests-old}/plot/sample/sample.typ (100%) rename {tests => tests-old}/plot/test.typ (100%) rename {tests => tests-old}/plot/vertical/ref/1.png (100%) rename {tests => tests-old}/plot/vertical/test.typ (100%) create mode 100644 tests/plots/orthorect-2d/scatter/test.typ diff --git a/src/plot.typ b/src/plot.typ index 641eb1b..1f47d34 100644 --- a/src/plot.typ +++ b/src/plot.typ @@ -1,5 +1,8 @@ +#import "/src/cetz.typ": draw, util #import "plots/orthorect-2d.typ" #import "plots/barycentric-2d.typ" +#import "plot/legend.typ" as plot-legend +#import "plot/axes.typ" // TODO: Refactor this into a better way of providing palettes @@ -15,8 +18,219 @@ return default-plot-style(i) } +// Get the default axis orientation +// depending on the axis name +#let get-default-axis-horizontal(name) = { + return lower(name).starts-with("x") +} + // Consider splitting into sevaral files -#let _create-axis-dict() +#let _handle-named-axis-args(ctx, axis-dict, options, plot-size) = { + import "/src/axes.typ" + + // Get axis option for name + let get-axis-option(axis-name, name, default) = { + let v = options.at(axis-name + "-" + name, default: default) + if v == auto { default } else { v } + } + + for (name, axis) in axis-dict { + if not "ticks" in axis { axis.ticks = () } + axis.label = get-axis-option(name, "label", $#name$) + + // Configure axis bounds + axis.min = get-axis-option(name, "min", axis.min) + axis.max = get-axis-option(name, "max", axis.max) + + assert(axis.min not in (none, auto) and + axis.max not in (none, auto), + message: "Axis min and max must be set.") + if axis.min == axis.max { + axis.min -= 1; axis.max += 1 + } + + axis.mode = get-axis-option(name, "mode", "lin") + axis.base = get-axis-option(name, "base", 10) + + // Configure axis orientation + axis.horizontal = get-axis-option(name, "horizontal", + get-default-axis-horizontal(name)) + + // Configure ticks + axis.ticks.list = get-axis-option(name, "ticks", ()) + axis.ticks.step = get-axis-option(name, "tick-step", axis.ticks.step) + axis.ticks.minor-step = get-axis-option(name, "minor-tick-step", axis.ticks.minor-step) + axis.ticks.decimals = get-axis-option(name, "decimals", 2) + axis.ticks.unit = get-axis-option(name, "unit", []) + axis.ticks.format = get-axis-option(name, "format", axis.ticks.format) + + // Axis break + axis.show-break = get-axis-option(name, "break", false) + axis.inset = get-axis-option(name, "inset", (0, 0)) + + // Configure grid + axis.ticks.grid = get-axis-option(name, "grid", false) + + axis-dict.at(name) = axis + } + + // Set axis options round two, after setting + // axis bounds + for (name, axis) in axis-dict { + let changed = false + + // Configure axis aspect ratio + let equal-to = get-axis-option(name, "equal", none) + if equal-to != none { + assert.eq(type(equal-to), str, + message: "Expected axis name.") + assert(equal-to != name, + message: "Axis can not be equal to itself.") + + let other = axis-dict.at(equal-to, default: none) + assert(other != none, + message: "Other axis must exist.") + assert(other.horizontal != axis.horizontal, + message: "Equal axes must have opposing orientation.") + + let (w, h) = plot-size + let ratio = if other.horizontal { + h / w + } else { + w / h + } + axis.min = other.min * ratio + axis.max = other.max * ratio + + changed = true + } + + if changed { + axis-dict.at(name) = axis + } + } + + for (name, axis) in axis-dict { + axis-dict.at(name) = axes.prepare-axis(ctx, axis, name) + } + + return axis-dict +} + +#let _create-axis-dict(ctx, data, annotations, anchors, options, size) = { + let axis-dict = (:) + for d in data + annotations { + if "axes" not in d { continue } + + for (i, name) in d.axes.enumerate() { + if not name in axis-dict { + axis-dict.insert(name, axes.axis( + min: none, max: none)) + } + + let axis = axis-dict.at(name) + let domain = if i == 0 { + d.at("x-domain", default: (0, 0)) + } else { + d.at("y-domain", default: (0, 0)) + } + if domain != (none, none) { + axis.min = util.min(axis.min, ..domain) + axis.max = util.max(axis.max, ..domain) + } + + axis-dict.at(name) = axis + } + } + + // Create axes for anchors + for a in anchors { + for (i, name) in a.axes.enumerate() { + if not name in axis-dict { + axis-dict.insert(name, axes.axis(min: none, max: none)) + } + } + } + + // Adjust axis bounds for annotations + for a in annotations { + let (x, y) = a.axes.map(name => axis-dict.at(name)) + (x, y) = calc-annotation-domain(ctx, x, y, a) + axis-dict.at(a.axes.at(0)) = x + axis-dict.at(a.axes.at(1)) = y + } + + // Set axis options + axis-dict = _handle-named-axis-args(ctx, axis-dict, options.named(), size) + return axis-dict +} + +#let _destructure-body(body) = { + + // early exit + if body == none {return ((),(),())} + + let data = () + let anchors = () + let annotations = () + for cmd in body { + assert(type(cmd) == dictionary and "type" in cmd, + message: "Expected plot sub-command in plot body") + + if cmd.type == "anchor" { + anchors.push(cmd) + } else if cmd.type == "annotation" { + annotations.push(cmd) + } else { + data.push(cmd) + } + } + + return (data, anchors, annotations) +} + +#let _prepare-data-styles(data, plot-style, mark-style) = { + for i in range(data.len()) { + if "style" not in data.at(i) { continue } + + let style-base = plot-style + if type(style-base) == function { + style-base = (style-base)(i) + } + assert.eq(type(style-base), dictionary, + message: "plot-style must be of type dictionary") + + if type(data.at(i).style) == function { + data.at(i).style = (data.at(i).style)(i) + } + assert.eq(type(style-base), dictionary, + message: "data plot-style must be of type dictionary") + + data.at(i).style = util.merge-dictionary( + style-base, data.at(i).style) + + if "mark-style" in data.at(i) { + let mark-style-base = mark-style + if type(mark-style-base) == function { + mark-style-base = (mark-style-base)(i) + } + assert.eq(type(mark-style-base), dictionary, + message: "mark-style must be of type dictionary") + + if type(data.at(i).mark-style) == function { + data.at(i).mark-style = (data.at(i).mark-style)(i) + } + + if type(data.at(i).mark-style) == dictionary { + data.at(i).mark-style = util.merge-dictionary( + mark-style-base, + data.at(i).mark-style + ) + } + } + } + return data +} #let plot( body, @@ -33,21 +247,142 @@ // TODO: Assert cetz min version here! - let data = () - let anchors = () - let annotations = () - let body = if body != none { body } else { () } + let (data, anchors, annotations) = _destructure-body(body) + let axis-dict = _create-axis-dict(ctx, data, anchors, annotations, options, size) + data = _prepare-data-styles(data, plot-style, mark-style) - for cmd in body { - assert(type(cmd) == dictionary and "type" in cmd, - message: "Expected plot sub-command in plot body") + draw.group(name: "plot", { + draw.anchor("origin", (0, 0)) + + // Prepare + for i in range(data.len()) { + if "axes" not in data.at(i) { continue } + + let axes = data.at(i).axes.map(name => axis-dict.at(name)) + let plot-ctx = axis-style.make-ctx(axes, size) + + if "plot-prepare" in data.at(i) { + data.at(i) = (data.at(i).plot-prepare)(data.at(i), plot-ctx) + assert(data.at(i) != none, + message: "Plot prepare(self, cxt) returned none!") + } + } + + // Background Annotations + for a in annotations.filter(a => a.background) { + let axes = a.axes.map(name => axis-dict.at(name)) + let plot-ctx = axis-style.make-ctx(axes, size) + + axis-style.data-viewport(axes, size, { + draw.anchor("default", (0, 0)) + a.body + }) + } + + // Fill + for d in data { + if "axes" not in d { continue } + + let axes = d.axes.map(name => axis-dict.at(name)) + let plot-ctx = axis-style.make-ctx(axes, size) + + axis-style.data-viewport(axes, size, { + draw.anchor("default", (0, 0)) + draw.set-style(..d.style) + + if "plot-fill" in d { + (d.plot-fill)(d, plot-ctx) + } + }) + } + + axis-style.draw-axes(size, axis-dict) + + // Stroke + Mark data + for d in data { + if "axes" not in d { continue } + + let axes = d.axes.map(name => axis-dict.at(name)) + let plot-ctx = axis-style.make-ctx(axes, size) + + axis-style.data-viewport(axes, size, { + draw.anchor("default", (0, 0)) + draw.set-style(..d.style) + + if "plot-stroke" in d { + (d.plot-stroke)(d, plot-ctx) + } + }) + + if "mark" in d and d.mark != none { + draw.group({ + draw.set-style(..d.style, ..d.mark-style) + mark.draw-mark(d.data, x, y, d.mark, d.mark-size, size) + }) + } + } + + // Foreground Annotations + for a in annotations.filter(a => not a.background) { + let axes = a.axes.map(name => axis-dict.at(name)) + let plot-ctx = make-ctx(axes, size) + + data-viewport(axes, size, { + draw.anchor("default", (0, 0)) + a.body + }) + } + + // Place anchors + for a in anchors { + let axes = a.axes.map(name => axis-dict.at(name)) + let plot-ctx = make-ctx(axes, size) + + let pt = a.position.enumerate().map(((i, v)) => { + if v == "min" { return axis-dict.at(a.axes.at(i)).min } + if v == "max" { return axis-dict.at(a.axes.at(i)).max } + return v + }) + pt = axes.transform-vec(size, x, y, none, pt) + if pt != none { + draw.anchor(a.name, pt) + } + } + }) + + if legend != none { + + let items = data.filter(d => "label" in d and d.label != none) + if items.len() > 0 { + + let legend-style = styles.resolve( + ctx.style, + base: plot-legend.default-style, + merge: legend-style, + root: "legend" + ) + + plot-legend.add-legend-anchors(legend-style, "plot", size) + + plot-legend.legend(legend, anchor: legend-anchor, { + for item in items { + let preview = if "plot-legend-preview" in item { + _ => {(item.plot-legend-preview)(item) } + } else { + auto + } + + plot-legend.item( + item.label, + preview, + mark: item.at("mark", default: none), + mark-size: item.at("mark-size", default: none), + mark-style: item.at("mark-style", default: none), + ..item.at("style", default: (:)) + ) + } + }, ..legend-style) - if cmd.type == "anchor" { - anchors.push(cmd) - } else if cmd.type == "annotation" { - annotations.push(cmd) - } else { - data.push(cmd) } } diff --git a/src/plot/ticks.typ b/src/plot/ticks.typ new file mode 100644 index 0000000..e69de29 diff --git a/tests/.gitignore b/tests-old/.gitignore similarity index 100% rename from tests/.gitignore rename to tests-old/.gitignore diff --git a/tests/axes/log-mode/ref/1.png b/tests-old/axes/log-mode/ref/1.png similarity index 100% rename from tests/axes/log-mode/ref/1.png rename to tests-old/axes/log-mode/ref/1.png diff --git a/tests/axes/log-mode/test.typ b/tests-old/axes/log-mode/test.typ similarity index 100% rename from tests/axes/log-mode/test.typ rename to tests-old/axes/log-mode/test.typ diff --git a/tests/axes/ref/1.png b/tests-old/axes/ref/1.png similarity index 100% rename from tests/axes/ref/1.png rename to tests-old/axes/ref/1.png diff --git a/tests/axes/test.typ b/tests-old/axes/test.typ similarity index 100% rename from tests/axes/test.typ rename to tests-old/axes/test.typ diff --git a/tests/chart/boxwhisker/ref/1.png b/tests-old/chart/boxwhisker/ref/1.png similarity index 100% rename from tests/chart/boxwhisker/ref/1.png rename to tests-old/chart/boxwhisker/ref/1.png diff --git a/tests/chart/boxwhisker/test.typ b/tests-old/chart/boxwhisker/test.typ similarity index 100% rename from tests/chart/boxwhisker/test.typ rename to tests-old/chart/boxwhisker/test.typ diff --git a/tests/chart/piechart/ref/1.png b/tests-old/chart/piechart/ref/1.png similarity index 100% rename from tests/chart/piechart/ref/1.png rename to tests-old/chart/piechart/ref/1.png diff --git a/tests/chart/piechart/test.typ b/tests-old/chart/piechart/test.typ similarity index 100% rename from tests/chart/piechart/test.typ rename to tests-old/chart/piechart/test.typ diff --git a/tests/chart/ref/1.png b/tests-old/chart/ref/1.png similarity index 100% rename from tests/chart/ref/1.png rename to tests-old/chart/ref/1.png diff --git a/tests/chart/test.typ b/tests-old/chart/test.typ similarity index 100% rename from tests/chart/test.typ rename to tests-old/chart/test.typ diff --git a/tests-old/helper.typ b/tests-old/helper.typ new file mode 100644 index 0000000..442acde --- /dev/null +++ b/tests-old/helper.typ @@ -0,0 +1,31 @@ +#import "/src/cetz.typ" +#import "/src/lib.typ" as cetz-plot + +/// Draw a cross at position pt +#let cross(pt, size: .25, ..style) = { + import cetz.draw: * + let len = size / 2 + line((rel: (-len,0), to: pt), + (rel: (len, 0), to: pt), stroke: green, ..style) + line((rel: (0,-len), to: pt), + (rel: (0, len), to: pt), stroke: green, ..style) +} + +/// Test case canvas surrounded by a red border +#let test-case(body, ..canvas-args, args: none) = { + if type(body) != function { + body = _ => { body } + args = (none,) + } else { + assert(type(args) == array and args.len() > 0, + message: "Function body requires args set!") + } + + for arg in args { + block(stroke: 2pt + red, + cetz.canvas(..canvas-args, { + body(arg) + }) + ) + } +} diff --git a/tests/plot/annotation/ref.png b/tests-old/plot/annotation/ref.png similarity index 100% rename from tests/plot/annotation/ref.png rename to tests-old/plot/annotation/ref.png diff --git a/tests/plot/annotation/ref/1.png b/tests-old/plot/annotation/ref/1.png similarity index 100% rename from tests/plot/annotation/ref/1.png rename to tests-old/plot/annotation/ref/1.png diff --git a/tests/plot/annotation/test.typ b/tests-old/plot/annotation/test.typ similarity index 100% rename from tests/plot/annotation/test.typ rename to tests-old/plot/annotation/test.typ diff --git a/tests/plot/bar/ref/1.png b/tests-old/plot/bar/ref/1.png similarity index 100% rename from tests/plot/bar/ref/1.png rename to tests-old/plot/bar/ref/1.png diff --git a/tests/plot/bar/test.typ b/tests-old/plot/bar/test.typ similarity index 100% rename from tests/plot/bar/test.typ rename to tests-old/plot/bar/test.typ diff --git a/tests/plot/boxwhisker/ref/1.png b/tests-old/plot/boxwhisker/ref/1.png similarity index 100% rename from tests/plot/boxwhisker/ref/1.png rename to tests-old/plot/boxwhisker/ref/1.png diff --git a/tests/plot/boxwhisker/test.typ b/tests-old/plot/boxwhisker/test.typ similarity index 100% rename from tests/plot/boxwhisker/test.typ rename to tests-old/plot/boxwhisker/test.typ diff --git a/tests/plot/broken-axes/ref/1.png b/tests-old/plot/broken-axes/ref/1.png similarity index 100% rename from tests/plot/broken-axes/ref/1.png rename to tests-old/plot/broken-axes/ref/1.png diff --git a/tests/plot/broken-axes/test.typ b/tests-old/plot/broken-axes/test.typ similarity index 100% rename from tests/plot/broken-axes/test.typ rename to tests-old/plot/broken-axes/test.typ diff --git a/tests/plot/contour/ref/1.png b/tests-old/plot/contour/ref/1.png similarity index 100% rename from tests/plot/contour/ref/1.png rename to tests-old/plot/contour/ref/1.png diff --git a/tests/plot/contour/test.typ b/tests-old/plot/contour/test.typ similarity index 100% rename from tests/plot/contour/test.typ rename to tests-old/plot/contour/test.typ diff --git a/tests/plot/equal-axis/ref/1.png b/tests-old/plot/equal-axis/ref/1.png similarity index 100% rename from tests/plot/equal-axis/ref/1.png rename to tests-old/plot/equal-axis/ref/1.png diff --git a/tests/plot/equal-axis/test.typ b/tests-old/plot/equal-axis/test.typ similarity index 100% rename from tests/plot/equal-axis/test.typ rename to tests-old/plot/equal-axis/test.typ diff --git a/tests/plot/format/ref/1.png b/tests-old/plot/format/ref/1.png similarity index 100% rename from tests/plot/format/ref/1.png rename to tests-old/plot/format/ref/1.png diff --git a/tests/plot/format/test.typ b/tests-old/plot/format/test.typ similarity index 100% rename from tests/plot/format/test.typ rename to tests-old/plot/format/test.typ diff --git a/tests/plot/grid/ref/1.png b/tests-old/plot/grid/ref/1.png similarity index 100% rename from tests/plot/grid/ref/1.png rename to tests-old/plot/grid/ref/1.png diff --git a/tests/plot/grid/test.typ b/tests-old/plot/grid/test.typ similarity index 100% rename from tests/plot/grid/test.typ rename to tests-old/plot/grid/test.typ diff --git a/tests/plot/hvline/ref/1.png b/tests-old/plot/hvline/ref/1.png similarity index 100% rename from tests/plot/hvline/ref/1.png rename to tests-old/plot/hvline/ref/1.png diff --git a/tests/plot/hvline/test.typ b/tests-old/plot/hvline/test.typ similarity index 100% rename from tests/plot/hvline/test.typ rename to tests-old/plot/hvline/test.typ diff --git a/tests/plot/legend/ref/1.png b/tests-old/plot/legend/ref/1.png similarity index 100% rename from tests/plot/legend/ref/1.png rename to tests-old/plot/legend/ref/1.png diff --git a/tests/plot/legend/test.typ b/tests-old/plot/legend/test.typ similarity index 100% rename from tests/plot/legend/test.typ rename to tests-old/plot/legend/test.typ diff --git a/tests/plot/line/between/ref/1.png b/tests-old/plot/line/between/ref/1.png similarity index 100% rename from tests/plot/line/between/ref/1.png rename to tests-old/plot/line/between/ref/1.png diff --git a/tests/plot/line/between/test.typ b/tests-old/plot/line/between/test.typ similarity index 100% rename from tests/plot/line/between/test.typ rename to tests-old/plot/line/between/test.typ diff --git a/tests/plot/line/fill/ref/1.png b/tests-old/plot/line/fill/ref/1.png similarity index 100% rename from tests/plot/line/fill/ref/1.png rename to tests-old/plot/line/fill/ref/1.png diff --git a/tests/plot/line/fill/test.typ b/tests-old/plot/line/fill/test.typ similarity index 100% rename from tests/plot/line/fill/test.typ rename to tests-old/plot/line/fill/test.typ diff --git a/tests/plot/line/line-type/ref/1.png b/tests-old/plot/line/line-type/ref/1.png similarity index 100% rename from tests/plot/line/line-type/ref/1.png rename to tests-old/plot/line/line-type/ref/1.png diff --git a/tests/plot/line/line-type/test.typ b/tests-old/plot/line/line-type/test.typ similarity index 100% rename from tests/plot/line/line-type/test.typ rename to tests-old/plot/line/line-type/test.typ diff --git a/tests/plot/line/linearization/ref.png b/tests-old/plot/line/linearization/ref.png similarity index 100% rename from tests/plot/line/linearization/ref.png rename to tests-old/plot/line/linearization/ref.png diff --git a/tests/plot/line/linearization/ref/1.png b/tests-old/plot/line/linearization/ref/1.png similarity index 100% rename from tests/plot/line/linearization/ref/1.png rename to tests-old/plot/line/linearization/ref/1.png diff --git a/tests/plot/line/linearization/test.typ b/tests-old/plot/line/linearization/test.typ similarity index 100% rename from tests/plot/line/linearization/test.typ rename to tests-old/plot/line/linearization/test.typ diff --git a/tests/plot/line/mark/ref/1.png b/tests-old/plot/line/mark/ref/1.png similarity index 100% rename from tests/plot/line/mark/ref/1.png rename to tests-old/plot/line/mark/ref/1.png diff --git a/tests/plot/line/mark/test.typ b/tests-old/plot/line/mark/test.typ similarity index 100% rename from tests/plot/line/mark/test.typ rename to tests-old/plot/line/mark/test.typ diff --git a/tests/plot/line/spline/ref.png b/tests-old/plot/line/spline/ref.png similarity index 100% rename from tests/plot/line/spline/ref.png rename to tests-old/plot/line/spline/ref.png diff --git a/tests/plot/line/spline/ref/1.png b/tests-old/plot/line/spline/ref/1.png similarity index 100% rename from tests/plot/line/spline/ref/1.png rename to tests-old/plot/line/spline/ref/1.png diff --git a/tests/plot/line/spline/test.typ b/tests-old/plot/line/spline/test.typ similarity index 100% rename from tests/plot/line/spline/test.typ rename to tests-old/plot/line/spline/test.typ diff --git a/tests/plot/marks/ref/1.png b/tests-old/plot/marks/ref/1.png similarity index 100% rename from tests/plot/marks/ref/1.png rename to tests-old/plot/marks/ref/1.png diff --git a/tests/plot/marks/test.typ b/tests-old/plot/marks/test.typ similarity index 100% rename from tests/plot/marks/test.typ rename to tests-old/plot/marks/test.typ diff --git a/tests/plot/mirror-axes/ref/1.png b/tests-old/plot/mirror-axes/ref/1.png similarity index 100% rename from tests/plot/mirror-axes/ref/1.png rename to tests-old/plot/mirror-axes/ref/1.png diff --git a/tests/plot/mirror-axes/test.typ b/tests-old/plot/mirror-axes/test.typ similarity index 100% rename from tests/plot/mirror-axes/test.typ rename to tests-old/plot/mirror-axes/test.typ diff --git a/tests/plot/parametric/ref/1.png b/tests-old/plot/parametric/ref/1.png similarity index 100% rename from tests/plot/parametric/ref/1.png rename to tests-old/plot/parametric/ref/1.png diff --git a/tests/plot/parametric/test.typ b/tests-old/plot/parametric/test.typ similarity index 100% rename from tests/plot/parametric/test.typ rename to tests-old/plot/parametric/test.typ diff --git a/tests/plot/ref.png b/tests-old/plot/ref.png similarity index 100% rename from tests/plot/ref.png rename to tests-old/plot/ref.png diff --git a/tests/plot/ref/1.png b/tests-old/plot/ref/1.png similarity index 100% rename from tests/plot/ref/1.png rename to tests-old/plot/ref/1.png diff --git a/tests/plot/reverse-axis/ref.png b/tests-old/plot/reverse-axis/ref.png similarity index 100% rename from tests/plot/reverse-axis/ref.png rename to tests-old/plot/reverse-axis/ref.png diff --git a/tests/plot/reverse-axis/ref/1.png b/tests-old/plot/reverse-axis/ref/1.png similarity index 100% rename from tests/plot/reverse-axis/ref/1.png rename to tests-old/plot/reverse-axis/ref/1.png diff --git a/tests/plot/reverse-axis/test.typ b/tests-old/plot/reverse-axis/test.typ similarity index 100% rename from tests/plot/reverse-axis/test.typ rename to tests-old/plot/reverse-axis/test.typ diff --git a/tests/plot/sample/sample.typ b/tests-old/plot/sample/sample.typ similarity index 100% rename from tests/plot/sample/sample.typ rename to tests-old/plot/sample/sample.typ diff --git a/tests/plot/test.typ b/tests-old/plot/test.typ similarity index 100% rename from tests/plot/test.typ rename to tests-old/plot/test.typ diff --git a/tests/plot/vertical/ref/1.png b/tests-old/plot/vertical/ref/1.png similarity index 100% rename from tests/plot/vertical/ref/1.png rename to tests-old/plot/vertical/ref/1.png diff --git a/tests/plot/vertical/test.typ b/tests-old/plot/vertical/test.typ similarity index 100% rename from tests/plot/vertical/test.typ rename to tests-old/plot/vertical/test.typ diff --git a/tests/plots/orthorect-2d/scatter/test.typ b/tests/plots/orthorect-2d/scatter/test.typ new file mode 100644 index 0000000..b70e132 --- /dev/null +++ b/tests/plots/orthorect-2d/scatter/test.typ @@ -0,0 +1,13 @@ +#set page(width: auto, height: auto) +#import "/tests/helper.typ": * + +#test-case({ + cetz-plot.plot( + axis-style: cetz-plot.orthorect-2d, + x-min: 0, x-max: 1, + y-min: 0, y-max: 1, + { + cetz.plot.add((x)=>x, domain: (0,1)) + } + ) +}) \ No newline at end of file From 9f21d9dffb89af058670a37e24f71b8e6b39d629 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 14:58:10 +0100 Subject: [PATCH 13/44] stepwise towards grid on orthorect-2s --- src/axes.typ | 924 ------------------ src/axes/axes.typ | 71 ++ src/axes/ticks.typ | 327 +++++++ src/lib.typ | 2 +- src/plot.typ | 7 +- src/plot/axes.typ | 37 - src/plot/mark.typ | 2 +- src/plot/styles.typ | 115 +++ src/plot/ticks.typ | 0 src/plots/orthorect-2d/grid.typ | 45 + src/plots/{ => orthorect-2d}/orthorect-2d.typ | 66 +- tests/plots/orthorect-2d/scatter/test.typ | 4 +- 12 files changed, 622 insertions(+), 978 deletions(-) delete mode 100644 src/axes.typ create mode 100644 src/axes/axes.typ create mode 100644 src/axes/ticks.typ delete mode 100644 src/plot/axes.typ create mode 100644 src/plot/styles.typ delete mode 100644 src/plot/ticks.typ create mode 100644 src/plots/orthorect-2d/grid.typ rename src/plots/{ => orthorect-2d}/orthorect-2d.typ (62%) diff --git a/src/axes.typ b/src/axes.typ deleted file mode 100644 index 3bd3b66..0000000 --- a/src/axes.typ +++ /dev/null @@ -1,924 +0,0 @@ -#import "/src/cetz.typ": util, draw, vector, matrix, styles, process, drawable, path-util, process -#import "/src/plot-old/formats.typ" - -#let typst-content = content - -/// Default axis style -/// -/// #show-parameter-block("tick-limit", "int", default: 100, [Upper major tick limit.]) -/// #show-parameter-block("minor-tick-limit", "int", default: 1000, [Upper minor tick limit.]) -/// #show-parameter-block("auto-tick-factors", "array", [List of tick factors used for automatic tick step determination.]) -/// #show-parameter-block("auto-tick-count", "int", [Number of ticks to generate by default.]) -/// #show-parameter-block("stroke", "stroke", [Axis stroke style.]) -/// #show-parameter-block("label.offset", "number", [Distance to move axis labels away from the axis.]) -/// #show-parameter-block("label.anchor", "anchor", [Anchor of the axis label to use for it's placement.]) -/// #show-parameter-block("label.angle", "angle", [Angle of the axis label.]) -/// #show-parameter-block("axis-layer", "float", [Layer to draw axes on (see @@on-layer() )]) -/// #show-parameter-block("grid-layer", "float", [Layer to draw the grid on (see @@on-layer() )]) -/// #show-parameter-block("background-layer", "float", [Layer to draw the background on (see @@on-layer() )]) -/// #show-parameter-block("padding", "number", [Extra distance between axes and plotting area. For schoolbook axes, this is the length of how much axes grow out of the plotting area.]) -/// #show-parameter-block("overshoot", "number", [School-book style axes only: Extra length to add to the end (right, top) of axes.]) -/// #show-parameter-block("tick.stroke", "stroke", [Major tick stroke style.]) -/// #show-parameter-block("tick.minor-stroke", "stroke", [Minor tick stroke style.]) -/// #show-parameter-block("tick.offset", ("number", "ratio"), [Major tick offset along the tick's direction, can be relative to the length.]) -/// #show-parameter-block("tick.minor-offset", ("number", "ratio"), [Minor tick offset along the tick's direction, can be relative to the length.]) -/// #show-parameter-block("tick.length", ("number"), [Major tick length.]) -/// #show-parameter-block("tick.minor-length", ("number", "ratio"), [Minor tick length, can be relative to the major tick length.]) -/// #show-parameter-block("tick.label.offset", ("number"), [Major tick label offset away from the tick.]) -/// #show-parameter-block("tick.label.angle", ("angle"), [Major tick label angle.]) -/// #show-parameter-block("tick.label.anchor", ("anchor"), [Anchor of major tick labels used for positioning.]) -/// #show-parameter-block("tick.label.show", ("auto", "bool"), default: auto, [Set visibility of tick labels. A value of `auto` shows tick labels for all but mirrored axes.]) -/// #show-parameter-block("grid.stroke", "stroke", [Major grid line stroke style.]) -/// #show-parameter-block("break-point.width", "number", [Axis break width along the axis.]) -/// #show-parameter-block("break-point.length", "number", [Axis break length.]) -/// #show-parameter-block("minor-grid.stroke", "stroke", [Minor grid line stroke style.]) -/// #show-parameter-block("shared-zero", ("bool", "content"), default: "$0$", [School-book style axes only: Content to display at the plots origin (0,0). If set to `false`, nothing is shown. Having this set, suppresses auto-generated ticks for $0$!]) -#let default-style = ( - tick-limit: 100, - minor-tick-limit: 1000, - auto-tick-factors: (1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10), // Tick factor to try - auto-tick-count: 11, // Number of ticks the plot tries to place - fill: none, - stroke: auto, - label: ( - offset: .2cm, // Axis label offset - anchor: auto, // Axis label anchor - angle: auto, // Axis label angle - ), - axis-layer: 0, - grid-layer: 0, - background-layer: 0, - padding: 0, - tick: ( - fill: none, - stroke: black + 1pt, - minor-stroke: black + .5pt, - offset: 0, - minor-offset: 0, - length: .1cm, // Tick length: Number - minor-length: 70%, // Minor tick length: Number, Ratio - label: ( - offset: .15cm, // Tick label offset - angle: 0deg, // Tick label angle - anchor: auto, // Tick label anchor - "show": auto, // Show tick labels for axes in use - ) - ), - break-point: ( - width: .75cm, - length: .15cm, - ), - grid: ( - stroke: (paint: gray.lighten(50%), thickness: 1pt), - ), - minor-grid: ( - stroke: (paint: gray.lighten(50%), thickness: .5pt), - ), -) - -// Default Scientific Style -#let default-style-scientific = util.merge-dictionary(default-style, ( - left: (tick: (label: (anchor: "east"))), - bottom: (tick: (label: (anchor: "north"))), - right: (tick: (label: (anchor: "west"))), - top: (tick: (label: (anchor: "south"))), - stroke: (cap: "square"), - padding: 0, -)) - -#let default-style-schoolbook = util.merge-dictionary(default-style, ( - x: (stroke: auto, fill: none, mark: (start: none, end: "straight"), - tick: (label: (anchor: "north"))), - y: (stroke: auto, fill: none, mark: (start: none, end: "straight"), - tick: (label: (anchor: "east"))), - label: (offset: .1cm), - origin: (label: (offset: .05cm)), - padding: .1cm, // Axis padding on both sides outsides the plotting area - overshoot: .5cm, // Axis end "overshoot" out of the plotting area - tick: ( - offset: -50%, - minor-offset: -50%, - length: .2cm, - minor-length: 70%, - ), - shared-zero: $0$, // Show zero tick label at (0, 0) -)) - -#let _prepare-style(ctx, style) = { - if type(style) != dictionary { return style } - - let res = util.resolve-number.with(ctx) - let rel-to(v, to) = { - if type(v) == ratio { - return v * to / 100% - } else { - return res(v) - } - } - - style.tick.length = res(style.tick.length) - style.tick.offset = rel-to(style.tick.offset, style.tick.length) - style.tick.minor-length = rel-to(style.tick.minor-length, style.tick.length) - style.tick.minor-offset = rel-to(style.tick.minor-offset, style.tick.minor-length) - style.tick.label.offset = res(style.tick.label.offset) - - // Break points - style.break-point.width = res(style.break-point.width) - style.break-point.length = res(style.break-point.length) - - // Padding - style.padding = res(style.padding) - - if "overshoot" in style { - style.overshoot = res(style.overshoot) - } - - return style -} - -#let _get-axis-style(ctx, style, name) = { - if not name in style { - return style - } - - style = styles.resolve(style, merge: style.at(name)) - return _prepare-style(ctx, style) -} - -#let _get-grid-type(axis) = { - let grid = axis.ticks.at("grid", default: false) - if grid == "major" or grid == true { return 1 } - if grid == "minor" { return 2 } - if grid == "both" { return 3 } - return 0 -} - -#let _inset-axis-points(ctx, style, axis, start, end) = { - if axis == none { return (start, end) } - - let (low, high) = axis.inset.map(v => util.resolve-number(ctx, v)) - - let is-horizontal = start.at(1) == end.at(1) - if is-horizontal { - start = vector.add(start, (low, 0)) - end = vector.sub(end, (high, 0)) - } else { - start = vector.add(start, (0, low)) - end = vector.sub(end, (0, high)) - } - return (start, end) -} - -#let _draw-axis-line(start, end, axis, is-horizontal, style) = { - let enabled = if axis != none and axis.show-break { - axis.min > 0 or axis.max < 0 - } else { false } - - if enabled { - let size = if is-horizontal { - (style.break-point.width, 0) - } else { - (0, style.break-point.width, 0) - } - - let up = if is-horizontal { - (0, style.break-point.length) - } else { - (style.break-point.length, 0) - } - - let add-break(is-end) = { - let a = () - let b = (rel: vector.scale(size, .3), update: false) - let c = (rel: vector.add(vector.scale(size, .4), vector.scale(up, -1)), update: false) - let d = (rel: vector.add(vector.scale(size, .6), vector.scale(up, +1)), update: false) - let e = (rel: vector.scale(size, .7), update: false) - let f = (rel: size) - - let mark = if is-end { - style.at("mark", default: none) - } - draw.line(a, b, c, d, e, f, stroke: style.stroke, mark: mark) - } - - draw.merge-path({ - draw.move-to(start) - if axis.min > 0 { - add-break(false) - draw.line((rel: size, to: start), end, mark: style.at("mark", default: none)) - } else if axis.max < 0 { - draw.line(start, (rel: vector.scale(size, -1), to: end)) - add-break(true) - } - }, stroke: style.stroke) - } else { - draw.line(start, end, stroke: style.stroke, mark: style.at("mark", default: none)) - } -} - -// Construct Axis Object -// -// - min (number): Minimum value -// - max (number): Maximum value -// - ticks (dictionary): Tick settings: -// - step (number): Major tic step -// - minor-step (number): Minor tic step -// - unit (content): Tick label suffix -// - decimals (int): Tick float decimal length -// - label (content): Axis label -// - mode (string): Axis scaling function. Takes `lin` or `log` -// - base (number): Base for tick labels when logarithmically scaled. -#let axis(min: -1, max: 1, label: none, - ticks: (step: auto, minor-step: none, - unit: none, decimals: 2, grid: false, - format: "float" - ), - mode: auto, base: auto) = ( - min: min, max: max, ticks: ticks, label: label, inset: (0, 0), show-break: false, mode: mode, base: base -) - -// Format a tick value -#let format-tick-value(value, tic-options) = { - // Without it we get negative zero in conversion - // to content! Typst has negative zero floats. - if value == 0 { value = 0 } - - let round(value, digits) = { - calc.round(value, digits: digits) - } - - let format-float(value, digits) = { - $#round(value, digits)$ - } - - if type(value) != typst-content { - let format = tic-options.at("format", default: "float") - if format == none { - value = [] - } else if type(format) == typst-content { - value = format - } else if type(format) == function { - value = (format)(value) - } else if format == "sci" { - value = formats.sci(value, digits: tic-options.at("decimals", default: 2)) - } else { - value = format-float(value, tic-options.at("decimals", default: 2)) - } - } else if type(value) != typst-content { - value = str(value) - } - - if tic-options.at("unit", default: none) != none { - value += tic-options.unit - } - return value -} - -// Get value on axis [0, 1] -// -// - axis (axis): Axis -// - v (number): Value -// -> float -#let value-on-axis(axis, v) = { - if v == none { return } - let (min, max) = (axis.min, axis.max) - let dt = max - min; if dt == 0 { dt = 1 } - - return (v - min) / dt -} - -// Compute list of linear ticks for axis -// -// - axis (axis): Axis -#let compute-linear-ticks(axis, style, add-zero: true) = { - let (min, max) = (axis.min, axis.max) - let dt = max - min; if (dt == 0) { dt = 1 } - let ticks = axis.ticks - let ferr = util.float-epsilon - let tick-limit = style.tick-limit - let minor-tick-limit = style.minor-tick-limit - - let l = () - if ticks != none { - let major-tick-values = () - if "step" in ticks and ticks.step != none { - assert(ticks.step >= 0, - message: "Axis tick step must be positive and non 0.") - if axis.min > axis.max { ticks.step *= -1 } - - let s = 1 / ticks.step - - let num-ticks = int(max * s + 1.5) - int(min * s) - assert(num-ticks <= tick-limit, - message: "Number of major ticks exceeds limit " + str(tick-limit)) - - let n = range(int(min * s), int(max * s + 1.5)) - for t in n { - let v = (t / s - min) / dt - if t / s == 0 and not add-zero { continue } - - if v >= 0 - ferr and v <= 1 + ferr { - l.push((v, format-tick-value(t / s, ticks), true)) - major-tick-values.push(v) - } - } - } - - if "minor-step" in ticks and ticks.minor-step != none { - assert(ticks.minor-step >= 0, - message: "Axis minor tick step must be positive") - if axis.min > axis.max { ticks.minor-step *= -1 } - - let s = 1 / ticks.minor-step - - let num-ticks = int(max * s + 1.5) - int(min * s) - assert(num-ticks <= minor-tick-limit, - message: "Number of minor ticks exceeds limit " + str(minor-tick-limit)) - - let n = range(int(min * s), int(max * s + 1.5)) - for t in n { - let v = (t / s - min) / dt - if v in major-tick-values { - // Prefer major ticks over minor ticks - continue - } - - if v != none and v >= 0 and v <= 1 + ferr { - l.push((v, none, false)) - } - } - } - - } - - return l -} - -// Compute list of linear ticks for axis -// -// - axis (axis): Axis -#let compute-logarithmic-ticks(axis, style, add-zero: true) = { - let ferr = util.float-epsilon - let (min, max) = ( - calc.log(calc.max(axis.min, ferr), base: axis.base), - calc.log(calc.max(axis.max, ferr), base: axis.base) - ) - let dt = max - min; if (dt == 0) { dt = 1 } - let ticks = axis.ticks - - let tick-limit = style.tick-limit - let minor-tick-limit = style.minor-tick-limit - let l = () - - if ticks != none { - let major-tick-values = () - if "step" in ticks and ticks.step != none { - assert(ticks.step >= 0, - message: "Axis tick step must be positive and non 0.") - if axis.min > axis.max { ticks.step *= -1 } - - let s = 1 / ticks.step - - let num-ticks = int(max * s + 1.5) - int(min * s) - assert(num-ticks <= tick-limit, - message: "Number of major ticks exceeds limit " + str(tick-limit)) - - let n = range( - int(min * s), - int(max * s + 1.5) - ) - - for t in n { - let v = (t / s - min) / dt - if t / s == 0 and not add-zero { continue } - - if v >= 0 - ferr and v <= 1 + ferr { - l.push((v, format-tick-value( calc.pow(axis.base, t / s), ticks), true)) - major-tick-values.push(v) - } - } - } - - if "minor-step" in ticks and ticks.minor-step != none { - assert(ticks.minor-step >= 0, - message: "Axis minor tick step must be positive") - if axis.min > axis.max { ticks.minor-step *= -1 } - - let s = 1 / ticks.step - let n = range(int(min * s)-1, int(max * s + 1.5)+1) - - for t in n { - for vv in range(1, int(axis.base / ticks.minor-step)) { - - let v = ( (calc.log(vv * ticks.minor-step, base: axis.base) + t)/ s - min) / dt - if v in major-tick-values {continue} - - if v != none and v >= 0 and v <= 1 + ferr { - l.push((v, none, false)) - } - - } - - } - } - } - - return l -} - -// Get list of fixed axis ticks -// -// - axis (axis): Axis object -#let fixed-ticks(axis) = { - let l = () - if "list" in axis.ticks { - for t in axis.ticks.list { - let (v, label) = (none, none) - if type(t) in (float, int) { - v = t - label = format-tick-value(t, axis.ticks) - } else { - (v, label) = t - } - - v = value-on-axis(axis, v) - if v != none and v >= 0 and v <= 1 { - l.push((v, label, true)) - } - } - } - return l -} - -// Compute list of axis ticks -// -// A tick triple has the format: -// (rel-value: float, label: content, major: bool) -// -// - axis (axis): Axis object -#let compute-ticks(axis, style, add-zero: true) = { - let find-max-n-ticks(axis, n: 11) = { - let dt = calc.abs(axis.max - axis.min) - let scale = calc.floor(calc.log(dt, base: 10) - 1) - if scale > 5 or scale < -5 {return none} - - let (step, best) = (none, 0) - for s in style.auto-tick-factors { - s = s * calc.pow(10, scale) - - let divs = calc.abs(dt / s) - if divs >= best and divs <= n { - step = s - best = divs - } - } - return step - } - - if axis == none or axis.ticks == none { return () } - if axis.ticks.step == auto { - axis.ticks.step = find-max-n-ticks(axis, n: style.auto-tick-count) - } - if axis.ticks.minor-step == auto { - axis.ticks.minor-step = if axis.ticks.step != none { - axis.ticks.step / 5 - } else { - none - } - } - - let ticks = if axis.mode == "log" { - compute-logarithmic-ticks(axis, style, add-zero: add-zero) - } else { - compute-linear-ticks(axis, style, add-zero: add-zero) - } - ticks += fixed-ticks(axis) - return ticks -} - -// Prepares the axis post creation. The given axis -// must be completely set-up, including its intervall. -// Returns the prepared axis -#let prepare-axis(ctx, axis, name) = { - let style = styles.resolve(ctx.style, root: "axes", - base: default-style-scientific) - style = _prepare-style(ctx, style) - style = _get-axis-style(ctx, style, name) - - if type(axis.inset) != array { - axis.inset = (axis.inset, axis.inset) - } - - axis.inset = axis.inset.map(v => util.resolve-number(ctx, v)) - - if axis.show-break { - if axis.min > 0 { - axis.inset.at(0) += style.break-point.width - } else if axis.max < 0 { - axis.inset.at(1) += style.break-point.width - } - } - - return axis -} - -// Transform a single vector along a x, y and z axis -// -// - size (vector): Coordinate system size -// - x-axis (axis): X axis -// - y-axis (axis): Y axis -// - z-axis (axis): Z axis -// - vec (vector): Input vector to transform -// -> vector -#let transform-vec(size, x-axis, y-axis, z-axis, vec) = { - - let (x,y,) = for (dim, axis) in (x-axis, y-axis).enumerate() { - - let s = size.at(dim) - axis.inset.sum() - let o = axis.inset.at(0) - - let transform-func(n) = if (axis.mode == "log") { - calc.log(calc.max(n, util.float-epsilon), base: axis.base) - } else {n} - - let range = transform-func(axis.max) - transform-func(axis.min) - - let f = s / range - ((transform-func(vec.at(dim)) - transform-func(axis.min)) * f + o,) - } - - return (x, y, 0) -} - -// Draw inside viewport coordinates of two axes -// -// - size (vector): Axis canvas size (relative to origin) -// - x (axis): Horizontal axis -// - y (axis): Vertical axis -// - z (axis): Z axis -// - name (string,none): Group name -#let axis-viewport(size, x, y, z, body, name: none) = { - draw.group(name: name, (ctx => { - let transform = ctx.transform - - ctx.transform = matrix.ident() - let (ctx, drawables, bounds) = process.many(ctx, util.resolve-body(ctx, body)) - - ctx.transform = transform - - drawables = drawables.map(d => { - if "segments" in d { - d.segments = d.segments.map(((kind, ..pts)) => { - (kind, ..pts.map(pt => { - transform-vec(size, x, y, none, pt) - })) - }) - } - if "pos" in d { - d.pos = transform-vec(size, x, y, none, d.pos) - } - return d - }) - - return ( - ctx: ctx, - drawables: drawable.apply-transform(ctx.transform, drawables) - ) - },)) -} - -// Draw grid lines for the ticks of an axis -// -// - cxt (context): -// - axis (dictionary): The axis -// - ticks (array): The computed ticks -// - low (vector): Start position of a grid-line at tick 0 -// - high (vector): End position of a grid-line at tick 0 -// - dir (vector): Normalized grid direction vector along the grid axis -// - style (style): Axis style -#let draw-grid-lines(ctx, axis, ticks, low, high, dir, style) = { - let offset = (0,0) - if axis.inset != none { - let (inset-low, inset-high) = axis.inset.map(v => util.resolve-number(ctx, v)) - offset = vector.scale(vector.norm(dir), inset-low) - dir = vector.sub(dir, vector.scale(vector.norm(dir), inset-low + inset-high)) - } - - let kind = _get-grid-type(axis) - if kind > 0 { - for (distance, label, is-major) in ticks { - let offset = vector.add(vector.scale(dir, distance), offset) - let start = vector.add(low, offset) - let end = vector.add(high, offset) - - // Draw a major line - if is-major and (kind == 1 or kind == 3) { - draw.line(start, end, stroke: style.grid.stroke) - } - // Draw a minor line - if not is-major and kind >= 2 { - draw.line(start, end, stroke: style.minor-grid.stroke) - } - } - } -} - -// Place a list of tick marks and labels along a path -#let place-ticks-on-line(ticks, start, stop, style, flip: false, is-mirror: false) = { - let dir = vector.sub(stop, start) - let norm = vector.norm((-dir.at(1), dir.at(0), dir.at(2, default: 0))) - - let def(v, d) = { - return if v == none or v == auto {d} else {v} - } - - let show-label = style.tick.label.show - if show-label == auto { - show-label = not is-mirror - } - - for (distance, label, is-major) in ticks { - let offset = style.tick.offset - let length = if is-major { style.tick.length } else { style.tick.minor-length } - if flip { - offset *= -1 - length *= -1 - } - - let pt = vector.lerp(start, stop, distance) - let a = vector.add(pt, vector.scale(norm, offset)) - let b = vector.add(a, vector.scale(norm, length)) - - draw.line(a, b, stroke: style.tick.stroke) - - if show-label and label != none { - let offset = style.tick.label.offset - if flip { - offset *= -1 - length *= -1 - } - - let c = vector.sub(if length <= 0 { b } else { a }, - vector.scale(norm, offset)) - - let angle = def(style.tick.label.angle, 0deg) - let anchor = def(style.tick.label.anchor, "center") - - draw.content(c, [#label], angle: angle, anchor: anchor) - } - } -} - -// Draw up to four axes in an "scientific" style at origin (0, 0) -// -// - size (array): Size (width, height) -// - left (axis): Left (y) axis -// - bottom (axis): Bottom (x) axis -// - right (axis): Right axis -// - top (axis): Top axis -// - name (string): Object name -// - draw-unset (bool): Draw axes that are set to `none` -// - ..style (any): Style -#let scientific(size: (1, 1), - left: none, - right: auto, - bottom: none, - top: auto, - draw-unset: true, - name: none, - ..style) = { - import draw: * - - if right == auto { - if left != none { - right = left; right.is-mirror = true - } else { - right = none - } - } - if top == auto { - if bottom != none { - top = bottom; top.is-mirror = true - } else { - top = none - } - } - - group(name: name, ctx => { - let (w, h) = size - anchor("origin", (0, 0)) - - let style = style.named() - style = styles.resolve(ctx.style, merge: style, root: "axes", - base: default-style-scientific) - style = _prepare-style(ctx, style) - - // Compute ticks - let x-ticks = compute-ticks(bottom, style) - let y-ticks = compute-ticks(left, style) - let x2-ticks = compute-ticks(top, style) - let y2-ticks = compute-ticks(right, style) - - // Draw frame - if style.fill != none { - on-layer(style.background-layer, { - rect((0,0), (w,h), fill: style.fill, stroke: none) - }) - } - - // Draw grid - group(name: "grid", ctx => { - let axes = ( - ("bottom", (0,0), (0,h), (+w,0), x-ticks, bottom), - ("top", (0,h), (0,0), (+w,0), x2-ticks, top), - ("left", (0,0), (w,0), (0,+h), y-ticks, left), - ("right", (w,0), (0,0), (0,+h), y2-ticks, right), - ) - for (name, start, end, direction, ticks, axis) in axes { - if axis == none { continue } - - let style = _get-axis-style(ctx, style, name) - let is-mirror = axis.at("is-mirror", default: false) - - if not is-mirror { - on-layer(style.grid-layer, { - draw-grid-lines(ctx, axis, ticks, start, end, direction, style) - }) - } - } - }) - - // Draw axes - group(name: "axes", { - let axes = ( - ("bottom", (0, 0), (w, 0), (0, -1), false, x-ticks, bottom,), - ("top", (0, h), (w, h), (0, +1), true, x2-ticks, top,), - ("left", (0, 0), (0, h), (-1, 0), true, y-ticks, left,), - ("right", (w, 0), (w, h), (+1, 0), false, y2-ticks, right,) - ) - let label-placement = ( - bottom: ("south", "north", 0deg), - top: ("north", "south", 0deg), - left: ("west", "south", 90deg), - right: ("east", "north", 90deg), - ) - - for (name, start, end, outsides, flip, ticks, axis) in axes { - let style = _get-axis-style(ctx, style, name) - let is-mirror = axis == none or axis.at("is-mirror", default: false) - let is-horizontal = name in ("bottom", "top") - - if style.padding != 0 { - let padding = vector.scale(outsides, style.padding) - start = vector.add(start, padding) - end = vector.add(end, padding) - } - - let (data-start, data-end) = _inset-axis-points(ctx, style, axis, start, end) - - let path = _draw-axis-line(start, end, axis, is-horizontal, style) - on-layer(style.axis-layer, { - group(name: "axis", { - if draw-unset or axis != none { - path; - place-ticks-on-line(ticks, data-start, data-end, style, flip: flip, is-mirror: is-mirror) - } - }) - - if axis != none and axis.label != none and not is-mirror { - let offset = vector.scale(outsides, style.label.offset) - let (group-anchor, content-anchor, angle) = label-placement.at(name) - - if style.label.anchor != auto { - content-anchor = style.label.anchor - } - if style.label.angle != auto { - angle = style.label.angle - } - - content((rel: offset, to: "axis." + group-anchor), - [#axis.label], - angle: angle, - anchor: content-anchor) - } - }) - } - }) - }) -} - -// Draw two axes in a "school book" style -// -// - x-axis (axis): X axis -// - y-axis (axis): Y axis -// - size (array): Size (width, height) -// - x-position (number): X Axis position -// - y-position (number): Y Axis position -// - name (string): Object name -// - ..style (any): Style -#let school-book(x-axis, y-axis, - size: (1, 1), - x-position: 0, - y-position: 0, - name: none, - ..style) = { - import draw: * - - group(name: name, ctx => { - let (w, h) = size - anchor("origin", (0, 0)) - - let style = style.named() - style = styles.resolve( - ctx.style, - merge: style, - root: "axes", - base: default-style-schoolbook) - style = _prepare-style(ctx, style) - - let x-position = calc.min(calc.max(y-axis.min, x-position), y-axis.max) - let y-position = calc.min(calc.max(x-axis.min, y-position), x-axis.max) - let x-y = value-on-axis(y-axis, x-position) * h - let y-x = value-on-axis(x-axis, y-position) * w - - let shared-zero = style.shared-zero != false and x-position == 0 and y-position == 0 - - let x-ticks = compute-ticks(x-axis, style, add-zero: not shared-zero) - let y-ticks = compute-ticks(y-axis, style, add-zero: not shared-zero) - - // Draw grid - group(name: "grid", ctx => { - let axes = ( - ("x", (0,0), (0,h), (+w,0), x-ticks, x-axis), - ("y", (0,0), (w,0), (0,+h), y-ticks, y-axis), - ) - - for (name, start, end, direction, ticks, axis) in axes { - if axis == none { continue } - - let style = _get-axis-style(ctx, style, name) - on-layer(style.grid-layer, { - draw-grid-lines(ctx, axis, ticks, start, end, direction, style) - }) - } - }) - - // Draw axes - group(name: "axes", { - let axes = ( - ("x", (0, x-y), (w, x-y), (1, 0), false, x-ticks, x-axis), - ("y", (y-x, 0), (y-x, h), (0, 1), true, y-ticks, y-axis), - ) - let label-pos = ( - x: ("north", (0,-1)), - y: ("east", (-1,0)), - ) - - on-layer(style.axis-layer, { - for (name, start, end, dir, flip, ticks, axis) in axes { - let style = _get-axis-style(ctx, style, name) - - let pad = style.padding - let overshoot = style.overshoot - let vstart = vector.sub(start, vector.scale(dir, pad)) - let vend = vector.add(end, vector.scale(dir, pad + overshoot)) - let is-horizontal = name == "x" - - let (data-start, data-end) = _inset-axis-points(ctx, style, axis, start, end) - group(name: "axis", { - _draw-axis-line(vstart, vend, axis, is-horizontal, style) - place-ticks-on-line(ticks, data-start, data-end, style, flip: flip) - }) - - if axis.label != none { - let (content-anchor, offset-dir) = label-pos.at(name) - - let angle = if style.label.angle not in (none, auto) { - style.label.angle - } else { 0deg } - if style.label.anchor not in (none, auto) { - content-anchor = style.label.anchor - } - - let offset = vector.scale(offset-dir, style.label.offset) - content((rel: offset, to: vend), - [#axis.label], - angle: angle, - anchor: content-anchor) - } - } - - if shared-zero { - let pt = (rel: (-style.tick.label.offset, -style.tick.label.offset), - to: (y-x, x-y)) - let zero = if type(style.shared-zero) == typst-content { - style.shared-zero - } else { - $0$ - } - content(pt, zero, anchor: "north-east") - } - }) - }) - }) -} diff --git a/src/axes/axes.typ b/src/axes/axes.typ new file mode 100644 index 0000000..a1ab1c1 --- /dev/null +++ b/src/axes/axes.typ @@ -0,0 +1,71 @@ +#import "/src/cetz.typ": styles, util +#import "/src/plot/styles.typ": default-style, prepare-style, get-axis-style + +// Construct Axis Object +// +// - min (number): Minimum value +// - max (number): Maximum value +// - ticks (dictionary): Tick settings: +// - step (number): Major tic step +// - minor-step (number): Minor tic step +// - unit (content): Tick label suffix +// - decimals (int): Tick float decimal length +// - label (content): Axis label +// - mode (string): Axis scaling function. Takes `lin` or `log` +// - base (number): Base for tick labels when logarithmically scaled. +#let axis( + min: -1, + max: 1, + label: none, + ticks: ( + step: auto, + minor-step: none, + unit: none, + decimals: 2, + grid: false, + format: "float" + ), + mode: + auto, + base: auto +) = ( + min: min, + max: max, + ticks: ticks, + label: label, + inset: (0, 0), + show-break: false, + mode: mode, + base: base +) + +// Prepares the axis post creation. The given axis +// must be completely set-up, including its intervall. +// Returns the prepared axis +#let prepare-axis(ctx, axis, name) = { + let style = styles.resolve( + ctx.style, + root: "axes", + base: default-style + ) + style = prepare-style(ctx, style) + style = get-axis-style(ctx, style, name) + + if type(axis.inset) != array { + axis.inset = (axis.inset, axis.inset) + } + + axis.inset = axis.inset.map(v => util.resolve-number(ctx, v)) + + if axis.show-break { + if axis.min > 0 { + axis.inset.at(0) += style.break-point.width + } else if axis.max < 0 { + axis.inset.at(1) += style.break-point.width + } + } + + return axis +} + +#import "ticks.typ" \ No newline at end of file diff --git a/src/axes/ticks.typ b/src/axes/ticks.typ new file mode 100644 index 0000000..75ef61c --- /dev/null +++ b/src/axes/ticks.typ @@ -0,0 +1,327 @@ +#import "/src/cetz.typ": vector, util, draw + +// Format a tick value +#let format-tick-value(value, tic-options) = { + // Without it we get negative zero in conversion + // to content! Typst has negative zero floats. + if value == 0 { value = 0 } + + let round(value, digits) = { + calc.round(value, digits: digits) + } + + let format-float(value, digits) = { + $#round(value, digits)$ + } + + let format-sci(value, digits) = { + let exponent = if value != 0 { + calc.floor(calc.log(calc.abs(value), base: 10)) + } else { + 0 + } + + let ee = calc.pow(10, calc.abs(exponent + 1)) + if exponent > 0 { + value = value / ee * 10 + } else if exponent < 0 { + value = value * ee * 10 + } + + value = round(value, digits) + if exponent <= -1 or exponent >= 1 { + return $#value times 10^#exponent$ + } + return $#value$ + } + + if type(value) != content { + let format = tic-options.at("format", default: "float") + if format == none { + value = [] + } else if type(format) == content { + value = format + } else if type(format) == function { + value = (format)(value) + } else if format == "sci" { + value = format-sci(value, tic-options.at("decimals", default: 2)) + } else { + value = format-float(value, tic-options.at("decimals", default: 2)) + } + } else { + value = str(value) + } + + if tic-options.at("unit", default: none) != none { + value += tic-options.unit + } + return value +} + +// Get value on axis [0, 1] +// +// - axis (axis): Axis +// - v (number): Value +// -> float +#let value-on-axis(axis, v) = { + if v == none { return } + let (min, max) = (axis.min, axis.max) + let dt = max - min; if dt == 0 { dt = 1 } + + return (v - min) / dt +} + +// Compute list of linear ticks for axis +// +// - axis (axis): Axis +#let compute-linear-ticks(axis, style, add-zero: true) = { + let (min, max) = (axis.min, axis.max) + let dt = max - min; if (dt == 0) { dt = 1 } + let ticks = axis.ticks + let ferr = util.float-epsilon + let tick-limit = style.tick-limit + let minor-tick-limit = style.minor-tick-limit + + let l = () + if ticks != none { + let major-tick-values = () + if "step" in ticks and ticks.step != none { + assert(ticks.step >= 0, + message: "Axis tick step must be positive and non 0.") + if axis.min > axis.max { ticks.step *= -1 } + + let s = 1 / ticks.step + + let num-ticks = int(max * s + 1.5) - int(min * s) + assert(num-ticks <= tick-limit, + message: "Number of major ticks exceeds limit " + str(tick-limit)) + + let n = range(int(min * s), int(max * s + 1.5)) + for t in n { + let v = (t / s - min) / dt + if t / s == 0 and not add-zero { continue } + + if v >= 0 - ferr and v <= 1 + ferr { + l.push((v, format-tick-value(t / s, ticks), true)) + major-tick-values.push(v) + } + } + } + + if "minor-step" in ticks and ticks.minor-step != none { + assert(ticks.minor-step >= 0, + message: "Axis minor tick step must be positive") + if axis.min > axis.max { ticks.minor-step *= -1 } + + let s = 1 / ticks.minor-step + + let num-ticks = int(max * s + 1.5) - int(min * s) + assert(num-ticks <= minor-tick-limit, + message: "Number of minor ticks exceeds limit " + str(minor-tick-limit)) + + let n = range(int(min * s), int(max * s + 1.5)) + for t in n { + let v = (t / s - min) / dt + if v in major-tick-values { + // Prefer major ticks over minor ticks + continue + } + + if v != none and v >= 0 and v <= 1 + ferr { + l.push((v, none, false)) + } + } + } + + } + + return l +} + +// Compute list of linear ticks for axis +// +// - axis (axis): Axis +#let compute-logarithmic-ticks(axis, style, add-zero: true) = { + let ferr = util.float-epsilon + let (min, max) = ( + calc.log(calc.max(axis.min, ferr), base: axis.base), + calc.log(calc.max(axis.max, ferr), base: axis.base) + ) + let dt = max - min; if (dt == 0) { dt = 1 } + let ticks = axis.ticks + + let tick-limit = style.tick-limit + let minor-tick-limit = style.minor-tick-limit + let l = () + + if ticks != none { + let major-tick-values = () + if "step" in ticks and ticks.step != none { + assert(ticks.step >= 0, + message: "Axis tick step must be positive and non 0.") + if axis.min > axis.max { ticks.step *= -1 } + + let s = 1 / ticks.step + + let num-ticks = int(max * s + 1.5) - int(min * s) + assert(num-ticks <= tick-limit, + message: "Number of major ticks exceeds limit " + str(tick-limit)) + + let n = range( + int(min * s), + int(max * s + 1.5) + ) + + for t in n { + let v = (t / s - min) / dt + if t / s == 0 and not add-zero { continue } + + if v >= 0 - ferr and v <= 1 + ferr { + l.push((v, format-tick-value( calc.pow(axis.base, t / s), ticks), true)) + major-tick-values.push(v) + } + } + } + + if "minor-step" in ticks and ticks.minor-step != none { + assert(ticks.minor-step >= 0, + message: "Axis minor tick step must be positive") + if axis.min > axis.max { ticks.minor-step *= -1 } + + let s = 1 / ticks.step + let n = range(int(min * s)-1, int(max * s + 1.5)+1) + + for t in n { + for vv in range(1, int(axis.base / ticks.minor-step)) { + + let v = ( (calc.log(vv * ticks.minor-step, base: axis.base) + t)/ s - min) / dt + if v in major-tick-values {continue} + + if v != none and v >= 0 and v <= 1 + ferr { + l.push((v, none, false)) + } + + } + + } + } + } + + return l +} + +// Get list of fixed axis ticks +// +// - axis (axis): Axis object +#let fixed-ticks(axis) = { + let l = () + if "list" in axis.ticks { + for t in axis.ticks.list { + let (v, label) = (none, none) + if type(t) in (float, int) { + v = t + label = format-tick-value(t, axis.ticks) + } else { + (v, label) = t + } + + v = value-on-axis(axis, v) + if v != none and v >= 0 and v <= 1 { + l.push((v, label, true)) + } + } + } + return l +} + +// Compute list of axis ticks +// +// A tick triple has the format: +// (rel-value: float, label: content, major: bool) +// +// - axis (axis): Axis object +#let compute-ticks(axis, style, add-zero: true) = { + let find-max-n-ticks(axis, n: 11) = { + let dt = calc.abs(axis.max - axis.min) + let scale = calc.floor(calc.log(dt, base: 10) - 1) + if scale > 5 or scale < -5 {return none} + + let (step, best) = (none, 0) + for s in style.auto-tick-factors { + s = s * calc.pow(10, scale) + + let divs = calc.abs(dt / s) + if divs >= best and divs <= n { + step = s + best = divs + } + } + return step + } + + if axis == none or axis.ticks == none { return () } + if axis.ticks.step == auto { + axis.ticks.step = find-max-n-ticks(axis, n: style.auto-tick-count) + } + if axis.ticks.minor-step == auto { + axis.ticks.minor-step = if axis.ticks.step != none { + axis.ticks.step / 5 + } else { + none + } + } + + let ticks = if axis.mode == "log" { + compute-logarithmic-ticks(axis, style, add-zero: add-zero) + } else { + compute-linear-ticks(axis, style, add-zero: add-zero) + } + ticks += fixed-ticks(axis) + return ticks +} + +// Place a list of tick marks and labels along a path +#let place-ticks-on-line(ticks, start, stop, style, flip: false, is-mirror: false) = { + let dir = vector.sub(stop, start) + let norm = vector.norm((-dir.at(1), dir.at(0), dir.at(2, default: 0))) + + let def(v, d) = { + return if v == none or v == auto {d} else {v} + } + + let show-label = style.tick.label.show + if show-label == auto { + show-label = not is-mirror + } + + for (distance, label, is-major) in ticks { + let offset = style.tick.offset + let length = if is-major { style.tick.length } else { style.tick.minor-length } + if flip { + offset *= -1 + length *= -1 + } + + let pt = vector.lerp(start, stop, distance) + let a = vector.add(pt, vector.scale(norm, offset)) + let b = vector.add(a, vector.scale(norm, length)) + + draw.line(a, b, stroke: style.tick.stroke) + + if show-label and label != none { + let offset = style.tick.label.offset + if flip { + offset *= -1 + length *= -1 + } + + let c = vector.sub(if length <= 0 { b } else { a }, + vector.scale(norm, offset)) + + let angle = def(style.tick.label.angle, 0deg) + let anchor = def(style.tick.label.anchor, "center") + + draw.content(c, [#label], angle: angle, anchor: anchor) + } + } +} \ No newline at end of file diff --git a/src/lib.typ b/src/lib.typ index 2ec7c33..0bfc928 100644 --- a/src/lib.typ +++ b/src/lib.typ @@ -1,5 +1,5 @@ #let version = version(0,1,0) -#import "/src/axes.typ" +// #import "/src/axes.typ" #import "/src/plot.typ": plot, orthorect-2d, barycentric-2d #import "/src/chart.typ" diff --git a/src/plot.typ b/src/plot.typ index 1f47d34..13db78d 100644 --- a/src/plot.typ +++ b/src/plot.typ @@ -1,8 +1,8 @@ -#import "/src/cetz.typ": draw, util -#import "plots/orthorect-2d.typ" +#import "/src/cetz.typ": draw, util, styles +#import "plots/orthorect-2d/orthorect-2d.typ" #import "plots/barycentric-2d.typ" #import "plot/legend.typ" as plot-legend -#import "plot/axes.typ" +#import "axes/axes.typ" // TODO: Refactor this into a better way of providing palettes @@ -26,7 +26,6 @@ // Consider splitting into sevaral files #let _handle-named-axis-args(ctx, axis-dict, options, plot-size) = { - import "/src/axes.typ" // Get axis option for name let get-axis-option(axis-name, name, default) = { diff --git a/src/plot/axes.typ b/src/plot/axes.typ deleted file mode 100644 index e83a3e1..0000000 --- a/src/plot/axes.typ +++ /dev/null @@ -1,37 +0,0 @@ -// Construct Axis Object -// -// - min (number): Minimum value -// - max (number): Maximum value -// - ticks (dictionary): Tick settings: -// - step (number): Major tic step -// - minor-step (number): Minor tic step -// - unit (content): Tick label suffix -// - decimals (int): Tick float decimal length -// - label (content): Axis label -// - mode (string): Axis scaling function. Takes `lin` or `log` -// - base (number): Base for tick labels when logarithmically scaled. -#let axis( - min: -1, - max: 1, - label: none, - ticks: ( - step: auto, - minor-step: none, - unit: none, - decimals: 2, - grid: false, - format: "float" - ), - mode: - auto, - base: auto -) = ( - min: min, - max: max, - ticks: ticks, - label: label, - inset: (0, 0), - show-break: false, - mode: mode, - base: base -) \ No newline at end of file diff --git a/src/plot/mark.typ b/src/plot/mark.typ index 9450d21..0e2ac52 100644 --- a/src/plot/mark.typ +++ b/src/plot/mark.typ @@ -1,5 +1,5 @@ #import "/src/cetz.typ": draw -#import "/src/axes.typ" +#import "/src/axes/axes.typ" // Draw mark at point with size #let draw-mark-shape(pt, size, mark, style) = { diff --git a/src/plot/styles.typ b/src/plot/styles.typ new file mode 100644 index 0000000..61179bc --- /dev/null +++ b/src/plot/styles.typ @@ -0,0 +1,115 @@ +#import "/src/cetz.typ": util, styles + +/// Default axis style +/// +/// #show-parameter-block("tick-limit", "int", default: 100, [Upper major tick limit.]) +/// #show-parameter-block("minor-tick-limit", "int", default: 1000, [Upper minor tick limit.]) +/// #show-parameter-block("auto-tick-factors", "array", [List of tick factors used for automatic tick step determination.]) +/// #show-parameter-block("auto-tick-count", "int", [Number of ticks to generate by default.]) +/// #show-parameter-block("stroke", "stroke", [Axis stroke style.]) +/// #show-parameter-block("label.offset", "number", [Distance to move axis labels away from the axis.]) +/// #show-parameter-block("label.anchor", "anchor", [Anchor of the axis label to use for it's placement.]) +/// #show-parameter-block("label.angle", "angle", [Angle of the axis label.]) +/// #show-parameter-block("axis-layer", "float", [Layer to draw axes on (see @@on-layer() )]) +/// #show-parameter-block("grid-layer", "float", [Layer to draw the grid on (see @@on-layer() )]) +/// #show-parameter-block("background-layer", "float", [Layer to draw the background on (see @@on-layer() )]) +/// #show-parameter-block("padding", "number", [Extra distance between axes and plotting area. For schoolbook axes, this is the length of how much axes grow out of the plotting area.]) +/// #show-parameter-block("overshoot", "number", [School-book style axes only: Extra length to add to the end (right, top) of axes.]) +/// #show-parameter-block("tick.stroke", "stroke", [Major tick stroke style.]) +/// #show-parameter-block("tick.minor-stroke", "stroke", [Minor tick stroke style.]) +/// #show-parameter-block("tick.offset", ("number", "ratio"), [Major tick offset along the tick's direction, can be relative to the length.]) +/// #show-parameter-block("tick.minor-offset", ("number", "ratio"), [Minor tick offset along the tick's direction, can be relative to the length.]) +/// #show-parameter-block("tick.length", ("number"), [Major tick length.]) +/// #show-parameter-block("tick.minor-length", ("number", "ratio"), [Minor tick length, can be relative to the major tick length.]) +/// #show-parameter-block("tick.label.offset", ("number"), [Major tick label offset away from the tick.]) +/// #show-parameter-block("tick.label.angle", ("angle"), [Major tick label angle.]) +/// #show-parameter-block("tick.label.anchor", ("anchor"), [Anchor of major tick labels used for positioning.]) +/// #show-parameter-block("tick.label.show", ("auto", "bool"), default: auto, [Set visibility of tick labels. A value of `auto` shows tick labels for all but mirrored axes.]) +/// #show-parameter-block("grid.stroke", "stroke", [Major grid line stroke style.]) +/// #show-parameter-block("break-point.width", "number", [Axis break width along the axis.]) +/// #show-parameter-block("break-point.length", "number", [Axis break length.]) +/// #show-parameter-block("minor-grid.stroke", "stroke", [Minor grid line stroke style.]) +/// #show-parameter-block("shared-zero", ("bool", "content"), default: "$0$", [School-book style axes only: Content to display at the plots origin (0,0). If set to `false`, nothing is shown. Having this set, suppresses auto-generated ticks for $0$!]) +#let default-style = ( + tick-limit: 100, + minor-tick-limit: 1000, + auto-tick-factors: (1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10), // Tick factor to try + auto-tick-count: 11, // Number of ticks the plot tries to place + fill: none, + stroke: auto, + label: ( + offset: .2cm, // Axis label offset + anchor: auto, // Axis label anchor + angle: auto, // Axis label angle + ), + axis-layer: 0, + grid-layer: 0, + background-layer: 0, + padding: 0, + tick: ( + fill: none, + stroke: black + 1pt, + minor-stroke: black + .5pt, + offset: 0, + minor-offset: 0, + length: .1cm, // Tick length: Number + minor-length: 70%, // Minor tick length: Number, Ratio + label: ( + offset: .15cm, // Tick label offset + angle: 0deg, // Tick label angle + anchor: auto, // Tick label anchor + "show": auto, // Show tick labels for axes in use + ) + ), + break-point: ( + width: .75cm, + length: .15cm, + ), + grid: ( + stroke: (paint: gray.lighten(50%), thickness: 1pt), + ), + minor-grid: ( + stroke: (paint: gray.lighten(50%), thickness: .5pt), + ), +) + +#let prepare-style(ctx, style) = { + if type(style) != dictionary { return style } + + let res = util.resolve-number.with(ctx) + let rel-to(v, to) = { + if type(v) == ratio { + return v * to / 100% + } else { + return res(v) + } + } + + style.tick.length = res(style.tick.length) + style.tick.offset = rel-to(style.tick.offset, style.tick.length) + style.tick.minor-length = rel-to(style.tick.minor-length, style.tick.length) + style.tick.minor-offset = rel-to(style.tick.minor-offset, style.tick.minor-length) + style.tick.label.offset = res(style.tick.label.offset) + + // Break points + style.break-point.width = res(style.break-point.width) + style.break-point.length = res(style.break-point.length) + + // Padding + style.padding = res(style.padding) + + if "overshoot" in style { + style.overshoot = res(style.overshoot) + } + + return style +} + +#let get-axis-style(ctx, style, name) = { + if not name in style { + return style + } + + style = styles.resolve(style, merge: style.at(name)) + return prepare-style(ctx, style) +} \ No newline at end of file diff --git a/src/plot/ticks.typ b/src/plot/ticks.typ deleted file mode 100644 index e69de29..0000000 diff --git a/src/plots/orthorect-2d/grid.typ b/src/plots/orthorect-2d/grid.typ new file mode 100644 index 0000000..3254c34 --- /dev/null +++ b/src/plots/orthorect-2d/grid.typ @@ -0,0 +1,45 @@ +#import "/src/cetz.typ": util, vector, draw + +#let _get-grid-type(axis) = { + let grid = axis.ticks.at("grid", default: false) + if grid == "major" or grid == true { return 1 } + if grid == "minor" { return 2 } + if grid == "both" { return 3 } + return 0 +} + +// Draw grid lines for the ticks of an axis +// +// - cxt (context): +// - axis (dictionary): The axis +// - ticks (array): The computed ticks +// - low (vector): Start position of a grid-line at tick 0 +// - high (vector): End position of a grid-line at tick 0 +// - dir (vector): Normalized grid direction vector along the grid axis +// - style (style): Axis style +#let draw-lines(ctx, axis, ticks, low, high, dir, style) = { + let offset = (0,0) + if axis.inset != none { + let (inset-low, inset-high) = axis.inset.map(v => util.resolve-number(ctx, v)) + offset = vector.scale(vector.norm(dir), inset-low) + dir = vector.sub(dir, vector.scale(vector.norm(dir), inset-low + inset-high)) + } + + let kind = _get-grid-type(axis) + if kind > 0 { + for (distance, label, is-major) in ticks { + let offset = vector.add(vector.scale(dir, distance), offset) + let start = vector.add(low, offset) + let end = vector.add(high, offset) + + // Draw a major line + if is-major and (kind == 1 or kind == 3) { + draw.line(start, end, stroke: style.grid.stroke) + } + // Draw a minor line + if not is-major and kind >= 2 { + draw.line(start, end, stroke: style.minor-grid.stroke) + } + } + } +} \ No newline at end of file diff --git a/src/plots/orthorect-2d.typ b/src/plots/orthorect-2d/orthorect-2d.typ similarity index 62% rename from src/plots/orthorect-2d.typ rename to src/plots/orthorect-2d/orthorect-2d.typ index 4cf9e23..9b98948 100644 --- a/src/plots/orthorect-2d.typ +++ b/src/plots/orthorect-2d/orthorect-2d.typ @@ -1,4 +1,17 @@ -#import "/src/cetz.typ": draw, matrix, process, util +#import "/src/cetz.typ": draw, matrix, process, util, drawable, styles +#import "/src/plot/styles.typ": default-style, prepare-style, get-axis-style +#import "/src/axes/axes.typ" + +#import "grid.typ" + +#let default-style-orthorect-2d = util.merge-dictionary(default-style, ( + left: (tick: (label: (anchor: "east"))), + bottom: (tick: (label: (anchor: "north"))), + right: (tick: (label: (anchor: "west"))), + top: (tick: (label: (anchor: "south"))), + stroke: (cap: "square"), + padding: 0, +)) #let make-ctx((x, y), size) = { @@ -64,12 +77,12 @@ if "segments" in d { d.segments = d.segments.map(((kind, ..pts)) => { (kind, ..pts.map(pt => { - transform-vec(size, (x, y, none), pt) + transform-vec(size, (x, y), pt) })) }) } if "pos" in d { - d.pos = transform-vec(size, (x, y, none), d.pos) + d.pos = transform-vec(size, (x, y), d.pos) } return d }) @@ -128,14 +141,47 @@ // Handle style let style = style.named() - style = styles.resolve(ctx.style, merge: style, root: "axes", - base: default-style-scientific) - style = _prepare-style(ctx, style) + style = styles.resolve( + ctx.style, + merge: style, + root: "axes", + base: default-style-orthorect-2d + ) + style = prepare-style(ctx, style) // Compute ticks - let x-ticks = compute-ticks(bottom, style) - let y-ticks = compute-ticks(left, style) - let x2-ticks = compute-ticks(top, style) - let y2-ticks = compute-ticks(right, style) + let x-ticks = axes.ticks.compute-ticks(bottom, style) + let y-ticks = axes.ticks.compute-ticks(left, style) + let x2-ticks = axes.ticks.compute-ticks(top, style) + let y2-ticks = axes.ticks.compute-ticks(right, style) + + // Draw frame + if style.fill != none { + draw.on-layer(style.background-layer, { + draw.rect((0,0), (w,h), fill: style.fill, stroke: none) + }) + } + + // Draw grid + draw.group(name: "grid", ctx => { + let axes = ( + ("bottom", (0,0), (0,h), (+w,0), x-ticks, bottom), + ("top", (0,h), (0,0), (+w,0), x2-ticks, top), + ("left", (0,0), (w,0), (0,+h), y-ticks, left), + ("right", (w,0), (0,0), (0,+h), y2-ticks, right), + ) + for (name, start, end, direction, ticks, axis) in axes { + if axis == none { continue } + + let style = get-axis-style(ctx, style, name) + let is-mirror = axis.at("is-mirror", default: false) + + if not is-mirror { + draw.on-layer(style.grid-layer, { + grid.draw-lines(ctx, axis, ticks, start, end, direction, style) + }) + } + } + }) }) } \ No newline at end of file diff --git a/tests/plots/orthorect-2d/scatter/test.typ b/tests/plots/orthorect-2d/scatter/test.typ index b70e132..c3a6f79 100644 --- a/tests/plots/orthorect-2d/scatter/test.typ +++ b/tests/plots/orthorect-2d/scatter/test.typ @@ -2,12 +2,14 @@ #import "/tests/helper.typ": * #test-case({ + cetz.draw.set-style(axes:( fill: luma(85%))) cetz-plot.plot( axis-style: cetz-plot.orthorect-2d, + size: (5,5), x-min: 0, x-max: 1, y-min: 0, y-max: 1, { - cetz.plot.add((x)=>x, domain: (0,1)) + cetz.plot.add((x)=>x, domain: (0,1), label: $y=x$) } ) }) \ No newline at end of file From ae2bd2ce44cc07232ec0a4f1b6edfb34654e9f7b Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 15:20:03 +0100 Subject: [PATCH 14/44] continue rampage --- src/plot.typ | 12 +- .../{ => barycentric-2d}/barycentric-2d.typ | 0 src/plots/orthorect-2d/axis.typ | 110 +++++++++++++ src/plots/orthorect-2d/orthorect-2d.typ | 147 +++++++----------- src/plots/orthorect-2d/transforms.typ | 87 +++++++++++ tests/plots/orthorect-2d/scatter/test.typ | 11 +- 6 files changed, 272 insertions(+), 95 deletions(-) rename src/plots/{ => barycentric-2d}/barycentric-2d.typ (100%) create mode 100644 src/plots/orthorect-2d/axis.typ create mode 100644 src/plots/orthorect-2d/transforms.typ diff --git a/src/plot.typ b/src/plot.typ index 13db78d..ba862d6 100644 --- a/src/plot.typ +++ b/src/plot.typ @@ -1,17 +1,21 @@ #import "/src/cetz.typ": draw, util, styles -#import "plots/orthorect-2d/orthorect-2d.typ" -#import "plots/barycentric-2d.typ" + #import "plot/legend.typ" as plot-legend #import "axes/axes.typ" +#import "plots/orthorect-2d/orthorect-2d.typ" +#import "plots/barycentric-2d/barycentric-2d.typ" + // TODO: Refactor this into a better way of providing palettes #let default-colors = (blue, red, green, yellow, black) #let default-plot-style(i) = { let color = default-colors.at(calc.rem(i, default-colors.len())) - return (stroke: color, - fill: color.lighten(75%)) + return ( + stroke: color, + fill: color.lighten(75%) + ) } #let default-mark-style(i) = { diff --git a/src/plots/barycentric-2d.typ b/src/plots/barycentric-2d/barycentric-2d.typ similarity index 100% rename from src/plots/barycentric-2d.typ rename to src/plots/barycentric-2d/barycentric-2d.typ diff --git a/src/plots/orthorect-2d/axis.typ b/src/plots/orthorect-2d/axis.typ new file mode 100644 index 0000000..8597321 --- /dev/null +++ b/src/plots/orthorect-2d/axis.typ @@ -0,0 +1,110 @@ +#import "/src/cetz.typ": draw, util, vector + +#let inset-axis-points(ctx, style, axis, start, end) = { + if axis == none { return (start, end) } + + let (low, high) = axis.inset.map(v => util.resolve-number(ctx, v)) + + let is-horizontal = start.at(1) == end.at(1) + if is-horizontal { + start = vector.add(start, (low, 0)) + end = vector.sub(end, (high, 0)) + } else { + start = vector.add(start, (0, low)) + end = vector.sub(end, (0, high)) + } + return (start, end) +} + +#let draw-axis-line(start, end, axis, is-horizontal, style) = { + let enabled = if axis != none and axis.show-break { + axis.min > 0 or axis.max < 0 + } else { false } + + if enabled { + let size = if is-horizontal { + (style.break-point.width, 0) + } else { + (0, style.break-point.width, 0) + } + + let up = if is-horizontal { + (0, style.break-point.length) + } else { + (style.break-point.length, 0) + } + + let add-break(is-end) = { + let a = () + let b = (rel: vector.scale(size, .3), update: false) + let c = (rel: vector.add(vector.scale(size, .4), vector.scale(up, -1)), update: false) + let d = (rel: vector.add(vector.scale(size, .6), vector.scale(up, +1)), update: false) + let e = (rel: vector.scale(size, .7), update: false) + let f = (rel: size) + + let mark = if is-end { + style.at("mark", default: none) + } + draw.line(a, b, c, d, e, f, stroke: style.stroke, mark: mark) + } + + draw.merge-path({ + draw.move-to(start) + if axis.min > 0 { + add-break(false) + draw.line((rel: size, to: start), end, mark: style.at("mark", default: none)) + } else if axis.max < 0 { + draw.line(start, (rel: vector.scale(size, -1), to: end)) + add-break(true) + } + }, stroke: style.stroke) + } else { + draw.line(start, end, stroke: style.stroke, mark: style.at("mark", default: none)) + } +} + +// Place a list of tick marks and labels along a path +#let place-ticks-on-line(ticks, start, stop, style, flip: false, is-mirror: false) = { + let dir = vector.sub(stop, start) + let norm = vector.norm((-dir.at(1), dir.at(0), dir.at(2, default: 0))) + + let def(v, d) = { + return if v == none or v == auto {d} else {v} + } + + let show-label = style.tick.label.show + if show-label == auto { + show-label = not is-mirror + } + + for (distance, label, is-major) in ticks { + let offset = style.tick.offset + let length = if is-major { style.tick.length } else { style.tick.minor-length } + if flip { + offset *= -1 + length *= -1 + } + + let pt = vector.lerp(start, stop, distance) + let a = vector.add(pt, vector.scale(norm, offset)) + let b = vector.add(a, vector.scale(norm, length)) + + draw.line(a, b, stroke: style.tick.stroke) + + if show-label and label != none { + let offset = style.tick.label.offset + if flip { + offset *= -1 + length *= -1 + } + + let c = vector.sub(if length <= 0 { b } else { a }, + vector.scale(norm, offset)) + + let angle = def(style.tick.label.angle, 0deg) + let anchor = def(style.tick.label.anchor, "center") + + draw.content(c, [#label], angle: angle, anchor: anchor) + } + } +} \ No newline at end of file diff --git a/src/plots/orthorect-2d/orthorect-2d.typ b/src/plots/orthorect-2d/orthorect-2d.typ index 9b98948..71f09ae 100644 --- a/src/plots/orthorect-2d/orthorect-2d.typ +++ b/src/plots/orthorect-2d/orthorect-2d.typ @@ -1,8 +1,10 @@ -#import "/src/cetz.typ": draw, matrix, process, util, drawable, styles +#import "/src/cetz.typ": draw, util, styles, vector #import "/src/plot/styles.typ": default-style, prepare-style, get-axis-style #import "/src/axes/axes.typ" #import "grid.typ" +#import "axis.typ": draw-axis-line, inset-axis-points, place-ticks-on-line +#import "transforms.typ": data-viewport, axis-viewport, #let default-style-orthorect-2d = util.merge-dictionary(default-style, ( left: (tick: (label: (anchor: "east"))), @@ -29,92 +31,6 @@ return (x: x, y: y, size: size, x-scale: x-scale, y-scale: y-scale) } -// Transform a single vector along a x, y and z axis -// -// - size (vector): Coordinate system size -// - x-axis (axis): X axis -// - y-axis (axis): Y axis -// - z-axis (axis): Z axis -// - vec (vector): Input vector to transform -// -> vector -#let transform-vec(size, axes, vec) = { - - let (x,y,) = for (dim, axis) in axes.enumerate() { - - let s = size.at(dim) - axis.inset.sum() - let o = axis.inset.at(0) - - let transform-func(n) = if (axis.mode == "log") { - calc.log(calc.max(n, util.float-epsilon), base: axis.base) - } else {n} - - let range = transform-func(axis.max) - transform-func(axis.min) - - let f = s / range - ((transform-func(vec.at(dim)) - transform-func(axis.min)) * f + o,) - } - - return (x, y, 0) -} - -// Draw inside viewport coordinates of two axes -// -// - size (vector): Axis canvas size (relative to origin) -// - x (axis): Horizontal axis -// - y (axis): Vertical axis -// - z (axis): Z axis -// - name (string,none): Group name -#let axis-viewport(size,(x, y,), body, name: none) = { - draw.group(name: name, (ctx => { - let transform = ctx.transform - - ctx.transform = matrix.ident() - let (ctx, drawables, bounds) = process.many(ctx, util.resolve-body(ctx, body)) - - ctx.transform = transform - - drawables = drawables.map(d => { - if "segments" in d { - d.segments = d.segments.map(((kind, ..pts)) => { - (kind, ..pts.map(pt => { - transform-vec(size, (x, y), pt) - })) - }) - } - if "pos" in d { - d.pos = transform-vec(size, (x, y), d.pos) - } - return d - }) - - return ( - ctx: ctx, - drawables: drawable.apply-transform(ctx.transform, drawables) - ) - },)) -} - -#let data-viewport((x, y), size, body, name: none) = { - if body == none or body == () { return } - - assert.ne(x.horizontal, y.horizontal, - message: "Data must use one horizontal and one vertical axis!") - - // If y is the horizontal axis, swap x and y - // coordinates by swapping the transformation - // matrix columns. - if y.horizontal { - (x, y) = (y, x) - body = draw.set-ctx(ctx => { - ctx.transform = matrix.swap-cols(ctx.transform, 0, 1) - return ctx - }) + body - } - - // Setup the viewport - axis-viewport(size, (x,y), body, name: name) -} - #let draw-axes( (w,h), axis-dict, @@ -183,5 +99,62 @@ } } }) + + // Draw axes + draw.group(name: "axes", { + let axes = ( + ("bottom", (0, 0), (w, 0), (0, -1), false, x-ticks, bottom,), + ("top", (0, h), (w, h), (0, +1), true, x2-ticks, top,), + ("left", (0, 0), (0, h), (-1, 0), true, y-ticks, left,), + ("right", (w, 0), (w, h), (+1, 0), false, y2-ticks, right,) + ) + let label-placement = ( + bottom: ("south", "north", 0deg), + top: ("north", "south", 0deg), + left: ("west", "south", 90deg), + right: ("east", "north", 90deg), + ) + + for (name, start, end, outsides, flip, ticks, axis) in axes { + let style = get-axis-style(ctx, style, name) + let is-mirror = axis == none or axis.at("is-mirror", default: false) + let is-horizontal = name in ("bottom", "top") + + if style.padding != 0 { + let padding = vector.scale(outsides, style.padding) + start = vector.add(start, padding) + end = vector.add(end, padding) + } + + let (data-start, data-end) = inset-axis-points(ctx, style, axis, start, end) + + let path = draw-axis-line(start, end, axis, is-horizontal, style) + draw.on-layer(style.axis-layer, { + draw.group(name: "axis", { + // if draw-unset or axis != none { + path; + place-ticks-on-line(ticks, data-start, data-end, style, flip: flip, is-mirror: is-mirror) + // } + }) + + if axis != none and axis.label != none and not is-mirror { + let offset = vector.scale(outsides, style.label.offset) + let (group-anchor, content-anchor, angle) = label-placement.at(name) + + if style.label.anchor != auto { + content-anchor = style.label.anchor + } + if style.label.angle != auto { + angle = style.label.angle + } + + draw.content((rel: offset, to: "axis." + group-anchor), + [#axis.label], + angle: angle, + anchor: content-anchor) + } + }) + } + }) }) } \ No newline at end of file diff --git a/src/plots/orthorect-2d/transforms.typ b/src/plots/orthorect-2d/transforms.typ new file mode 100644 index 0000000..15e191f --- /dev/null +++ b/src/plots/orthorect-2d/transforms.typ @@ -0,0 +1,87 @@ +#import "/src/cetz.typ": draw, matrix, process, util, drawable + +// Transform a single vector along a x, y and z axis +// +// - size (vector): Coordinate system size +// - x-axis (axis): X axis +// - y-axis (axis): Y axis +// - z-axis (axis): Z axis +// - vec (vector): Input vector to transform +// -> vector +#let transform-vec(size, axes, vec) = { + + let (x,y,) = for (dim, axis) in axes.enumerate() { + + let s = size.at(dim) - axis.inset.sum() + let o = axis.inset.at(0) + + let transform-func(n) = if (axis.mode == "log") { + calc.log(calc.max(n, util.float-epsilon), base: axis.base) + } else {n} + + let range = transform-func(axis.max) - transform-func(axis.min) + + let f = s / range + ((transform-func(vec.at(dim)) - transform-func(axis.min)) * f + o,) + } + + return (x, y, 0) +} + +// Draw inside viewport coordinates of two axes +// +// - size (vector): Axis canvas size (relative to origin) +// - x (axis): Horizontal axis +// - y (axis): Vertical axis +// - z (axis): Z axis +// - name (string,none): Group name +#let axis-viewport(size,(x, y,), body, name: none) = { + draw.group(name: name, (ctx => { + let transform = ctx.transform + + ctx.transform = matrix.ident() + let (ctx, drawables, bounds) = process.many(ctx, util.resolve-body(ctx, body)) + + ctx.transform = transform + + drawables = drawables.map(d => { + if "segments" in d { + d.segments = d.segments.map(((kind, ..pts)) => { + (kind, ..pts.map(pt => { + transform-vec(size, (x, y), pt) + })) + }) + } + if "pos" in d { + d.pos = transform-vec(size, (x, y), d.pos) + } + return d + }) + + return ( + ctx: ctx, + drawables: drawable.apply-transform(ctx.transform, drawables) + ) + },)) +} + +#let data-viewport((x, y), size, body, name: none) = { + if body == none or body == () { return } + + assert.ne(x.horizontal, y.horizontal, + message: "Data must use one horizontal and one vertical axis!") + + // If y is the horizontal axis, swap x and y + // coordinates by swapping the transformation + // matrix columns. + if y.horizontal { + (x, y) = (y, x) + body = draw.set-ctx(ctx => { + ctx.transform = matrix.swap-cols(ctx.transform, 0, 1) + return ctx + }) + body + } + + // Setup the viewport + axis-viewport(size, (x,y), body, name: name) +} \ No newline at end of file diff --git a/tests/plots/orthorect-2d/scatter/test.typ b/tests/plots/orthorect-2d/scatter/test.typ index c3a6f79..bdd045d 100644 --- a/tests/plots/orthorect-2d/scatter/test.typ +++ b/tests/plots/orthorect-2d/scatter/test.typ @@ -2,14 +2,17 @@ #import "/tests/helper.typ": * #test-case({ - cetz.draw.set-style(axes:( fill: luma(85%))) + // cetz.draw.set-style(axes:( fill: luma(85%))) cetz-plot.plot( axis-style: cetz-plot.orthorect-2d, size: (5,5), - x-min: 0, x-max: 1, - y-min: 0, y-max: 1, + // x-min: 1, x-max: 100, x-tick-step: 1, x-minor-tick-step: 1, + // x-mode: "log", + x-grid: "both", + y-min: 0, y-max: 10, + y-grid: "both", { - cetz.plot.add((x)=>x, domain: (0,1), label: $y=x$) + cetz.plot.add((x)=>x, domain: (0,10), label: $y=x$, line: "raw") } ) }) \ No newline at end of file From 544afbd790121d5009612d1aa036db4c9d3d033b Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 16:55:26 +0100 Subject: [PATCH 15/44] directory restructure --- src/axes/axes.typ | 1 + src/{plot-old/formats.typ => axes/format.typ} | 8 +- src/{plot-old => axes}/violin.typ | 0 src/chart.typ | 4 - src/chart/barchart.typ | 141 ----- src/chart/barcol-common.typ | 40 -- src/chart/boxwhisker.typ | 97 --- src/chart/columnchart.typ | 141 ----- src/chart/piechart.typ | 492 ---------------- src/lib.typ | 8 +- src/plot-old/annotation.typ | 76 --- src/plot-old/bar.typ | 264 --------- src/plot-old/boxwhisker.typ | 117 ---- src/plot-old/contour.typ | 350 ----------- src/plot-old/errorbar.typ | 118 ---- src/plot-old/legend.typ | 236 -------- src/plot-old/line.typ | 508 ---------------- src/plot-old/mark.typ | 45 -- src/plot-old/sample.typ | 79 --- src/plot-old/util.typ | 372 ------------ src/plot.typ | 5 +- src/plot.typ.old | 552 ------------------ src/plot/add.typ | 1 + src/plot/axis-style.typ | 5 + .../barycentric-2d/barycentric-2d.typ | 0 .../axis-styles}/orthorect-2d/axis.typ | 0 .../axis-styles}/orthorect-2d/grid.typ | 0 .../orthorect-2d/orthorect-2d.typ | 0 .../axis-styles}/orthorect-2d/transforms.typ | 0 src/plot/axis-styles/polar-2d/axis.typ | 110 ++++ src/plot/axis-styles/polar-2d/grid.typ | 45 ++ src/plot/axis-styles/polar-2d/polar-2d.typ | 160 +++++ src/plot/axis-styles/polar-2d/transforms.typ | 87 +++ src/{ => plot}/elements/anchor.typ | 0 tests/plots/orthorect-2d/scatter/test.typ | 2 +- tests/plots/polar-2d/scatter/test.typ | 19 + 36 files changed, 440 insertions(+), 3643 deletions(-) rename src/{plot-old/formats.typ => axes/format.typ} (96%) rename src/{plot-old => axes}/violin.typ (100%) delete mode 100644 src/chart.typ delete mode 100644 src/chart/barchart.typ delete mode 100644 src/chart/barcol-common.typ delete mode 100644 src/chart/boxwhisker.typ delete mode 100644 src/chart/columnchart.typ delete mode 100644 src/chart/piechart.typ delete mode 100644 src/plot-old/annotation.typ delete mode 100644 src/plot-old/bar.typ delete mode 100644 src/plot-old/boxwhisker.typ delete mode 100644 src/plot-old/contour.typ delete mode 100644 src/plot-old/errorbar.typ delete mode 100644 src/plot-old/legend.typ delete mode 100644 src/plot-old/line.typ delete mode 100644 src/plot-old/mark.typ delete mode 100644 src/plot-old/sample.typ delete mode 100644 src/plot-old/util.typ delete mode 100644 src/plot.typ.old create mode 100644 src/plot/add.typ create mode 100644 src/plot/axis-style.typ rename src/{plots => plot/axis-styles}/barycentric-2d/barycentric-2d.typ (100%) rename src/{plots => plot/axis-styles}/orthorect-2d/axis.typ (100%) rename src/{plots => plot/axis-styles}/orthorect-2d/grid.typ (100%) rename src/{plots => plot/axis-styles}/orthorect-2d/orthorect-2d.typ (100%) rename src/{plots => plot/axis-styles}/orthorect-2d/transforms.typ (100%) create mode 100644 src/plot/axis-styles/polar-2d/axis.typ create mode 100644 src/plot/axis-styles/polar-2d/grid.typ create mode 100644 src/plot/axis-styles/polar-2d/polar-2d.typ create mode 100644 src/plot/axis-styles/polar-2d/transforms.typ rename src/{ => plot}/elements/anchor.typ (100%) create mode 100644 tests/plots/polar-2d/scatter/test.typ diff --git a/src/axes/axes.typ b/src/axes/axes.typ index a1ab1c1..58afcbe 100644 --- a/src/axes/axes.typ +++ b/src/axes/axes.typ @@ -1,5 +1,6 @@ #import "/src/cetz.typ": styles, util #import "/src/plot/styles.typ": default-style, prepare-style, get-axis-style +#import "format.typ" // Construct Axis Object // diff --git a/src/plot-old/formats.typ b/src/axes/format.typ similarity index 96% rename from src/plot-old/formats.typ rename to src/axes/format.typ index c51dbc4..0a3bf2d 100644 --- a/src/plot-old/formats.typ +++ b/src/axes/format.typ @@ -42,7 +42,7 @@ /// /// ```example /// plot.plot(size: (5,1), -/// x-format: plot.formats.fraction, +/// x-format: axes.formats.fraction, /// x-tick-step: 1/5, /// y-tick-step: none, { /// plot.add(calc.sin, domain: (-1, 1)) @@ -63,7 +63,7 @@ /// /// ```example /// plot.plot(size: (5,1), -/// x-format: plot.formats.multiple-of, +/// x-format: axes.formats.multiple-of, /// x-tick-step: calc.pi/4, /// y-tick-step: none, { /// plot.add(calc.sin, domain: (-calc.pi, 1.5 * calc.pi)) @@ -107,7 +107,7 @@ /// /// ```example /// plot.plot(size: (5,1), -/// x-format: plot.formats.sci, +/// x-format: axes.formats.sci, /// x-tick-step: 1e3, /// y-tick-step: none, { /// plot.add(x => x, domain: (-2e3, 2e3)) @@ -136,4 +136,4 @@ return $#value times 10^#exponent$ } return $#value$ -} +} \ No newline at end of file diff --git a/src/plot-old/violin.typ b/src/axes/violin.typ similarity index 100% rename from src/plot-old/violin.typ rename to src/axes/violin.typ diff --git a/src/chart.typ b/src/chart.typ deleted file mode 100644 index 251cf65..0000000 --- a/src/chart.typ +++ /dev/null @@ -1,4 +0,0 @@ -#import "chart/boxwhisker.typ": boxwhisker, boxwhisker-default-style -#import "chart/barchart.typ": barchart, barchart-default-style -#import "chart/columnchart.typ": columnchart, columnchart-default-style -#import "chart/piechart.typ": piechart, piechart-default-style diff --git a/src/chart/barchart.typ b/src/chart/barchart.typ deleted file mode 100644 index ec7df40..0000000 --- a/src/chart/barchart.typ +++ /dev/null @@ -1,141 +0,0 @@ -#import "/src/cetz.typ": draw, styles, palette - -#import "/src/plot.typ" - -#let barchart-default-style = ( - axes: (tick: (length: 0), grid: (stroke: (dash: "dotted"))), - bar-width: .8, - cluster-gap: 0, - error: ( - whisker-size: .25, - ), - y-inset: 1, -) - -/// Draw a bar chart. A bar chart is a chart that represents data with -/// rectangular bars that grow from left to right, proportional to the values -/// they represent. -/// -/// = Styling -/// *Root*: `barchart`. -/// #show-parameter-block("bar-width", "float", default: .8, [ -/// Width of a single bar (basic) or a cluster of bars (clustered) in the plot.]) -/// #show-parameter-block("y-inset", "float", default: 1, [ -/// Distance of the plot data to the plot's edges on the y-axis of the plot.]) -/// You can use any `plot` or `axes` related style keys, too. -/// -/// The `barchart` function is a wrapper of the `plot` API. Arguments passed -/// to `..plot-args` are passed to the `plot.plot` function. -/// -/// - data (array): Array of data rows. A row can be of type array or -/// dictionary, with `label-key` and `value-key` being -/// the keys to access a rows label and value(s). -/// -/// *Example* -/// ```typc -/// (([A], 1), ([B], 2), ([C], 3),) -/// ``` -/// - label-key (int,string): Key to access the label of a data row. -/// This key is used as argument to the -/// rows `.at(..)` function. -/// - value-key (int,string): Key(s) to access values of a data row. -/// These keys are used as argument to the -/// rows `.at(..)` function. -/// - error-key (none,int,string): Key(s) to access error values of a data row. -/// These keys are used as argument to the -/// rows `.at(..)` function. -/// - mode (string): Chart mode: -/// / basic: Single bar per data row -/// / clustered: Group of bars per data row -/// / stacked: Stacked bars per data row -/// / stacked100: Stacked bars per data row relative -/// to the sum of the row -/// - size (array): Chart size as width and height tuple in canvas unist; -/// width can be set to `auto`. -/// - bar-style (style,function): Style or function (idx => style) to use for -/// each bar, accepts a palette function. -/// - x-unit (content,auto): Tick suffix added to each tick label -/// - y-label (content,none): Y axis label -/// - x-label (content,none): x axis label -/// - labels (none,content): Legend labels per x value group -/// - ..plot-args (any): Arguments to pass to `plot.plot` -#let barchart(data, - label-key: 0, - value-key: 1, - error-key: none, - mode: "basic", - size: (auto, 1), - bar-style: palette.red, - x-label: none, - x-unit: auto, - y-label: none, - labels: none, - ..plot-args - ) = { - assert(type(label-key) in (int, str)) - if mode == "basic" { - assert(type(value-key) in (int, str)) - } else { - assert(type(value-key) == array) - } - - if type(value-key) != array { - value-key = (value-key,) - } - - if error-key == none { - error-key = () - } else if type(error-key) != array { - error-key = (error-key,) - } - - if type(size) != array { - size = (size, auto) - } - if size.at(1) == auto { - size.at(1) = (data.len() + 1) - } - - let y-tic-list = data.enumerate().map(((i, t)) => { - (data.len() - i - 1, t.at(label-key)) - }) - - let x-unit = x-unit - if x-unit == auto { - x-unit = if mode == "stacked100" {[%]} else [] - } - - data = data.enumerate().map(((i, d)) => { - (data.len() - i - 1, value-key.map(k => d.at(k, default: 0)).flatten(), error-key.map(k => d.at(k, default: 0)).flatten()) - }) - - draw.group(ctx => { - let style = styles.resolve(ctx.style, merge: (:), - root: "barchart", base: barchart-default-style) - draw.set-style(..style) - - let y-inset = calc.max(style.y-inset, style.bar-width / 2) - plot.plot(size: size, - axis-style: "scientific-auto", - x-label: x-label, - x-grid: true, - y-label: y-label, - y-min: -y-inset, - y-max: data.len() + y-inset - 1, - y-tick-step: none, - y-ticks: y-tic-list, - plot-style: bar-style, - ..plot-args, - { - plot.add-bar(data, - x-key: 0, - y-key: 1, - error-key: if mode in ("basic", "clustered") { 2 }, - mode: mode, - labels: labels, - bar-width: -style.bar-width, - cluster-gap: style.cluster-gap, - axes: ("y", "x")) - }) - }) -} diff --git a/src/chart/barcol-common.typ b/src/chart/barcol-common.typ deleted file mode 100644 index 0c09a52..0000000 --- a/src/chart/barcol-common.typ +++ /dev/null @@ -1,40 +0,0 @@ -// Valid bar- and columnchart modes -#let barchart-modes = ( - "basic", "clustered", "stacked", "stacked100" -) - -// Functions for max value calculation -#let barchart-max-value-fn = ( - basic: (data, value-key) => { - calc.max(0, ..data.map(t => t.at(value-key))) - }, - clustered: (data, value-key) => { - calc.max(0, ..data.map(t => calc.max( - ..value-key.map(k => t.at(k))))) - }, - stacked: (data, value-key) => { - calc.max(0, ..data.map(t => - value-key.map(k => t.at(k)).sum())) - }, - stacked100: (..) => { - 100 - } -) - -// Functions for min value calculation -#let barchart-min-value-fn = ( - basic: (data, value-key) => { - calc.min(0, ..data.map(t => t.at(value-key))) - }, - clustered: (data, value-key) => { - calc.min(0, ..data.map(t => calc.max( - ..value-key.map(k => t.at(k))))) - }, - stacked: (data, value-key) => { - calc.min(0, ..data.map(t => - value-key.map(k => t.at(k)).sum())) - }, - stacked100: (..) => { - 0 - } -) diff --git a/src/chart/boxwhisker.typ b/src/chart/boxwhisker.typ deleted file mode 100644 index 1d643cf..0000000 --- a/src/chart/boxwhisker.typ +++ /dev/null @@ -1,97 +0,0 @@ -#import "/src/cetz.typ": draw, styles, palette, util, vector, intersection - -#import "/src/plot.typ" - -#let boxwhisker-default-style = ( - axes: (tick: (length: 0), grid: (stroke: (dash: "dotted"))), - box-width: 0.75, - whisker-width: 0.5, - mark-size: 0.15, -) - -/// Add one or more box or whisker plots. -/// -/// #example(``` -/// cetz.chart.boxwhisker(size: (2,2), label-key: none, -/// y-min: 0, y-max: 70, y-tick-step: 20, -/// (x: 1, min: 15, max: 60, -/// q1: 25, q2: 35, q3: 50)) -/// ```) -/// -/// = Styling -/// *Root* `boxwhisker` -/// #show-parameter-block("box-width", "float", default: .75, [ -/// The width of the box. Since boxes are placed 1 unit next to each other, -/// a width of $1$ would make neighbouring boxes touch.]) -/// #show-parameter-block("whisker-width", "float", default: .5, [ -/// The width of the whisker, that is the horizontal bar on the top and bottom -/// of the box.]) -/// #show-parameter-block("mark-size", "float", default: .15, [ -/// The scaling of the mark for the boxes outlier values in canvas units.]) -/// You can use any `plot` or `axes` related style keys, too. -/// -/// - data (array, dictionary): Dictionary or array of dictionaries containing the -/// needed entries to plot box and whisker plot. -/// -/// See `plot.add-boxwhisker` for more details. -/// -/// *Examples:* -/// - ```typc -/// (x: 1 // Location on x-axis -/// outliers: (7, 65, 69), // Optional outliers -/// min: 15, max: 60 // Minimum and maximum -/// q1: 25, // Quartiles: Lower -/// q2: 35, // Median -/// q3: 50) // Upper -/// ``` -/// - size (array) : Size of chart. If the second entry is auto, it automatically scales to accommodate the number of entries plotted -/// - label-key (integer, string): Index in the array where labels of each entry is stored -/// - mark (string): Mark to use for plotting outliers. Set `none` to disable. Defaults to "x" -/// - ..plot-args (any): Additional arguments are passed to `plot.plot` -#let boxwhisker(data, - size: (1, auto), - label-key: 0, - mark: "*", - ..plot-args - ) = { - if type(data) == dictionary { data = (data,) } - - if type(size) != array { - size = (size, auto) - } - if size.at(1) == auto { - size.at(1) = (data.len() + 1) - } - - let x-tick-list = data.enumerate().map(((i, t)) => { - (i + 1, if label-key != none { t.at(label-key, default: i) } else { [] }) - }) - - draw.group(ctx => { - let style = styles.resolve(ctx.style, merge: (:), - root: "boxwhisker", base: boxwhisker-default-style) - draw.set-style(..style) - - plot.plot( - size: size, - axis-style: "scientific-auto", - x-tick-step: none, - x-ticks: x-tick-list, - y-grid: true, - x-label: none, - y-label: none, - ..plot-args, - { - for (i, row) in data.enumerate() { - plot.add-boxwhisker( - (x: i + 1, ..row), - box-width: style.box-width, - whisker-width: style.whisker-width, - style: (:), - mark: mark, - mark-size: style.mark-size - ) - } - }) - }) -} diff --git a/src/chart/columnchart.typ b/src/chart/columnchart.typ deleted file mode 100644 index 5a4fcb3..0000000 --- a/src/chart/columnchart.typ +++ /dev/null @@ -1,141 +0,0 @@ -#import "/src/cetz.typ": draw, styles, palette, util, vector, intersection - -#import "/src/plot.typ" - -#let columnchart-default-style = ( - axes: (tick: (length: 0), grid: (stroke: (dash: "dotted"))), - bar-width: .8, - cluster-gap: 0, - error: ( - whisker-size: .25, - ), - x-inset: 1, -) - -/// Draw a column chart. A column chart is a chart that represents data with -/// rectangular bars that grow from bottom to top, proportional to the values -/// they represent. -/// -/// = Styling -/// *Root*: `columnchart`. -/// #show-parameter-block("bar-width", "float", default: .8, [ -/// Width of a single bar (basic) or a cluster of bars (clustered) in the plot.]) -/// #show-parameter-block("x-inset", "float", default: 1, [ -/// Distance of the plot data to the plot's edges on the x-axis of the plot.]) -/// You can use any `plot` or `axes` related style keys, too. -/// -/// The `columnchart` function is a wrapper of the `plot` API. Arguments passed -/// to `..plot-args` are passed to the `plot.plot` function. -/// -/// - data (array): Array of data rows. A row can be of type array or -/// dictionary, with `label-key` and `value-key` being -/// the keys to access a rows label and value(s). -/// -/// *Example* -/// ```typc -/// (([A], 1), ([B], 2), ([C], 3),) -/// ``` -/// - label-key (int,string): Key to access the label of a data row. -/// This key is used as argument to the -/// rows `.at(..)` function. -/// - value-key (int,string): Key(s) to access value(s) of data row. -/// These keys are used as argument to the -/// rows `.at(..)` function. -/// - error-key (none,int,string): Key(s) to access error values of a data row. -/// These keys are used as argument to the -/// rows `.at(..)` function. -/// - mode (string): Chart mode: -/// / basic: Single bar per data row -/// / clustered: Group of bars per data row -/// / stacked: Stacked bars per data row -/// / stacked100: Stacked bars per data row relative -/// to the sum of the row -/// - size (array): Chart size as width and height tuple in canvas unist; -/// width can be set to `auto`. -/// - bar-style (style,function): Style or function (idx => style) to use for -/// each bar, accepts a palette function. -/// - y-unit (content,auto): Tick suffix added to each tick label -/// - y-label (content,none): Y axis label -/// - x-label (content,none): x axis label -/// - labels (none,content): Legend labels per y value group -/// - ..plot-args (any): Arguments to pass to `plot.plot` -#let columnchart(data, - label-key: 0, - value-key: 1, - error-key: none, - mode: "basic", - size: (auto, 1), - bar-style: palette.red, - x-label: none, - y-unit: auto, - y-label: none, - labels: none, - ..plot-args - ) = { - assert(type(label-key) in (int, str)) - if mode == "basic" { - assert(type(value-key) in (int, str)) - } - - if type(value-key) != array { - value-key = (value-key,) - } - - if error-key == none { - error-key = () - } else if type(error-key) != array { - error-key = (error-key,) - } - - if type(size) != array { - size = (auto, size) - } - if size.at(0) == auto { - size.at(0) = (data.len() + 1) - } - - let x-tic-list = data.enumerate().map(((i, t)) => { - (i, t.at(label-key)) - }) - - let y-unit = y-unit - if y-unit == auto { - y-unit = if mode == "stacked100" {[%]} else [] - } - - data = data.enumerate().map(((i, d)) => { - (i, value-key.map(k => d.at(k)).flatten(), error-key.map(k => d.at(k, default: 0)).flatten()) - }) - - draw.group(ctx => { - let style = styles.resolve(ctx.style, merge: (:), - root: "columnchart", base: columnchart-default-style) - draw.set-style(..style) - - let x-inset = calc.max(style.x-inset, style.bar-width / 2) - plot.plot(size: size, - axis-style: "scientific-auto", - y-grid: true, - y-label: y-label, - x-min: -x-inset, - x-max: data.len() + x-inset - 1, - x-tick-step: none, - x-ticks: x-tic-list, - x-label: x-label, - plot-style: bar-style, - ..plot-args, - { - plot.add-bar(data, - x-key: 0, - y-key: 1, - error-key: if mode in ("basic", "clustered") { 2 }, - mode: mode, - labels: labels, - bar-width: style.bar-width, - cluster-gap: style.cluster-gap, - error-style: style.error, - whisker-size: style.error.whisker-size, - axes: ("x", "y")) - }) - }) -} diff --git a/src/chart/piechart.typ b/src/chart/piechart.typ deleted file mode 100644 index dc4a526..0000000 --- a/src/chart/piechart.typ +++ /dev/null @@ -1,492 +0,0 @@ -#import "/src/cetz.typ": draw, styles, palette, util, vector, intersection -#import util: circle-arclen - -#import "/src/plot/legend.typ" - -// Piechart Label Kind -#let label-kind = (value: "VALUE", percentage: "%", label: "LABEL") - -// Piechart Default Style -#let default-style = ( - stroke: auto, - fill: auto, - /// Outer chart radius - radius: 1, - /// Inner slice radius - inner-radius: 0, - /// Gap between items. This can be a canvas length or an angle - gap: 0.5deg, - /// Outset offset, absolute or relative to radius - outset-offset: 10%, - /// Pie outset mode: - /// - "OFFSET": Offset slice position by outset-offset - /// - "RADIUS": Offset slice radius by outset-offset (the slice gets scaled) - outset-mode: "OFFSET", - /// Pie start angle - start: 90deg, - /// Pie stop angle - stop: 360deg + 90deg, - /// Pie rotation direction (true = clockwise, false = anti-clockwise) - clockwise: true, - outer-label: ( - /// Label kind - /// If set to a function, that function gets called with (value, label) of each item - content: label-kind.label, - /// Absolute radius or percentage of radius - radius: 125%, - /// Absolute angle or auto to use secant of the slice as direction - angle: 0deg, - /// Label anchor - anchor: "center", - ), - inner-label: ( - /// Label kind - /// If set to a function, that function gets called with (value, label) of each item - content: none, - /// Absolute radius or percentage of the mid between radius and inner-radius - radius: 150%, - /// Absolute angle or auto to use secant of the slice as direction - angle: 0deg, - /// Label anchor - anchor: "center", - ), - legend: ( - ..legend.default-style, - - /// Label used for the legend - /// The legend gets rendered as soon as at least one item with a label - /// exists and the `legend-label.content` is set != none. This field - /// accepts the same values as inner-label.content or outer-label.content. - label: "LABEL", - - /// Anchor of the charts data bounding box to place the legend relative to - position: "south", - - /// Anchor of the legend bounding box to use as origin - anchor: "north", - - /// Custom preview function override - /// The function takes an item dictionary an is responsible for drawing - /// the preview icon. Stroke and fill styles are set to match the items - /// style. - preview: none, - - /// See lenged.typ for the following style keys - orientation: ltr, - offset: (0,-.5em), - stroke: none, - item: ( - spacing: .25, - preview: ( - width: .3, - height: .3, - ), - ), - ) -) -#let piechart-default-style = default-style - - -/// Draw a pie- or donut-chart -/// -/// #example(``` -/// import cetz.chart -/// let data = (24, 31, 18, 21, 23, 18, 27, 17, 26, 13) -/// let colors = gradient.linear(red, blue, green, yellow) -/// -/// chart.piechart( -/// data, -/// radius: 1.5, -/// slice-style: colors, -/// inner-radius: .5, -/// outer-label: (content: "%",)) -/// ```) -/// -/// = Styling -/// *Root* `piechart` \ -/// #show-parameter-block("radius", ("number"), [ -/// Outer radius of the chart.], default: 1) -/// #show-parameter-block("inner-radius", ("number"), [ -/// Inner radius of the chart slices. If greater than zero, the chart becomes -/// a "donut-chart".], default: 0) -/// #show-parameter-block("gap", ("number", "angle"), [ -/// Gap between chart slices to leave empty. This does not increase the charts -/// radius by pushing slices outwards, but instead shrinks the slice. Big -/// values can result in slices becoming invisible if no space is left.], default: 0.5deg) -/// #show-parameter-block("outset-offset", ("number", "ratio"), [ -/// Absolute, or radius relative distance to push slices marked for -/// "outsetting" outwards from the center of the chart.], default: 10%) -/// #show-parameter-block("outset-offset", ("string"), [ -/// The mode of how to perform "outsetting" of slices: -/// - "OFFSET": Offset slice position by `outset-offset`, increasing their gap to their siblings -/// - "RADIUS": Offset slice radius by `outset-offset`, which scales the slice and leaves the gap unchanged], default: "OFFSET") -/// #show-parameter-block("start", ("angle"), [ -/// The pie-charts start angle (ccw). You can use this to draw charts not forming a full circle.], default: 90deg) -/// #show-parameter-block("stop", ("angle"), [ -/// The pie-charts stop angle (ccw).], default: 360deg + 90deg) -/// #show-parameter-block("clockwise", ("bool"), [ -/// The pie-charts rotation direction.], default: true) -/// #show-parameter-block("outer-label.content", ("none","string","function"), [ -/// Content to display outsides the charts slices. -/// There are the following predefined values: -/// / LABEL: Display the slices label (see `label-key`) -/// / %: Display the percentage of the items value in relation to the sum of -/// all values, rounded to the next integer -/// / VALUE: Display the slices value -/// If passed a `` of the format `(value, label) => content`, -/// that function gets called with each slices value and label and must return -/// content, that gets displayed.], default: "LABEL") -/// #show-parameter-block("outer-label.radius", ("number","ratio"), [ -/// Absolute, or radius relative distance from the charts center to position -/// outer labels at.], default: 125%) -/// #show-parameter-block("outer-label.angle", ("angle","auto"), [ -/// The angle of the outer label. If passed `auto`, the label gets rotated, -/// so that the baseline is parallel to the slices secant. ], default: 0deg) -/// #show-parameter-block("outer-label.anchor", ("string"), [ -/// The anchor of the outer label to use for positioning.], default: "center") -/// #show-parameter-block("inner-label.content", ("none","string","function"), [ -/// Content to display insides the charts slices. -/// See `outer-label.content` for the possible values.], default: none) -/// #show-parameter-block("inner-label.radius", ("number","ratio"), [ -/// Distance of the inner label to the charts center. If passed a ``, -/// that ratio is relative to the mid between the inner and outer radius (`inner-radius` and `radius`) -/// of the chart], default: 150%) -/// #show-parameter-block("inner-label.angle", ("angle","auto"), [ -/// See `outer-label.angle`.], default: 0deg) -/// #show-parameter-block("inner-label.anchor", ("string"), [ -/// See `outer-label.anchor`.], default: "center") -/// #show-parameter-block("legend.label", ("none","string","function"), [ -/// See `outer-label.content`. The legend gets shown if this key is set != none.], default: "LABEL") -/// -/// = Anchors -/// The chart places one anchor per item at the radius of it's slice that -/// gets named `"item-"` (outer radius) and `"item--inner"` (inner radius), -/// where index is the index of the sclice data in `data`. -/// -/// - data (array): Array of data items. A data item can be: -/// - A number: A number that is used as the fraction of the slice -/// - An array: An array which is read depending on value-key, label-key and outset-key -/// - A dictionary: A dictionary which is read depending on value-key, label-key and outset-key -/// - value-key (none,int,string): Key of the "value" of a data item. If for example -/// data items are passed as dictionaries, the value-key is the key of the dictionary to -/// access the items chart value. -/// - label-key (none,int,string): Same as the value-key but for getting an items label content. -/// - outset-key (none,int,string): Same as the value-key but for getting if an item should get outset (highlighted). The -/// outset can be a bool, float or ratio. If of type `bool`, the outset distance from the -/// style gets used. -/// - outset (none,int,array): A single or multiple indices of items that should get offset from the center to the outsides -/// of the chart. Only used if outset-key is none! -/// - slice-style (function,array,gradient): Slice style of the following types: -/// - function: A function of the form `index => style` that must return a style dictionary. -/// This can be a `palette` function. -/// - array: An array of style dictionaries or fill colors of at least one item. For each slice the style at the slices -/// index modulo the arrays length gets used. -/// - gradient: A gradient that gets sampled for each data item using the the slices -/// index divided by the number of slices as position on the gradient. -/// If one of stroke or fill is not in the style dictionary, it is taken from the charts style. -#let piechart(data, - value-key: none, - label-key: none, - outset-key: none, - outset: none, - slice-style: palette.red, - name: none, - ..style) = { - import draw: * - - // Prepare data by converting it to tuples of the format - // (value, label, outset) - data = data.enumerate().map(((i, item)) => ( - if value-key != none { - item.at(value-key) - } else { - item - }, - if label-key != none { - item.at(label-key) - } else { - none - }, - if outset-key != none { - item.at(outset-key, default: false) - } else if outset != none { - i == outset or (type(outset) == array and i in outset) - } else { - false - } - )) - - let sum = data.map(((value, ..)) => value).sum() - if sum == 0 { - sum = 1 - } - - group(name: name, ctx => { - anchor("default", (0,0)) - - let style = styles.resolve(ctx, - merge: style.named(), root: "piechart", base: default-style) - - let gap = style.gap - if type(gap) != angle { - gap = gap / (2 * calc.pi * style.radius) * 360deg - } - assert(gap < 360deg / data.len(), - message: "Gap angle is too big for " + str(data.len()) + "items. Maximum gap angle: " + repr(360deg / data.len())) - - let radius = style.radius - assert(radius > 0, - message: "Radius must be > 0.") - - let inner-radius = style.inner-radius - assert(inner-radius >= 0 and inner-radius <= radius, - message: "Radius must be >= 0 and <= radius.") - - assert(style.outset-mode in ("OFFSET", "RADIUS"), - message: "Outset mode must be 'OFFSET' or 'RADIUS', but is: " + str(style.outset-mode)) - - let style-at = if type(slice-style) == function { - slice-style - } else if type(slice-style) == array { - i => { - let s = slice-style.at(calc.rem(i, slice-style.len())) - if type(s) == color { - (fill: s) - } else { - s - } - } - } else if type(slice-style) == gradient { - i => (fill: slice-style.sample(i / data.len() * 100%)) - } - - let start-angle = style.start - let stop-angle = style.stop - let f = (stop-angle - start-angle) / sum - - let get-item-label(item, kind) = { - let (value, label, ..) = item - if kind == label-kind.value { - [#value] - } else if kind == label-kind.percentage { - [#{calc.round(value / sum * 100)}%] - } else if kind == label-kind.label { - label - } else if type(kind) == function { - (kind)(value, label) - } - } - - let start = start-angle - let enum-items = if style.clockwise { - data.enumerate().rev() - } else { - data.enumerate() - } - group(name: "chart", { - for (i, item) in enum-items { - let (value, label, outset) = item - if value == 0 { continue } - - let origin = (0,0) - let radius = radius - let inner-radius = inner-radius - - // Calculate item angles - let delta = f * value - let end = start + delta - - // Apply item outset - let outset-offset = if outset == true { - style.outset-offset - } else if outset == false { - 0 - } else if type(outset) in (float, ratio) { - outset - } else { - panic("Invalid type for outset. Expected bool, float or ratio, got: " + repr(outset)) - } - if type(outset-offset) == ratio { - outset-offset = outset-offset * radius / 100% - } - - if outset-offset != 0 { - if style.outset-mode == "OFFSET" { - let dir = (calc.cos((start + end) / 2), calc.sin((start + end) / 2)) - origin = vector.add(origin, vector.scale(dir, outset-offset)) - radius += outset-offset - } else { - radius += outset-offset - if inner-radius > 0 { - inner-radius += outset-offset - } - } - } - - // Calculate gap angles - let outer-gap = gap - let gap-dist = outer-gap / 360deg * 2 * calc.pi * radius - let inner-gap = if inner-radius > 0 { - gap-dist / (2 * calc.pi * inner-radius) * 360deg - } else { - 1 / calc.pi * 360deg - } - - // Calculate angle deltas - let outer-angle = end - start - outer-gap * 2 - let inner-angle = end - start - inner-gap * 2 - let mid-angle = (start + end) / 2 - - // Skip negative values - if outer-angle < 0deg { - // TODO: Add a warning as soon as Typst is ready! - continue - } - - // A sharp item is an item that should be round but is sharp due to the gap being big - let is-sharp = inner-radius == 0 or circle-arclen(inner-radius, angle: inner-angle) > circle-arclen(radius, angle: outer-angle) - - let inner-origin = vector.add(origin, if inner-radius == 0 { - if gap-dist >= 0 { - let outer-end = vector.scale((calc.cos(end - outer-gap), calc.sin(end - outer-gap)), radius) - let inner-end = vector.scale((calc.cos(end - inner-gap), calc.sin(end - inner-gap)), gap-dist) - let outer-start = vector.scale((calc.cos(start + outer-gap), calc.sin(start + outer-gap)), radius) - let inner-start = vector.scale((calc.cos(start + inner-gap), calc.sin(start + inner-gap)), gap-dist) - - intersection.line-line(outer-end, inner-end, outer-start, inner-start, ray: true) - } else { - (0,0) - } - } else if is-sharp { - let outer-end = vector.scale((calc.cos(end - outer-gap), calc.sin(end - outer-gap)), radius) - let inner-end = vector.scale((calc.cos(end - inner-gap), calc.sin(end - inner-gap)), inner-radius) - let outer-start = vector.scale((calc.cos(start + outer-gap), calc.sin(start + outer-gap)), radius) - let inner-start = vector.scale((calc.cos(start + inner-gap), calc.sin(start + inner-gap)), inner-radius) - - intersection.line-line(outer-end, inner-end, outer-start, inner-start, ray: true) - } else { - (0,0) - }) - - // Draw one segment - let stroke = style-at(i).at("stroke", default: style.stroke) - let fill = style-at(i).at("fill", default: style.fill) - if data.len() == 1 { - // If the chart has only one segment, we may have to fake a path - // with a hole in it by using a combination of multiple arcs. - if inner-radius > 0 { - // Split the circle/arc into two arcs - // and fill them - merge-path({ - arc(origin, start: start-angle, stop: mid-angle, radius: radius, anchor: "origin") - arc(origin, stop: start-angle, start: mid-angle, radius: inner-radius, anchor: "origin") - }, close: false, fill:fill, stroke: none) - merge-path({ - arc(origin, start: mid-angle, stop: stop-angle, radius: radius, anchor: "origin") - arc(origin, stop: mid-angle, start: stop-angle, radius: inner-radius, anchor: "origin") - }, close: false, fill:fill, stroke: none) - - // Create arcs for the inner and outer border and stroke them. - // If the chart is not a full circle, we have to merge two arc - // at their ends to create closing lines - if stroke != none { - if calc.abs(stop-angle - start-angle) != 360deg { - merge-path({ - arc(origin, start: start, stop: end, radius: inner-radius, anchor: "origin") - arc(origin, start: end, stop: start, radius: radius, anchor: "origin") - }, close: true, fill: none, stroke: stroke) - } else { - arc(origin, start: start, stop: end, radius: inner-radius, fill: none, stroke: stroke, anchor: "origin") - arc(origin, start: start, stop: end, radius: radius, fill: none, stroke: stroke, anchor: "origin") - } - } - } else { - arc(origin, start: start, stop: end, radius: radius, fill: fill, stroke: stroke, mode: "PIE", anchor: "origin") - } - } else { - // Draw a normal segment - if inner-origin != none { - merge-path({ - arc(origin, start: start + outer-gap, stop: end - outer-gap, anchor: "origin", - radius: radius) - if inner-radius > 0 and not is-sharp { - if inner-angle < 0deg { - arc(inner-origin, stop: end - inner-gap, delta: inner-angle, anchor: "origin", - radius: inner-radius) - } else { - arc(inner-origin, start: end - inner-gap, delta: -inner-angle, anchor: "origin", - radius: inner-radius) - } - } else { - line((rel: (end - outer-gap, radius), to: origin), - inner-origin, - (rel: (start + outer-gap, radius), to: origin)) - } - }, close: true, fill: fill, stroke: stroke) - } - } - - // Place outer label - let outer-label = get-item-label(item, style.outer-label.content) - if outer-label != none { - let r = style.outer-label.radius - if type(r) == ratio {r = r * radius / 100%} - - let dir = (r * calc.cos(mid-angle), r * calc.sin(mid-angle)) - let pt = vector.add(origin, dir) - - let angle = style.outer-label.angle - if angle == auto { - angle = vector.add(pt, (dir.at(1), -dir.at(0))) - } - - content(pt, outer-label, angle: angle, anchor: style.outer-label.anchor) - } - - // Place inner label - let inner-label = get-item-label(item, style.inner-label.content) - if inner-label != none { - let r = style.inner-label.radius - if type(r) == ratio {r = r * (radius + inner-radius) / 200%} - - let dir = (r * calc.cos(mid-angle), r * calc.sin(mid-angle)) - let pt = vector.add(origin, dir) - - let angle = style.inner-label.angle - if angle == auto { - angle = vector.add(pt, (dir.at(1), -dir.at(0))) - } - - content(pt, inner-label, angle: angle, anchor: style.inner-label.anchor) - } - - // Place item anchor - anchor("item-" + str(i), (rel: (mid-angle, radius), to: origin)) - anchor("item-" + str(i) + "-inner", (rel: (mid-angle, inner-radius), to: origin)) - - start = end - } - }) - - legend.legend((name: "chart", anchor: style.legend.position), { - let preview-fn = if style.legend.preview != none { - style.legend.preview - } else { - (_) => { rect((0,0), (1,1)) } - } - - for (i, item) in enum-items.rev() { - let label = get-item-label(item, style.legend.label) - let preview = (item) => { - let stroke = style-at(i).at("stroke", default: style.stroke) - let fill = style-at(i).at("fill", default: style.fill) - - set-style(stroke: stroke, fill: fill) - preview-fn(item) - } - - legend.item(label, preview) - } - }, ..style.at("legend", default: (:))) - }) -} diff --git a/src/lib.typ b/src/lib.typ index 0bfc928..e6e9c7d 100644 --- a/src/lib.typ +++ b/src/lib.typ @@ -1,5 +1,7 @@ #let version = version(0,1,0) -// #import "/src/axes.typ" -#import "/src/plot.typ": plot, orthorect-2d, barycentric-2d -#import "/src/chart.typ" +#import "/src/axes/axes.typ" +#import "/src/plot.typ": plot +#import "/src/plot/axis-style.typ" +#import "/src/plot/add.typ" + diff --git a/src/plot-old/annotation.typ b/src/plot-old/annotation.typ deleted file mode 100644 index 19fb2e5..0000000 --- a/src/plot-old/annotation.typ +++ /dev/null @@ -1,76 +0,0 @@ -#import "/src/cetz.typ" -#import cetz: draw, process, util, matrix -#import "util.typ" -#import "sample.typ" - -/// Add an annotation to the plot -/// -/// An annotation is a sub-canvas that uses the plots coordinates specified -/// by its x and y axis. -/// -/// #example(``` -/// import cetz.plot -/// plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { -/// plot.add(domain: (0, 2*calc.pi), calc.sin) -/// plot.annotate({ -/// rect((0, -1), (calc.pi, 1), fill: rgb(50,50,200,50)) -/// content((calc.pi, 0), [Here]) -/// }) -/// }) -/// ```) -/// -/// Bounds calculation is done naively, therefore fixed size content _can_ grow -/// out of the plot. You can adjust the padding manually to adjust for that. The -/// feature of solving the correct bounds for fixed size elements might be added -/// in the future. -/// -/// - body (drawable): Elements to draw -/// - axes (axes): X and Y axis names -/// - resize (bool): If true, the plots axes get adjusted to contain the annotation -/// - padding (none,number,dictionary): Annotation padding that is used for axis -/// adjustment -/// - background (bool): If true, the annotation is drawn behind all plots, in the background. -/// If false, the annotation is drawn above all plots. -#let annotate(body, axes: ("x", "y"), resize: true, padding: none, background: false) = { - (( - type: "annotation", - body: { - draw.set-style(mark: (transform-shape: false)) - body; - }, - axes: axes, - resize: resize, - background: background, - padding: cetz.util.as-padding-dict(padding), - ),) -} - -// Returns the adjusted axes for the annotation object -// -// -> array Tuple of x and y axis -#let calc-annotation-domain(ctx, x, y, annotation) = { - if not annotation.resize { - return (x, y) - } - - ctx.transform = matrix.ident() - let (ctx: ctx, bounds: bounds, drawables: _) = process.many(ctx, annotation.body) - if bounds == none { - return (x, y) - } - - let (x-min, y-min, ..) = bounds.low - let (x-max, y-max, ..) = bounds.high - - x-min -= annotation.padding.left - x-max += annotation.padding.right - y-min -= annotation.padding.bottom - y-max += annotation.padding.top - - x.min = calc.min(x.min, x-min) - x.max = calc.max(x.max, x-max) - y.min = calc.min(y.min, y-min) - y.max = calc.max(y.max, y-max) - - return (x, y) -} diff --git a/src/plot-old/bar.typ b/src/plot-old/bar.typ deleted file mode 100644 index 2c40233..0000000 --- a/src/plot-old/bar.typ +++ /dev/null @@ -1,264 +0,0 @@ -#import "/src/cetz.typ": draw, util - -#import "errorbar.typ": draw-errorbar - -#let _transform-row(row, x-key, y-key, error-key) = { - let x = row.at(x-key) - let y = if y-key == auto { - row.slice(1) - } else if type(y-key) == array { - y-key.map(k => row.at(k, default: 0)) - } else { - row.at(y-key, default: 0) - } - let err = if error-key == none { - 0 - } else if type(error-key) == array { - error-key.map(k => row.at(k, default: 0)) - } else { - row.at(error-key, default: 0) - } - - if type(y) != array { y = (y,) } - if type(err) != array { err = (err,) } - - (x, y.flatten(), err.flatten()) -} - -// Get a single items min and maximum y-value -#let _minmax-value(row) = { - let min = none - let max = none - - let y = row.at(1) - let e = row.at(2) - for i in range(0, y.len()) { - let i-min = y.at(i) - e.at(i, default: 0) - if min == none { min = i-min } - else { min = calc.min(min, i-min) } - - let i-max = y.at(i) + e.at(i, default: 0) - if max == none { max = i-max } - else { max = calc.max(max, i-max) } - } - - return (min: min, max: max) -} - -// Functions for max value calculation -#let _max-value-fn = ( - basic: (data, min: 0) => { - calc.max(min, ..data.map(t => _minmax-value(t).max)) - }, - clustered: (data, min: 0) => { - calc.max(min, ..data.map(t => _minmax-value(t).max)) - }, - stacked: (data, min: 0) => { - calc.max(min, ..data.map(t => t.at(1).sum())) - }, - stacked100: (.., min: 0) => {min + 100} -) - -// Functions for min value calculation -#let _min-value-fn = ( - basic: (data, min: 0) => { - calc.min(min, ..data.map(t => _minmax-value(t).min)) - }, - clustered: (data, min: 0) => { - calc.min(min, ..data.map(t => _minmax-value(t).min)) - }, - stacked: (data, min: 0) => { - calc.min(min, ..data.map(t => t.at(1).sum())) - }, - stacked100: (.., min: 0) => {min} -) - -#let _prepare(self, ctx) = { - return self -} - -#let _get-x-offset(position, width) = { - if position == "start" { 0 } - else if position == "end" { width } - else { width / 2 } -} - -#let _draw-rects(filling, self, ctx, ..args) = { - let x-axis = ctx.x - let y-axis = ctx.y - - let bars = () - let errors = () - - let w = self.bar-width - for d in self.data { - let (x, n, len, y-min, y-max, err) = d - - let w = self.bar-width - let gap = self.cluster-gap * if w > 0 { -1 } else { +1 } - w += gap * (len - 1) - - let x-offset = _get-x-offset(self.bar-position, self.bar-width) - x-offset += gap * n - - let left = x - x-offset - let right = left + w - let width = (right - left) / len - - if self.mode in ("basic", "clustered") { - left = left + width * n - right = left + width - } - - if (left <= x-axis.max and right >= x-axis.min and - y-min <= y-axis.max and y-max >= y-axis.min) { - left = calc.max(left, x-axis.min) - right = calc.min(right, x-axis.max) - y-min = calc.max(y-min, y-axis.min) - y-max = calc.min(y-max, y-axis.max) - - draw.rect((left, y-min), (right, y-max)) - - if not filling and err != 0 { - let y-whisker-size = self.whisker-size * ctx.x-scale - draw-errorbar(((left + right) / 2, y-max), - 0, err, 0, y-whisker-size / 2, self.style + self.error-style) - } - } - } -} - -#let _stroke(self, ctx) = { - _draw-rects(false, self, ctx, fill: none) -} - -#let _fill(self, ctx) = { - _draw-rects(true, self, ctx, stroke: none) -} - -/// Add a bar- or column-chart to the plot -/// -/// A bar- or column-chart is a chart where values are drawn as rectangular boxes. -/// -/// - data (array): Array of data items. An item is an array containing a x an one or more y values. -/// For example `(0, 1)` or `(0, 10, 5, 30)`. Depending on the `mode`, the data items -/// get drawn as either clustered or stacked rects. -/// - x-key: (int,string): Key to use for retreiving a bars x-value from a single data entry. -/// This value gets passed to the `.at(...)` function of a data item. -/// - y-key: (auto,int,string,array): Key to use for retreiving a bars y-value. For clustered/stacked -/// data, this must be set to a list of keys (e.g. `range(1, 4)`). If set to `auto`, att but the first -/// array-values of a data item are used as y-values. -/// - error-key: (none,int,string): Key to use for retreiving a bars y-error. -/// - mode (string): The mode on how to group data items into bars: -/// / basic: Add one bar per data value. If the data contains multiple values, -/// group those bars next to each other. -/// / clustered: Like "basic", but take into account the maximum number of values of all items -/// and group each cluster of bars together having the width of the widest cluster. -/// / stacked: Stack bars of subsequent item values onto the previous bar, generating bars -/// with the height of the sume of all an items values. -/// / stacked100: Like "stacked", but scale each bar to height $100$, making the different -/// bars percentages of the sum of an items values. -/// - labels (none,content,array): A single legend label for "basic" bar-charts, or a -/// a list of legend labels per bar category, if the mode is one of "clustered", "stacked" or "stacked100". -/// - bar-width (float): Width of one data item on the y axis -/// - bar-position (string): Positioning of data items relative to their x value. -/// - "start": The lower edge of the data item is on the x value (left aligned) -/// - "center": The data item is centered on the x value -/// - "end": The upper edge of the data item is on the x value (right aligned) -/// - cluster-gap (float): Spacing between bars insides a cluster. -/// - style (dictionary): Plot style -/// - axes (axes): Plot axes. To draw a horizontal growing bar chart, you can swap the x and y axes. -#let add-bar(data, - x-key: 0, - y-key: auto, - error-key: none, - mode: "basic", - labels: none, - bar-width: 1, - bar-position: "center", - cluster-gap: 0, - whisker-size: .25, - error-style: (:), - style: (:), - axes: ("x", "y")) = { - assert(mode in ("basic", "clustered", "stacked", "stacked100"), - message: "Mode must be basic, clustered, stacked or stacked100, but is " + mode) - assert(bar-position in ("start", "center", "end"), - message: "Invalid bar-position '" + bar-position + "'. Allowed values are: start, center, end") - assert(bar-width != 0, - message: "Option bar-width must be != 0, but is " + str(bar-width)) - if error-key != none { - assert(y-key != auto, - message: "Bar value-key must be set != auto if error-key is set") - assert(mode in ("basic", "clustered"), - message: "Error bars are supported for basic or clustered only, got " + mode) - } - - // Transform data to (x, y, error) triplets - let data = data.map(row => _transform-row(row, x-key, y-key, error-key)) - - let n = util.max(..data.map(d => d.at(1).len())) - let x-offset = _get-x-offset(bar-position, bar-width) - let x-domain = (util.min(..data.map(d => d.at(0))) - x-offset, - util.max(..data.map(d => d.at(0))) - x-offset + bar-width) - let y-domain = (_min-value-fn.at(mode)(data), - _max-value-fn.at(mode)(data)) - - // For stacked 100%, multiply each column/bar - if mode == "stacked100" { - data = data.map(((x, y, err)) => { - let f = 100 / y.sum() - return (x, y.map(v => v * f), err) - }) - } - - // Transform data from (x, ..y) to (x, n, len, y-min, y-max) per y - let stacked = mode in ("stacked", "stacked100") - let clustered = mode == "clustered" - let bar-data = if mode == "basic" { - range(0, data.len()).map(_ => ()) - } else { - range(0, n).map(_ => ()) - } - - let j = 0 - for (x, y, err) in data { - let len = if clustered { n } else { y.len() } - let sum = 0 - for (i, y) in y.enumerate() { - let err = err.at(i, default: 0) - if stacked { - bar-data.at(i).push((x, i, len, sum, sum + y, err)) - } else if clustered { - bar-data.at(i).push((x, i, len, 0, y, err)) - } else { - bar-data.at(j).push((x, i, len, 0, y, err)) - } - sum += y - } - j += 1 - } - - let labels = if type(labels) == array { labels } else { (labels,) } - range(0, bar-data.len()).map(i => ( - type: "bar", - label: labels.at(i, default: none), - axes: axes, - mode: mode, - data: bar-data.at(i), - x-domain: x-domain, - y-domain: y-domain, - style: style, - bar-width: bar-width, - bar-position: bar-position, - cluster-gap: cluster-gap, - whisker-size: whisker-size, - error-style: error-style, - plot-prepare: _prepare, - plot-stroke: _stroke, - plot-fill: _fill, - plot-legend-preview: self => { - draw.rect((0,0), (1,1), ..self.style) - } - )) -} diff --git a/src/plot-old/boxwhisker.typ b/src/plot-old/boxwhisker.typ deleted file mode 100644 index 9b34c5e..0000000 --- a/src/plot-old/boxwhisker.typ +++ /dev/null @@ -1,117 +0,0 @@ -#import "/src/cetz.typ": draw, util - -/// Add one or more box or whisker plots -/// -/// #example(``` -/// cetz.plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { -/// cetz.plot.add-boxwhisker((x: 1, // Location on x-axis -/// outliers: (7, 65, 69), // Optional outlier values -/// min: 15, max: 60, // Minimum and maximum -/// q1: 25, // Quartiles: Lower -/// q2: 35, // Median -/// q3: 50)) // Upper -/// }) -/// ```) -/// -/// - data (array, dictionary): dictionary or array of dictionaries containing the -/// needed entries to plot box and whisker plot. -/// -/// The following fields are supported: -/// - `x` (number) X-axis value -/// - `min` (number) Minimum value -/// - `max` (number) Maximum value -/// - `q1`, `q2`, `q3` (number) Quartiles from lower to to upper -/// - `outliers` (array of number) Optional outliers -/// -/// - axes (array): Name of the axes to use ("x", "y"), note that not all -/// plot styles are able to display a custom axis! -/// - style (style): Style to use, can be used with a palette function -/// - box-width (float): Width from edge-to-edge of the box of the box and whisker in plot units. Defaults to 0.75 -/// - whisker-width (float): Width from edge-to-edge of the whisker of the box and whisker in plot units. Defaults to 0.5 -/// - mark (string): Mark to use for plotting outliers. Set `none` to disable. Defaults to "x" -/// - mark-size (float): Size of marks for plotting outliers. Defaults to 0.15 -/// - label (none,content): Legend label to show for this plot. -#let add-boxwhisker(data, - label: none, - axes: ("x", "y"), - style: (:), - box-width: 0.75, - whisker-width: 0.5, - mark: "*", - mark-size: 0.15) = { - // Add multiple boxes as multiple calls to - // add-boxwhisker - if type(data) == array { - for it in data { - add-boxwhisker( - it, - axes:axes, - style: style, - box-width: box-width, - whisker-width: whisker-width, - mark: mark, - mark-size: mark-size) - } - return - } - - assert("x" in data, message: "Specify 'x', the x value at which to display the box and whisker") - assert("q1" in data, message: "Specify 'q1', the lower quartile") - assert("q2" in data, message: "Specify 'q2', the median") - assert("q3" in data, message: "Specify 'q3', the upper quartile") - assert("min" in data, message: "Specify 'min', the minimum excluding outliers") - assert("max" in data, message: "Specify 'max', the maximum excluding outliers") - assert(data.q1 <= data.q2 and data.q2 <= data.q3, - message: "The quartiles q1, q2 and q3 must follow q1 < q2 < q3") - assert(data.min <= data.q1 and data.max >= data.q2, - message: "The minimum and maximum must be <= q1 and >= q3") - - // Y domain - let max-value = util.max(data.max, ..data.at("outliers", default: ())) - let min-value = util.min(data.min, ..data.at("outliers", default: ())) - - let prepare(self, ctx) = { - return self - } - - let stroke(self, ctx) = { - let data = self.bw-data - - // Box - draw.rect((data.x - box-width / 2, data.q1), - (data.x + box-width / 2, data.q3), - ..self.style) - - // Mean - draw.line((data.x - box-width / 2, data.q2), - (data.x + box-width / 2, data.q2), - ..self.style) - - // whiskers - let whisker(x, start, end) = { - draw.line((x, start),(x, end),..self.style) - draw.line((x - whisker-width / 2, end),(x + whisker-width / 2, end), ..self.style) - } - whisker(data.x, data.q3, data.max) - whisker(data.x, data.q1, data.min) - } - - (( - type: "boxwhisker", - label: label, - axes: axes, - bw-data: data, - style: style, - plot-prepare: prepare, - plot-stroke: stroke, - x-domain: (data.x - calc.max(whisker-width, box-width), - data.x + calc.max(whisker-width, box-width)), - y-domain: (min-value, max-value), - ) + (if "outliers" in data { ( - type: "boxwhisker-outliers", - data: data.outliers.map(it => (data.x, it)), - mark: mark, - mark-size: mark-size, - mark-style: (:) - ) }),) -} diff --git a/src/plot-old/contour.typ b/src/plot-old/contour.typ deleted file mode 100644 index db611c9..0000000 --- a/src/plot-old/contour.typ +++ /dev/null @@ -1,350 +0,0 @@ -#import "/src/cetz.typ": draw - -#import "util.typ" -#import "sample.typ" - -// Find contours of a 2D array by using marching squares algorithm -// -// - data (array): A 2D array of floats where the first index is the row and the second index is the column -// - offset (float): Z value threshold of a cell compare with `op` to, to count as true -// - op (auto,string,function): Z value comparison oparator: -// / `">", ">=", "<", "<=", "!=", "=="`: Use the passed operator to compare z. -// / `auto`: Use ">=" for positive z values, "<=" for negative z values. -// / ``: If set to a function, that function gets called -// with two arguments, the z value `z1` to compare against and -// the z value `z2` of the data and must return a boolean: `(z1, z2) => boolean`. -// - interpolate (bool): Enable cell interpolation for smoother lines -// - contour-limit (int): Contour limit after which the algorithm panics -// -> array: Array of contour point arrays -#let find-contours(data, offset, op: auto, interpolate: true, contour-limit: 50) = { - assert(data != none and type(data) == array, - message: "Data must be of type array") - assert(type(offset) in (int, float), - message: "Offset must be numeric") - - let n-rows = data.len() - let n-cols = data.at(0).len() - if n-rows < 2 or n-cols < 2 { - return () - } - - assert(op == auto or type(op) in (str, function), - message: "Operator must be of type auto, string or function") - if op == auto { - op = if offset < 0 { "<=" } else { ">=" } - } - if type(op) == str { - assert(op in ("<", "<=", ">", ">=", "==", "!="), - message: "Operator must be one of: <, <=, >, >=, != or ==") - } - - // Return if data is set - let is-set = if type(op) == function { - v => op(offset, v) - } else if op == "==" { - v => v == offset - } else if op == "!=" { - v => v != offset - } else if op == "<" { - v => v < offset - } else if op == "<=" { - v => v <= offset - } else if op == ">" { - v => v > offset - } else if op == ">=" { - v => v >= offset - } - - // Build a binary map that has 0 for unset and 1 for set cells - let bin-data = data.map(r => r.map(is-set)) - - // Get binary data at x, y - let get-bin(x, y) = { - if x >= 0 and x < n-cols and y >= 0 and y < n-rows { - return bin-data.at(y).at(x) - } - return false - } - - // Get data point for x, y coordinate - let get-data(x, y) = { - if x >= 0 and x < n-cols and y >= 0 and y < n-rows { - return float(data.at(y).at(x)) - } - return none - } - - // Get case (0 to 15) - let get-case(tl, tr, bl, br) = { - int(tl) * 8 + int(tr) * 4 + int(br) * 2 + int(bl) - } - - let lerp(a, b) = { - if a == b { return a } - else if a == none { return 1 } - else if b == none { return 0 } - return (offset - a) / (b - a) - } - - // List of all found contours - let contours = () - - let segments = () - for y in range(-1, n-rows) { - for x in range(-1, n-cols) { - let tl = get-bin(x, y) - let tr = get-bin(x+1, y) - let bl = get-bin(x, y+1) - let br = get-bin(x+1, y+1) - - // Corner data - // - // nw-----ne - // | | - // | | - // | | - // sw-----se - let nw = get-data(x, y) - let ne = get-data(x+1, y) - let se = get-data(x+1, y+1) - let sw = get-data(x, y+1) - - // Interpolated edge points - // - // +-- a --+ - // | | - // d b - // | | - // +-- c --+ - let a = (x + .5, y) - let b = (x + 1, y + .5) - let c = (x + .5, y + 1) - let d = (x, y + .5) - if interpolate { - a = (x + lerp(nw, ne), y) - b = (x + 1, y + lerp(ne, se)) - c = (x + lerp(sw, se), y + 1) - d = (x, y + lerp(nw, sw)) - } - - let case = get-case(tl, tr, bl, br) - if case in (1, 14) { - segments.push((d, c)) - } else if case in (2, 13) { - segments.push((b, c)) - } else if case in (3, 12) { - segments.push((d, b)) - } else if case in (4, 11) { - segments.push((a, b)) - } else if case == 5 { - segments.push((d, a)) - segments.push((c, b)) - } else if case in (6, 9) { - segments.push((c, a)) - } else if case in (7, 8) { - segments.push((d, a)) - } else if case == 10 { - segments.push((a, b)) - segments.push((c, d)) - } - } - } - - // Join lines to one or more contours - // This is done by searching for the next line - // that starts at the current contours head or tail - // point. If found, push the other coordinate to - // the contour. If no line could be found, push a - // new contour. - let contours = () - while segments.len() > 0 { - if contours.len() == 0 { - contours.push(segments.remove(0)) - } - - let found = false - - let i = 0 - while i < segments.len() { - let (a, b) = segments.at(i) - let (h, t) = (contours.last().first(), - contours.last().last()) - if a == t { - contours.last().push(b) - segments.remove(i) - found = true - } else if b == t { - contours.last().push(a) - segments.remove(i) - found = true - } else if a == h { - contours.last().insert(0, b) - segments.remove(i) - found = true - } else if b == h { - contours.last().insert(0, a) - segments.remove(i) - found = true - } else { - i += 1 - } - } - - // Insert the next contour - if not found { - contours.push(segments.remove(0)) - } - - // Check limit - assert(contours.len() <= contour-limit, - message: "Countour limit reached! Raise contour-limit if you " + - "think this is not an error") - } - - return contours -} - -// Prepare line data -#let _prepare(self, ctx) = { - let (x, y) = (ctx.x, ctx.y) - - self.contours = self.contours.map(c => { - c.stroke-paths = util.compute-stroke-paths(c.line-data, - (x.min, y.min), (x.max, y.max)) - - if self.fill { - c.fill-paths = util.compute-fill-paths(c.line-data, - (x.min, y.min), (x.max, y.max)) - } - return c - }) - - return self -} - -// Stroke line data -#let _stroke(self, ctx) = { - for c in self.contours { - for p in c.stroke-paths { - draw.line(..p, fill: none, close: p.first() == p.last()) - } - } -} - -// Fill line data -#let _fill(self, ctx) = { - if not self.fill { return } - for c in self.contours { - for p in c.fill-paths { - draw.line(..p, stroke: none, close: p.first() == p.last()) - } - } -} - -/// Add a contour plot of a sampled function or a matrix. -/// -/// #example(``` -/// cetz.plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { -/// cetz.plot.add-contour(x-domain: (-3, 3), y-domain: (-3, 3), -/// style: (fill: rgb(50,50,250,50)), -/// fill: true, -/// op: "<", // Find contours where data < z -/// z: (2.5, 2, 1), // Z values to find contours for -/// (x, y) => calc.sqrt(x * x + y * y)) -/// }) -/// ```) -/// -/// - data (array, function): A function of the signature `(x, y) => z` -/// or an array of arrays of floats (a matrix) where the first -/// index is the row and the second index is the column. -/// - z (float, array): Z values to plot. Contours containing values -/// above z (z >= 0) or below z (z < 0) get plotted. -/// If you specify multiple z values, they get plotted in the order of specification. -/// - x-domain (domain): X axis domain used if `data` is a function, that is the -/// domain inside the function gets sampled. -/// - y-domain (domain): Y axis domain used if `data` is a function, see `x-domain`. -/// - x-samples (int): X axis domain samples (2 < n). Note that contour finding -/// can be quite slow. Using a big sample count can improve accuracy but can -/// also lead to bad compilation performance. -/// - y-samples (int): Y axis domain samples (2 < n) -/// - interpolate (bool): Use linear interpolation between sample values which can -/// improve the resulting plot, especially if the contours are curved. -/// - op (auto,string,function): Z value comparison oparator: -/// / `">", ">=", "<", "<=", "!=", "=="`: Use the operator for comparison of `z` to -/// the values from `data`. -/// / `auto`: Use ">=" for positive z values, "<=" for negative z values. -/// / ``: Call comparison function of the format `(plot-z, data-z) => boolean`, -/// where `plot-z` is the z-value from the plots `z` argument and `data-z` -/// is the z-value of the data getting plotted. The function must return true -/// if at the combinations of arguments a contour is detected. -/// - fill (bool): Fill each contour -/// - style (style): Style to use for plotting, can be used with a palette function. Note -/// that all z-levels use the same style! -/// - axes (axes): Name of the axes to use for plotting. -/// - limit (int): Limit of contours to create per z value before the function panics -/// - label (none,content): Plot legend label to show. The legend preview for -/// contour plots is a little rectangle drawn with the contours style. -#let add-contour(data, - label: none, - z: (1,), - x-domain: (0, 1), - y-domain: (0, 1), - x-samples: 25, - y-samples: 25, - interpolate: true, - op: auto, - axes: ("x", "y"), - style: (:), - fill: false, - limit: 50, - ) = { - // Sample a x/y function - if type(data) == function { - data = sample.sample-fn2(data, - x-domain, y-domain, - x-samples, y-samples) - } - - // Find matrix dimensions - assert(type(data) == array) - let (x-min, x-max) = x-domain - let dx = (x-max - x-min) / (data.at(0).len() - 1) - let (y-min, y-max) = y-domain - let dy = (y-max - y-min) / (data.len() - 1) - - let contours = () - let z = if type(z) == array { z } else { (z,) } - for z in z { - for contour in find-contours(data, z, op: op, interpolate: interpolate, contour-limit: limit) { - let line-data = contour.map(pt => { - (pt.at(0) * dx + x-min, - pt.at(1) * dy + y-min) - }) - - contours.push(( - z: z, - line-data: line-data, - )) - } - } - - return (( - type: "contour", - label: label, - contours: contours, - axes: axes, - x-domain: x-domain, - y-domain: y-domain, - style: style, - fill: fill, - mark: none, - mark-style: none, - plot-prepare: _prepare, - plot-stroke: _stroke, - plot-fill: _fill, - plot-legend-preview: self => { - if not self.fill { self.style.fill = none } - draw.rect((0,0), (1,1), ..self.style) - } - ),) -} diff --git a/src/plot-old/errorbar.typ b/src/plot-old/errorbar.typ deleted file mode 100644 index e8ec68c..0000000 --- a/src/plot-old/errorbar.typ +++ /dev/null @@ -1,118 +0,0 @@ -#import "/src/cetz.typ": draw, util, vector - -#let _draw-whisker(pt, dir, ..style) = { - let a = vector.add(pt, vector.scale(dir, -1)) - let b = vector.add(pt, vector.scale(dir, +1)) - - draw.line(a, b, ..style) -} - -#let draw-errorbar(pt, x, y, x-whisker-size, y-whisker-size, style) = { - if type(x) != array { x = (-x, x) } - if type(y) != array { y = (-y, y) } - - let (x-min, x-max) = x - let x-min-pt = vector.add(pt, (x-min, 0)) - let x-max-pt = vector.add(pt, (x-max, 0)) - if x-min != 0 or x-max != 0 { - draw.line(x-min-pt, x-max-pt, ..style) - if x-whisker-size > 0 { - if x-min != 0 { - _draw-whisker(x-min-pt, (0, x-whisker-size), ..style) - } - if x-max != 0 { - _draw-whisker(x-max-pt, (0, x-whisker-size), ..style) - } - } - } - - let (y-min, y-max) = y - let y-min-pt = vector.add(pt, (0, y-min)) - let y-max-pt = vector.add(pt, (0, y-max)) - if y-min != 0 or y-max != 0 { - draw.line(y-min-pt, y-max-pt, ..style) - if y-whisker-size > 0 { - if y-min != 0 { - _draw-whisker(y-min-pt, (y-whisker-size, 0), ..style) - } - if y-max != 0 { - _draw-whisker(y-max-pt, (y-whisker-size, 0), ..style) - } - } - } -} - -#let _prepare(self, ctx) = { - return self -} - -#let _stroke(self, ctx) = { - let x-whisker-size = self.whisker-size * ctx.y-scale - let y-whisker-size = self.whisker-size * ctx.x-scale - - draw-errorbar((self.x, self.y), - self.x-error, self.y-error, - x-whisker-size, y-whisker-size, - self.style) -} - -/// Add x- and/or y-error bars -/// -/// - pt (tuple): Error-bar center coordinate tuple: `(x, y)` -/// - x-error: (float,tuple): Single error or tuple of errors along the x-axis -/// - y-error: (float,tuple): Single error or tuple of errors along the y-axis -/// - mark: (none,string): Mark symbol to show at the error position (`pt`). -/// - mark-size: (number): Size of the mark symbol. -/// - mark-style: (style): Extra style to apply to the mark symbol. -/// - whisker-size (float): Width of the error bar whiskers in canvas units. -/// - style (dictionary): Style for the error bars -/// - label: (none,content): Label to tsh -/// - axes (axes): Plot axes. To draw a horizontal growing bar chart, you can swap the x and y axes. -#let add-errorbar(pt, - x-error: 0, - y-error: 0, - label: none, - mark: "o", - mark-size: .2, - mark-style: (:), - whisker-size: .5, - style: (:), - axes: ("x", "y")) = { - assert(x-error != 0 or y-error != 0, - message: "Either x-error or y-error must be set.") - - let (x, y) = pt - - if type(x-error) != array { - x-error = (x-error, x-error) - } - if type(y-error) != array { - y-error = (y-error, y-error) - } - - x-error.at(0) = calc.abs(x-error.at(0)) * -1 - y-error.at(0) = calc.abs(y-error.at(0)) * -1 - - let x-domain = x-error.map(v => v + x) - let y-domain = y-error.map(v => v + y) - - return (( - type: "errorbar", - label: label, - axes: axes, - data: ((x,y),), - x: x, - y: y, - x-error: x-error, - y-error: y-error, - x-domain: x-domain, - y-domain: y-domain, - mark: mark, - mark-size: mark-size, - mark-style: mark-style, - whisker-size: whisker-size, - style: style, - plot-prepare: _prepare, - plot-stroke: _stroke, - ),) -} diff --git a/src/plot-old/legend.typ b/src/plot-old/legend.typ deleted file mode 100644 index f26a43b..0000000 --- a/src/plot-old/legend.typ +++ /dev/null @@ -1,236 +0,0 @@ -#import "/src/cetz.typ" -#import cetz: draw, styles -#import draw: group - -#import "mark.typ": draw-mark-shape - -#let default-style = ( - orientation: ttb, - default-position: "north-east", - layer: 1, // Legend layer - fill: rgb(255,255,255,200), // Legend background - stroke: black, // Legend border - padding: .1, // Legend border padding - offset: (0, 0), // Legend displacement - spacing: .1, // Spacing between anchor and legend - item: ( - radius: 0, - spacing: .05, // Spacing between items - preview: ( - width: .75, // Preview width - height: .3, // Preview height - margin: .1 // Distance between preview and label - ) - ), - radius: 0, - scale: 100%, -) - -// Map position to legend group anchor -#let auto-group-anchor = ( - inner-north-west: "north-west", - inner-north: "north", - inner-north-east: "north-east", - inner-south-west: "south-west", - inner-south: "south", - inner-south-east: "south-east", - inner-west: "west", - inner-east: "east", - north-west: "north-east", - north: "south", - north-east: "north-west", - south-west: "south-east", - south: "north", - south-east: "south-west", - east: "west", - west: "east", -) - -// Generate legend positioning anchors -#let add-legend-anchors(style, element, size) = { - import draw: * - let (w, h) = size - let (xo, yo) = { - let spacing = style.at("spacing", default: (0, 0)) - if type(spacing) == array { - spacing - } else { - (spacing, spacing) - } - } - - anchor("north", (rel: (w / 2, yo), to: (element + ".north", "-|", element + ".origin"))) - anchor("south", (rel: (w / 2, -yo), to: (element + ".south", "-|", element + ".origin"))) - anchor("east", (rel: (xo, h / 2), to: (element + ".east", "|-", element + ".origin"))) - anchor("west", (rel: (-xo, h / 2), to: (element + ".west", "|-", element + ".origin"))) - anchor("north-east", (rel: (xo, h), to: (element + ".north-east", "|-", element + ".origin"))) - anchor("north-west", (rel: (-xo, h), to: (element + ".north-west", "|-", element + ".origin"))) - anchor("south-east", (rel: (xo, 0), to: (element + ".south-east", "|-", element + ".origin"))) - anchor("south-west", (rel: (-xo, 0), to: (element + ".south-west", "|-", element + ".origin"))) - anchor("inner-north", (rel: (w / 2, h - yo), to: element + ".origin")) - anchor("inner-north-east", (rel: (w - xo, h - yo), to: element + ".origin")) - anchor("inner-north-west", (rel: (yo, h - yo), to: element + ".origin")) - anchor("inner-south", (rel: (w / 2, yo), to: element + ".origin")) - anchor("inner-south-east", (rel: (w - xo, yo), to: element + ".origin")) - anchor("inner-south-west", (rel: (xo, yo), to: element + ".origin")) - anchor("inner-east", (rel: (w - xo, h / 2), to: element + ".origin")) - anchor("inner-west", (rel: (xo, h / 2), to: element + ".origin")) -} - -// Draw a generic item preview -#let draw-generic-preview(item) = { - import draw: * - - if item.at("fill", default: false) { - rect((0,0), (1,1), ..item.style) - } else { - line((0,.5), (1,.5), ..item.style) - } -} - -/// Construct a legend item for use with the `legend` function -/// -/// - label (none, auto, content): Legend label or auto to use the enumerated default label -/// - preview (auto, function): Legend preview icon function of the format `item => elements`. -/// Note that the canvas bounds for drawing the preview are (0,0) to (1,1). -/// - mark: (none,string): Legend mark symbol -/// - mark-style: (none,dictionary): Mark style -/// - mark-size: (number): Mark size -/// - style (styles): Style keys for the single item -#let item(label, preview, mark: none, mark-style: (:), mark-size: 1, ..style) = { - assert.eq(style.pos().len(), 0, - message: "Unexpected positional arguments") - return ((label: label, preview: preview, - mark: mark, mark-style: mark-style, mark-size: mark-size, - style: style.named()),) -} - -/// Draw a legend -#let legend(position, items, name: "legend", ..style) = group(name: name, ctx => { - draw.anchor("default", ()) - let items = if items != none { items.filter(v => v.label != none) } else { () } - if items == () { - return - } - - let style = styles.resolve( - ctx.style, merge: style.named(), base: default-style, root: "legend") - assert(style.orientation in (ttb, ltr), - message: "Unsupported legend orientation.") - - // Scaling - draw.scale(style.scale) - - // Position - let position = if position == auto { - style.default-position - } else { - position - } - - // Adjust anchor - if style.anchor == auto { - style.anchor = if type(position) == str { - auto-group-anchor.at(position, default: "north-west") - } else { - "north-west" - } - } - - // Apply offset - if style.offset not in (none, (0,0)) { - position = (rel: style.offset, to: position) - } - - // Draw items - draw.on-layer(style.layer, { - draw.group(name: "items", padding: style.padding, ctx => { - import draw: * - - set-origin(position) - anchor("default", (0,0)) - - let pt = (0, 0) - for (i, item) in items.enumerate() { - let (label, preview) = item - if label == none { - continue - } else if label == auto { - label = $ f_(#i) $ - } - - group({ - anchor("default", (0,0)) - - let row-height = style.item.preview.height - let preview-width = style.item.preview.width - let preview-a = (0, -row-height / 2) - let preview-b = (preview-width, +row-height / 2) - let label-west = (preview-width + style.item.preview.margin, 0) - - // Draw item preview - let draw-preview = if preview == auto { draw-generic-preview } else { preview } - group({ - set-viewport(preview-a, preview-b, bounds: (1, 1, 0)) - (draw-preview)(item) - }) - - // Draw mark preview - let mark = item.at("mark", default: none) - if mark != none { - draw-mark-shape((preview-a, 50%, preview-b), - calc.min(style.item.preview.width / 2, item.mark-size), - mark, - item.mark-style) - } - - // Draw label - content(label-west, - align(left + horizon, label), - name: "label", anchor: "west") - }, name: "item", anchor: if style.orientation == ltr { "west" } else { "north-west" }) - - if style.orientation == ttb { - set-origin((rel: (0, -style.item.spacing), - to: "item.south-west")) - } else if style.orientation == ltr { - set-origin((rel: (style.item.spacing, 0), - to: "item.east")) - } - } - }, anchor: style.anchor) - }) - - // Fill legend background - draw.on-layer(style.layer - .5, { - draw.rect("items.south-west", - "items.north-east", fill: style.fill, stroke: style.stroke, radius: style.radius) - }) -}) - -/// Function for manually adding a legend item from within -/// a plot environment -/// -/// - label (content): Legend label -/// - preview (auto,function): Legend preview function of the format `() => elements`. -/// The preview canvas bounds are between (0,0) and (1,1). -/// If set to `auto`, a straight line is drawn. -/// -/// ```example -/// add-legend([Custom item], preview _ => { -/// draw.rect((0,0), (1,1)) // Draw a rect as preview -/// }) -/// ``` -#let add-legend(label, preview: auto) = { - assert(preview == auto or type(preview) == function, - message: "Expected auto or function, got " + repr(type(preview))) - - return (( - type: "legend-item", - label: label, - style: (:), - axes: ("x", "y"), - ) + if preview != auto { - (plot-legend-preview: _ => { preview() }) - },) -} diff --git a/src/plot-old/line.typ b/src/plot-old/line.typ deleted file mode 100644 index 12b5c6c..0000000 --- a/src/plot-old/line.typ +++ /dev/null @@ -1,508 +0,0 @@ -#import "/src/cetz.typ": draw - -#import "util.typ" -#import "sample.typ" - -// Transform points -// -// - data (array): Data points -// - line (str,dictionary): Line line -#let transform-lines(data, line) = { - let hvh-data(t) = { - if type(t) == ratio { - t = t / 1% - } - t = calc.max(0, calc.min(t, 1)) - - let pts = () - - let len = data.len() - for i in range(0, len) { - pts.push(data.at(i)) - - if i < len - 1 { - let (a, b) = (data.at(i), data.at(i+1)) - if t == 0 { - pts.push((a.at(0), b.at(1))) - } else if t == 1 { - pts.push((b.at(0), a.at(1))) - } else { - let x = a.at(0) + (b.at(0) - a.at(0)) * t - pts.push((x, a.at(1))) - pts.push((x, b.at(1))) - } - } - } - return pts - } - - if type(line) == str { - line = (type: line) - } - - let line-type = line.at("type", default: "linear") - assert(line-type in ("raw", "linear", "spline", "vh", "hv", "hvh")) - - // Transform data into line-data - let line-data = if line-type == "linear" { - return util.linearized-data(data, line.at("epsilon", default: 0)) - } else if line-type == "spline" { - return util.sampled-spline-data(data, - line.at("tension", default: .5), - line.at("samples", default: 15)) - } else if line-type == "vh" { - return hvh-data(0) - } else if line-type == "hv" { - return hvh-data(1) - } else if line-type == "hvh" { - return hvh-data(line.at("mid", default: .5)) - } else { - return data - } -} - -// Fill a plot by generating a fill path to y value `to` -#let fill-segments-to(segments, to) = { - for s in segments { - let low = calc.min(..s.map(v => v.at(0))) - let high = calc.max(..s.map(v => v.at(0))) - - let origin = (low, to) - let target = (high, to) - - draw.line(origin, ..s, target, stroke: none) - } -} - -// Fill a shape by generating a fill path for each segment -#let fill-shape(paths) = { - for p in paths { - draw.line(..p, stroke: none) - } -} - -// Prepare line data -#let _prepare(self, ctx) = { - let (x, y) = (ctx.x, ctx.y) - - // Generate stroke paths - self.stroke-paths = util.compute-stroke-paths(self.line-data, - (x.min, y.min), (x.max, y.max)) - - // Compute fill paths if filling is requested - self.hypograph = self.at("hypograph", default: false) - self.epigraph = self.at("epigraph", default: false) - self.fill = self.at("fill", default: false) - if self.hypograph or self.epigraph or self.fill { - self.fill-paths = util.compute-fill-paths(self.line-data, - (x.min, y.min), (x.max, y.max)) - } - - return self -} - -// Stroke line data -#let _stroke(self, ctx) = { - let (x, y) = (ctx.x, ctx.y) - - for p in self.stroke-paths { - draw.line(..p, fill: none) - } -} - -// Fill line data -#let _fill(self, ctx) = { - let (x, y) = (ctx.x, ctx.y) - - if self.hypograph { - fill-segments-to(self.fill-paths, y.min) - } - if self.epigraph { - fill-segments-to(self.fill-paths, y.max) - } - if self.fill { - if self.at("fill-type", default: "axis") == "shape" { - fill-shape(self.fill-paths) - } else { - fill-segments-to(self.fill-paths, - calc.max(calc.min(y.max, 0), y.min)) - } - } -} - -/// Add data to a plot environment. -/// -/// Note: You can use this for scatter plots by setting -/// the stroke style to `none`: `add(..., style: (stroke: none))`. -/// -/// Must be called from the body of a `plot(..)` command. -/// -/// - domain (domain): Domain of `data`, if `data` is a function. Has no effect -/// if `data` is not a function. -/// - hypograph (bool): Fill hypograph; uses the `hypograph` style key for -/// drawing -/// - epigraph (bool): Fill epigraph; uses the `epigraph` style key for -/// drawing -/// - fill (bool): Fill the shape of the plot -/// - fill-type (string): Fill type: -/// / `"axis"`: Fill the shape to y = 0 -/// / `"shape"`: Fill the complete shape -/// - samples (int): Number of times the `data` function gets called for -/// sampling y-values. Only used if `data` is of type function. This parameter gets -/// passed onto `sample-fn`. -/// - sample-at (array): Array of x-values the function gets sampled at in addition -/// to the default sampling. This parameter gets passed to `sample-fn`. -/// - line (string, dictionary): Line type to use. The following types are -/// supported: -/// / `"linear"`: Draw linear lines between points -/// / `"spline"`: Calculate a Catmull-Rom through all points -/// / `"vh"`: Move vertical and then horizontal -/// / `"hv"`: Move horizontal and then vertical -/// / `"hvh"`: Add a vertical step in the middle -/// / `"raw"`: Like linear, but without linearization taking place. This is -/// meant as a "fallback" for either bad performance or bugs. -/// -/// If the value is a dictionary, the type must be -/// supplied via the `type` key. The following extra -/// attributes are supported: -/// / `"samples" `: Samples of splines -/// / `"tension" `: Tension of splines -/// / `"mid" `: Mid-Point of hvh lines (0 to 1) -/// / `"epsilon" `: Linearization slope epsilon for -/// use with `"linear"`, defaults to 0. -/// -/// #example(``` -/// import cetz.plot -/// let points(offset: 0) = ((0,0), (1,1), (2,0), (3,1), (4,0)).map(((x,y)) => { -/// (x,y + offset * 1.5) -/// }) -/// plot.plot(size: (12, 3), axis-style: none, { -/// plot.add(points(offset: 5), line: (type: "hvh", mid: .1)) -/// plot.add(points(offset: 4), line: "hvh") -/// plot.add(points(offset: 3), line: "hv") -/// plot.add(points(offset: 2), line: "vh") -/// plot.add(points(offset: 1), line: "spline") -/// plot.add(points(offset: 0), line: "linear") -/// }) -/// ```, vertical: true) -/// -/// - style (style): Style to use, can be used with a `palette` function -/// - axes (axes): Name of the axes to use for plotting. Reversing the axes -/// means rotating the plot by 90 degrees. -/// - mark (string): Mark symbol to place at each distinct value of the -/// graph. Uses the `mark` style key of `style` for drawing. -/// - mark-size (float): Mark size in cavas units -/// - data (array,function): Array of 2D data points (numeric) or a function -/// of the form `x => y`, where `x` is a value in `domain` -/// and `y` must be numeric or a 2D vector (for parametric functions). -/// #example(``` -/// import cetz.plot -/// plot.plot(size: (2, 2), axis-style: none, { -/// // Using an array of points: -/// plot.add(((0,0), (calc.pi/2,1), -/// (1.5*calc.pi,-1), (2*calc.pi,0))) -/// // Sampling a function: -/// plot.add(domain: (0, 2*calc.pi), calc.sin) -/// }) -/// ```) -/// - label (none,content): Legend label to show for this plot. -#let add(domain: auto, - hypograph: false, - epigraph: false, - fill: false, - fill-type: "axis", - style: (:), - mark: none, - mark-size: .2, - mark-style: (:), - samples: 50, - sample-at: (), - line: "linear", - axes: ("x", "y"), - label: none, - data - ) = { - // If data is of type function, sample it - if type(data) == function { - data = sample.sample-fn(data, domain, samples, sample-at: sample-at) - } - - // Transform data - let line-data = transform-lines(data, line) - - // Get x-domain - let x-domain = ( - calc.min(..line-data.map(t => t.at(0))), - calc.max(..line-data.map(t => t.at(0))) - ) - - // Get y-domain - let y-domain = if line-data != none {( - calc.min(..line-data.map(t => t.at(1))), - calc.max(..line-data.map(t => t.at(1))) - )} - - (( - type: "line", - label: label, - data: data, /* Raw data */ - line-data: line-data, /* Transformed data */ - axes: axes, - x-domain: x-domain, - y-domain: y-domain, - epigraph: epigraph, - hypograph: hypograph, - fill: fill, - fill-type: fill-type, - style: style, - mark: mark, - mark-size: mark-size, - mark-style: mark-style, - plot-prepare: _prepare, - plot-stroke: _stroke, - plot-fill: _fill, - plot-legend-preview: self => { - if self.fill or self.epigraph or self.hypograph { - draw.rect((0,0), (1,1), ..self.style) - } else { - draw.line((0,.5), (1,.5), ..self.style) - } - } - ),) -} - -/// Add horizontal lines at one or more y-values. Every lines start and end points -/// are at their axis bounds. -/// -/// #example(``` -/// cetz.plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { -/// cetz.plot.add(domain: (0, 4*calc.pi), calc.sin) -/// // Add 3 horizontal lines -/// cetz.plot.add-hline(-.5, 0, .5) -/// }) -/// ```) -/// -/// - ..y (float): Y axis value(s) to add a line at -/// - min (auto,float): X axis minimum value or auto to take the axis minimum -/// - max (auto,float): X axis maximum value or auto to take the axis maximum -/// - axes (array): Name of the axes to use for plotting -/// - style (style): Style to use, can be used with a palette function -/// - label (none,content): Legend label to show for this plot. -#let add-hline(..y, - min: auto, - max: auto, - axes: ("x", "y"), - style: (:), - label: none, - ) = { - assert(y.pos().len() >= 1, - message: "Specify at least one y value") - assert(y.named().len() == 0) - - let prepare(self, ctx) = { - let (x-min, x-max) = (ctx.x.min, ctx.x.max) - let (y-min, y-max) = (ctx.y.min, ctx.y.max) - let x-min = if min == auto { x-min } else { min } - let x-max = if max == auto { x-max } else { max } - - self.lines = self.y.filter(y => y >= y-min and y <= y-max) - .map(y => ((x-min, y), (x-max, y))) - return self - } - - let stroke(self, ctx) = { - for (a, b) in self.lines { - draw.line(a, b, fill: none) - } - } - - let x-min = if min == auto { none } else { min } - let x-max = if max == auto { none } else { max } - - (( - type: "hline", - label: label, - y: y.pos(), - x-domain: (x-min, x-max), - y-domain: (calc.min(..y.pos()), calc.max(..y.pos())), - axes: axes, - style: style, - plot-prepare: prepare, - plot-stroke: stroke, - ),) -} - -/// Add vertical lines at one or more x-values. Every lines start and end points -/// are at their axis bounds. -/// -/// #example(``` -/// cetz.plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { -/// cetz.plot.add(domain: (0, 2*calc.pi), calc.sin) -/// // Add 3 vertical lines -/// cetz.plot.add-vline(calc.pi/2, calc.pi, 3*calc.pi/2) -/// }) -/// ```) -/// -/// - ..x (float): X axis values to add a line at -/// - min (auto,float): Y axis minimum value or auto to take the axis minimum -/// - max (auto,float): Y axis maximum value or auto to take the axis maximum -/// - axes (array): Name of the axes to use for plotting, note that not all -/// plot styles are able to display a custom axis! -/// - style (style): Style to use, can be used with a palette function -/// - label (none,content): Legend label to show for this plot. -#let add-vline(..x, - min: auto, - max: auto, - axes: ("x", "y"), - style: (:), - label: none, - ) = { - assert(x.pos().len() >= 1, - message: "Specify at least one x value") - assert(x.named().len() == 0) - - let prepare(self, ctx) = { - let (x-min, x-max) = (ctx.x.min, ctx.x.max) - let (y-min, y-max) = (ctx.y.min, ctx.y.max) - let y-min = if min == auto { y-min } else { min } - let y-max = if max == auto { y-max } else { max } - - self.lines = self.x.filter(x => x >= x-min and x <= x-max) - .map(x => ((x, y-min), (x, y-max))) - return self - } - - let stroke(self, ctx) = { - for (a, b) in self.lines { - draw.line(a, b, fill: none) - } - } - - let y-min = if min == auto { none } else { min } - let y-max = if max == auto { none } else { max } - - (( - type: "vline", - label: label, - x: x.pos(), - x-domain: (calc.min(..x.pos()), calc.max(..x.pos())), - y-domain: (y-min, y-max), - axes: axes, - style: style, - plot-prepare: prepare, - plot-stroke: stroke - ),) -} - -/// Fill the area between two graphs. This behaves same as `add` but takes -/// a pair of data instead of a single data array/function. -/// The area between both function plots gets filled. For a more detailed -/// explanation of the arguments, see @@add(). -/// -/// This can be used to display an error-band of a function. -/// -/// #example(``` -/// cetz.plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { -/// cetz.plot.add-fill-between(domain: (0, 2*calc.pi), -/// calc.sin, // First function/data -/// calc.cos) // Second function/data -/// }) -/// ```) -/// -/// - domain (domain): Domain of both `data-a` and `data-b`. The domain is used for -/// sampling functions only and has no effect on data arrays. -/// - samples (int): Number of times the `data-a` and `data-b` function gets called for -/// sampling y-values. Only used if `data-a` or `data-b` is of -/// type function. -/// - sample-at (array): Array of x-values the function(s) get sampled at in addition -/// to the default sampling. -/// - line (string, dictionary): Line type to use, see @@add(). -/// - style (style): Style to use, can be used with a palette function. -/// - label (none,content): Legend label to show for this plot. -/// - axes (array): Name of the axes to use for plotting. -/// - data-a (array,function): Data of the first plot, see @@add(). -/// - data-b (array,function): Data of the second plot, see @@add(). -#let add-fill-between(data-a, - data-b, - domain: auto, - samples: 50, - sample-at: (), - line: "linear", - axes: ("x", "y"), - label: none, - style: (:)) = { - // If data is of type function, sample it - if type(data-a) == function { - data-a = sample.sample-fn(data-a, domain, samples, sample-at: sample-at) - } - if type(data-b) == function { - data-b = sample.sample-fn(data-b, domain, samples, sample-at: sample-at) - } - - // Transform data - let line-a-data = transform-lines(data-a, line) - let line-b-data = transform-lines(data-b, line) - - // Get x-domain - let x-domain = ( - calc.min(..line-a-data.map(t => t.at(0)), - ..line-b-data.map(t => t.at(0))), - calc.max(..line-a-data.map(t => t.at(0)), - ..line-b-data.map(t => t.at(0))) - ) - - // Get y-domain - let y-domain = if line-a-data != none and line-b-data != none {( - calc.min(..line-a-data.map(t => t.at(1)), - ..line-b-data.map(t => t.at(1))), - calc.max(..line-a-data.map(t => t.at(1)), - ..line-b-data.map(t => t.at(1))) - )} - - let prepare(self, ctx) = { - let (x, y) = (ctx.x, ctx.y) - - // Generate stroke paths - self.stroke-paths = ( - a: util.compute-stroke-paths(self.line-data.a, - (x.min, y.min), (x.max, y.max)), - b: util.compute-stroke-paths(self.line-data.b, - (x.min, y.min), (x.max, y.max)) - ) - - // Generate fill paths - self.fill-paths = util.compute-fill-paths(self.line-data.a + self.line-data.b.rev(), - (x.min, y.min), (x.max, y.max)) - - return self - } - - let stroke(self, ctx) = { - for p in self.stroke-paths.a { - draw.line(..p, fill: none) - } - for p in self.stroke-paths.b { - draw.line(..p, fill: none) - } - } - - let fill(self, ctx) = { - fill-shape(self.fill-paths) - } - - (( - type: "fill-between", - label: label, - axes: axes, - line-data: (a: line-a-data, b: line-b-data), - x-domain: x-domain, - y-domain: y-domain, - style: style, - plot-prepare: prepare, - plot-stroke: stroke, - plot-fill: fill, - plot-legend-preview: self => { - draw.rect((0,0), (1,1), ..self.style) - } - ),) -} diff --git a/src/plot-old/mark.typ b/src/plot-old/mark.typ deleted file mode 100644 index 9450d21..0000000 --- a/src/plot-old/mark.typ +++ /dev/null @@ -1,45 +0,0 @@ -#import "/src/cetz.typ": draw -#import "/src/axes.typ" - -// Draw mark at point with size -#let draw-mark-shape(pt, size, mark, style) = { - let sx = size - let sy = size - - let bl(pt) = (rel: (-sx/2, -sy/2), to: pt) - let br(pt) = (rel: (sx/2, -sy/2), to: pt) - let tl(pt) = (rel: (-sx/2, sy/2), to: pt) - let tr(pt) = (rel: (sx/2, sy/2), to: pt) - let ll(pt) = (rel: (-sx/2, 0), to: pt) - let rr(pt) = (rel: (sx/2, 0), to: pt) - let tt(pt) = (rel: (0, sy/2), to: pt) - let bb(pt) = (rel: (0, -sy/2), to: pt) - - if mark == "o" { - draw.circle(pt, radius: (sx/2, sy/2), ..style) - } else if mark == "square" { - draw.rect(bl(pt), tr(pt), ..style) - } else if mark == "triangle" { - draw.line(bl(pt), br(pt), tt(pt), close: true, ..style) - } else if mark == "*" or mark == "x" { - draw.line(bl(pt), tr(pt), ..style) - draw.line(tl(pt), br(pt), ..style) - } else if mark == "+" { - draw.line(ll(pt), rr(pt), ..style); - draw.line(tt(pt), bb(pt), ..style) - } else if mark == "-" { - draw.line(ll(pt), rr(pt), ..style) - } else if mark == "|" { - draw.line(tt(pt), bb(pt), ..style) - } -} - -#let draw-mark(pts, x, y, mark, mark-size, plot-size) = { - let pts = pts.map(pt => { - axes.transform-vec(plot-size, x, y, none, pt) - }).filter(pt => pt != none) - - for pt in pts { - draw-mark-shape(pt, mark-size, mark, (:)) - } -} diff --git a/src/plot-old/sample.typ b/src/plot-old/sample.typ deleted file mode 100644 index 3ad881d..0000000 --- a/src/plot-old/sample.typ +++ /dev/null @@ -1,79 +0,0 @@ -/// Sample the given single parameter function `samples` times, with values -/// evenly spaced within the range given by `domain` and return each -/// sampled `y` value in an array as `(x, y)` tuple. -/// -/// If the functions first return value is a tuple `(x, y)`, then all return values -/// must be a tuple. -/// -/// - fn (function): Function to sample of the form `(x) => y` or `(t) => (x, y)`, where -/// `x` or `t` are `float` values within the domain specified by `domain`. -/// - domain (domain): Domain of `fn` used as bounding interval for the sampling points. -/// - samples (int): Number of samples in domain. -/// - sample-at (array): List of x values the function gets sampled at in addition -/// to the `samples` number of samples. Values outsides the -/// specified domain are legal. -/// -> array: Array of (x, y) tuples -#let sample-fn(fn, domain, samples, sample-at: ()) = { - assert(samples + sample-at.len() >= 2, - message: "You must at least sample 2 values") - assert(type(domain) == array and domain.len() == 2, - message: "Domain must be a tuple") - - let (lo, hi) = domain - - let y0 = (fn)(lo) - let is-vector = type(y0) == array - if not is-vector { - y0 = ((lo, y0), ) - } else { - y0 = (y0, ) - } - - let pts = sample-at + range(0, samples).map(t => lo + t / (samples - 1) * (hi - lo)) - pts = pts.sorted() - - return pts.map(x => { - if is-vector { - (fn)(x) - } else { - (x, (fn)(x)) - } - }) -} - -/// Samples the given two parameter function with `x-samples` and -/// `y-samples` values evenly spaced within the range given by -/// `x-domain` and `y-domain` and returns each sampled output in -/// an array. -/// -/// - fn (function): Function of the form `(x, y) => z` with all values being numbers. -/// - x-domain (domain): Domain used as bounding interval for sampling point's x -/// values. -/// - y-domain (domain): Domain used as bounding interval for sampling point's y -/// values. -/// - x-samples (int): Number of samples in the x-domain. -/// - y-samples (int): Number of samples in the y-domain. -/// -> array: Array of z scalars -#let sample-fn2(fn, x-domain, y-domain, x-samples, y-samples) = { - assert(x-samples >= 2, - message: "You must at least sample 2 x-values") - assert(y-samples >= 2, - message: "You must at least sample 2 y-values") - assert(type(x-domain) == array and x-domain.len() == 2, - message: "X-Domain must be a tuple") - assert(type(y-domain) == array and y-domain.len() == 2, - message: "Y-Domain must be a tuple") - - let (x-min, x-max) = x-domain - let (y-min, y-max) = y-domain - let y-pts = range(0, y-samples) - let x-pts = range(0, x-samples) - - return y-pts.map(y => { - let y = y / (y-samples - 1) * (y-max - y-min) + y-min - return x-pts.map(x => { - let x = x / (x-samples - 1) * (x-max - x-min) + x-min - return float((fn)(x, y)) - }) - }) -} diff --git a/src/plot-old/util.typ b/src/plot-old/util.typ deleted file mode 100644 index bff54fc..0000000 --- a/src/plot-old/util.typ +++ /dev/null @@ -1,372 +0,0 @@ -#import "/src/cetz.typ" -#import cetz.util: bezier - -/// Clip line-strip in rect -/// -/// - points (array): Array of vectors representing a line-strip -/// - low (vector): Lower clip-window coordinate -/// - high (vector): Upper clip-window coordinate -/// -> array List of line-strips representing the paths insides the clip-window -#let clipped-paths(points, low, high, fill: false) = { - let (min-x, max-x) = (calc.min(low.at(0), high.at(0)), - calc.max(low.at(0), high.at(0))) - let (min-y, max-y) = (calc.min(low.at(1), high.at(1)), - calc.max(low.at(1), high.at(1))) - - let in-rect(pt) = { - return (pt.at(0) >= min-x and pt.at(0) <= max-x and - pt.at(1) >= min-y and pt.at(1) <= max-y) - } - - let interpolated-end(a, b) = { - if in-rect(a) and in-rect(b) { - return b - } - - let (x1, y1, ..) = a - let (x2, y2, ..) = b - - if x2 - x1 == 0 { - return (x2, calc.min(max-y, calc.max(y2, min-y))) - } - - if y2 - y1 == 0 { - return (calc.min(max-x, calc.max(x2, min-x)), y2) - } - - let m = (y2 - y1) / (x2 - x1) - let n = y2 - m * x2 - - let x = x2 - let y = y2 - - y = calc.min(max-y, calc.max(y, min-y)) - x = (y - n) / m - - x = calc.min(max-x, calc.max(x, min-x)) - y = m * x + n - - return (x, y) - } - - // Append path to paths and return paths - // - // If path starts or ends with a vector of another part, merge those - // paths instead appending path as a new path. - let append-path(paths, path) = { - if path.len() <= 1 { - return paths - } - - let cmp(a, b) = { - return a.map(calc.round.with(digits: 8)) == b.map(calc.round.with(digits: 8)) - } - - let added = false - for i in range(0, paths.len()) { - let p = paths.at(i) - if cmp(p.first(), path.last()) { - paths.at(i) = path + p - added = true - } else if cmp(p.first(), path.first()) { - paths.at(i) = path.rev() + p - added = true - } else if cmp(p.last(), path.first()) { - paths.at(i) = p + path - added = true - } else if cmp(p.last(), path.last()) { - paths.at(i) = p + path.rev() - added = true - } - if added { break } - } - - if not added { - paths.push(path) - } - return paths - } - - let clamped-pt(pt) = { - return (calc.max(min-x, calc.min(pt.at(0), max-x)), - calc.max(min-y, calc.min(pt.at(1), max-y))) - } - - let paths = () - - let path = () - let prev = points.at(0) - let was-inside = in-rect(prev) - if was-inside { - path.push(prev) - } else if fill { - path.push(clamped-pt(prev)) - } - - for i in range(1, points.len()) { - let prev = points.at(i - 1) - let pt = points.at(i) - - let is-inside = in-rect(pt) - - let (x1, y1) = prev - let (x2, y2) = pt - - // Ignore lines if both ends are outsides the x-window and on the - // same side. - if (x1 < min-x and x2 < min-x) or (x1 > max-x and x2 > max-x) { - if fill { - let clamped = clamped-pt(pt) - if path.last() != clamped { - path.push(clamped) - } - } - was-inside = false - continue - } - - if is-inside { - if was-inside { - path.push(pt) - } else { - path.push(interpolated-end(pt, prev)) - path.push(pt) - } - } else { - if was-inside { - path.push(interpolated-end(prev, pt)) - } else { - let (a, b) = (interpolated-end(pt, prev), - interpolated-end(prev, pt)) - if in-rect(a) and in-rect(b) { - path.push(a) - path.push(b) - } else if fill { - let clamped = clamped-pt(pt) - if path.last() != clamped { - path.push(clamped) - } - } - } - - if path.len() > 0 and not fill { - paths = append-path(paths, path) - path = () - } - } - - was-inside = is-inside - } - - // Append clamped last point if filling - if fill and not in-rect(points.last()) { - path.push(clamped-pt(points.last())) - } - - if path.len() > 1 { - paths = append-path(paths, path) - } - - return paths -} - -/// Compute clipped stroke paths -/// -/// - points (array): X/Y data points -/// - low (vector): Lower clip-window coordinate -/// - high (vector): Upper clip-window coordinate -/// -> array List of stroke paths -#let compute-stroke-paths(points, low, high) = { - clipped-paths(points, low, high, fill: false) -} - -/// Compute clipped fill path -/// -/// - points (array): X/Y data points -/// - low (vector): Lower clip-window coordinate -/// - high (vector): Upper clip-window coordinate -/// -> array List of fill paths -#let compute-fill-paths(points, low, high) = { - clipped-paths(points, low, high, fill: true) -} - -/// Return points of a sampled catmull-rom through the -/// input points. -/// -/// - points (array): Array of input vectors -/// - tension (float): Catmull-Rom tension -/// - samples (int): Number of samples -/// -> array Array of vectors -#let sampled-spline-data(points, tension, samples) = { - assert(samples >= 1 and samples <= 100, - message: "Must at least use 1 sample per curve") - - let curves = bezier.catmull-to-cubic(points, tension) - let pts = () - for c in curves { - for t in range(0, samples + 1) { - let t = t / samples - pts.push(bezier.cubic-point(..c, t)) - } - } - return pts -} - -/// Simplify linear data by "detecting" linear sections -/// and skipping points until the slope changes. -/// This can have a huge impact on the number of lines -/// getting rendered. -/// -/// - data (array): Data points -/// - epsilon (float): Curvature threshold to treat data as linear -#let linearized-data(data, epsilon) = { - let pts = () - // Current slope, set to none if infinite - let dx = none - // Previous point, last skipped point - let prev = none - let skipped = none - // Current direction - let dir = 0 - - let len = data.len() - for i in range(0, len) { - let pt = data.at(i) - if prev != none and i < len - 1 { - let new-dir = pt.at(0) - prev.at(0) - if new-dir == 0 { - // Infinite slope - if dx != none { - if skipped != none {pts.push(skipped); skipped = none} - pts.push(pt) - } else { - skipped = pt - } - dx = none - } else { - // Push the previous and the current point - // if slope or direction changed - let new-dx = ((pt.at(1) - prev.at(1)) / new-dir) - if dx == none or calc.abs(new-dx - dx) > epsilon or (new-dir * dir) < 0 { - if skipped != none {pts.push(skipped); skipped = none} - pts.push(pt) - - dx = new-dx - dir = new-dir - } else { - skipped = pt - } - } - } else { - if skipped != none {pts.push(skipped); skipped = none} - pts.push(pt) - } - - prev = pt - } - - return pts -} - -// Get the default axis orientation -// depending on the axis name -#let get-default-axis-horizontal(name) = { - return lower(name).starts-with("x") -} - -// Setup axes dictionary -// -// - axis-dict (dictionary): Existing axis dictionary -// - options (dictionary): Named arguments -// - plot-size (tuple): Plot width, height tuple -#let setup-axes(ctx, axis-dict, options, plot-size) = { - import "/src/axes.typ" - - // Get axis option for name - let get-axis-option(axis-name, name, default) = { - let v = options.at(axis-name + "-" + name, default: default) - if v == auto { default } else { v } - } - - for (name, axis) in axis-dict { - if not "ticks" in axis { axis.ticks = () } - axis.label = get-axis-option(name, "label", $#name$) - - // Configure axis bounds - axis.min = get-axis-option(name, "min", axis.min) - axis.max = get-axis-option(name, "max", axis.max) - - assert(axis.min not in (none, auto) and - axis.max not in (none, auto), - message: "Axis min and max must be set.") - if axis.min == axis.max { - axis.min -= 1; axis.max += 1 - } - - axis.mode = get-axis-option(name, "mode", "lin") - axis.base = get-axis-option(name, "base", 10) - - // Configure axis orientation - axis.horizontal = get-axis-option(name, "horizontal", - get-default-axis-horizontal(name)) - - // Configure ticks - axis.ticks.list = get-axis-option(name, "ticks", ()) - axis.ticks.step = get-axis-option(name, "tick-step", axis.ticks.step) - axis.ticks.minor-step = get-axis-option(name, "minor-tick-step", axis.ticks.minor-step) - axis.ticks.decimals = get-axis-option(name, "decimals", 2) - axis.ticks.unit = get-axis-option(name, "unit", []) - axis.ticks.format = get-axis-option(name, "format", axis.ticks.format) - - // Axis break - axis.show-break = get-axis-option(name, "break", false) - axis.inset = get-axis-option(name, "inset", (0, 0)) - - // Configure grid - axis.ticks.grid = get-axis-option(name, "grid", false) - - axis-dict.at(name) = axis - } - - // Set axis options round two, after setting - // axis bounds - for (name, axis) in axis-dict { - let changed = false - - // Configure axis aspect ratio - let equal-to = get-axis-option(name, "equal", none) - if equal-to != none { - assert.eq(type(equal-to), str, - message: "Expected axis name.") - assert(equal-to != name, - message: "Axis can not be equal to itself.") - - let other = axis-dict.at(equal-to, default: none) - assert(other != none, - message: "Other axis must exist.") - assert(other.horizontal != axis.horizontal, - message: "Equal axes must have opposing orientation.") - - let (w, h) = plot-size - let ratio = if other.horizontal { - h / w - } else { - w / h - } - axis.min = other.min * ratio - axis.max = other.max * ratio - - changed = true - } - - if changed { - axis-dict.at(name) = axis - } - } - - for (name, axis) in axis-dict { - axis-dict.at(name) = axes.prepare-axis(ctx, axis, name) - } - - return axis-dict -} diff --git a/src/plot.typ b/src/plot.typ index ba862d6..7aa0611 100644 --- a/src/plot.typ +++ b/src/plot.typ @@ -1,10 +1,9 @@ #import "/src/cetz.typ": draw, util, styles #import "plot/legend.typ" as plot-legend +#import "plot/axis-style.typ" #import "axes/axes.typ" -#import "plots/orthorect-2d/orthorect-2d.typ" -#import "plots/barycentric-2d/barycentric-2d.typ" // TODO: Refactor this into a better way of providing palettes @@ -238,7 +237,7 @@ #let plot( body, size: (1,1), - axis-style: orthorect-2d, + axis-style: axis-style.orthorect-2d, name: none, plot-style: default-plot-style, mark-style: default-mark-style, diff --git a/src/plot.typ.old b/src/plot.typ.old deleted file mode 100644 index 04578fd..0000000 --- a/src/plot.typ.old +++ /dev/null @@ -1,552 +0,0 @@ -#import "/src/cetz.typ": util, draw, matrix, vector, styles, palette -#import util: bezier - -#import "/src/axes.typ" -#import "/src/plot/sample.typ": sample-fn, sample-fn2 -#import "/src/plot/line.typ": add, add-hline, add-vline, add-fill-between -#import "/src/plot/contour.typ": add-contour -#import "/src/plot/boxwhisker.typ": add-boxwhisker -#import "/src/plot/util.typ" as plot-util -#import "/src/plot/legend.typ" as plot-legend -#import "/src/plot/annotation.typ": annotate, calc-annotation-domain -#import "/src/plot/bar.typ": add-bar -#import "/src/plot/errorbar.typ": add-errorbar -#import "/src/plot/mark.typ" -#import "/src/plot/formats.typ" -#import plot-legend: add-legend - -#let default-colors = (blue, red, green, yellow, black) - -#let default-plot-style(i) = { - let color = default-colors.at(calc.rem(i, default-colors.len())) - return (stroke: color, - fill: color.lighten(75%)) -} - -#let default-mark-style(i) = { - return default-plot-style(i) -} - -/// Create a plot environment. Data to be plotted is given by passing it to the -/// `plot.add` or other plotting functions. The plot environment supports different -/// axis styles to draw, see its parameter `axis-style:`. -/// -/// #example(``` -/// plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { -/// plot.add(((0,0), (1,1), (2,.5), (4,3))) -/// }) -/// ```) -/// -/// To draw elements insides a plot, using the plots coordinate system, use -/// the `plot.annotate(..)` function. -/// -/// = parameters -/// -/// = Options -/// -/// You can use the following options to customize each axis of the plot. You must pass them as named arguments prefixed by the axis name followed by a dash (`-`) they should target. Example: `x-min: 0`, `y-ticks: (..)` or `x2-label: [..]`. -/// -/// #show-parameter-block("label", ("none", "content"), default: "none", [ -/// The axis' label. If and where the label is drawn depends on the `axis-style`.]) -/// #show-parameter-block("min", ("auto", "float"), default: "auto", [ -/// Axis lower domain value. If this is set greater than than `max`, the axis' direction is swapped]) -/// #show-parameter-block("max", ("auto", "float"), default: "auto", [ -/// Axis upper domain value. If this is set to a lower value than `min`, the axis' direction is swapped]) -/// #show-parameter-block("equal", ("string"), default: "none", [ -/// Set the axis domain to keep a fixed aspect ratio by multiplying the other axis domain by the plots aspect ratio, -/// depending on the other axis orientation (see `horizontal`). -/// This can be useful to force one axis to grow or shrink with another one. -/// You can only "lock" two axes of different orientations. -/// #example(``` -/// plot.plot(size: (2,1), x-tick-step: 1, y-tick-step: 1, -/// x-equal: "y", -/// { -/// plot.add(domain: (0, 2 * calc.pi), -/// t => (calc.cos(t), calc.sin(t))) -/// }) -/// ```) -/// ]) -/// #show-parameter-block("horizontal", ("bool"), default: "axis name dependant", [ -/// If true, the axis is considered an axis that gets drawn horizontally, vertically otherwise. -/// The default value depends on the axis name on axis creation. Axes which name start with `x` have this -/// set to `true`, all others have it set to `false`. Each plot has to use one horizontal and one -/// vertical axis for plotting, a combination of two y-axes will panic: ("y", "y2"). -/// ]) -/// #show-parameter-block("tick-step", ("none", "auto", "float"), default: "auto", [ -/// The increment between tick marks on the axis. If set to `auto`, an -/// increment is determined. When set to `none`, incrementing tick marks are disabled.]) -/// #show-parameter-block("minor-tick-step", ("none", "float"), default: "none", [ -/// Like `tick-step`, but for minor tick marks. In contrast to ticks, minor ticks do not have labels.]) -/// #show-parameter-block("ticks", ("none", "array"), default: "none", [ -/// A List of custom tick marks to additionally draw along the axis. They can be passed as -/// an array of `` values or an array of `(, )` tuples for -/// setting custom tick mark labels per mark. -/// -/// #example(``` -/// plot.plot(x-tick-step: none, y-tick-step: none, -/// x-min: 0, x-max: 4, -/// x-ticks: (1, 2, 3), -/// y-min: 1, y-max: 2, -/// y-ticks: ((1, [One]), (2, [Two])), -/// { -/// plot.add(((0,0),)) -/// }) -/// ```) -/// -/// Examples: `(1, 2, 3)` or `((1, [One]), (2, [Two]), (3, [Three]))`]) -/// #show-parameter-block("format", ("none", "string", "function"), default: "float", [ -/// How to format the tick label: You can give a function that takes a `` and return -/// `` to use as the tick label. You can also give one of the predefined options: -/// / float: Floating point formatting rounded to two digits after the point (see `decimals`) -/// / sci: Scientific formatting with $times 10^n$ used as exponet syntax -/// -/// #example(``` -/// let formatter(v) = if v != 0 {$ #{v/calc.pi} pi $} else {$ 0 $} -/// plot.plot(x-tick-step: calc.pi, y-tick-step: none, -/// x-min: 0, x-max: 2 * calc.pi, -/// x-format: formatter, -/// { -/// plot.add(((0,0),)) -/// }) -/// ```) -/// ]) -/// #show-parameter-block("decimals", ("int"), default: "2", [ -/// Number of decimals digits to display for tick labels, if the format is set -/// to `"float"`. -/// ]) -/// #show-parameter-block("unit", ("none", "content"), default: "none", [ -/// Suffix to append to all tick labels. -/// ]) -/// #show-parameter-block("mode", ("none", "string"), default: "none", [ -/// The scaling function of the axis. Takes `lin` (default) for linear scaling, -/// and `log` for logarithmic scaling.]) -/// #show-parameter-block("base", ("none", "number"), default: "none", [ -/// The base to be used when labeling axis ticks in logarithmic scaling]) -/// #show-parameter-block("grid", ("bool", "string"), default: "false", [ -/// If `true` or `"major"`, show grid lines for all major ticks. If set -/// to `"minor"`, show grid lines for minor ticks only. -/// The value `"both"` enables grid lines for both, major- and minor ticks. -/// -/// #example(``` -/// plot.plot(x-tick-step: 1, y-tick-step: 1, -/// y-minor-tick-step: .2, -/// x-min: 0, x-max: 2, x-grid: true, -/// y-min: 0, y-max: 2, y-grid: "both", { -/// plot.add(((0,0),)) -/// }) -/// ```) -/// ]) -/// #show-parameter-block("break", ("bool"), default: "false", [ -/// If true, add a "sawtooth" at the start or end of the axis line, depending -/// on the axis bounds. If the axis min. value is > 0, a sawtooth is added -/// to the start of the axes, if the axis max. value is < 0, a sawtooth is added -/// to its end.]) -/// -/// - body (body): Calls of `plot.add` or `plot.add-*` commands. Note that normal drawing -/// commands like `line` or `rect` are not allowed inside the plots body, instead wrap -/// them in `plot.annotate`, which lets you select the axes used for drawing. -/// - size (array): Plot size tuple of `(, )` in canvas units. -/// This is the plots inner plotting size without axes and labels. -/// - axis-style (none, string): How the axes should be styled: -/// / scientific: Frames plot area using a rectangle and draw axes `x` (bottom), `y` (left), `x2` (top), and `y2` (right) around it. -/// If `x2` or `y2` are unset, they mirror their opposing axis. -/// / scientific-auto: Draw set (used) axes `x` (bottom), `y` (left), `x2` (top) and `y2` (right) around -/// the plotting area, forming a rect if all axes are in use or a L-shape if only `x` and `y` are in use. -/// / school-book: Draw axes `x` (horizontal) and `y` (vertical) as arrows pointing to the right/top with both crossing at $(0, 0)$ -/// / left: Draw axes `x` and `y` as arrows, while the y axis stays on the left (at `x.min`) -/// and the x axis at the bottom (at `y.min`) -/// / `none`: Draw no axes (and no ticks). -/// -/// #example(``` -/// let opts = (x-tick-step: none, y-tick-step: none, size: (2,1)) -/// let data = cetz.plot.add(((-1,-1), (1,1),), mark: "o") -/// -/// for name in (none, "school-book", "left", "scientific") { -/// plot.plot(axis-style: name, ..opts, data, name: "plot") -/// content(((0,-1), "-|", "plot.south"), repr(name)) -/// set-origin((3.5,0)) -/// } -/// ```, vertical: true) -/// - plot-style (style,function): Styling to use for drawing plot graphs. -/// This style gets inherited by all plots and supports `palette` functions. -/// The following style keys are supported: -/// #show-parameter-block("stroke", ("none", "stroke"), default: 1pt, [ -/// Stroke style to use for stroking the graph. -/// ]) -/// #show-parameter-block("fill", ("none", "paint"), default: none, [ -/// Paint to use for filled graphs. Note that not all graphs may support filling and -/// that you may have to enable filling per graph, see `plot.add(fill: ..)`. -/// ]) -/// - mark-style (style,function): Styling to use for drawing plot marks. -/// This style gets inherited by all plots and supports `palette` functions. -/// The following style keys are supported: -/// #show-parameter-block("stroke", ("none", "stroke"), default: 1pt, [ -/// Stroke style to use for stroking the mark. -/// ]) -/// #show-parameter-block("fill", ("none", "paint"), default: none, [ -/// Paint to use for filling marks. -/// ]) -/// - fill-below (bool): If true, the filled shape of plots is drawn _below_ axes. -/// - name (string): The plots element name to be used when referring to anchors -/// - legend (none, auto, coordinate): The position the legend will be drawn at. See plot-legends for information about legends. If set to ``, the legend's "default-placement" styling will be used. If set to a ``, it will be taken as relative to the plot's origin. -/// - legend-anchor (auto, string): Anchor of the legend group to use as its origin. -/// If set to `auto` and `lengend` is one of the predefined legend anchors, the -/// opposite anchor to `legend` gets used. -/// - legend-style (style): Style key-value overwrites for the legend style with style root `legend`. -/// - ..options (any): Axis options, see _options_ below. -#let plot(body, - size: (1, 1), - axis-style: "scientific", - name: none, - plot-style: default-plot-style, - mark-style: default-mark-style, - fill-below: true, - legend: auto, - legend-anchor: auto, - legend-style: (:), - ..options - ) = draw.group(name: name, ctx => { - // TODO: Assert cetz min version here! - - // Create plot context object - let make-ctx(x, y, size) = { - assert(x != none, message: "X axis does not exist") - assert(y != none, message: "Y axis does not exist") - assert(size.at(0) > 0 and size.at(1) > 0, message: "Plot size must be > 0") - - let x-scale = ((x.max - x.min) / size.at(0)) - let y-scale = ((y.max - y.min) / size.at(1)) - - if y.horizontal { - (x-scale, y-scale) = (y-scale, x-scale) - } - - return (x: x, y: y, size: size, x-scale: x-scale, y-scale: y-scale) - } - - // Setup data viewport - let data-viewport(data, x, y, size, body, name: none) = { - if body == none or body == () { return } - - assert.ne(x.horizontal, y.horizontal, - message: "Data must use one horizontal and one vertical axis!") - - // If y is the horizontal axis, swap x and y - // coordinates by swapping the transformation - // matrix columns. - if y.horizontal { - (x, y) = (y, x) - body = draw.set-ctx(ctx => { - ctx.transform = matrix.swap-cols(ctx.transform, 0, 1) - return ctx - }) + body - } - - // Setup the viewport - axes.axis-viewport(size, x, y, none, body, name: name) - } - - let data = () - let anchors = () - let annotations = () - let body = if body != none { body } else { () } - - for cmd in body { - assert(type(cmd) == dictionary and "type" in cmd, - message: "Expected plot sub-command in plot body") - if cmd.type == "anchor" { - anchors.push(cmd) - } else if cmd.type == "annotation" { - annotations.push(cmd) - } else { data.push(cmd) } - } - - assert(axis-style in (none, "scientific", "scientific-auto", "school-book", "left"), - message: "Invalid plot style") - - // Create axes for data & annotations - let axis-dict = (:) - for d in data + annotations { - if "axes" not in d { continue } - - for (i, name) in d.axes.enumerate() { - if not name in axis-dict { - axis-dict.insert(name, axes.axis( - min: none, max: none)) - } - - let axis = axis-dict.at(name) - let domain = if i == 0 { - d.at("x-domain", default: (0, 0)) - } else { - d.at("y-domain", default: (0, 0)) - } - if domain != (none, none) { - axis.min = util.min(axis.min, ..domain) - axis.max = util.max(axis.max, ..domain) - } - - axis-dict.at(name) = axis - } - } - - // Create axes for anchors - for a in anchors { - for (i, name) in a.axes.enumerate() { - if not name in axis-dict { - axis-dict.insert(name, axes.axis(min: none, max: none)) - } - } - } - - // Adjust axis bounds for annotations - for a in annotations { - let (x, y) = a.axes.map(name => axis-dict.at(name)) - (x, y) = calc-annotation-domain(ctx, x, y, a) - axis-dict.at(a.axes.at(0)) = x - axis-dict.at(a.axes.at(1)) = y - } - - // Set axis options - axis-dict = plot-util.setup-axes(ctx, axis-dict, options.named(), size) - - // Prepare styles - for i in range(data.len()) { - if "style" not in data.at(i) { continue } - - let style-base = plot-style - if type(style-base) == function { - style-base = (style-base)(i) - } - assert.eq(type(style-base), dictionary, - message: "plot-style must be of type dictionary") - - if type(data.at(i).style) == function { - data.at(i).style = (data.at(i).style)(i) - } - assert.eq(type(style-base), dictionary, - message: "data plot-style must be of type dictionary") - - data.at(i).style = util.merge-dictionary( - style-base, data.at(i).style) - - if "mark-style" in data.at(i) { - let mark-style-base = mark-style - if type(mark-style-base) == function { - mark-style-base = (mark-style-base)(i) - } - assert.eq(type(mark-style-base), dictionary, - message: "mark-style must be of type dictionary") - - if type(data.at(i).mark-style) == function { - data.at(i).mark-style = (data.at(i).mark-style)(i) - } - - if type(data.at(i).mark-style) == dictionary { - data.at(i).mark-style = util.merge-dictionary( - mark-style-base, - data.at(i).mark-style - ) - } - } - } - - draw.group(name: "plot", { - draw.anchor("origin", (0, 0)) - - // Prepare - for i in range(data.len()) { - if "axes" not in data.at(i) { continue } - - let (x, y) = data.at(i).axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - if "plot-prepare" in data.at(i) { - data.at(i) = (data.at(i).plot-prepare)(data.at(i), plot-ctx) - assert(data.at(i) != none, - message: "Plot prepare(self, cxt) returned none!") - } - } - - // Background Annotations - for a in annotations.filter(a => a.background) { - let (x, y) = a.axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - data-viewport(a, x, y, size, { - draw.anchor("default", (0, 0)) - a.body - }) - } - - // Fill - if fill-below { - for d in data { - if "axes" not in d { continue } - - let (x, y) = d.axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - data-viewport(d, x, y, size, { - draw.anchor("default", (0, 0)) - draw.set-style(..d.style) - - if "plot-fill" in d { - (d.plot-fill)(d, plot-ctx) - } - }) - } - } - - if axis-style in ("scientific", "scientific-auto") { - let draw-unset = if axis-style == "scientific" { - true - } else { - false - } - - let mirror = if axis-style == "scientific" { - auto - } else { - none - } - - axes.scientific( - size: size, - draw-unset: draw-unset, - bottom: axis-dict.at("x", default: none), - top: axis-dict.at("x2", default: mirror), - left: axis-dict.at("y", default: none), - right: axis-dict.at("y2", default: mirror),) - } else if axis-style == "left" { - axes.school-book( - size: size, - axis-dict.x, - axis-dict.y, - x-position: axis-dict.y.min, - y-position: axis-dict.x.min) - } else if axis-style == "school-book" { - axes.school-book( - size: size, - axis-dict.x, - axis-dict.y,) - } - - // Stroke + Mark data - for d in data { - if "axes" not in d { continue } - - let (x, y) = d.axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - data-viewport(d, x, y, size, { - draw.anchor("default", (0, 0)) - draw.set-style(..d.style) - - if not fill-below and "plot-fill" in d { - (d.plot-fill)(d, plot-ctx) - } - if "plot-stroke" in d { - (d.plot-stroke)(d, plot-ctx) - } - }) - - if "mark" in d and d.mark != none { - draw.group({ - draw.set-style(..d.style, ..d.mark-style) - mark.draw-mark(d.data, x, y, d.mark, d.mark-size, size) - }) - } - } - - // Foreground Annotations - for a in annotations.filter(a => not a.background) { - let (x, y) = a.axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - data-viewport(a, x, y, size, { - draw.anchor("default", (0, 0)) - a.body - }) - } - - // Place anchors - for a in anchors { - let (x, y) = a.axes.map(name => axis-dict.at(name)) - let plot-ctx = make-ctx(x, y, size) - - let pt = a.position.enumerate().map(((i, v)) => { - if v == "min" { return axis-dict.at(a.axes.at(i)).min } - if v == "max" { return axis-dict.at(a.axes.at(i)).max } - return v - }) - pt = axes.transform-vec(size, x, y, none, pt) - if pt != none { - draw.anchor(a.name, pt) - } - } - }) - - // Draw the legend - if legend != none { - let items = data.filter(d => "label" in d and d.label != none) - if items.len() > 0 { - let legend-style = styles.resolve(ctx.style, - base: plot-legend.default-style, merge: legend-style, root: "legend") - - plot-legend.add-legend-anchors(legend-style, "plot", size) - plot-legend.legend(legend, anchor: legend-anchor, { - for item in items { - let preview = if "plot-legend-preview" in item { - _ => {(item.plot-legend-preview)(item) } - } else { - auto - } - - plot-legend.item(item.label, preview, - mark: item.at("mark", default: none), - mark-size: item.at("mark-size", default: none), - mark-style: item.at("mark-style", default: none), - ..item.at("style", default: (:))) - } - }, ..legend-style) - } - } - - draw.copy-anchors("plot") -}) - -/// Add an anchor to a plot environment -/// -/// This function is similar to `draw.anchor` but it takes an additional -/// axis tuple to specify which axis coordinate system to use. -/// -/// #example(``` -/// import cetz.plot -/// import cetz.draw: * -/// plot.plot(size: (2,2), name: "plot", -/// x-tick-step: none, y-tick-step: none, { -/// plot.add(((0,0), (1,1), (2,.5), (4,3))) -/// plot.add-anchor("pt", (1,1)) -/// }) -/// -/// line("plot.pt", ((), "|-", (0,1.5)), mark: (start: ">"), name: "line") -/// content("line.end", [Here], anchor: "south", padding: .1) -/// ```) -/// -/// - name (string): Anchor name -/// - position (tuple): Tuple of x and y values. -/// Both values can have the special values "min" and -/// "max", which resolve to the axis min/max value. -/// Position is in axis space defined by the axes passed to `axes`. -/// - axes (tuple): Name of the axes to use `("x", "y")` as coordinate -/// system for `position`. Note that both axes must be used, -/// as `add-anchors` does not create them on demand. -#let add-anchor(name, position, axes: ("x", "y")) = { - (( - type: "anchor", - name: name, - position: position, - axes: axes, - ),) -} diff --git a/src/plot/add.typ b/src/plot/add.typ new file mode 100644 index 0000000..acbe6c0 --- /dev/null +++ b/src/plot/add.typ @@ -0,0 +1 @@ +#import "elements/anchor.typ" \ No newline at end of file diff --git a/src/plot/axis-style.typ b/src/plot/axis-style.typ new file mode 100644 index 0000000..155d5a3 --- /dev/null +++ b/src/plot/axis-style.typ @@ -0,0 +1,5 @@ +// #import "axis-styles/barycentric-2d/barycentric-2d.typ" +#import "axis-styles/polar-2d/polar-2d.typ" +#import "axis-styles/orthorect-2d/orthorect-2d.typ" +// #import "axis-styles/barycentric-2d/barycentric-2d.typ" +// #import "axis-styles/barycentric-2d/barycentric-2d.typ" \ No newline at end of file diff --git a/src/plots/barycentric-2d/barycentric-2d.typ b/src/plot/axis-styles/barycentric-2d/barycentric-2d.typ similarity index 100% rename from src/plots/barycentric-2d/barycentric-2d.typ rename to src/plot/axis-styles/barycentric-2d/barycentric-2d.typ diff --git a/src/plots/orthorect-2d/axis.typ b/src/plot/axis-styles/orthorect-2d/axis.typ similarity index 100% rename from src/plots/orthorect-2d/axis.typ rename to src/plot/axis-styles/orthorect-2d/axis.typ diff --git a/src/plots/orthorect-2d/grid.typ b/src/plot/axis-styles/orthorect-2d/grid.typ similarity index 100% rename from src/plots/orthorect-2d/grid.typ rename to src/plot/axis-styles/orthorect-2d/grid.typ diff --git a/src/plots/orthorect-2d/orthorect-2d.typ b/src/plot/axis-styles/orthorect-2d/orthorect-2d.typ similarity index 100% rename from src/plots/orthorect-2d/orthorect-2d.typ rename to src/plot/axis-styles/orthorect-2d/orthorect-2d.typ diff --git a/src/plots/orthorect-2d/transforms.typ b/src/plot/axis-styles/orthorect-2d/transforms.typ similarity index 100% rename from src/plots/orthorect-2d/transforms.typ rename to src/plot/axis-styles/orthorect-2d/transforms.typ diff --git a/src/plot/axis-styles/polar-2d/axis.typ b/src/plot/axis-styles/polar-2d/axis.typ new file mode 100644 index 0000000..8597321 --- /dev/null +++ b/src/plot/axis-styles/polar-2d/axis.typ @@ -0,0 +1,110 @@ +#import "/src/cetz.typ": draw, util, vector + +#let inset-axis-points(ctx, style, axis, start, end) = { + if axis == none { return (start, end) } + + let (low, high) = axis.inset.map(v => util.resolve-number(ctx, v)) + + let is-horizontal = start.at(1) == end.at(1) + if is-horizontal { + start = vector.add(start, (low, 0)) + end = vector.sub(end, (high, 0)) + } else { + start = vector.add(start, (0, low)) + end = vector.sub(end, (0, high)) + } + return (start, end) +} + +#let draw-axis-line(start, end, axis, is-horizontal, style) = { + let enabled = if axis != none and axis.show-break { + axis.min > 0 or axis.max < 0 + } else { false } + + if enabled { + let size = if is-horizontal { + (style.break-point.width, 0) + } else { + (0, style.break-point.width, 0) + } + + let up = if is-horizontal { + (0, style.break-point.length) + } else { + (style.break-point.length, 0) + } + + let add-break(is-end) = { + let a = () + let b = (rel: vector.scale(size, .3), update: false) + let c = (rel: vector.add(vector.scale(size, .4), vector.scale(up, -1)), update: false) + let d = (rel: vector.add(vector.scale(size, .6), vector.scale(up, +1)), update: false) + let e = (rel: vector.scale(size, .7), update: false) + let f = (rel: size) + + let mark = if is-end { + style.at("mark", default: none) + } + draw.line(a, b, c, d, e, f, stroke: style.stroke, mark: mark) + } + + draw.merge-path({ + draw.move-to(start) + if axis.min > 0 { + add-break(false) + draw.line((rel: size, to: start), end, mark: style.at("mark", default: none)) + } else if axis.max < 0 { + draw.line(start, (rel: vector.scale(size, -1), to: end)) + add-break(true) + } + }, stroke: style.stroke) + } else { + draw.line(start, end, stroke: style.stroke, mark: style.at("mark", default: none)) + } +} + +// Place a list of tick marks and labels along a path +#let place-ticks-on-line(ticks, start, stop, style, flip: false, is-mirror: false) = { + let dir = vector.sub(stop, start) + let norm = vector.norm((-dir.at(1), dir.at(0), dir.at(2, default: 0))) + + let def(v, d) = { + return if v == none or v == auto {d} else {v} + } + + let show-label = style.tick.label.show + if show-label == auto { + show-label = not is-mirror + } + + for (distance, label, is-major) in ticks { + let offset = style.tick.offset + let length = if is-major { style.tick.length } else { style.tick.minor-length } + if flip { + offset *= -1 + length *= -1 + } + + let pt = vector.lerp(start, stop, distance) + let a = vector.add(pt, vector.scale(norm, offset)) + let b = vector.add(a, vector.scale(norm, length)) + + draw.line(a, b, stroke: style.tick.stroke) + + if show-label and label != none { + let offset = style.tick.label.offset + if flip { + offset *= -1 + length *= -1 + } + + let c = vector.sub(if length <= 0 { b } else { a }, + vector.scale(norm, offset)) + + let angle = def(style.tick.label.angle, 0deg) + let anchor = def(style.tick.label.anchor, "center") + + draw.content(c, [#label], angle: angle, anchor: anchor) + } + } +} \ No newline at end of file diff --git a/src/plot/axis-styles/polar-2d/grid.typ b/src/plot/axis-styles/polar-2d/grid.typ new file mode 100644 index 0000000..3254c34 --- /dev/null +++ b/src/plot/axis-styles/polar-2d/grid.typ @@ -0,0 +1,45 @@ +#import "/src/cetz.typ": util, vector, draw + +#let _get-grid-type(axis) = { + let grid = axis.ticks.at("grid", default: false) + if grid == "major" or grid == true { return 1 } + if grid == "minor" { return 2 } + if grid == "both" { return 3 } + return 0 +} + +// Draw grid lines for the ticks of an axis +// +// - cxt (context): +// - axis (dictionary): The axis +// - ticks (array): The computed ticks +// - low (vector): Start position of a grid-line at tick 0 +// - high (vector): End position of a grid-line at tick 0 +// - dir (vector): Normalized grid direction vector along the grid axis +// - style (style): Axis style +#let draw-lines(ctx, axis, ticks, low, high, dir, style) = { + let offset = (0,0) + if axis.inset != none { + let (inset-low, inset-high) = axis.inset.map(v => util.resolve-number(ctx, v)) + offset = vector.scale(vector.norm(dir), inset-low) + dir = vector.sub(dir, vector.scale(vector.norm(dir), inset-low + inset-high)) + } + + let kind = _get-grid-type(axis) + if kind > 0 { + for (distance, label, is-major) in ticks { + let offset = vector.add(vector.scale(dir, distance), offset) + let start = vector.add(low, offset) + let end = vector.add(high, offset) + + // Draw a major line + if is-major and (kind == 1 or kind == 3) { + draw.line(start, end, stroke: style.grid.stroke) + } + // Draw a minor line + if not is-major and kind >= 2 { + draw.line(start, end, stroke: style.minor-grid.stroke) + } + } + } +} \ No newline at end of file diff --git a/src/plot/axis-styles/polar-2d/polar-2d.typ b/src/plot/axis-styles/polar-2d/polar-2d.typ new file mode 100644 index 0000000..71f09ae --- /dev/null +++ b/src/plot/axis-styles/polar-2d/polar-2d.typ @@ -0,0 +1,160 @@ +#import "/src/cetz.typ": draw, util, styles, vector +#import "/src/plot/styles.typ": default-style, prepare-style, get-axis-style +#import "/src/axes/axes.typ" + +#import "grid.typ" +#import "axis.typ": draw-axis-line, inset-axis-points, place-ticks-on-line +#import "transforms.typ": data-viewport, axis-viewport, + +#let default-style-orthorect-2d = util.merge-dictionary(default-style, ( + left: (tick: (label: (anchor: "east"))), + bottom: (tick: (label: (anchor: "north"))), + right: (tick: (label: (anchor: "west"))), + top: (tick: (label: (anchor: "south"))), + stroke: (cap: "square"), + padding: 0, +)) + + +#let make-ctx((x, y), size) = { + assert(x != none, message: "X axis does not exist") + assert(y != none, message: "Y axis does not exist") + assert(size.at(0) > 0 and size.at(1) > 0, message: "Plot size must be > 0") + + let x-scale = ((x.max - x.min) / size.at(0)) + let y-scale = ((y.max - y.min) / size.at(1)) + + if y.horizontal { + (x-scale, y-scale) = (y-scale, x-scale) + } + + return (x: x, y: y, size: size, x-scale: x-scale, y-scale: y-scale) +} + +#let draw-axes( + (w,h), + axis-dict, + name: none, + ..style +) = { + let bottom = axis-dict.at("x", default: none) + let top = axis-dict.at("x2", default: auto) + let left = axis-dict.at("y", default: none) + let right = axis-dict.at("y2", default: auto) + + if (top == auto){ + top = bottom + top.is-mirror = true + } + + if (right == auto){ + right = bottom + right.is-mirror = true + } + + draw.group(name: name, ctx => { + draw.anchor("origin", (0, 0)) + + // Handle style + let style = style.named() + style = styles.resolve( + ctx.style, + merge: style, + root: "axes", + base: default-style-orthorect-2d + ) + style = prepare-style(ctx, style) + + // Compute ticks + let x-ticks = axes.ticks.compute-ticks(bottom, style) + let y-ticks = axes.ticks.compute-ticks(left, style) + let x2-ticks = axes.ticks.compute-ticks(top, style) + let y2-ticks = axes.ticks.compute-ticks(right, style) + + // Draw frame + if style.fill != none { + draw.on-layer(style.background-layer, { + draw.rect((0,0), (w,h), fill: style.fill, stroke: none) + }) + } + + // Draw grid + draw.group(name: "grid", ctx => { + let axes = ( + ("bottom", (0,0), (0,h), (+w,0), x-ticks, bottom), + ("top", (0,h), (0,0), (+w,0), x2-ticks, top), + ("left", (0,0), (w,0), (0,+h), y-ticks, left), + ("right", (w,0), (0,0), (0,+h), y2-ticks, right), + ) + for (name, start, end, direction, ticks, axis) in axes { + if axis == none { continue } + + let style = get-axis-style(ctx, style, name) + let is-mirror = axis.at("is-mirror", default: false) + + if not is-mirror { + draw.on-layer(style.grid-layer, { + grid.draw-lines(ctx, axis, ticks, start, end, direction, style) + }) + } + } + }) + + // Draw axes + draw.group(name: "axes", { + let axes = ( + ("bottom", (0, 0), (w, 0), (0, -1), false, x-ticks, bottom,), + ("top", (0, h), (w, h), (0, +1), true, x2-ticks, top,), + ("left", (0, 0), (0, h), (-1, 0), true, y-ticks, left,), + ("right", (w, 0), (w, h), (+1, 0), false, y2-ticks, right,) + ) + let label-placement = ( + bottom: ("south", "north", 0deg), + top: ("north", "south", 0deg), + left: ("west", "south", 90deg), + right: ("east", "north", 90deg), + ) + + for (name, start, end, outsides, flip, ticks, axis) in axes { + let style = get-axis-style(ctx, style, name) + let is-mirror = axis == none or axis.at("is-mirror", default: false) + let is-horizontal = name in ("bottom", "top") + + if style.padding != 0 { + let padding = vector.scale(outsides, style.padding) + start = vector.add(start, padding) + end = vector.add(end, padding) + } + + let (data-start, data-end) = inset-axis-points(ctx, style, axis, start, end) + + let path = draw-axis-line(start, end, axis, is-horizontal, style) + draw.on-layer(style.axis-layer, { + draw.group(name: "axis", { + // if draw-unset or axis != none { + path; + place-ticks-on-line(ticks, data-start, data-end, style, flip: flip, is-mirror: is-mirror) + // } + }) + + if axis != none and axis.label != none and not is-mirror { + let offset = vector.scale(outsides, style.label.offset) + let (group-anchor, content-anchor, angle) = label-placement.at(name) + + if style.label.anchor != auto { + content-anchor = style.label.anchor + } + if style.label.angle != auto { + angle = style.label.angle + } + + draw.content((rel: offset, to: "axis." + group-anchor), + [#axis.label], + angle: angle, + anchor: content-anchor) + } + }) + } + }) + }) +} \ No newline at end of file diff --git a/src/plot/axis-styles/polar-2d/transforms.typ b/src/plot/axis-styles/polar-2d/transforms.typ new file mode 100644 index 0000000..15e191f --- /dev/null +++ b/src/plot/axis-styles/polar-2d/transforms.typ @@ -0,0 +1,87 @@ +#import "/src/cetz.typ": draw, matrix, process, util, drawable + +// Transform a single vector along a x, y and z axis +// +// - size (vector): Coordinate system size +// - x-axis (axis): X axis +// - y-axis (axis): Y axis +// - z-axis (axis): Z axis +// - vec (vector): Input vector to transform +// -> vector +#let transform-vec(size, axes, vec) = { + + let (x,y,) = for (dim, axis) in axes.enumerate() { + + let s = size.at(dim) - axis.inset.sum() + let o = axis.inset.at(0) + + let transform-func(n) = if (axis.mode == "log") { + calc.log(calc.max(n, util.float-epsilon), base: axis.base) + } else {n} + + let range = transform-func(axis.max) - transform-func(axis.min) + + let f = s / range + ((transform-func(vec.at(dim)) - transform-func(axis.min)) * f + o,) + } + + return (x, y, 0) +} + +// Draw inside viewport coordinates of two axes +// +// - size (vector): Axis canvas size (relative to origin) +// - x (axis): Horizontal axis +// - y (axis): Vertical axis +// - z (axis): Z axis +// - name (string,none): Group name +#let axis-viewport(size,(x, y,), body, name: none) = { + draw.group(name: name, (ctx => { + let transform = ctx.transform + + ctx.transform = matrix.ident() + let (ctx, drawables, bounds) = process.many(ctx, util.resolve-body(ctx, body)) + + ctx.transform = transform + + drawables = drawables.map(d => { + if "segments" in d { + d.segments = d.segments.map(((kind, ..pts)) => { + (kind, ..pts.map(pt => { + transform-vec(size, (x, y), pt) + })) + }) + } + if "pos" in d { + d.pos = transform-vec(size, (x, y), d.pos) + } + return d + }) + + return ( + ctx: ctx, + drawables: drawable.apply-transform(ctx.transform, drawables) + ) + },)) +} + +#let data-viewport((x, y), size, body, name: none) = { + if body == none or body == () { return } + + assert.ne(x.horizontal, y.horizontal, + message: "Data must use one horizontal and one vertical axis!") + + // If y is the horizontal axis, swap x and y + // coordinates by swapping the transformation + // matrix columns. + if y.horizontal { + (x, y) = (y, x) + body = draw.set-ctx(ctx => { + ctx.transform = matrix.swap-cols(ctx.transform, 0, 1) + return ctx + }) + body + } + + // Setup the viewport + axis-viewport(size, (x,y), body, name: name) +} \ No newline at end of file diff --git a/src/elements/anchor.typ b/src/plot/elements/anchor.typ similarity index 100% rename from src/elements/anchor.typ rename to src/plot/elements/anchor.typ diff --git a/tests/plots/orthorect-2d/scatter/test.typ b/tests/plots/orthorect-2d/scatter/test.typ index bdd045d..34e12bd 100644 --- a/tests/plots/orthorect-2d/scatter/test.typ +++ b/tests/plots/orthorect-2d/scatter/test.typ @@ -4,7 +4,7 @@ #test-case({ // cetz.draw.set-style(axes:( fill: luma(85%))) cetz-plot.plot( - axis-style: cetz-plot.orthorect-2d, + axis-style: cetz-plot.axis-style.orthorect-2d, size: (5,5), // x-min: 1, x-max: 100, x-tick-step: 1, x-minor-tick-step: 1, // x-mode: "log", diff --git a/tests/plots/polar-2d/scatter/test.typ b/tests/plots/polar-2d/scatter/test.typ new file mode 100644 index 0000000..68cdbf2 --- /dev/null +++ b/tests/plots/polar-2d/scatter/test.typ @@ -0,0 +1,19 @@ +#set page(width: auto, height: auto) +#import "/tests/helper.typ": * + +#test-case({ + // cetz.draw.set-style(axes:( fill: luma(85%))) + cetz-plot.plot( + axis-style: cetz-plot.axis-style.polar-2d, + size: (5,5), + x-tick-step: calc.pi/2, + // x-mode: "log", + x-grid: "both", + x-format: cetz-plot.axes.format.multiple-of, + // y-min: -1, y-max: 1, + y-grid: "both", + { + cetz.plot.add(calc.sin, domain: (0,2*calc.pi), label: $y=x$, line: "raw") + } + ) +}) \ No newline at end of file From 86c347429c57557fa4335ed052db471ac9849422 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 17:17:27 +0100 Subject: [PATCH 16/44] rampage pt 2 --- src/plot/axis-style.typ | 4 +- src/plot/axis-styles/generic.typ | 0 src/plot/axis-styles/orthorect-2d/grid.typ | 1 + src/plot/axis-styles/orthorect-2d/impl.typ | 1 + .../axis-styles/orthorect-2d/orthorect-2d.typ | 19 ++--- src/plot/axis-styles/polar-2d/grid.typ | 48 ++++++++----- src/plot/axis-styles/polar-2d/impl.typ | 1 + src/plot/axis-styles/polar-2d/polar-2d.typ | 65 +++++++----------- tests/plots/orthorect-2d/scatter/out/1.png | Bin 0 -> 86699 bytes tests/plots/polar-2d/scatter/out/1.png | Bin 0 -> 102228 bytes 10 files changed, 71 insertions(+), 68 deletions(-) create mode 100644 src/plot/axis-styles/generic.typ create mode 100644 src/plot/axis-styles/orthorect-2d/impl.typ create mode 100644 src/plot/axis-styles/polar-2d/impl.typ create mode 100644 tests/plots/orthorect-2d/scatter/out/1.png create mode 100644 tests/plots/polar-2d/scatter/out/1.png diff --git a/src/plot/axis-style.typ b/src/plot/axis-style.typ index 155d5a3..81b09cd 100644 --- a/src/plot/axis-style.typ +++ b/src/plot/axis-style.typ @@ -1,5 +1,5 @@ // #import "axis-styles/barycentric-2d/barycentric-2d.typ" -#import "axis-styles/polar-2d/polar-2d.typ" -#import "axis-styles/orthorect-2d/orthorect-2d.typ" +#import "axis-styles/polar-2d/impl.typ" as polar-2d +#import "axis-styles/orthorect-2d/impl.typ" as orthorect-2d // #import "axis-styles/barycentric-2d/barycentric-2d.typ" // #import "axis-styles/barycentric-2d/barycentric-2d.typ" \ No newline at end of file diff --git a/src/plot/axis-styles/generic.typ b/src/plot/axis-styles/generic.typ new file mode 100644 index 0000000..e69de29 diff --git a/src/plot/axis-styles/orthorect-2d/grid.typ b/src/plot/axis-styles/orthorect-2d/grid.typ index 3254c34..33ada22 100644 --- a/src/plot/axis-styles/orthorect-2d/grid.typ +++ b/src/plot/axis-styles/orthorect-2d/grid.typ @@ -1,5 +1,6 @@ #import "/src/cetz.typ": util, vector, draw +// Refactor opporunity: #let _get-grid-type(axis) = { let grid = axis.ticks.at("grid", default: false) if grid == "major" or grid == true { return 1 } diff --git a/src/plot/axis-styles/orthorect-2d/impl.typ b/src/plot/axis-styles/orthorect-2d/impl.typ new file mode 100644 index 0000000..2eb1829 --- /dev/null +++ b/src/plot/axis-styles/orthorect-2d/impl.typ @@ -0,0 +1 @@ +#import "orthorect-2d.typ": make-ctx, data-viewport, draw-axes \ No newline at end of file diff --git a/src/plot/axis-styles/orthorect-2d/orthorect-2d.typ b/src/plot/axis-styles/orthorect-2d/orthorect-2d.typ index 71f09ae..0704dab 100644 --- a/src/plot/axis-styles/orthorect-2d/orthorect-2d.typ +++ b/src/plot/axis-styles/orthorect-2d/orthorect-2d.typ @@ -6,14 +6,17 @@ #import "axis.typ": draw-axis-line, inset-axis-points, place-ticks-on-line #import "transforms.typ": data-viewport, axis-viewport, -#let default-style-orthorect-2d = util.merge-dictionary(default-style, ( - left: (tick: (label: (anchor: "east"))), - bottom: (tick: (label: (anchor: "north"))), - right: (tick: (label: (anchor: "west"))), - top: (tick: (label: (anchor: "south"))), - stroke: (cap: "square"), - padding: 0, -)) +#let default-style-orthorect-2d = util.merge-dictionary( + default-style, + ( + left: (tick: (label: (anchor: "east"))), + bottom: (tick: (label: (anchor: "north"))), + right: (tick: (label: (anchor: "west"))), + top: (tick: (label: (anchor: "south"))), + stroke: (cap: "square"), + padding: 0, + ) +) #let make-ctx((x, y), size) = { diff --git a/src/plot/axis-styles/polar-2d/grid.typ b/src/plot/axis-styles/polar-2d/grid.typ index 3254c34..596dab3 100644 --- a/src/plot/axis-styles/polar-2d/grid.typ +++ b/src/plot/axis-styles/polar-2d/grid.typ @@ -1,5 +1,6 @@ #import "/src/cetz.typ": util, vector, draw +// Refactor opporunity: #let _get-grid-type(axis) = { let grid = axis.ticks.at("grid", default: false) if grid == "major" or grid == true { return 1 } @@ -17,29 +18,42 @@ // - high (vector): End position of a grid-line at tick 0 // - dir (vector): Normalized grid direction vector along the grid axis // - style (style): Axis style -#let draw-lines(ctx, axis, ticks, low, high, dir, style) = { +#let draw-lines(ctx, axis, ticks, radius, style) = { let offset = (0,0) if axis.inset != none { let (inset-low, inset-high) = axis.inset.map(v => util.resolve-number(ctx, v)) - offset = vector.scale(vector.norm(dir), inset-low) - dir = vector.sub(dir, vector.scale(vector.norm(dir), inset-low + inset-high)) + offset = inset-low } - let kind = _get-grid-type(axis) - if kind > 0 { - for (distance, label, is-major) in ticks { - let offset = vector.add(vector.scale(dir, distance), offset) - let start = vector.add(low, offset) - let end = vector.add(high, offset) + if kind == 0 {return} - // Draw a major line - if is-major and (kind == 1 or kind == 3) { - draw.line(start, end, stroke: style.grid.stroke) - } - // Draw a minor line - if not is-major and kind >= 2 { - draw.line(start, end, stroke: style.minor-grid.stroke) - } + if axis.horizontal { + for (distance, label, is-major) in ticks { + let theta = distance * calc.pi * 2 + draw.line( + (radius, radius), + ( + radius * (calc.cos(theta) + 1), + radius * (calc.sin(theta) + 1) + ), + stroke: if is-major and (kind == 1 or kind == 3) { + style.grid.stroke + } else if not is-major and kind >= 2 { + style.minor-grid.stroke + } + ) + } + } else { + for (distance, label, is-major) in ticks { + draw.circle( + (radius, radius), + radius: distance * radius, + stroke: if is-major and (kind == 1 or kind == 3) { + style.grid.stroke + } else if not is-major and (kind >= 2) { + style.minor-grid.stroke + } + ) } } } \ No newline at end of file diff --git a/src/plot/axis-styles/polar-2d/impl.typ b/src/plot/axis-styles/polar-2d/impl.typ new file mode 100644 index 0000000..e0d2afc --- /dev/null +++ b/src/plot/axis-styles/polar-2d/impl.typ @@ -0,0 +1 @@ +#import "polar-2d.typ": make-ctx, data-viewport, draw-axes \ No newline at end of file diff --git a/src/plot/axis-styles/polar-2d/polar-2d.typ b/src/plot/axis-styles/polar-2d/polar-2d.typ index 71f09ae..94f31ed 100644 --- a/src/plot/axis-styles/polar-2d/polar-2d.typ +++ b/src/plot/axis-styles/polar-2d/polar-2d.typ @@ -6,16 +6,17 @@ #import "axis.typ": draw-axis-line, inset-axis-points, place-ticks-on-line #import "transforms.typ": data-viewport, axis-viewport, -#let default-style-orthorect-2d = util.merge-dictionary(default-style, ( - left: (tick: (label: (anchor: "east"))), - bottom: (tick: (label: (anchor: "north"))), - right: (tick: (label: (anchor: "west"))), - top: (tick: (label: (anchor: "south"))), - stroke: (cap: "square"), - padding: 0, -)) - - +#let default-style-polar-2d = util.merge-dictionary( + default-style, + ( + distal: (tick: (label: (anchor: "north-east", offset: 0.25))), + angular: (tick: (label: (anchor: "center", offset: 0.35))), + stroke: (cap: "square"), + padding: 0, + ) +) + +// Consider refactor #let make-ctx((x, y), size) = { assert(x != none, message: "X axis does not exist") assert(y != none, message: "Y axis does not exist") @@ -37,20 +38,10 @@ name: none, ..style ) = { - let bottom = axis-dict.at("x", default: none) - let top = axis-dict.at("x2", default: auto) - let left = axis-dict.at("y", default: none) - let right = axis-dict.at("y2", default: auto) - - if (top == auto){ - top = bottom - top.is-mirror = true - } + let angular = axis-dict.at("x", default: none) + let distal = axis-dict.at("y", default: none) - if (right == auto){ - right = bottom - right.is-mirror = true - } + let radius = calc.min(w,h)/2 draw.group(name: name, ctx => { draw.anchor("origin", (0, 0)) @@ -61,15 +52,13 @@ ctx.style, merge: style, root: "axes", - base: default-style-orthorect-2d + base: default-style ) style = prepare-style(ctx, style) // Compute ticks - let x-ticks = axes.ticks.compute-ticks(bottom, style) - let y-ticks = axes.ticks.compute-ticks(left, style) - let x2-ticks = axes.ticks.compute-ticks(top, style) - let y2-ticks = axes.ticks.compute-ticks(right, style) + let angular-ticks = axes.ticks.compute-ticks(angular, style) + let distal-ticks = axes.ticks.compute-ticks(distal, style) // Draw frame if style.fill != none { @@ -81,10 +70,8 @@ // Draw grid draw.group(name: "grid", ctx => { let axes = ( - ("bottom", (0,0), (0,h), (+w,0), x-ticks, bottom), - ("top", (0,h), (0,0), (+w,0), x2-ticks, top), - ("left", (0,0), (w,0), (0,+h), y-ticks, left), - ("right", (w,0), (0,0), (0,+h), y2-ticks, right), + ("angular", (0,0), (0,h), (+w,0), angular-ticks, angular), + ("distal", (0,0), (w,0), (0,+h), distal-ticks, distal), ) for (name, start, end, direction, ticks, axis) in axes { if axis == none { continue } @@ -94,7 +81,7 @@ if not is-mirror { draw.on-layer(style.grid-layer, { - grid.draw-lines(ctx, axis, ticks, start, end, direction, style) + grid.draw-lines(ctx, axis, ticks, radius, style) }) } } @@ -103,16 +90,12 @@ // Draw axes draw.group(name: "axes", { let axes = ( - ("bottom", (0, 0), (w, 0), (0, -1), false, x-ticks, bottom,), - ("top", (0, h), (w, h), (0, +1), true, x2-ticks, top,), - ("left", (0, 0), (0, h), (-1, 0), true, y-ticks, left,), - ("right", (w, 0), (w, h), (+1, 0), false, y2-ticks, right,) + ("angular", (0, 0), (w, 0), (0, -1), false, angular-ticks, angular,), + ("distal", (0, 0), (0, h), (-1, 0), true, distal-ticks, distal,), ) let label-placement = ( - bottom: ("south", "north", 0deg), - top: ("north", "south", 0deg), - left: ("west", "south", 90deg), - right: ("east", "north", 90deg), + angular: ("south", "north", 0deg), + distal: ("north", "south", 0deg), ) for (name, start, end, outsides, flip, ticks, axis) in axes { diff --git a/tests/plots/orthorect-2d/scatter/out/1.png b/tests/plots/orthorect-2d/scatter/out/1.png new file mode 100644 index 0000000000000000000000000000000000000000..53a7ab6361dddf5b39ea83c3997754364ffb6a33 GIT binary patch literal 86699 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 literal 0 HcmV?d00001 diff --git a/tests/plots/polar-2d/scatter/out/1.png b/tests/plots/polar-2d/scatter/out/1.png new file mode 100644 index 0000000000000000000000000000000000000000..a393ccf7f0a16dd9c643fa089ef2deb51fedaf79 GIT binary patch literal 102228 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�uJtw|7>YmhNe#A%x@>QV!wW}mew=VKO| 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 literal 0 HcmV?d00001 From d64efb33352936f4d89ea0b2c0cb4d8804e0bee9 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 17:47:52 +0100 Subject: [PATCH 17/44] stepwise towards polar --- src/plot/axis-styles/polar-2d/polar-2d.typ | 138 +++++++++++-------- src/plot/axis-styles/polar-2d/transforms.typ | 25 ++-- tests/plots/polar-2d/scatter/test.typ | 12 +- 3 files changed, 97 insertions(+), 78 deletions(-) diff --git a/src/plot/axis-styles/polar-2d/polar-2d.typ b/src/plot/axis-styles/polar-2d/polar-2d.typ index 94f31ed..0aee21b 100644 --- a/src/plot/axis-styles/polar-2d/polar-2d.typ +++ b/src/plot/axis-styles/polar-2d/polar-2d.typ @@ -44,7 +44,7 @@ let radius = calc.min(w,h)/2 draw.group(name: name, ctx => { - draw.anchor("origin", (0, 0)) + draw.anchor("origin", (radius, radius)) // Handle style let style = style.named() @@ -52,7 +52,7 @@ ctx.style, merge: style, root: "axes", - base: default-style + base: default-style-polar-2d ) style = prepare-style(ctx, style) @@ -63,81 +63,101 @@ // Draw frame if style.fill != none { draw.on-layer(style.background-layer, { - draw.rect((0,0), (w,h), fill: style.fill, stroke: none) + draw.circle("origin", radius: radius, fill: style.fill, stroke: none) }) } // Draw grid draw.group(name: "grid", ctx => { let axes = ( - ("angular", (0,0), (0,h), (+w,0), angular-ticks, angular), - ("distal", (0,0), (w,0), (0,+h), distal-ticks, distal), + ("x", angular-ticks, angular), + ("y", distal-ticks, distal) ) - for (name, start, end, direction, ticks, axis) in axes { + for (name, ticks, axis) in axes { if axis == none { continue } let style = get-axis-style(ctx, style, name) - let is-mirror = axis.at("is-mirror", default: false) - - if not is-mirror { - draw.on-layer(style.grid-layer, { - grid.draw-lines(ctx, axis, ticks, radius, style) - }) - } + draw.on-layer(style.grid-layer, { + grid.draw-lines(ctx, axis, ticks, radius, style) + }) } }) // Draw axes draw.group(name: "axes", { - let axes = ( - ("angular", (0, 0), (w, 0), (0, -1), false, angular-ticks, angular,), - ("distal", (0, 0), (0, h), (-1, 0), true, distal-ticks, distal,), - ) - let label-placement = ( - angular: ("south", "north", 0deg), - distal: ("north", "south", 0deg), - ) - - for (name, start, end, outsides, flip, ticks, axis) in axes { - let style = get-axis-style(ctx, style, name) - let is-mirror = axis == none or axis.at("is-mirror", default: false) - let is-horizontal = name in ("bottom", "top") - - if style.padding != 0 { - let padding = vector.scale(outsides, style.padding) - start = vector.add(start, padding) - end = vector.add(end, padding) - } - - let (data-start, data-end) = inset-axis-points(ctx, style, axis, start, end) - let path = draw-axis-line(start, end, axis, is-horizontal, style) - draw.on-layer(style.axis-layer, { + // Render distal + draw.on-layer(style.axis-layer, { draw.group(name: "axis", { - // if draw-unset or axis != none { - path; - place-ticks-on-line(ticks, data-start, data-end, style, flip: flip, is-mirror: is-mirror) - // } - }) - - if axis != none and axis.label != none and not is-mirror { - let offset = vector.scale(outsides, style.label.offset) - let (group-anchor, content-anchor, angle) = label-placement.at(name) - - if style.label.anchor != auto { - content-anchor = style.label.anchor + if distal != none { + // To do: Allow finer control over placement + draw-axis-line( + "origin", + (radius, radius*2), + distal, + false, + style + ) + + place-ticks-on-line( + distal-ticks, + (radius, radius), + (radius, radius*2), + style, + flip: false, // not needed + is-mirror: false // Maybe support negative theta? + ) } - if style.label.angle != auto { - angle = style.label.angle - } - - draw.content((rel: offset, to: "axis." + group-anchor), - [#axis.label], - angle: angle, - anchor: content-anchor) - } - }) - } + }) + }) + // let axes = ( + // ("angular", (0, 0), (w, 0), (0, -1), false, angular-ticks, angular,), + // ("distal", (0, 0), (0, h), (-1, 0), true, distal-ticks, distal,), + // ) + // let label-placement = ( + // angular: ("south", "north", 0deg), + // distal: ("north", "south", 0deg), + // ) + + // for (name, start, end, outsides, flip, ticks, axis) in axes { + // let style = get-axis-style(ctx, style, name) + // let is-mirror = axis == none or axis.at("is-mirror", default: false) + // let is-horizontal = name in ("bottom", "top") + + // if style.padding != 0 { + // let padding = vector.scale(outsides, style.padding) + // start = vector.add(start, padding) + // end = vector.add(end, padding) + // } + + // let (data-start, data-end) = inset-axis-points(ctx, style, axis, start, end) + + // draw.on-layer(style.axis-layer, { + // draw.group(name: "axis", { + // if axis != none { + // draw-axis-line(start, end, axis, is-horizontal, style) + // place-ticks-on-line(ticks, data-start, data-end, style, flip: flip, is-mirror: is-mirror) + // } + // }) + + // if axis != none and axis.label != none and not is-mirror { + // let offset = vector.scale(outsides, style.label.offset) + // let (group-anchor, content-anchor, angle) = label-placement.at(name) + + // if style.label.anchor != auto { + // content-anchor = style.label.anchor + // } + // if style.label.angle != auto { + // angle = style.label.angle + // } + + // draw.content((rel: offset, to: "axis." + group-anchor), + // [#axis.label], + // angle: angle, + // anchor: content-anchor) + // } + // }) + // } }) }) } \ No newline at end of file diff --git a/src/plot/axis-styles/polar-2d/transforms.typ b/src/plot/axis-styles/polar-2d/transforms.typ index 15e191f..f2bac4d 100644 --- a/src/plot/axis-styles/polar-2d/transforms.typ +++ b/src/plot/axis-styles/polar-2d/transforms.typ @@ -8,24 +8,17 @@ // - z-axis (axis): Z axis // - vec (vector): Input vector to transform // -> vector -#let transform-vec(size, axes, vec) = { +#let transform-vec(size, (angular, distal), vec) = { - let (x,y,) = for (dim, axis) in axes.enumerate() { + let radius = calc.min(..size) + let x-norm = (vec.at(0) - angular.min) / (angular.max - angular.min) + let y-norm = (vec.at(1) - distal.min) / (distal.max - distal.min) + let theta = 2 * calc.pi * x-norm + let dist = (radius/2) * y-norm + let x = dist * calc.cos(theta) + let y = dist * calc.sin(theta) - let s = size.at(dim) - axis.inset.sum() - let o = axis.inset.at(0) - - let transform-func(n) = if (axis.mode == "log") { - calc.log(calc.max(n, util.float-epsilon), base: axis.base) - } else {n} - - let range = transform-func(axis.max) - transform-func(axis.min) - - let f = s / range - ((transform-func(vec.at(dim)) - transform-func(axis.min)) * f + o,) - } - - return (x, y, 0) + (radius/2 + x, radius/2 + y) } // Draw inside viewport coordinates of two axes diff --git a/tests/plots/polar-2d/scatter/test.typ b/tests/plots/polar-2d/scatter/test.typ index 68cdbf2..609fb46 100644 --- a/tests/plots/polar-2d/scatter/test.typ +++ b/tests/plots/polar-2d/scatter/test.typ @@ -2,7 +2,7 @@ #import "/tests/helper.typ": * #test-case({ - // cetz.draw.set-style(axes:( fill: luma(85%))) + cetz.draw.set-style(axes:( fill: luma(91.37%).transparentize(90%))) cetz-plot.plot( axis-style: cetz-plot.axis-style.polar-2d, size: (5,5), @@ -10,10 +10,16 @@ // x-mode: "log", x-grid: "both", x-format: cetz-plot.axes.format.multiple-of, - // y-min: -1, y-max: 1, + y-min: -1, y-max: 1, y-tick-step: 0.5, y-minor-tick-step: 0.1, y-grid: "both", { - cetz.plot.add(calc.sin, domain: (0,2*calc.pi), label: $y=x$, line: "raw") + cetz.plot.add( + calc.sin, + domain: (0,2*calc.pi), + label: $y=x$, + line: "raw", + samples: 100 + ) } ) }) \ No newline at end of file From ba183aab235ae7f616f074520b8e23ada68187f4 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 18:23:22 +0100 Subject: [PATCH 18/44] further work --- src/plot/axis-styles/polar-2d/axis.typ | 54 ++++++++++++++++++++++ src/plot/axis-styles/polar-2d/polar-2d.typ | 40 +++++++++++----- src/plot/styles.typ | 6 +-- tests/plots/orthorect-2d/scatter/test.typ | 6 +-- tests/plots/polar-2d/scatter/test.typ | 11 +++-- 5 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/plot/axis-styles/polar-2d/axis.typ b/src/plot/axis-styles/polar-2d/axis.typ index 8597321..54e2fb3 100644 --- a/src/plot/axis-styles/polar-2d/axis.typ +++ b/src/plot/axis-styles/polar-2d/axis.typ @@ -107,4 +107,58 @@ draw.content(c, [#label], angle: angle, anchor: anchor) } } +} + +#let place-ticks-on-radius(ticks, radius, style) = { + + let center = (radius,radius) + + // Early exit + let show-label = style.tick.label.show + if (show-label not in (auto, true)) {return} + + let def(v, d) = { + return if v == none or v == auto {d} else {v} + } + + for (distance, label, is-major) in ticks { + + // Early exit for overlapping tick + if (distance == 1){continue} + + let theta = (2 * distance) * calc.pi + let dist = radius + + let offset = style.tick.offset + let length = if is-major { style.tick.length } else { style.tick.minor-length } + + let a = dist + offset + let b = a - length + + draw.line( + (a * calc.sin(theta) + radius, a * calc.cos(theta) + radius), + (b * calc.sin(theta) + radius, b * calc.cos(theta) + radius), + stroke: style.tick.stroke + ) + + if (label != none){ + let offset = style.tick.label.offset + + // let c = vector.sub(if length <= 0 { b } else { a }, + // vector.scale(norm, offset)) + + let c = a + offset + + let angle = def(style.tick.label.angle, 0deg) + let anchor = def(style.tick.label.anchor, "center") + + draw.content( + (c * calc.sin(theta) + radius, c * calc.cos(theta) + radius), + [#label], + angle: angle, + anchor: anchor + ) + } + } + } \ No newline at end of file diff --git a/src/plot/axis-styles/polar-2d/polar-2d.typ b/src/plot/axis-styles/polar-2d/polar-2d.typ index 0aee21b..0596d5a 100644 --- a/src/plot/axis-styles/polar-2d/polar-2d.typ +++ b/src/plot/axis-styles/polar-2d/polar-2d.typ @@ -3,14 +3,14 @@ #import "/src/axes/axes.typ" #import "grid.typ" -#import "axis.typ": draw-axis-line, inset-axis-points, place-ticks-on-line +#import "axis.typ": draw-axis-line, inset-axis-points, place-ticks-on-line, place-ticks-on-radius #import "transforms.typ": data-viewport, axis-viewport, #let default-style-polar-2d = util.merge-dictionary( default-style, ( - distal: (tick: (label: (anchor: "north-east", offset: 0.25))), - angular: (tick: (label: (anchor: "center", offset: 0.35))), + distal: (tick: (label: (anchor: "north-east", offset: -0.2))), + angular: (tick: (label: (anchor: "center", offset: 0.35,), length: 5pt)), stroke: (cap: "square"), padding: 0, ) @@ -91,21 +91,37 @@ draw.group(name: "axis", { if distal != none { // To do: Allow finer control over placement - draw-axis-line( - "origin", - (radius, radius*2), - distal, - false, - style + draw.line( + "origin", (rel:(0, radius)), + stroke: style.stroke, + mark: style.at("mark", default: none) ) place-ticks-on-line( distal-ticks, (radius, radius), (radius, radius*2), - style, - flip: false, // not needed - is-mirror: false // Maybe support negative theta? + prepare-style(ctx, style.distal), + ) + } + }) + }) + + draw.on-layer(style.axis-layer, { + draw.group(name: "axis", { + if angular != none { + // To do: Allow finer control over placement + draw.circle( + "origin", + radius: radius, + stroke: style.stroke, + mark: style.at("mark", default: none) + ) + + place-ticks-on-radius( + angular-ticks, + (radius), + prepare-style(ctx, style.angular), ) } }) diff --git a/src/plot/styles.typ b/src/plot/styles.typ index 61179bc..ea9e212 100644 --- a/src/plot/styles.typ +++ b/src/plot/styles.typ @@ -92,11 +92,11 @@ style.tick.label.offset = res(style.tick.label.offset) // Break points - style.break-point.width = res(style.break-point.width) - style.break-point.length = res(style.break-point.length) + // style.break-point.width = res(style.break-point.width) + // style.break-point.length = res(style.break-point.length) // Padding - style.padding = res(style.padding) + // style.padding = res(style.padding) if "overshoot" in style { style.overshoot = res(style.overshoot) diff --git a/tests/plots/orthorect-2d/scatter/test.typ b/tests/plots/orthorect-2d/scatter/test.typ index 34e12bd..95b1a01 100644 --- a/tests/plots/orthorect-2d/scatter/test.typ +++ b/tests/plots/orthorect-2d/scatter/test.typ @@ -1,18 +1,18 @@ -#set page(width: auto, height: auto) +// #set page(width: auto, height: auto) #import "/tests/helper.typ": * #test-case({ // cetz.draw.set-style(axes:( fill: luma(85%))) cetz-plot.plot( axis-style: cetz-plot.axis-style.orthorect-2d, - size: (5,5), + size: (12,7), // x-min: 1, x-max: 100, x-tick-step: 1, x-minor-tick-step: 1, // x-mode: "log", x-grid: "both", y-min: 0, y-max: 10, y-grid: "both", { - cetz.plot.add((x)=>x, domain: (0,10), label: $y=x$, line: "raw") + cetz.plot.add((x)=>{- 0.5*x*x + 3*x + 0.025*x*x*x }, domain: (0,10), label: $y=x$, line: "raw") } ) }) \ No newline at end of file diff --git a/tests/plots/polar-2d/scatter/test.typ b/tests/plots/polar-2d/scatter/test.typ index 609fb46..b8e509b 100644 --- a/tests/plots/polar-2d/scatter/test.typ +++ b/tests/plots/polar-2d/scatter/test.typ @@ -1,15 +1,18 @@ -#set page(width: auto, height: auto) +// #set page(width: auto, height: auto) #import "/tests/helper.typ": * #test-case({ cetz.draw.set-style(axes:( fill: luma(91.37%).transparentize(90%))) cetz-plot.plot( axis-style: cetz-plot.axis-style.polar-2d, - size: (5,5), - x-tick-step: calc.pi/2, - // x-mode: "log", + size: (16,9), + + x-tick-step: calc.pi / 4, + x-minor-tick-step: calc.pi / 16, x-grid: "both", + x-min: 0, x-max: 2 * calc.pi, x-format: cetz-plot.axes.format.multiple-of, + y-min: -1, y-max: 1, y-tick-step: 0.5, y-minor-tick-step: 0.1, y-grid: "both", { From a7f41a4e649cb4fc4cff3c3e8759daab8eab4309 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 18:27:53 +0100 Subject: [PATCH 19/44] start defining xy --- src/plot/elements/xy.typ | 4 ++++ tests/plots/orthorect-2d/scatter/test.typ | 1 + tests/plots/polar-2d/scatter/test.typ | 10 +++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 src/plot/elements/xy.typ diff --git a/src/plot/elements/xy.typ b/src/plot/elements/xy.typ new file mode 100644 index 0000000..434b093 --- /dev/null +++ b/src/plot/elements/xy.typ @@ -0,0 +1,4 @@ + +#let xy() = { + +} \ No newline at end of file diff --git a/tests/plots/orthorect-2d/scatter/test.typ b/tests/plots/orthorect-2d/scatter/test.typ index 95b1a01..99f2cac 100644 --- a/tests/plots/orthorect-2d/scatter/test.typ +++ b/tests/plots/orthorect-2d/scatter/test.typ @@ -13,6 +13,7 @@ y-grid: "both", { cetz.plot.add((x)=>{- 0.5*x*x + 3*x + 0.025*x*x*x }, domain: (0,10), label: $y=x$, line: "raw") + } ) }) \ No newline at end of file diff --git a/tests/plots/polar-2d/scatter/test.typ b/tests/plots/polar-2d/scatter/test.typ index b8e509b..29dcaf7 100644 --- a/tests/plots/polar-2d/scatter/test.typ +++ b/tests/plots/polar-2d/scatter/test.typ @@ -21,7 +21,15 @@ domain: (0,2*calc.pi), label: $y=x$, line: "raw", - samples: 100 + samples: 50 + ) + + cetz.plot.add( + (t)=>calc.pow(calc.sin(t),2), + domain: (0, 2* calc.pi), + line: "raw", + samples: 50, + label: $sin^2 (x)$ ) } ) From 8e9f87fc7ce752092c9725f5db00b086e7655694 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 18:39:33 +0100 Subject: [PATCH 20/44] further work --- src/plot/add.typ | 3 +- src/plot/elements/anchor.typ | 2 +- src/plot/elements/xy.typ | 508 ++++++++++++++++++++- src/plot/sample.typ | 79 ++++ src/plot/util.typ | 372 +++++++++++++++ tests/plots/orthorect-2d/scatter/out/1.png | Bin 86699 -> 146460 bytes tests/plots/orthorect-2d/scatter/ref/1.png | Bin 0 -> 23643 bytes tests/plots/orthorect-2d/scatter/test.typ | 2 +- tests/plots/polar-2d/scatter/out/1.png | Bin 102228 -> 326992 bytes tests/plots/polar-2d/scatter/ref/1.png | Bin 0 -> 98526 bytes tests/plots/polar-2d/scatter/test.typ | 4 +- 11 files changed, 963 insertions(+), 7 deletions(-) create mode 100644 src/plot/sample.typ create mode 100644 src/plot/util.typ create mode 100644 tests/plots/orthorect-2d/scatter/ref/1.png create mode 100644 tests/plots/polar-2d/scatter/ref/1.png diff --git a/src/plot/add.typ b/src/plot/add.typ index acbe6c0..da47246 100644 --- a/src/plot/add.typ +++ b/src/plot/add.typ @@ -1 +1,2 @@ -#import "elements/anchor.typ" \ No newline at end of file +#import "elements/anchor.typ": anchor +#import "elements/xy.typ": xy \ No newline at end of file diff --git a/src/plot/elements/anchor.typ b/src/plot/elements/anchor.typ index b5ef3f4..604d179 100644 --- a/src/plot/elements/anchor.typ +++ b/src/plot/elements/anchor.typ @@ -24,7 +24,7 @@ /// - axes (tuple): Name of the axes to use `("x", "y")` as coordinate /// system for `position`. Note that both axes must be used, /// as `add-anchors` does not create them on demand. -#let add-anchor(name, position, axes: ("x", "y")) = { +#let anchor(name, position, axes: ("x", "y")) = { (( type: "anchor", name: name, diff --git a/src/plot/elements/xy.typ b/src/plot/elements/xy.typ index 434b093..4e84ff2 100644 --- a/src/plot/elements/xy.typ +++ b/src/plot/elements/xy.typ @@ -1,4 +1,508 @@ +#import "/src/cetz.typ": draw -#let xy() = { - +#import "/src/plot/util.typ" +#import "/src/plot/sample.typ" + +// Transform points +// +// - data (array): Data points +// - line (str,dictionary): Line line +#let transform-lines(data, line) = { + let hvh-data(t) = { + if type(t) == ratio { + t = t / 1% + } + t = calc.max(0, calc.min(t, 1)) + + let pts = () + + let len = data.len() + for i in range(0, len) { + pts.push(data.at(i)) + + if i < len - 1 { + let (a, b) = (data.at(i), data.at(i+1)) + if t == 0 { + pts.push((a.at(0), b.at(1))) + } else if t == 1 { + pts.push((b.at(0), a.at(1))) + } else { + let x = a.at(0) + (b.at(0) - a.at(0)) * t + pts.push((x, a.at(1))) + pts.push((x, b.at(1))) + } + } + } + return pts + } + + if type(line) == str { + line = (type: line) + } + + let line-type = line.at("type", default: "linear") + assert(line-type in ("raw", "linear", "spline", "vh", "hv", "hvh")) + + // Transform data into line-data + let line-data = if line-type == "linear" { + return util.linearized-data(data, line.at("epsilon", default: 0)) + } else if line-type == "spline" { + return util.sampled-spline-data(data, + line.at("tension", default: .5), + line.at("samples", default: 15)) + } else if line-type == "vh" { + return hvh-data(0) + } else if line-type == "hv" { + return hvh-data(1) + } else if line-type == "hvh" { + return hvh-data(line.at("mid", default: .5)) + } else { + return data + } +} + +// Fill a plot by generating a fill path to y value `to` +#let fill-segments-to(segments, to) = { + for s in segments { + let low = calc.min(..s.map(v => v.at(0))) + let high = calc.max(..s.map(v => v.at(0))) + + let origin = (low, to) + let target = (high, to) + + draw.line(origin, ..s, target, stroke: none) + } +} + +// Fill a shape by generating a fill path for each segment +#let fill-shape(paths) = { + for p in paths { + draw.line(..p, stroke: none) + } +} + +// Prepare line data +#let _prepare(self, ctx) = { + let (x, y) = (ctx.x, ctx.y) + + // Generate stroke paths + self.stroke-paths = util.compute-stroke-paths(self.line-data, + (x.min, y.min), (x.max, y.max)) + + // Compute fill paths if filling is requested + self.hypograph = self.at("hypograph", default: false) + self.epigraph = self.at("epigraph", default: false) + self.fill = self.at("fill", default: false) + if self.hypograph or self.epigraph or self.fill { + self.fill-paths = util.compute-fill-paths(self.line-data, + (x.min, y.min), (x.max, y.max)) + } + + return self +} + +// Stroke line data +#let _stroke(self, ctx) = { + let (x, y) = (ctx.x, ctx.y) + + for p in self.stroke-paths { + draw.line(..p, fill: none) + } +} + +// Fill line data +#let _fill(self, ctx) = { + let (x, y) = (ctx.x, ctx.y) + + if self.hypograph { + fill-segments-to(self.fill-paths, y.min) + } + if self.epigraph { + fill-segments-to(self.fill-paths, y.max) + } + if self.fill { + if self.at("fill-type", default: "axis") == "shape" { + fill-shape(self.fill-paths) + } else { + fill-segments-to(self.fill-paths, + calc.max(calc.min(y.max, 0), y.min)) + } + } +} + +/// Add data to a plot environment. +/// +/// Note: You can use this for scatter plots by setting +/// the stroke style to `none`: `add(..., style: (stroke: none))`. +/// +/// Must be called from the body of a `plot(..)` command. +/// +/// - domain (domain): Domain of `data`, if `data` is a function. Has no effect +/// if `data` is not a function. +/// - hypograph (bool): Fill hypograph; uses the `hypograph` style key for +/// drawing +/// - epigraph (bool): Fill epigraph; uses the `epigraph` style key for +/// drawing +/// - fill (bool): Fill the shape of the plot +/// - fill-type (string): Fill type: +/// / `"axis"`: Fill the shape to y = 0 +/// / `"shape"`: Fill the complete shape +/// - samples (int): Number of times the `data` function gets called for +/// sampling y-values. Only used if `data` is of type function. This parameter gets +/// passed onto `sample-fn`. +/// - sample-at (array): Array of x-values the function gets sampled at in addition +/// to the default sampling. This parameter gets passed to `sample-fn`. +/// - line (string, dictionary): Line type to use. The following types are +/// supported: +/// / `"linear"`: Draw linear lines between points +/// / `"spline"`: Calculate a Catmull-Rom through all points +/// / `"vh"`: Move vertical and then horizontal +/// / `"hv"`: Move horizontal and then vertical +/// / `"hvh"`: Add a vertical step in the middle +/// / `"raw"`: Like linear, but without linearization taking place. This is +/// meant as a "fallback" for either bad performance or bugs. +/// +/// If the value is a dictionary, the type must be +/// supplied via the `type` key. The following extra +/// attributes are supported: +/// / `"samples" `: Samples of splines +/// / `"tension" `: Tension of splines +/// / `"mid" `: Mid-Point of hvh lines (0 to 1) +/// / `"epsilon" `: Linearization slope epsilon for +/// use with `"linear"`, defaults to 0. +/// +/// #example(``` +/// import cetz.plot +/// let points(offset: 0) = ((0,0), (1,1), (2,0), (3,1), (4,0)).map(((x,y)) => { +/// (x,y + offset * 1.5) +/// }) +/// plot.plot(size: (12, 3), axis-style: none, { +/// plot.add(points(offset: 5), line: (type: "hvh", mid: .1)) +/// plot.add(points(offset: 4), line: "hvh") +/// plot.add(points(offset: 3), line: "hv") +/// plot.add(points(offset: 2), line: "vh") +/// plot.add(points(offset: 1), line: "spline") +/// plot.add(points(offset: 0), line: "linear") +/// }) +/// ```, vertical: true) +/// +/// - style (style): Style to use, can be used with a `palette` function +/// - axes (axes): Name of the axes to use for plotting. Reversing the axes +/// means rotating the plot by 90 degrees. +/// - mark (string): Mark symbol to place at each distinct value of the +/// graph. Uses the `mark` style key of `style` for drawing. +/// - mark-size (float): Mark size in cavas units +/// - data (array,function): Array of 2D data points (numeric) or a function +/// of the form `x => y`, where `x` is a value in `domain` +/// and `y` must be numeric or a 2D vector (for parametric functions). +/// #example(``` +/// import cetz.plot +/// plot.plot(size: (2, 2), axis-style: none, { +/// // Using an array of points: +/// plot.add(((0,0), (calc.pi/2,1), +/// (1.5*calc.pi,-1), (2*calc.pi,0))) +/// // Sampling a function: +/// plot.add(domain: (0, 2*calc.pi), calc.sin) +/// }) +/// ```) +/// - label (none,content): Legend label to show for this plot. +#let xy(domain: auto, + hypograph: false, + epigraph: false, + fill: false, + fill-type: "axis", + style: (:), + mark: none, + mark-size: .2, + mark-style: (:), + samples: 50, + sample-at: (), + line: "linear", + axes: ("x", "y"), + label: none, + data + ) = { + // If data is of type function, sample it + if type(data) == function { + data = sample.sample-fn(data, domain, samples, sample-at: sample-at) + } + + // Transform data + let line-data = transform-lines(data, line) + + // Get x-domain + let x-domain = ( + calc.min(..line-data.map(t => t.at(0))), + calc.max(..line-data.map(t => t.at(0))) + ) + + // Get y-domain + let y-domain = if line-data != none {( + calc.min(..line-data.map(t => t.at(1))), + calc.max(..line-data.map(t => t.at(1))) + )} + + (( + type: "line", + label: label, + data: data, /* Raw data */ + line-data: line-data, /* Transformed data */ + axes: axes, + x-domain: x-domain, + y-domain: y-domain, + epigraph: epigraph, + hypograph: hypograph, + fill: fill, + fill-type: fill-type, + style: style, + mark: mark, + mark-size: mark-size, + mark-style: mark-style, + plot-prepare: _prepare, + plot-stroke: _stroke, + plot-fill: _fill, + plot-legend-preview: self => { + if self.fill or self.epigraph or self.hypograph { + draw.rect((0,0), (1,1), ..self.style) + } else { + draw.line((0,.5), (1,.5), ..self.style) + } + } + ),) +} + +/// Add horizontal lines at one or more y-values. Every lines start and end points +/// are at their axis bounds. +/// +/// #example(``` +/// cetz.plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { +/// cetz.plot.add(domain: (0, 4*calc.pi), calc.sin) +/// // Add 3 horizontal lines +/// cetz.plot.add-hline(-.5, 0, .5) +/// }) +/// ```) +/// +/// - ..y (float): Y axis value(s) to add a line at +/// - min (auto,float): X axis minimum value or auto to take the axis minimum +/// - max (auto,float): X axis maximum value or auto to take the axis maximum +/// - axes (array): Name of the axes to use for plotting +/// - style (style): Style to use, can be used with a palette function +/// - label (none,content): Legend label to show for this plot. +#let add-hline(..y, + min: auto, + max: auto, + axes: ("x", "y"), + style: (:), + label: none, + ) = { + assert(y.pos().len() >= 1, + message: "Specify at least one y value") + assert(y.named().len() == 0) + + let prepare(self, ctx) = { + let (x-min, x-max) = (ctx.x.min, ctx.x.max) + let (y-min, y-max) = (ctx.y.min, ctx.y.max) + let x-min = if min == auto { x-min } else { min } + let x-max = if max == auto { x-max } else { max } + + self.lines = self.y.filter(y => y >= y-min and y <= y-max) + .map(y => ((x-min, y), (x-max, y))) + return self + } + + let stroke(self, ctx) = { + for (a, b) in self.lines { + draw.line(a, b, fill: none) + } + } + + let x-min = if min == auto { none } else { min } + let x-max = if max == auto { none } else { max } + + (( + type: "hline", + label: label, + y: y.pos(), + x-domain: (x-min, x-max), + y-domain: (calc.min(..y.pos()), calc.max(..y.pos())), + axes: axes, + style: style, + plot-prepare: prepare, + plot-stroke: stroke, + ),) +} + +/// Add vertical lines at one or more x-values. Every lines start and end points +/// are at their axis bounds. +/// +/// #example(``` +/// cetz.plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { +/// cetz.plot.add(domain: (0, 2*calc.pi), calc.sin) +/// // Add 3 vertical lines +/// cetz.plot.add-vline(calc.pi/2, calc.pi, 3*calc.pi/2) +/// }) +/// ```) +/// +/// - ..x (float): X axis values to add a line at +/// - min (auto,float): Y axis minimum value or auto to take the axis minimum +/// - max (auto,float): Y axis maximum value or auto to take the axis maximum +/// - axes (array): Name of the axes to use for plotting, note that not all +/// plot styles are able to display a custom axis! +/// - style (style): Style to use, can be used with a palette function +/// - label (none,content): Legend label to show for this plot. +#let add-vline(..x, + min: auto, + max: auto, + axes: ("x", "y"), + style: (:), + label: none, + ) = { + assert(x.pos().len() >= 1, + message: "Specify at least one x value") + assert(x.named().len() == 0) + + let prepare(self, ctx) = { + let (x-min, x-max) = (ctx.x.min, ctx.x.max) + let (y-min, y-max) = (ctx.y.min, ctx.y.max) + let y-min = if min == auto { y-min } else { min } + let y-max = if max == auto { y-max } else { max } + + self.lines = self.x.filter(x => x >= x-min and x <= x-max) + .map(x => ((x, y-min), (x, y-max))) + return self + } + + let stroke(self, ctx) = { + for (a, b) in self.lines { + draw.line(a, b, fill: none) + } + } + + let y-min = if min == auto { none } else { min } + let y-max = if max == auto { none } else { max } + + (( + type: "vline", + label: label, + x: x.pos(), + x-domain: (calc.min(..x.pos()), calc.max(..x.pos())), + y-domain: (y-min, y-max), + axes: axes, + style: style, + plot-prepare: prepare, + plot-stroke: stroke + ),) +} + +/// Fill the area between two graphs. This behaves same as `add` but takes +/// a pair of data instead of a single data array/function. +/// The area between both function plots gets filled. For a more detailed +/// explanation of the arguments, see @@add(). +/// +/// This can be used to display an error-band of a function. +/// +/// #example(``` +/// cetz.plot.plot(size: (2,2), x-tick-step: none, y-tick-step: none, { +/// cetz.plot.add-fill-between(domain: (0, 2*calc.pi), +/// calc.sin, // First function/data +/// calc.cos) // Second function/data +/// }) +/// ```) +/// +/// - domain (domain): Domain of both `data-a` and `data-b`. The domain is used for +/// sampling functions only and has no effect on data arrays. +/// - samples (int): Number of times the `data-a` and `data-b` function gets called for +/// sampling y-values. Only used if `data-a` or `data-b` is of +/// type function. +/// - sample-at (array): Array of x-values the function(s) get sampled at in addition +/// to the default sampling. +/// - line (string, dictionary): Line type to use, see @@add(). +/// - style (style): Style to use, can be used with a palette function. +/// - label (none,content): Legend label to show for this plot. +/// - axes (array): Name of the axes to use for plotting. +/// - data-a (array,function): Data of the first plot, see @@add(). +/// - data-b (array,function): Data of the second plot, see @@add(). +#let add-fill-between(data-a, + data-b, + domain: auto, + samples: 50, + sample-at: (), + line: "linear", + axes: ("x", "y"), + label: none, + style: (:)) = { + // If data is of type function, sample it + if type(data-a) == function { + data-a = sample.sample-fn(data-a, domain, samples, sample-at: sample-at) + } + if type(data-b) == function { + data-b = sample.sample-fn(data-b, domain, samples, sample-at: sample-at) + } + + // Transform data + let line-a-data = transform-lines(data-a, line) + let line-b-data = transform-lines(data-b, line) + + // Get x-domain + let x-domain = ( + calc.min(..line-a-data.map(t => t.at(0)), + ..line-b-data.map(t => t.at(0))), + calc.max(..line-a-data.map(t => t.at(0)), + ..line-b-data.map(t => t.at(0))) + ) + + // Get y-domain + let y-domain = if line-a-data != none and line-b-data != none {( + calc.min(..line-a-data.map(t => t.at(1)), + ..line-b-data.map(t => t.at(1))), + calc.max(..line-a-data.map(t => t.at(1)), + ..line-b-data.map(t => t.at(1))) + )} + + let prepare(self, ctx) = { + let (x, y) = (ctx.x, ctx.y) + + // Generate stroke paths + self.stroke-paths = ( + a: util.compute-stroke-paths(self.line-data.a, + (x.min, y.min), (x.max, y.max)), + b: util.compute-stroke-paths(self.line-data.b, + (x.min, y.min), (x.max, y.max)) + ) + + // Generate fill paths + self.fill-paths = util.compute-fill-paths(self.line-data.a + self.line-data.b.rev(), + (x.min, y.min), (x.max, y.max)) + + return self + } + + let stroke(self, ctx) = { + for p in self.stroke-paths.a { + draw.line(..p, fill: none) + } + for p in self.stroke-paths.b { + draw.line(..p, fill: none) + } + } + + let fill(self, ctx) = { + fill-shape(self.fill-paths) + } + + (( + type: "fill-between", + label: label, + axes: axes, + line-data: (a: line-a-data, b: line-b-data), + x-domain: x-domain, + y-domain: y-domain, + style: style, + plot-prepare: prepare, + plot-stroke: stroke, + plot-fill: fill, + plot-legend-preview: self => { + draw.rect((0,0), (1,1), ..self.style) + } + ),) } \ No newline at end of file diff --git a/src/plot/sample.typ b/src/plot/sample.typ new file mode 100644 index 0000000..e813256 --- /dev/null +++ b/src/plot/sample.typ @@ -0,0 +1,79 @@ +/// Sample the given single parameter function `samples` times, with values +/// evenly spaced within the range given by `domain` and return each +/// sampled `y` value in an array as `(x, y)` tuple. +/// +/// If the functions first return value is a tuple `(x, y)`, then all return values +/// must be a tuple. +/// +/// - fn (function): Function to sample of the form `(x) => y` or `(t) => (x, y)`, where +/// `x` or `t` are `float` values within the domain specified by `domain`. +/// - domain (domain): Domain of `fn` used as bounding interval for the sampling points. +/// - samples (int): Number of samples in domain. +/// - sample-at (array): List of x values the function gets sampled at in addition +/// to the `samples` number of samples. Values outsides the +/// specified domain are legal. +/// -> array: Array of (x, y) tuples +#let sample-fn(fn, domain, samples, sample-at: ()) = { + assert(samples + sample-at.len() >= 2, + message: "You must at least sample 2 values") + assert(type(domain) == array and domain.len() == 2, + message: "Domain must be a tuple") + + let (lo, hi) = domain + + let y0 = (fn)(lo) + let is-vector = type(y0) == array + if not is-vector { + y0 = ((lo, y0), ) + } else { + y0 = (y0, ) + } + + let pts = sample-at + range(0, samples).map(t => lo + t / (samples - 1) * (hi - lo)) + pts = pts.sorted() + + return pts.map(x => { + if is-vector { + (fn)(x) + } else { + (x, (fn)(x)) + } + }) +} + +/// Samples the given two parameter function with `x-samples` and +/// `y-samples` values evenly spaced within the range given by +/// `x-domain` and `y-domain` and returns each sampled output in +/// an array. +/// +/// - fn (function): Function of the form `(x, y) => z` with all values being numbers. +/// - x-domain (domain): Domain used as bounding interval for sampling point's x +/// values. +/// - y-domain (domain): Domain used as bounding interval for sampling point's y +/// values. +/// - x-samples (int): Number of samples in the x-domain. +/// - y-samples (int): Number of samples in the y-domain. +/// -> array: Array of z scalars +#let sample-fn2(fn, x-domain, y-domain, x-samples, y-samples) = { + assert(x-samples >= 2, + message: "You must at least sample 2 x-values") + assert(y-samples >= 2, + message: "You must at least sample 2 y-values") + assert(type(x-domain) == array and x-domain.len() == 2, + message: "X-Domain must be a tuple") + assert(type(y-domain) == array and y-domain.len() == 2, + message: "Y-Domain must be a tuple") + + let (x-min, x-max) = x-domain + let (y-min, y-max) = y-domain + let y-pts = range(0, y-samples) + let x-pts = range(0, x-samples) + + return y-pts.map(y => { + let y = y / (y-samples - 1) * (y-max - y-min) + y-min + return x-pts.map(x => { + let x = x / (x-samples - 1) * (x-max - x-min) + x-min + return float((fn)(x, y)) + }) + }) +} \ No newline at end of file diff --git a/src/plot/util.typ b/src/plot/util.typ new file mode 100644 index 0000000..045ceb4 --- /dev/null +++ b/src/plot/util.typ @@ -0,0 +1,372 @@ +#import "/src/cetz.typ" +#import cetz.util: bezier + +/// Clip line-strip in rect +/// +/// - points (array): Array of vectors representing a line-strip +/// - low (vector): Lower clip-window coordinate +/// - high (vector): Upper clip-window coordinate +/// -> array List of line-strips representing the paths insides the clip-window +#let clipped-paths(points, low, high, fill: false) = { + let (min-x, max-x) = (calc.min(low.at(0), high.at(0)), + calc.max(low.at(0), high.at(0))) + let (min-y, max-y) = (calc.min(low.at(1), high.at(1)), + calc.max(low.at(1), high.at(1))) + + let in-rect(pt) = { + return (pt.at(0) >= min-x and pt.at(0) <= max-x and + pt.at(1) >= min-y and pt.at(1) <= max-y) + } + + let interpolated-end(a, b) = { + if in-rect(a) and in-rect(b) { + return b + } + + let (x1, y1, ..) = a + let (x2, y2, ..) = b + + if x2 - x1 == 0 { + return (x2, calc.min(max-y, calc.max(y2, min-y))) + } + + if y2 - y1 == 0 { + return (calc.min(max-x, calc.max(x2, min-x)), y2) + } + + let m = (y2 - y1) / (x2 - x1) + let n = y2 - m * x2 + + let x = x2 + let y = y2 + + y = calc.min(max-y, calc.max(y, min-y)) + x = (y - n) / m + + x = calc.min(max-x, calc.max(x, min-x)) + y = m * x + n + + return (x, y) + } + + // Append path to paths and return paths + // + // If path starts or ends with a vector of another part, merge those + // paths instead appending path as a new path. + let append-path(paths, path) = { + if path.len() <= 1 { + return paths + } + + let cmp(a, b) = { + return a.map(calc.round.with(digits: 8)) == b.map(calc.round.with(digits: 8)) + } + + let added = false + for i in range(0, paths.len()) { + let p = paths.at(i) + if cmp(p.first(), path.last()) { + paths.at(i) = path + p + added = true + } else if cmp(p.first(), path.first()) { + paths.at(i) = path.rev() + p + added = true + } else if cmp(p.last(), path.first()) { + paths.at(i) = p + path + added = true + } else if cmp(p.last(), path.last()) { + paths.at(i) = p + path.rev() + added = true + } + if added { break } + } + + if not added { + paths.push(path) + } + return paths + } + + let clamped-pt(pt) = { + return (calc.max(min-x, calc.min(pt.at(0), max-x)), + calc.max(min-y, calc.min(pt.at(1), max-y))) + } + + let paths = () + + let path = () + let prev = points.at(0) + let was-inside = in-rect(prev) + if was-inside { + path.push(prev) + } else if fill { + path.push(clamped-pt(prev)) + } + + for i in range(1, points.len()) { + let prev = points.at(i - 1) + let pt = points.at(i) + + let is-inside = in-rect(pt) + + let (x1, y1) = prev + let (x2, y2) = pt + + // Ignore lines if both ends are outsides the x-window and on the + // same side. + if (x1 < min-x and x2 < min-x) or (x1 > max-x and x2 > max-x) { + if fill { + let clamped = clamped-pt(pt) + if path.last() != clamped { + path.push(clamped) + } + } + was-inside = false + continue + } + + if is-inside { + if was-inside { + path.push(pt) + } else { + path.push(interpolated-end(pt, prev)) + path.push(pt) + } + } else { + if was-inside { + path.push(interpolated-end(prev, pt)) + } else { + let (a, b) = (interpolated-end(pt, prev), + interpolated-end(prev, pt)) + if in-rect(a) and in-rect(b) { + path.push(a) + path.push(b) + } else if fill { + let clamped = clamped-pt(pt) + if path.last() != clamped { + path.push(clamped) + } + } + } + + if path.len() > 0 and not fill { + paths = append-path(paths, path) + path = () + } + } + + was-inside = is-inside + } + + // Append clamped last point if filling + if fill and not in-rect(points.last()) { + path.push(clamped-pt(points.last())) + } + + if path.len() > 1 { + paths = append-path(paths, path) + } + + return paths +} + +/// Compute clipped stroke paths +/// +/// - points (array): X/Y data points +/// - low (vector): Lower clip-window coordinate +/// - high (vector): Upper clip-window coordinate +/// -> array List of stroke paths +#let compute-stroke-paths(points, low, high) = { + clipped-paths(points, low, high, fill: false) +} + +/// Compute clipped fill path +/// +/// - points (array): X/Y data points +/// - low (vector): Lower clip-window coordinate +/// - high (vector): Upper clip-window coordinate +/// -> array List of fill paths +#let compute-fill-paths(points, low, high) = { + clipped-paths(points, low, high, fill: true) +} + +/// Return points of a sampled catmull-rom through the +/// input points. +/// +/// - points (array): Array of input vectors +/// - tension (float): Catmull-Rom tension +/// - samples (int): Number of samples +/// -> array Array of vectors +#let sampled-spline-data(points, tension, samples) = { + assert(samples >= 1 and samples <= 100, + message: "Must at least use 1 sample per curve") + + let curves = bezier.catmull-to-cubic(points, tension) + let pts = () + for c in curves { + for t in range(0, samples + 1) { + let t = t / samples + pts.push(bezier.cubic-point(..c, t)) + } + } + return pts +} + +/// Simplify linear data by "detecting" linear sections +/// and skipping points until the slope changes. +/// This can have a huge impact on the number of lines +/// getting rendered. +/// +/// - data (array): Data points +/// - epsilon (float): Curvature threshold to treat data as linear +#let linearized-data(data, epsilon) = { + let pts = () + // Current slope, set to none if infinite + let dx = none + // Previous point, last skipped point + let prev = none + let skipped = none + // Current direction + let dir = 0 + + let len = data.len() + for i in range(0, len) { + let pt = data.at(i) + if prev != none and i < len - 1 { + let new-dir = pt.at(0) - prev.at(0) + if new-dir == 0 { + // Infinite slope + if dx != none { + if skipped != none {pts.push(skipped); skipped = none} + pts.push(pt) + } else { + skipped = pt + } + dx = none + } else { + // Push the previous and the current point + // if slope or direction changed + let new-dx = ((pt.at(1) - prev.at(1)) / new-dir) + if dx == none or calc.abs(new-dx - dx) > epsilon or (new-dir * dir) < 0 { + if skipped != none {pts.push(skipped); skipped = none} + pts.push(pt) + + dx = new-dx + dir = new-dir + } else { + skipped = pt + } + } + } else { + if skipped != none {pts.push(skipped); skipped = none} + pts.push(pt) + } + + prev = pt + } + + return pts +} + +// Get the default axis orientation +// depending on the axis name +#let get-default-axis-horizontal(name) = { + return lower(name).starts-with("x") +} + +// Setup axes dictionary +// +// - axis-dict (dictionary): Existing axis dictionary +// - options (dictionary): Named arguments +// - plot-size (tuple): Plot width, height tuple +#let setup-axes(ctx, axis-dict, options, plot-size) = { + import "/src/axes.typ" + + // Get axis option for name + let get-axis-option(axis-name, name, default) = { + let v = options.at(axis-name + "-" + name, default: default) + if v == auto { default } else { v } + } + + for (name, axis) in axis-dict { + if not "ticks" in axis { axis.ticks = () } + axis.label = get-axis-option(name, "label", $#name$) + + // Configure axis bounds + axis.min = get-axis-option(name, "min", axis.min) + axis.max = get-axis-option(name, "max", axis.max) + + assert(axis.min not in (none, auto) and + axis.max not in (none, auto), + message: "Axis min and max must be set.") + if axis.min == axis.max { + axis.min -= 1; axis.max += 1 + } + + axis.mode = get-axis-option(name, "mode", "lin") + axis.base = get-axis-option(name, "base", 10) + + // Configure axis orientation + axis.horizontal = get-axis-option(name, "horizontal", + get-default-axis-horizontal(name)) + + // Configure ticks + axis.ticks.list = get-axis-option(name, "ticks", ()) + axis.ticks.step = get-axis-option(name, "tick-step", axis.ticks.step) + axis.ticks.minor-step = get-axis-option(name, "minor-tick-step", axis.ticks.minor-step) + axis.ticks.decimals = get-axis-option(name, "decimals", 2) + axis.ticks.unit = get-axis-option(name, "unit", []) + axis.ticks.format = get-axis-option(name, "format", axis.ticks.format) + + // Axis break + axis.show-break = get-axis-option(name, "break", false) + axis.inset = get-axis-option(name, "inset", (0, 0)) + + // Configure grid + axis.ticks.grid = get-axis-option(name, "grid", false) + + axis-dict.at(name) = axis + } + + // Set axis options round two, after setting + // axis bounds + for (name, axis) in axis-dict { + let changed = false + + // Configure axis aspect ratio + let equal-to = get-axis-option(name, "equal", none) + if equal-to != none { + assert.eq(type(equal-to), str, + message: "Expected axis name.") + assert(equal-to != name, + message: "Axis can not be equal to itself.") + + let other = axis-dict.at(equal-to, default: none) + assert(other != none, + message: "Other axis must exist.") + assert(other.horizontal != axis.horizontal, + message: "Equal axes must have opposing orientation.") + + let (w, h) = plot-size + let ratio = if other.horizontal { + h / w + } else { + w / h + } + axis.min = other.min * ratio + axis.max = other.max * ratio + + changed = true + } + + if changed { + axis-dict.at(name) = axis + } + } + + for (name, axis) in axis-dict { + axis-dict.at(name) = axes.prepare-axis(ctx, axis, name) + } + + return axis-dict +} \ No newline at end of file diff --git a/tests/plots/orthorect-2d/scatter/out/1.png b/tests/plots/orthorect-2d/scatter/out/1.png index 53a7ab6361dddf5b39ea83c3997754364ffb6a33..ec7292455643587885c0b04f5ee36464a5f5c20d 100644 GIT binary patch literal 146460 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 literal 86699 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 diff --git a/tests/plots/orthorect-2d/scatter/ref/1.png b/tests/plots/orthorect-2d/scatter/ref/1.png new file mode 100644 index 0000000000000000000000000000000000000000..ca5cecf49f552e697389030686b2cc32910ab44d GIT binary patch literal 23643 zcmd_S1z1&Ew=ld_qypAat@44@Pzvub4&$DE$IoFtD&e3Czx%{Oig>Ik{ph6&!8zRE|G7tztCIo^Q ziF_42$&UCAuBv1r{7+@=M^{I$+<-ukn;174j=xVEs?m-hKp^hw@j9Rnb^0;5%94PK zRIKV12qYj23jQ8I0zFo)K_I+);MqHPA88kG`S33r;h_HX&C7AJF@FvA_r}Z7|6$Dk zLgVG=e_;Fbn}48oIh8+ayd3>68!rcdPvuY7>MtArY$4h9VY~n)1oGvg>?XachMb(- zkb<1tz#jNR?Wa3imw$Tnw>ta+-p`MIzPrTe=SSX&HToi?7}IR7uPFHGB<~#(d;080 zf5>?EE~U9E!@wvvJ+G@s-~jpcM}_G$0ZfFO%J}%)ioLgm1myhXSk zeRAWX1kQ4We|gD;^WtA$a^Up&$IFIBWsRwcsc<-jh=#4zYV)0(0#nm`RzxHUk?^#F zc6ukv#m7W|>mhD2140%TyId+zN9IDV8KpL7daDP z#yQq5C4IppQ9Vn6%`eks{Q1e+bheuc_8hqhg7uhUD;}oH(4aT*rA!w=Af@WBMyjCB zUCk>e(MDyXHhbxP%R{g-<-9FPT3oSo8Dcbu`+#pj)z^KiDph=+Eqg9|sxaZ)xczC87Y* zK?xj&`bfrE?$Q zqj%|9GZPfSb&0zQNon4M8O(-bU}ih5Z`n_xR=wbSWQ3q(#H5cB%(hHf&bRa{9~xch z*X=FbwhRf%h-H&t$;^lswMt}Rl}#8fC$0x4p{MtiiIUW|0;F;wcO^3WTpQe6UGq73 zI5@&f#@5u;)dTQ~M^?9=Ok2_4M!mj|0{K`^fQ~rZ^&m==GD6Fc^FZlQA;M-p<_4v? z9TF1K&XKAuG**0`i<|p)Jaqus(%#V>UDjTFMczrGr zG7V^cd0_lm{;J1EhSkqKBpwAC_7>MSjtlQQEO4^3$9Cyu?`+xAE;ToM56>SSDoRGQ z;h=hdR%av#Hmit-g8{H!neixpnklqz$jNz_+1EBVh+%WCmz1P<&3?#tjV6GJ5JTfU z@R|BrkRbU9oa-w0o{p)~57~3&4;a0a$QdXeF`)(87&1MG6s$yxn|&*}he{usjQ4QC zs%i*!bmlsE9kg1&ybx__CXCABBs0CSs#No3A3PxlUWjoDZ<;|y^y)Q`&HX&LH8!Ra z+Dd-jWL8m-vDkL{vgoZiFUGL2$}H0xRHbY;3Q zS<2@O{5KwH_i5zsNHbZ9q~_sEr{M^!$Fw~U@E5c6Lmw`cr}vt(N|Mi;pm9p9lccMd zIOuD*;6ENZsrTB0xlAoRKgGDhyV7KAU|{eeH#Z?ZzN5Vz6$66>A6-{hm*C=a_WSqb zoSZ5bM$80gXlPGxyL)lpZb8>1M@xpE1N2HwvsR+w`m9{6iX=$lJqjjDul{_~$ zcic58DX9e~amqf8{YUX#FUP)qeG{!{Zfa_3X4c)-mX(#o&cv!MV#U zBPl8Q>eVYpCnqFC#OmtmklB9oHF)Bd%)ptY4?^9E`xVY+@yM29;G7!_?O8(=O((C}i+`>Y} zqRNH_4JD-_)s+%MXXjJ%6LE3z)7BZ8yh$1|$1^Fkw$Cjs0p8~^y$17dsD$Aji@P?$ z_3KqtRcflLegZBob!|GwGh32WCw?~+6cldXzI}FfM)qxBU;zd@p<5gn7`TIuxOn}? zsE<@tRZBTFG&GW5NeGLH#U~}I1Ij<^9;%8I-irzqm?6Lt|-a zxxBpGiS}Uz1wL2t=g-6B`^U$|`huaRk$6NzM0f7cFo>&^8c+FY(DLgzIjL~&9UUDR z?jY(R{5E7rXsE3l!{f&#b#>faT+V={?Ck7pY-H8FV`5@X0~;E69$&p=?`dJ>{1;vt z!oq%jZ@cvBj1`oX+8fPn4EUG-{kRqt{f(~eb);fnwfnmzm}V$Fda@a*eHOo!8?9+S+mB^{)%KO zH}wNWWmLqQNUSM=GQwh4Mb3%&UZ4)pMa~~`5xO!hI@Sv>w%xVWg7 zlEOXNJh%nV3-w%iRZ~$>ktZ*P1C+tubLzEMHOhJ2ot-A8rd?fKbPNnI*UJV%pgEMH zfr=O~Dg#^yn6$ZB0GLnrL7|)du)X*1-vb)^`}T1zG8!IcOJJeC4_g>R>yavyeNwgU@?2ks4ZO+f%>Lbt0 z%rvs9K_HqY{DR{#Qd<&72Rr517w^;4)3H9awTTc`R#l<692^{M(L8+ku#E>uH35Y8 zUT1fAwN%KPgFPO+=FHHL95_)c|6&Oik6L$@aP7J%jN z@9%GHY@C~$+uz@BX=%x+H>50zDVul46=r?P&aW#!E$Z=7SC zu0lZKB@GicbDxD!_(=#4=4U!=AiK3V$LcIDIYToItMg%NuQ&-42qBPRUxr(o4I2`} zL38-!1!G-+aFd%l2a68f#U}VZo0+icLNy2hkbhZ{nxS&j=Qz4bK3&DMa&pa<&J9z+ zU_G-#CjwKyTUAkrVDr7rSl)9a3T>^@-Q;HTh(5i|su=zn$y8-h{QuyOMOyBooYjNTkTlOZ5 z_&!Z3y#}CH5Zv&;+nd4aT(b6UdQOc~tG+Qe_r7NdIAvxPPK@}BfHr{hkp?1km2w7F zY&f?178Vxz`fl~WH6t9VCR6OLW1ZmpdpL+*9`ZT5Y(Ji7>oejr69hNE^cQ>&+fzpF zHDU2>N|`Xz|Bv>}zGbqoFIH9PzD2$W6>bA> zgwJolXm2rzR_7j?(igsHOl;1Al}K^Q``7pH^AeRG&R4JHJ-?W9-ZQzV7N@kfvDwuX z3Qq&lJHdd3>^C)URYeR`DKgN4s}adx#1@BA55u*HhkYxcwV42+k`dVlx@F4VSpNL8 zGc#H&@&4S*1YHLODLtq|LqkgsfZ&q7W`jaqt)*~_-*^KoI*|Mmt!;sm+=EIh2I1jK zF*|deGGWQ=GYoBg0TfL7H{j0EcBDvuKO3}jb0blx1gcmerLV7##|;0+ByfvQd-8fV z4q9&y#{gB(FA+|W+2>$wP5#P+C7!ke7?vYv*OC#&OHX+4AUKheB^V2Rj?EcK>7E74 zkiI7@zuX4m`}Nxmgy(3iU%#~z(I$MngMf#HfTC)fCQQ_{6RB~u;p)6;W!A^cO&r=0 ze{z}Yj&~}`7=U3-PP&@)E?E_Sj zL86a3AgM=RDSD3Wl^ULp78Zh1^qor>7fptaD zHw;{>(_e)CaN+qQg#I((`EQ7wTaP8-N&OfYR+c2DgVo8d6Idj{D~G{1Z}S%;hj`XZ zzobCsyV=+)fXAwaiXmusvj*9$8u|9~%;|St@619Zz2BrD&swPUCr^`RXg-vnX7TcN zWaxQz{*hg*J|aFeUVw^dw+Jk92KrawSsnQFTsM6a3 z2MWw_GdHxqpdL7iQBhH4Wn~NnfQfHlIy!#i?@s^1r}a&)XN zDoO#;R(LF22i(7wwu94CE*_qSy1I#hf%M-RfHg2EtgnZHA2V~>9pJPVgyxEb>i`GJ z!GZmJWqFx~3`8zjjUyvU;e5b}ySa6E_?9_B09Q#TOd#4W08fMi7`rF}Hu9$&{YTT; zQ!B{<4bX+2y$c;jM@L)RvdMN}=ufrG%{Mq9Z%~?=nz|!MzMKzDAvK>B98c3F?e!{` zDRXmh5Zt{RC_g$jM*dPyZ?U%V^JjiHRN$Bi@B>ub9>YfiLpo&I_a;lm$Hy<-AdL7} z2W6Vw++>XDs5>nm;!{x>>nSK|Yis-R+8H`EF+mqdTl|GAkoH1VwB%5Ms&Yt(g@}X% z57j#%z)6~_|(+Y_&5&g^&j_$6B82xui<$nc@iac7vLOa1KpDoioUY87VPKY zapkveQKIR}dF3@VIUv)uxrkV7Xkj6ZvgyUmS!#&|dB+&U)@S1t@>S$P+$+zxxRvZd ztVM{o;7158ujfZHMTpmH$YQvdWKu);#@(4J9!$u`Au!^vIQYMnt@+vH{1ezyCzq+7 z9TPC3w2MhQvxF8tacJuh%(iA?{otgSRu6FdKr3!8%#kCpy*L-f0Twgchx zzDT$wVb7BFrg6oXT7g0bm~X*Gd>yBrAvyXS^#QL={4a0e0q91 zI;zYMLhFc#2;dd<^}Uz@lTsq7O5OwPK_NoVz)%v#Ea5Aqr>|e~2m=Fy?4^Czc0u1V z@c2L!QT9QA&ggld@_F7d-UxqTGBd0xH`MtdZs5)5^f7K+UAjg_K=!gG%sK$25*GRD z>1b*B`3r_l4A}^8iiCdwPigpO!!Z4FGqHXxxI$x(S-of<8! zahY-u;BukQ-Q6J;Kq(Iit#@^HCL|`xmgpk6Nk>DuqdZ*hRH2y~MbkP8ia;8VSW`v|<344jYYK6SEL zq~Vl&zUDY|NWlTIh(r6RzrX&yEbjrv^x<+`Zrg;(E%IwG(zFDm$(TK%nHw_H0^ech z*cV=&=T|(Pw#&X%%r=dplF17<`lBZvWDlTJJH3dboRCmnHi}TIZugCk%DMwn9f3zdAx*#3)ItXwAV+Sg~iu`O(+;;-Lr4G`fm`J0ah!VoRo2 zR9dT~*)fvwFLTQu3tbOS`eB43Rkk%|8tX(8wi33FtdC@AlI54QMy+~SbY!E7crq@M zV`NtRWiU;sN1?0!P2RJz^qg(Jz>ongp=}_B`F)++Bu*>QG&52rMW*4xymmRVeK@kc zh06TdEoM%cw6>MNv;FzBk~ye|PiiebBTw#kWYRt3n&2{xk{-CCy&^ePP!|j9zc`kN`F7VRNVu~7 zi6(|&F=IB=fsQ8(PetEL+KEKb>oL>4gs#nrYj9QMuBDrlJCM89L0B+QuvNVkAtjdc zWkOck3M#L_KoakL7RRW3ZlsQx|MW3YQ~>=#%>nzF{LUG|<-7Xq$XXjc+WuHsA@kN| zXcH^Kasjacu8rs`WE9ue)Nqam-obp6re5vc7JK?wG|H`!>(LinphSS?V*EbBMH4X3 zE5*l$dq?#77lApomp2CYRi5UYM80#d`I`2wmeB=&jK?R8^ipHmh?$?Ar}%RZXj77x zkhj$ajpL1|>gc?k4}pd$qQ+4n>Uw+_;i24*6$~`LKfTO*Nw+!iPt$=XzTpVpoQgcu z1$uVzzpL%xFh}rK&6lt+tZ<-31=ziF9&EmwSwTrTnaI76hycgUqHOsar}D-5*S)c# z^!SdA@!U@wxh#mKW?yRXBoEf6zOf?F z#&ydnl9;IS#CWdpeM?0sWjKtlCkZ_v@#S;s%asee3u_aXqI4=MnzTAvv^qk}VeK9B z2yU_M;j|zWlIW>+#W!6lp_2H(P=`HihOO=EfmTq0jt}1v`KT+tzwT#yM!9&pt&0yzVf(rt0VA&5HT+B7WpM^=-m6A*PNcOwkd9OJN1ccTQs`7 zTvhm>xql_V#{=7A@hfswq^_i;j=&{uke8U+Oz4q!G)td#4mM>#`Og`v$Yj@x$s&<| ztWJnE9&~ccpIeoKrrJ6exlCW+OS87eC6mv3>It=~YvT3`s91`wS_W_kz7qJLbTyk5 z7nQpqF^IQB;=?#Q^;P&Z>>bVyLyu2k`!SOYOzi$IrF@*Mmbx0h7a$L4^z+%s^KWQ- zobCJEyLtsUWj+Q@PBqfhksZ3}NA(RLp;BBNF1f>oO5Zp!ahy4I$Y~~;Wws$_uCH5c zw{&)A@=j1|tcTvc4^{i~lWyktEXdaZUG2>V2M1YLSOS!>5b|O0yjy<$cK7sko~na3 z1y>tL)59St9%tk2_n3>r=-0F}G!Uvoo8~7OpCc5rq64bkRVh>M+NlLu&W?q1-3=4% z(d2!c&`_>fcf!k9nn zl_ZW&dy&o{Dk>;YVe`w&M~CfP59gcWw0A~1s{6``PusKMv2BTR>t4LbyN}=fsIeTi zP4S(*imcb}pX93FMr7xuN235pl8D@WGdMgu?}L{mom z;cW^LQjM+V*oXI~9&<-E*)d`j=Uq)9lO^4))E`(-NmOdHF=nb}LGK<)MyBho2{Z>_ z-*6I&ykhHqyj|4}i$Ah;c$h11tcG=u|HX@lAk2O8l!gRa1y(`ot&^mQ+;rX%J8;4n zvb$x%xXl9+jaM@c(nM))4YS2Zan=D3k_~qP`{X@bmSURdg_lQ%0bB4#A$D z>JZY>)3-DF`1(F(VlsOf?&#g~>C>k=t)P$)$384Mx!VGkH4`^m?}+hzzx#rO-QLA2 zw(m*bei_Gtl4b)VZi`jT%SOoQO8+V6tGxkoYo`-^RtZGtBSGU#(v6baa$Eo!2Eo{G_ zgmK6cTcTtvX+~zI7JLJe2@&Ml_>i63caboQzVoyN-7gxJEWpR?f?0ddKHC|bl0QE_ zAvp0aVvJ724YV^{JDED0;4!k5C-GUyAXXf7Gi$KzwGuB$-s5z+DAXtq!RBALx41K% zF=4YS^Sz#^+9^In{nKsFyKD;Ry~oITp%lHx?_LQnETeGGpS_uSNKcQ;H_gV3iS)du zh zF04-T8_ajI!Q?Y>zhdaD@nW#QYgfy?VbAsHPP$7oEkRcYS)(TBmIp&e}Q zwNF@BjN&L+@$cO6*n7gt+INiKF-Yb8R`B(<(_sXh`2$Zw>mJKnK0y6c=r{f90WP02 zvda~*wQG$~H5o%X0&-veUPvK0Q+t*_;@f^6-?IiaueXX(>KW=sB1oUaFs0 zB1%(WFG`1INr|`^1@>16T<*;^oQl*;=($@IDGIwd8ivauZ0Pc2o}}W{t)}3GT%Epx z;cROYe!tO5j`1#kdcB0{l|U5DlJe`F+M&Z)?eEI^srG2Hq$ZqKo;A%B_ALjs&W)H1 zuDt@OWF4j%>ntgT1$3|8i&YG=2-08+ja>jY?OkBy10VJxCRA*9)UCxGDOW*H*t7R6 z)O!Q|d;T261N{RbU0ktBuLhIV6uFas!*}BYX4leAqvluGp5XiEHcp~0j#;-xu4M_< z3oi~plPehG2^zpBMpm`fqbIHWQs-H#B${@(D3f7d;;6LCj2^NDX6$t%_KkZW*~c_L z-he(HF^n5qfeG|3hs7B9H&9=!`ETu@s0{CKoXm28~s24pwuDfBtrVy z0$Dm!^&V}{EkT`y!m9xb??jdEq&`F=s`Gum7WHO z1YnKv3dt`7Nd5p7Vvj;8PD}AIjiPBH)LA)+%h*o06h89MGZ7b=j zj@kO}VDze2>f6!1euT8S?l0L5vQ_IbcfMU@(u!0(xT#Tey0&zK0*>-;@0Q+RNTcli z7OPPHV<~rw&sCHAGt;290@c=HH0cDe-Bpc)zP8?jhSRe&+k?3dAt@4)N?K8x^hlW+ z+AJxW0ojd)GxpPM(D9okF_zBc(OfGo+?j&#wONLbOA+nZKA^TLuB73~)y65Nh%qy78@6i+CN8Vps z(5|bElDr9Bid}auk2$DW;zd@T+TS*>-rFZV@1~UycvzyKPYH6YyerrE?7vkhtqBCX zp4M5+*d!dRtMTa5LLk}{S_mI4+ez$V&=Q-z#E9KjIl3N(2@la;YdYz_EwC{)y#o2V zcf~{A`8idHb6wvtTvFyO6sljkL>hZ0Y5p4I&GEDW*l$XdWQ~`2k6)mc z*ztU+>0oBI^ZUY?e_4E=-c`u7Hx*%?BJH42nF)(A&fLqA(J}k6K%RAa$^5C|M6o31 zTVRq?8vE-@PnOw2<_=p6jPKI2@HOFkm@y)Ap!4|=y%K2=^nDj-dshYhCBoVe36?qq zwkQWO@u_3UE*T;{$J=Q-Zhm|mqHV0LuCS7ulv<-mx|-4*9?y8Toe81Nhm-0XJ&iqM zJxI`xQ=pO{w6)KGncDk7&tp|gH?uNl%z{K0t3D>EI7mu?$lTp+4CF0TRNiBta`#}l zow&S|b#gd4Z8^PT1j6)>H>9SitMhAA=_TfPu3mo)qT%GVD7SuxB#4(SToJYb{D6Q#zd@YZT|pmEv?TYsMwTbEtwRfw;70*vB6rsu-fOFO=Tzy@oY z{<2`TY{P+rI>C(-EVyu>$wMZ?_kY0}1+TL9Sln(FS(^8G2MHVTF%P~%e!G?S`Cj90D z)Lv)foG*uKgU!9c|Iq*?g`x4_8w{q^ubP=zT4q(qGFzA!z)}B>5N5#p9j7SDANg(| z>a7B<8ViaYGJj2jW??;sjJu+r_z;yD@0Rwm`~vbUP2Ti3f^^QCP@GfCh=ADsDj+X^v^ZkAPF!%7Ipt>QhIK#iG>9t z0|P@`@7P#;SXhzknd1ys=60S@W#41Qn-4TvPp0VJ@(2MIFmUNq2L%qjK_KkcVXAVw zIByQb6B7iJCN)iayO8f3r@Z&x?ZRc#b>%KA6O+~SM-Pd3mWBEG1U5^uJ=Ge!jga$a zFJ8RZzP=gruU zuCX)65txY+bpurbK=ZCriJxJXyY7Z=MlxLBxz$+db4_e47_K+hRpK4Ys@G$*waOGt zpQP!#*bfSg%PJ^HNlFe54F%2`lANBK?PQW4?JzafIGZ;-k^;OrJkIL~FMP_%szy}P)k41+OeyCAA$hkKCW%4BVY9zF5W42g}p^X_;CG$7J>p*F=jZQ%g{0dy+m{Eze1>j?6-D#h$;q@dhq zm`2KopS0|B8wYP60TS}U$h!#Ht#s_|?F|g59dm1HIKzY^BO?iUTvgdZ<#(P;-XTuT zKGRZ9UE%!E{VXyjrlPEji1U@n^0)MkXgZL;9;>omDl;BJMMIOTm%9=AKx4RN^0beP zL4|BFW^B&g%niE0xWP4F2u()}cFLiauZp-g@bKj8P zfiLb;bfT3wz}WLqxs0WKNx-84K%=x3n4}IQl)5e@_0g`#)S!CJ^4ZK_$xC+cAakYtv=F!{00G^x)?{xKda=O8^^>hg zp!vQYlgh7L914Kxid!5R^yN5=Y+vGzZ+HRw?!n`76GQ8XhRD-s-XDE$sg@@ap!;g% zx#S%qRb7(7YgoeKPI9*6!%Wu+;~!7(B`s(ky3(AMzaY3ouLp&`Yokt`aVq=Agq9Iv zDyH62flPHNl(7+P%KYPh^%C0J+92NouYKd@#w39<|L8SKzFP;F2^iw;?ruP4@JAAl z8x-Tf*18h`4-QA&21waxnX$rmCs5xO92^V^XF%W$0^*?pjnHo+Q@-a)eN393ph8xe zgrS?ZNLgruZkC`f~>ja4wX)Ij+e0Xxn0K zWknP=bi`p65>p$l`yYV7yPyine7Nvl+#_aYoP3}e9@!`MAmB}FtWNM4+yZubYeh9TOGO7-Q)XwPRv4-jZqrfEfdBeSM)@p z!+*(}zTm@hPlVh)Nl5bk^B10C>)$qf=N5>HPbc*qG?K>afbVv5N$KkD_B#n1;CBpG z78a5^7Gl1R61|@&8K{wwk#W_-9lvyL@Zf|KX^?`&jmBoegqdJgHc!5T;6_uS+p@)P zOhS4vJ6!YXqD6i0Kf0~)Bdt%;o>fGPDINfhl2I_@G!rK>$N4!$0XBoi8QQWo8 z+ztPzv#-Z+UOiW!#(BxJOv^5h=0m|@tdi!w9 z)(@1R8{6I$wYaXu+1b<~*u?O#eLrrnVAIUz=g+S#ye4|`s2FTcUe!;_F3qN_M?&RjpiC5YVG`B7l4aNP98y!adO0h}}3Ne5UK=x2~ zZtY-;U87>+&5!%^iv=CyiXh}A;AQ|5@^-6$0P*J5C_C_a3|!Kvrih~lzR%<>2P0Vn zEey;?nhg)}K|@l1iLzTA4bX94kA>SCdoBu~gaZ6U<=+#4J}rgHL>wUMK5=whIxeYU zO#}K=nIy}U({4Yv0QygkWCraqqRVsjD)8mF;ekZD>~B*sfE!l6zAWV+9jx5`VV z41aq;rhSEWikSa%N9#_9SS;@)yfV|J#x27fhk-q; zbeB*187Qkmjg>umK=4=!ZRxCcIbkuMtOYjx#$A>KPCK*OSDYtECAKA*%>B_v{P1~e z)80Twr&X;itg3Ptx8qb95n=u%rZKWo0Ny^9)icbn@$<9dKkA1gjkPjE?U=Nvv{Po3t3{4@>T8Pit-a z`0?Z0WX2!WBw@P674^@2F zJU4D|5h_(w9`*^yMa^ZFmfDy|;a}FJ6n_bKAFQyPYe{_1b_zUkhP=HFkC637K$JzP z2NIXOf=zM2JJn{xUz#7Q&T)V02Y)?Z2_0V}=^X4z+Ftg|`a!({5wI)IuiD`1VsC#K z1^qfS8AU_WX{`2mq7&}2%-JfI84Y+|M`JM^Wwu)%x8dM+J3n1nSt-%)sSlXwJ|?fb z*;`ucJXm@)0yrB*;eck-hg)-7%FJN>N;bBT4aGa2*+U|vl zUm_ZwQNL!!Z*u%$x9N`e50u)%#@1@BmS#GOW&5_R`Yy$rzv^i3YkoHra~FhNAi-?M z=>)jw;ZnoNRsz{?T?`wdm%kJ5@?NS43dwbkdN@0tr+qU60Ub~=5>5U#7n?Ahh{%w2BqaJXZnD)Ma(wk{G4z0ZcBrFWU*aL-Z+!+CkT|N5^rO=WjKqE{Z}=;J zu{p53%ybX!{DEfxhu#-^LT`B~p*qz+rfFU94c@&8 z!^Lys%KY*}8$IW!CYh(V+)@VX3h0ZRXgxE)JNN_Ie`1{*2}U8=E#$*=1R9eC~McSeY*y5dK?kdo>F;aUh0C+>TA zT@_&Bj)RINlvZ>g`+TSV6GhssgLx{Ml8FR2fi+#~PJ~B04%<3ht-wjL=D91CMxL5d zZZcTv`S!Kdgzdrl#D(3bH@{VqZI~b2k|Y2q9dbG!TCl{cRofVz4%IsBJb3T`>~+n0 zuh&+rwkP^N?vP2+1=TNkq5iPl<-RQ0?9@~;!L00T4%-#kT3)R!jVX;gH}i}$;_t!N zPh1uFfnZm2tJLi*qo9BxqV4kz?b*Pb?DL6L8aSQZQM6v8toOtSMxP(@ToAAX)e1H@ zHv^}TTna=LPO!WRv+>GD+q4^9Duj0#n)Qd?&%0byK|VT!E9BU2Twny`Idyq0ZGkFj zka&s&XQg&_<_xQ*cK7z0OFC)An$6t^+zxfo$6201Aa9Xn1V28$je|44u%KHq;#O>T zk#G3glCV`u7&+w*6T%KvB}MpsZ?R}~hIDE#4(yoC`07SCmEtgt>mRr~Dr zaiC2EA$@v<6k~RUA+oOtrll;L^$d;f@by99oX=QN4>!`m%(Tjo1l2~AnA&`tJUX#0b?>0!8f|M1B9=xmw-p>Ri=HfC2V7AM_ zuWHsT*pPpFZVxI9DI>B}>BV?J{ZON(rYaR7cmpV}8Kt~IdoE%OU*Dz~nDnABMJjcB zTN}y@e2`LTEmBWUk07k3p`igpcRW0-@m-)A)zJSQFTDLR*Ot0-+;cfwU0rWx_luMv z7^s~u$j`^VejcTqr&p%jb_MdW0($DrljRH+`0(&>ds`-Y!GM_~R-7WNOqoubE;KYW z{0pJ2i*ookU&|nzd1)D2VsafCAmKh z4r*JmNrH8i>Vqh&nhD&%^br%$PY-ZVS>h$0M}k@^Utix|1B%%UP?uO!S!wU!0M4*! zObJt9hr0(f&-V6)^(~i1iwJF9MWDjv)YADVmm!;%R5LQbtRe??Y|q8AlH9TIt=Y!f zTDwTGyu2Ko=>gV=o+-2MPGB=fVo0{D{)Q@_jY$(5bcmWgRQt~JZnVhI*ZjD}n(6$- zU*CKdw4x4sM!}97+&_%2gr9jx=#xdm9)g3ajoqG=kmnrheLls|$2qaVx%$P4Tdkpa z4A%B(&SCPT{`}yAgSq62Hua4=Os`uhe6c)}*(~|6LG+`$Io$~Aw1EPMuzs~Rs;dV) zg%~L;?C7sK3JlvH7jUgc!O1BC0s;z`v*V~k+q2`n?UuewsWm(c8#40*c55F;E6H4n zr)p{w@1A-kaXFCrCUDrIDqH{~6aFqW)w9k=Z%>w)M~#Y6Aq++uY|oo*Fl^8Dyu-rx!Y6fF0(1wffeI`N@-_ zhrmsd#Bex0+yW4$>YSF;_Q1ib_nJ|KhJBPtXl6mRThoo#&KDABsgVRSRU7Lf_1^~Z z;Vh`_Y5CD<)DsS+f^rfQxvF$aaaOQlfv+RRokP zKzr_@%E+ISK{QtXO<5VEdm@k{#-HJ`Nlx5VvN3pW&(~DheZE+xR9W zH|33n*BNv}mfgt6sFJgLkt`Bz0Ppn*8dK^qGG!-vY#M?LY8auF7@{l*xBYNDP?$M{ zL-}fRs8nU!h$w3KQOFcbeiV&yPmC*Lq8=)=KNfGMsOYO%As>A~`IgOc_M^wOC@9HJc zR&ZO?yq1F}#Pcft0_k3eQ;n8shnu=ne3JZcN_ z0^i>Clw;4J5A|A2K1JgXNxGv(@V_9U3>;Kww$`~_)MI~WSW5XiBGsL;8i4VvbSzBe zOQ0S>KpOf7XzE80<}(1}GLS8Y$Dkf51-x1lz5YfShvpoCdn3Sp87`1rs8(5d@87dR zJ4&EcJ)LiR-dZ{zAuAUe>O=dzJUf&%IBUA~QgnEYD$J~Tz-W6Cq%FDvsl2@&V-g+ik%uY2$h z%Z8(Dw)$P|6-L9OPcx)&qqP(kQxV=~7onVYe$A)bncfyb^&)VgPJ~u2hB_U)LYkj9 z6ib|E@{!T0J^kBQeE0j4_>W#9B=7c#G5~H%km8Vaz`S;NYDj^l#f!y{*+WXMPQv-< z3dyzcsGd^8zDmi!rOo321abT%WZy0;wARz{>$k2NOh}&c58XI=?V4>bdi6qm3do}Q zRDI$xM6cyW`(1Hk#>WA3&khI(9-L+T`R)SW?;$?chT8rPccAh{xdkg(lRZ&Gbk34 ziXr9WyVPoeSYeM1Ya4X*6#oFNUZp0<#a|?;d{{HvYNp5RD|@x?LzVsBirmqBOYmJi z!DPj%h3-VoL+$>?4Dt;QyY)`v{n=(Tb_A86fw%8TqOTUq#d4bKIa}r(+vDo700(B_ z85upL>1!*%-qJ2l1!YOXSgTIjeV;CwboMrt2CHV->&wxuZr}^?Y-6_APu07y$6Mzee;@aGDCt>>&2~IvE*Zy6!&e0m z0t$dUJ5((;Dp5rGx&u)uCE9=jIvws1@cIx!q8EZeFtZgxr(VnURf|BHnU8x>3P;Bt1D}z1@;12?7b3vRSH*TXj zKbd@S5w1KYhar4lZt~>36kmbgaF--I?g2bmXv=bh>j0A-l|GF)%t63CKd!c-aid19+7R zF3Tom!C+`m2A<;uDksY!k_oDD&rzOP*MXhrvJc%2YFK1zDsO~DT+ z0-wE^k-y1{JU?wh_xplQ+50KV^W9yAJS(dFacb&nETMabGv9(err)8>(DS{lwwTd+ z9#6c%y#dPhA0=?+b1Bp=&2^67H9ffg^y}y-$D>DDM45Xjd(Y+b7^>a9IkmmKyj)yd zW*W{9p%eC6TK8(Dz0vBynWi=!klh4_4O5-^T06jA(R2fflL?Iv$PXhTB7Rzr5luHT zHg2*M-&n9{hz9>>=KkK^=S;DyD=S8z z=x%wLori~^S;<~z1qRc05lr0(@|MT~M^?dM@G#-mF52ei1vpaPeD_Z2s%vV%zBq6X zF)=V^_acwL@e^^%oz+?V`WN662nVzb4B%K=N=nM)$t=}wbGF|6+uaGCKFB0Mb>*uPLjc?Z_Q&;=gKY_i@i0CA~w zJ^XMgNb3K^ zVge4(l@}D4ySlnMIXRh`nYp-d$8^M!K@q;L)+aNf(BAt}HZvqZ$=i~%pfbFgI z^>uMHuso2@!;6cH-@eJPfzz=Z+}sir*u5v|GKSzBRPi?j6v(vP!>x~JjxxuT_sF&x zZ#fU_>ovn{&1Jpkl9QOZva?Umy+*$a@jwj^7y3da!u-C7wf7j~cAw(qNx^R2bjWu; zIIo$iKj_}|wcQE5>*{o@^kPqihbw=>Wp{stiX@is=FL1z@K1`r{aynV55p_xo52_2 z+}vWNr(1h583O$LfcDP-DDZ=K{Lke3D^kIe2p|;zKY%>V@Y9!AWQYFaD=a^M^WxU!(aQt$%Zee_r_E0&O6h5cj9&gZ?<5>gDR{ zv$M0+;UC8-D9FxU*#m#<9fL=X@B-DJ8?)ii{<3iZmhR4d{^QZa4%aghyTmQ z%i(`V_Y$K&-uweM_+0+9@p6F6m4T!Ct116wBYgBi^XR`di{AH=%bl(Fop1&y8TlX^*8m`zZ8T2H}uKveV8JgN}6vPY{|R}&Huh} z`L)gZLks;)%3UhRze$__b=v|~nexNGG}HfSCAAtb;8may_fw6#GkkKtOZFf7&yRk-`-Q^!_jdTjfImO_`RG55#c4_GW9OrYEk{I=apU!(ow z2Sfi#KV3@izqiBhn&KDAksQ-R6-VNCuU!asQlKiq`B$>@L6ivN#zSQ66z zBA)(*TKvP4y;P9@Ln`V=^#3cB@qfL{|KKJ3*#~$3oX)`GfD6&z>u~8^?Ez4KCNclZ z9sa#);&Sc&aftLY4hpzl!`i(lV-RO-1ofKJ@K3&3fMhKsUkcRCK`uXw0#Dw3gnzc` zkJd_f0{1T)RYCES`=7lDZ-nH71mds2{@!>wz(0)nUue7>{cD6CG(G{- 0.5*x*x + 3*x + 0.025*x*x*x }, domain: (0,10), label: $y=x$, line: "raw") + cetz-plot.add.xy((x)=>{- 0.5*x*x + 3*x + 0.025*x*x*x }, domain: (0,10), label: $y=x$, line: "raw") } ) diff --git a/tests/plots/polar-2d/scatter/out/1.png b/tests/plots/polar-2d/scatter/out/1.png index a393ccf7f0a16dd9c643fa089ef2deb51fedaf79..f6ea3f5f3169c64ac007737fcd0f03e0b7d89e11 100644 GIT binary patch literal 326992 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 literal 102228 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�uJtw|7>YmhNe#A%x@>QV!wW}mew=VKO| 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 diff --git a/tests/plots/polar-2d/scatter/ref/1.png b/tests/plots/polar-2d/scatter/ref/1.png new file mode 100644 index 0000000000000000000000000000000000000000..8f24d298e8feec4be246e1f042ef7cff51aed67d GIT binary patch literal 98526 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 F{{U$TtCRo$ literal 0 HcmV?d00001 diff --git a/tests/plots/polar-2d/scatter/test.typ b/tests/plots/polar-2d/scatter/test.typ index 29dcaf7..1fc5f81 100644 --- a/tests/plots/polar-2d/scatter/test.typ +++ b/tests/plots/polar-2d/scatter/test.typ @@ -16,7 +16,7 @@ y-min: -1, y-max: 1, y-tick-step: 0.5, y-minor-tick-step: 0.1, y-grid: "both", { - cetz.plot.add( + cetz-plot.add.xy( calc.sin, domain: (0,2*calc.pi), label: $y=x$, @@ -24,7 +24,7 @@ samples: 50 ) - cetz.plot.add( + cetz-plot.add.xy( (t)=>calc.pow(calc.sin(t),2), domain: (0, 2* calc.pi), line: "raw", From 648bccc951665e1f913dbed70b2744ca41c6b72b Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 21:41:41 +0100 Subject: [PATCH 21/44] Move clipping logic to axis style --- src/plot/axis-styles/orthorect-2d/clipper.typ | 184 +++++++++++++++++ .../axis-styles/orthorect-2d/orthorect-2d.typ | 11 +- src/plot/axis-styles/polar-2d/clipper.typ | 187 +++++++++++++++++ src/plot/axis-styles/polar-2d/polar-2d.typ | 11 +- src/plot/elements/xy.typ | 18 +- src/plot/util.typ | 189 ------------------ 6 files changed, 401 insertions(+), 199 deletions(-) create mode 100644 src/plot/axis-styles/orthorect-2d/clipper.typ create mode 100644 src/plot/axis-styles/polar-2d/clipper.typ diff --git a/src/plot/axis-styles/orthorect-2d/clipper.typ b/src/plot/axis-styles/orthorect-2d/clipper.typ new file mode 100644 index 0000000..8dd9e1a --- /dev/null +++ b/src/plot/axis-styles/orthorect-2d/clipper.typ @@ -0,0 +1,184 @@ +/// Clip line-strip in rect +/// +/// - points (array): Array of vectors representing a line-strip +/// - low (vector): Lower clip-window coordinate +/// - high (vector): Upper clip-window coordinate +/// -> array List of line-strips representing the paths insides the clip-window +#let clipped-paths-rect(points, ctx, fill: false) = { + let (low, high) = ctx.clip + let (min-x, max-x) = (calc.min(low.at(0), high.at(0)), + calc.max(low.at(0), high.at(0))) + let (min-y, max-y) = (calc.min(low.at(1), high.at(1)), + calc.max(low.at(1), high.at(1))) + + let in-rect(pt) = { + return (pt.at(0) >= min-x and pt.at(0) <= max-x and + pt.at(1) >= min-y and pt.at(1) <= max-y) + } + + let interpolated-end(a, b) = { + if in-rect(a) and in-rect(b) { + return b + } + + let (x1, y1, ..) = a + let (x2, y2, ..) = b + + if x2 - x1 == 0 { + return (x2, calc.min(max-y, calc.max(y2, min-y))) + } + + if y2 - y1 == 0 { + return (calc.min(max-x, calc.max(x2, min-x)), y2) + } + + let m = (y2 - y1) / (x2 - x1) + let n = y2 - m * x2 + + let x = x2 + let y = y2 + + y = calc.min(max-y, calc.max(y, min-y)) + x = (y - n) / m + + x = calc.min(max-x, calc.max(x, min-x)) + y = m * x + n + + return (x, y) + } + + // Append path to paths and return paths + // + // If path starts or ends with a vector of another part, merge those + // paths instead appending path as a new path. + let append-path(paths, path) = { + if path.len() <= 1 { + return paths + } + + let cmp(a, b) = { + return a.map(calc.round.with(digits: 8)) == b.map(calc.round.with(digits: 8)) + } + + let added = false + for i in range(0, paths.len()) { + let p = paths.at(i) + if cmp(p.first(), path.last()) { + paths.at(i) = path + p + added = true + } else if cmp(p.first(), path.first()) { + paths.at(i) = path.rev() + p + added = true + } else if cmp(p.last(), path.first()) { + paths.at(i) = p + path + added = true + } else if cmp(p.last(), path.last()) { + paths.at(i) = p + path.rev() + added = true + } + if added { break } + } + + if not added { + paths.push(path) + } + return paths + } + + let clamped-pt(pt) = { + return (calc.max(min-x, calc.min(pt.at(0), max-x)), + calc.max(min-y, calc.min(pt.at(1), max-y))) + } + + let paths = () + + let path = () + let prev = points.at(0) + let was-inside = in-rect(prev) + if was-inside { + path.push(prev) + } else if fill { + path.push(clamped-pt(prev)) + } + + for i in range(1, points.len()) { + let prev = points.at(i - 1) + let pt = points.at(i) + + let is-inside = in-rect(pt) + + let (x1, y1) = prev + let (x2, y2) = pt + + // Ignore lines if both ends are outsides the x-window and on the + // same side. + if (x1 < min-x and x2 < min-x) or (x1 > max-x and x2 > max-x) { + if fill { + let clamped = clamped-pt(pt) + if path.last() != clamped { + path.push(clamped) + } + } + was-inside = false + continue + } + + if is-inside { + if was-inside { + path.push(pt) + } else { + path.push(interpolated-end(pt, prev)) + path.push(pt) + } + } else { + if was-inside { + path.push(interpolated-end(prev, pt)) + } else { + let (a, b) = (interpolated-end(pt, prev), + interpolated-end(prev, pt)) + if in-rect(a) and in-rect(b) { + path.push(a) + path.push(b) + } else if fill { + let clamped = clamped-pt(pt) + if path.last() != clamped { + path.push(clamped) + } + } + } + + if path.len() > 0 and not fill { + paths = append-path(paths, path) + path = () + } + } + + was-inside = is-inside + } + + // Append clamped last point if filling + if fill and not in-rect(points.last()) { + path.push(clamped-pt(points.last())) + } + + if path.len() > 1 { + paths = append-path(paths, path) + } + + return paths +} + +/// Compute clipped stroke paths +/// +/// - points (array): X/Y data points +/// - low (vector): Lower clip-window coordinate +/// - high (vector): Upper clip-window coordinate +/// -> array List of stroke paths +#let compute-stroke-paths = clipped-paths-rect.with(fill: false) +/// Compute clipped fill path +/// +/// - points (array): X/Y data points +/// - low (vector): Lower clip-window coordinate +/// - high (vector): Upper clip-window coordinate +/// -> array List of fill paths +#let compute-fill-paths = clipped-paths-rect.with(fill: true) \ No newline at end of file diff --git a/src/plot/axis-styles/orthorect-2d/orthorect-2d.typ b/src/plot/axis-styles/orthorect-2d/orthorect-2d.typ index 0704dab..28b6808 100644 --- a/src/plot/axis-styles/orthorect-2d/orthorect-2d.typ +++ b/src/plot/axis-styles/orthorect-2d/orthorect-2d.typ @@ -5,6 +5,7 @@ #import "grid.typ" #import "axis.typ": draw-axis-line, inset-axis-points, place-ticks-on-line #import "transforms.typ": data-viewport, axis-viewport, +#import "clipper.typ" #let default-style-orthorect-2d = util.merge-dictionary( default-style, @@ -31,7 +32,15 @@ (x-scale, y-scale) = (y-scale, x-scale) } - return (x: x, y: y, size: size, x-scale: x-scale, y-scale: y-scale) + return ( + axes: (x,y), + size: size, + x-scale: x-scale, + y-scale: y-scale, + clip: ((x.min, y.min), (x.max, y.max)), + compute-stroke-paths: clipper.compute-stroke-paths, + compute-fill-paths: clipper.compute-fill-paths + ) } #let draw-axes( diff --git a/src/plot/axis-styles/polar-2d/clipper.typ b/src/plot/axis-styles/polar-2d/clipper.typ new file mode 100644 index 0000000..1241da6 --- /dev/null +++ b/src/plot/axis-styles/polar-2d/clipper.typ @@ -0,0 +1,187 @@ +#import "/src/cetz.typ" +#import cetz.util: bezier + +/// Clip line-strip in rect +/// +/// - points (array): Array of vectors representing a line-strip +/// - low (vector): Lower clip-window coordinate +/// - high (vector): Upper clip-window coordinate +/// -> array List of line-strips representing the paths insides the clip-window +#let clipped-paths-circle(points, ctx, fill: false) = { + let (low, high) = ctx.clip + let (min-x, max-x) = (calc.min(low.at(0), high.at(0)), + calc.max(low.at(0), high.at(0))) + let (min-y, max-y) = (calc.min(low.at(1), high.at(1)), + calc.max(low.at(1), high.at(1))) + + let in-rect(pt) = { + return (pt.at(0) >= min-x and pt.at(0) <= max-x and + pt.at(1) >= min-y and pt.at(1) <= max-y) + } + + let interpolated-end(a, b) = { + if in-rect(a) and in-rect(b) { + return b + } + + let (x1, y1, ..) = a + let (x2, y2, ..) = b + + if x2 - x1 == 0 { + return (x2, calc.min(max-y, calc.max(y2, min-y))) + } + + if y2 - y1 == 0 { + return (calc.min(max-x, calc.max(x2, min-x)), y2) + } + + let m = (y2 - y1) / (x2 - x1) + let n = y2 - m * x2 + + let x = x2 + let y = y2 + + y = calc.min(max-y, calc.max(y, min-y)) + x = (y - n) / m + + x = calc.min(max-x, calc.max(x, min-x)) + y = m * x + n + + return (x, y) + } + + // Append path to paths and return paths + // + // If path starts or ends with a vector of another part, merge those + // paths instead appending path as a new path. + let append-path(paths, path) = { + if path.len() <= 1 { + return paths + } + + let cmp(a, b) = { + return a.map(calc.round.with(digits: 8)) == b.map(calc.round.with(digits: 8)) + } + + let added = false + for i in range(0, paths.len()) { + let p = paths.at(i) + if cmp(p.first(), path.last()) { + paths.at(i) = path + p + added = true + } else if cmp(p.first(), path.first()) { + paths.at(i) = path.rev() + p + added = true + } else if cmp(p.last(), path.first()) { + paths.at(i) = p + path + added = true + } else if cmp(p.last(), path.last()) { + paths.at(i) = p + path.rev() + added = true + } + if added { break } + } + + if not added { + paths.push(path) + } + return paths + } + + let clamped-pt(pt) = { + return (calc.max(min-x, calc.min(pt.at(0), max-x)), + calc.max(min-y, calc.min(pt.at(1), max-y))) + } + + let paths = () + + let path = () + let prev = points.at(0) + let was-inside = in-rect(prev) + if was-inside { + path.push(prev) + } else if fill { + path.push(clamped-pt(prev)) + } + + for i in range(1, points.len()) { + let prev = points.at(i - 1) + let pt = points.at(i) + + let is-inside = in-rect(pt) + + let (x1, y1) = prev + let (x2, y2) = pt + + // Ignore lines if both ends are outsides the x-window and on the + // same side. + if (x1 < min-x and x2 < min-x) or (x1 > max-x and x2 > max-x) { + if fill { + let clamped = clamped-pt(pt) + if path.last() != clamped { + path.push(clamped) + } + } + was-inside = false + continue + } + + if is-inside { + if was-inside { + path.push(pt) + } else { + path.push(interpolated-end(pt, prev)) + path.push(pt) + } + } else { + if was-inside { + path.push(interpolated-end(prev, pt)) + } else { + let (a, b) = (interpolated-end(pt, prev), + interpolated-end(prev, pt)) + if in-rect(a) and in-rect(b) { + path.push(a) + path.push(b) + } else if fill { + let clamped = clamped-pt(pt) + if path.last() != clamped { + path.push(clamped) + } + } + } + + if path.len() > 0 and not fill { + paths = append-path(paths, path) + path = () + } + } + + was-inside = is-inside + } + + // Append clamped last point if filling + if fill and not in-rect(points.last()) { + path.push(clamped-pt(points.last())) + } + + if path.len() > 1 { + paths = append-path(paths, path) + } + + return paths +} + +/// Compute clipped stroke paths +/// +/// - points (array): X/Y data points +/// - low (vector): Lower clip-window coordinate +/// - high (vector): Upper clip-window coordinate +/// -> array List of stroke paths +#let compute-stroke-paths = clipped-paths-circle.with(fill: false) +/// Compute clipped fill path +/// +/// - points (array): X/Y data points +/// - low (vector): Lower clip-window coordinate +/// - high (vector): Upper clip-window coordinate +/// -> array List of fill paths +#let compute-fill-paths = clipped-paths-circle.with(fill: true) \ No newline at end of file diff --git a/src/plot/axis-styles/polar-2d/polar-2d.typ b/src/plot/axis-styles/polar-2d/polar-2d.typ index 0596d5a..a0ec317 100644 --- a/src/plot/axis-styles/polar-2d/polar-2d.typ +++ b/src/plot/axis-styles/polar-2d/polar-2d.typ @@ -5,6 +5,7 @@ #import "grid.typ" #import "axis.typ": draw-axis-line, inset-axis-points, place-ticks-on-line, place-ticks-on-radius #import "transforms.typ": data-viewport, axis-viewport, +#import "clipper.typ" #let default-style-polar-2d = util.merge-dictionary( default-style, @@ -29,7 +30,15 @@ (x-scale, y-scale) = (y-scale, x-scale) } - return (x: x, y: y, size: size, x-scale: x-scale, y-scale: y-scale) + return ( + axes: (x,y), + size: size, + x-scale: x-scale, + y-scale: y-scale, + clip: ((x.min, y.min), (x.max, y.max)), // TODO: Change to radius + compute-stroke-paths: clipper.compute-stroke-paths, + compute-fill-paths: clipper.compute-fill-paths + ) } #let draw-axes( diff --git a/src/plot/elements/xy.typ b/src/plot/elements/xy.typ index 4e84ff2..bfe1b5b 100644 --- a/src/plot/elements/xy.typ +++ b/src/plot/elements/xy.typ @@ -83,11 +83,13 @@ // Prepare line data #let _prepare(self, ctx) = { - let (x, y) = (ctx.x, ctx.y) + let (x, y) = (ctx.axes.at(0), ctx.axes.at(1)) // Generate stroke paths - self.stroke-paths = util.compute-stroke-paths(self.line-data, - (x.min, y.min), (x.max, y.max)) + self.stroke-paths = (ctx.compute-stroke-paths)( + self.line-data, + ctx, + ) // Compute fill paths if filling is requested self.hypograph = self.at("hypograph", default: false) @@ -103,8 +105,6 @@ // Stroke line data #let _stroke(self, ctx) = { - let (x, y) = (ctx.x, ctx.y) - for p in self.stroke-paths { draw.line(..p, fill: none) } @@ -112,7 +112,7 @@ // Fill line data #let _fill(self, ctx) = { - let (x, y) = (ctx.x, ctx.y) + let (x, y) = (ctx.axes.at(0), ctx.axes.at(1)) if self.hypograph { fill-segments-to(self.fill-paths, y.min) @@ -124,8 +124,10 @@ if self.at("fill-type", default: "axis") == "shape" { fill-shape(self.fill-paths) } else { - fill-segments-to(self.fill-paths, - calc.max(calc.min(y.max, 0), y.min)) + fill-segments-to( + self.fill-paths, + calc.max(calc.min(y.max, 0), y.min) + ) } } } diff --git a/src/plot/util.typ b/src/plot/util.typ index 045ceb4..b6c3f3d 100644 --- a/src/plot/util.typ +++ b/src/plot/util.typ @@ -1,195 +1,6 @@ #import "/src/cetz.typ" #import cetz.util: bezier -/// Clip line-strip in rect -/// -/// - points (array): Array of vectors representing a line-strip -/// - low (vector): Lower clip-window coordinate -/// - high (vector): Upper clip-window coordinate -/// -> array List of line-strips representing the paths insides the clip-window -#let clipped-paths(points, low, high, fill: false) = { - let (min-x, max-x) = (calc.min(low.at(0), high.at(0)), - calc.max(low.at(0), high.at(0))) - let (min-y, max-y) = (calc.min(low.at(1), high.at(1)), - calc.max(low.at(1), high.at(1))) - - let in-rect(pt) = { - return (pt.at(0) >= min-x and pt.at(0) <= max-x and - pt.at(1) >= min-y and pt.at(1) <= max-y) - } - - let interpolated-end(a, b) = { - if in-rect(a) and in-rect(b) { - return b - } - - let (x1, y1, ..) = a - let (x2, y2, ..) = b - - if x2 - x1 == 0 { - return (x2, calc.min(max-y, calc.max(y2, min-y))) - } - - if y2 - y1 == 0 { - return (calc.min(max-x, calc.max(x2, min-x)), y2) - } - - let m = (y2 - y1) / (x2 - x1) - let n = y2 - m * x2 - - let x = x2 - let y = y2 - - y = calc.min(max-y, calc.max(y, min-y)) - x = (y - n) / m - - x = calc.min(max-x, calc.max(x, min-x)) - y = m * x + n - - return (x, y) - } - - // Append path to paths and return paths - // - // If path starts or ends with a vector of another part, merge those - // paths instead appending path as a new path. - let append-path(paths, path) = { - if path.len() <= 1 { - return paths - } - - let cmp(a, b) = { - return a.map(calc.round.with(digits: 8)) == b.map(calc.round.with(digits: 8)) - } - - let added = false - for i in range(0, paths.len()) { - let p = paths.at(i) - if cmp(p.first(), path.last()) { - paths.at(i) = path + p - added = true - } else if cmp(p.first(), path.first()) { - paths.at(i) = path.rev() + p - added = true - } else if cmp(p.last(), path.first()) { - paths.at(i) = p + path - added = true - } else if cmp(p.last(), path.last()) { - paths.at(i) = p + path.rev() - added = true - } - if added { break } - } - - if not added { - paths.push(path) - } - return paths - } - - let clamped-pt(pt) = { - return (calc.max(min-x, calc.min(pt.at(0), max-x)), - calc.max(min-y, calc.min(pt.at(1), max-y))) - } - - let paths = () - - let path = () - let prev = points.at(0) - let was-inside = in-rect(prev) - if was-inside { - path.push(prev) - } else if fill { - path.push(clamped-pt(prev)) - } - - for i in range(1, points.len()) { - let prev = points.at(i - 1) - let pt = points.at(i) - - let is-inside = in-rect(pt) - - let (x1, y1) = prev - let (x2, y2) = pt - - // Ignore lines if both ends are outsides the x-window and on the - // same side. - if (x1 < min-x and x2 < min-x) or (x1 > max-x and x2 > max-x) { - if fill { - let clamped = clamped-pt(pt) - if path.last() != clamped { - path.push(clamped) - } - } - was-inside = false - continue - } - - if is-inside { - if was-inside { - path.push(pt) - } else { - path.push(interpolated-end(pt, prev)) - path.push(pt) - } - } else { - if was-inside { - path.push(interpolated-end(prev, pt)) - } else { - let (a, b) = (interpolated-end(pt, prev), - interpolated-end(prev, pt)) - if in-rect(a) and in-rect(b) { - path.push(a) - path.push(b) - } else if fill { - let clamped = clamped-pt(pt) - if path.last() != clamped { - path.push(clamped) - } - } - } - - if path.len() > 0 and not fill { - paths = append-path(paths, path) - path = () - } - } - - was-inside = is-inside - } - - // Append clamped last point if filling - if fill and not in-rect(points.last()) { - path.push(clamped-pt(points.last())) - } - - if path.len() > 1 { - paths = append-path(paths, path) - } - - return paths -} - -/// Compute clipped stroke paths -/// -/// - points (array): X/Y data points -/// - low (vector): Lower clip-window coordinate -/// - high (vector): Upper clip-window coordinate -/// -> array List of stroke paths -#let compute-stroke-paths(points, low, high) = { - clipped-paths(points, low, high, fill: false) -} - -/// Compute clipped fill path -/// -/// - points (array): X/Y data points -/// - low (vector): Lower clip-window coordinate -/// - high (vector): Upper clip-window coordinate -/// -> array List of fill paths -#let compute-fill-paths(points, low, high) = { - clipped-paths(points, low, high, fill: true) -} - /// Return points of a sampled catmull-rom through the /// input points. /// From b06bc2095308ddc7391a687c49a7b9f6b314419b Mon Sep 17 00:00:00 2001 From: James R Swift Date: Wed, 31 Jul 2024 21:58:02 +0100 Subject: [PATCH 22/44] Move grid to background, better colors --- src/plot.typ | 10 +++++++-- src/plot/elements/xy.typ | 6 +++-- src/plot/styles.typ | 4 ++-- tests/plots/orthorect-2d/scatter/test.typ | 27 +++++++++++++++++++---- tests/plots/polar-2d/scatter/test.typ | 6 +++-- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/plot.typ b/src/plot.typ index 7aa0611..21cf098 100644 --- a/src/plot.typ +++ b/src/plot.typ @@ -7,13 +7,19 @@ // TODO: Refactor this into a better way of providing palettes -#let default-colors = (blue, red, green, yellow, black) +#let default-colors = ( + rgb("#1982c4"), + rgb("#ff595e"), + rgb("#ffca3a"), + rgb("#8ac926"), + rgb("#6a4c93") +) #let default-plot-style(i) = { let color = default-colors.at(calc.rem(i, default-colors.len())) return ( stroke: color, - fill: color.lighten(75%) + fill: color.transparentize(80%) ) } diff --git a/src/plot/elements/xy.typ b/src/plot/elements/xy.typ index bfe1b5b..082a2e7 100644 --- a/src/plot/elements/xy.typ +++ b/src/plot/elements/xy.typ @@ -96,8 +96,10 @@ self.epigraph = self.at("epigraph", default: false) self.fill = self.at("fill", default: false) if self.hypograph or self.epigraph or self.fill { - self.fill-paths = util.compute-fill-paths(self.line-data, - (x.min, y.min), (x.max, y.max)) + self.fill-paths = (ctx.compute-fill-paths)( + self.line-data, + ctx, + ) } return self diff --git a/src/plot/styles.typ b/src/plot/styles.typ index ea9e212..3e5f4e0 100644 --- a/src/plot/styles.typ +++ b/src/plot/styles.typ @@ -42,8 +42,8 @@ anchor: auto, // Axis label anchor angle: auto, // Axis label angle ), - axis-layer: 0, - grid-layer: 0, + axis-layer: 2, + grid-layer: -1, background-layer: 0, padding: 0, tick: ( diff --git a/tests/plots/orthorect-2d/scatter/test.typ b/tests/plots/orthorect-2d/scatter/test.typ index 40f0a56..9e03e83 100644 --- a/tests/plots/orthorect-2d/scatter/test.typ +++ b/tests/plots/orthorect-2d/scatter/test.typ @@ -6,14 +6,33 @@ cetz-plot.plot( axis-style: cetz-plot.axis-style.orthorect-2d, size: (12,7), - // x-min: 1, x-max: 100, x-tick-step: 1, x-minor-tick-step: 1, - // x-mode: "log", + + x-tick-step: calc.pi / 4, + x-minor-tick-step: calc.pi / 16, x-grid: "both", - y-min: 0, y-max: 10, + x-min: 0, x-max: 2 * calc.pi, + x-format: cetz-plot.axes.format.multiple-of, + + y-min: -1, y-max: 1, y-tick-step: 0.5, y-minor-tick-step: 0.1, y-grid: "both", { - cetz-plot.add.xy((x)=>{- 0.5*x*x + 3*x + 0.025*x*x*x }, domain: (0,10), label: $y=x$, line: "raw") + cetz-plot.add.xy( + calc.sin, + domain: (0,2*calc.pi), + label: $y=x$, + line: "raw", + samples: 100, + epigraph: true, + ) + cetz-plot.add.xy( + (t)=>calc.pow(calc.sin(t),2), + domain: (0, 2* calc.pi), + line: "raw", + samples: 100, + hypograph: true, + label: $sin^2 (x)$ + ) } ) }) \ No newline at end of file diff --git a/tests/plots/polar-2d/scatter/test.typ b/tests/plots/polar-2d/scatter/test.typ index 1fc5f81..5b28841 100644 --- a/tests/plots/polar-2d/scatter/test.typ +++ b/tests/plots/polar-2d/scatter/test.typ @@ -21,14 +21,16 @@ domain: (0,2*calc.pi), label: $y=x$, line: "raw", - samples: 50 + samples: 100, + epigraph: true, ) cetz-plot.add.xy( (t)=>calc.pow(calc.sin(t),2), domain: (0, 2* calc.pi), line: "raw", - samples: 50, + samples: 100, + hypograph: true, label: $sin^2 (x)$ ) } From 45c225f24921d5c3570fa07170ccf59014f532ec Mon Sep 17 00:00:00 2001 From: James R Swift Date: Thu, 1 Aug 2024 09:52:39 +0100 Subject: [PATCH 23/44] Properly style breakpoints and padding --- src/plot.typ | 1 - src/plot/styles.typ | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/plot.typ b/src/plot.typ index 21cf098..396dda8 100644 --- a/src/plot.typ +++ b/src/plot.typ @@ -4,7 +4,6 @@ #import "plot/axis-style.typ" #import "axes/axes.typ" - // TODO: Refactor this into a better way of providing palettes #let default-colors = ( diff --git a/src/plot/styles.typ b/src/plot/styles.typ index 3e5f4e0..8bebd1b 100644 --- a/src/plot/styles.typ +++ b/src/plot/styles.typ @@ -92,11 +92,16 @@ style.tick.label.offset = res(style.tick.label.offset) // Break points - // style.break-point.width = res(style.break-point.width) - // style.break-point.length = res(style.break-point.length) + if "break-point" in style { + style.break-point.width = res(style.break-point.width) + style.break-point.length = res(style.break-point.length) + } + // Padding - // style.padding = res(style.padding) + if "padding" in style { + style.padding = res(style.padding) + } if "overshoot" in style { style.overshoot = res(style.overshoot) From 799d3287888430e18eaec5c1a501713c872c7292 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Thu, 1 Aug 2024 09:59:25 +0100 Subject: [PATCH 24/44] Import violin --- src/axes/violin.typ | 7 +- src/plot/add.typ | 7 +- src/plot/elements/violin.typ | 134 +++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 src/plot/elements/violin.typ diff --git a/src/axes/violin.typ b/src/axes/violin.typ index 3f8f876..539e5db 100644 --- a/src/axes/violin.typ +++ b/src/axes/violin.typ @@ -1,6 +1,5 @@ #import "/src/cetz.typ": draw -#import "util.typ" -#import "sample.typ" +#import "/src/plot/sample.typ" #let kernel-normal(x, stdev: 1.5) = { (1/calc.sqrt(2*calc.pi*calc.pow(stdev,2))) * calc.exp( - (x*x)/(2*calc.pow(stdev,2))) @@ -21,7 +20,7 @@ } let (x, y) = (ctx.x, ctx.y) - let stroke-paths = util.compute-stroke-paths(path, (x.min, y.min), (x.max, y.max)) + let stroke-paths = ctx.compute-stroke-paths(path, (x.min, y.min), (x.max, y.max)) for p in stroke-paths{ let args = arguments(..p, closed: self.side == "both") @@ -93,7 +92,7 @@ /// - mark-style (dictionary): (unused, will eventually be used to render interquartile ranges). /// - axes (axes): (unstable, documentation to follow once completed). /// - label (none, content): The name of the category to be shown in the legend. -#let add-violin( +#let violin( data, x-key: 0, y-key: 1, diff --git a/src/plot/add.typ b/src/plot/add.typ index da47246..374b19c 100644 --- a/src/plot/add.typ +++ b/src/plot/add.typ @@ -1,2 +1,7 @@ #import "elements/anchor.typ": anchor -#import "elements/xy.typ": xy \ No newline at end of file + +// Continuous Continuous +#import "elements/xy.typ": xy + +// Categorical Continuous +#import "elements/violin.typ": violin diff --git a/src/plot/elements/violin.typ b/src/plot/elements/violin.typ new file mode 100644 index 0000000..539e5db --- /dev/null +++ b/src/plot/elements/violin.typ @@ -0,0 +1,134 @@ +#import "/src/cetz.typ": draw +#import "/src/plot/sample.typ" + +#let kernel-normal(x, stdev: 1.5) = { + (1/calc.sqrt(2*calc.pi*calc.pow(stdev,2))) * calc.exp( - (x*x)/(2*calc.pow(stdev,2))) +} + +#let _violin-render(self, ctx, violin, filling: true) = { + let path = range(self.samples) + .map((t)=>violin.min + (violin.max - violin.min) * (t /self.samples )) + .map((u)=>(u, (violin.convolve)(u))) + .map(((u,v)) => { + (violin.x-position + v, u) + }) + + if self.side == "both"{ + path += path.rev().map(((x,y))=> {(2 * violin.x-position - x,y)}) + } else if self.side == "left"{ + path = path.map( ((x,y))=>{(2 * violin.x-position - x,y)}) + } + + let (x, y) = (ctx.x, ctx.y) + let stroke-paths = ctx.compute-stroke-paths(path, (x.min, y.min), (x.max, y.max)) + + for p in stroke-paths{ + let args = arguments(..p, closed: self.side == "both") + if filling { + args = arguments(..args, stroke: none) + } else { + args = arguments(..args, fill: none) + } + draw.line(..self.style, ..args) + } +} + +#let _plot-prepare(self, ctx) = { + self.violins = self.data.map(entry=> { + let points = entry.at(self.y-key) + let (min, max) = (calc.min(..points), calc.max(..points)) + let range = calc.abs(max - min) + ( + x-position: entry.at(self.x-key), + points: points, + length: points.len(), + min: min - (self.extents * range), + max: max + (self.extents * range), + convolve: (t) => { + points.map((y)=>(self.kernel)((y - t)/self.bandwidth)).sum() / (points.len() * self.bandwidth) + } + ) + }) + return self +} + +#let _plot-stroke(self, ctx) = { + for violin in self.violins { + _violin-render(self, ctx, violin, filling: false) + } +} + +#let _plot-fill(self, ctx) = { + for violin in self.violins { + _violin-render(self, ctx, violin, filling: true) + } +} + +#let _plot-legend-preview(self) = { + draw.rect((0,0), (1,1), ..self.style) +} + + +/// Add a violin plot +/// +/// A violin plot is a chart that can be used to compare the distribution of continuous +/// data between categories. +/// +/// - data (array): Array of data items. An item is an array containing an `x` and one +/// or more `y` values. +/// - x-key (int, string): Key to use for retreiving the `x` position of the violin. +/// - y-key (int, string): Key to use for retreiving values of points within the category. +/// - side (string): The sides of the violin to be rendered: +/// / left: Plot only the left side of the violin. +/// / right: Plot only the right side of the violin. +/// / both: Plot both sides of the violin. +/// - kernel (function): The kernel density estimator function, which takes a single +/// `x` value relative to the center of a distribution (0) and +/// normalized by the bandwidth +/// - bandwidth (float): The smoothing parameter of the kernel. +/// - extents (float): The extension of the domain, expressed as a fraction of spread. +/// - samples (int): The number of samples of the kernel to render. +/// - style (dictionary): Style override dictionary. +/// - mark-style (dictionary): (unused, will eventually be used to render interquartile ranges). +/// - axes (axes): (unstable, documentation to follow once completed). +/// - label (none, content): The name of the category to be shown in the legend. +#let violin( + data, + x-key: 0, + y-key: 1, + side: "right", + kernel: kernel-normal.with(stdev: 1.5), + bandwidth: 1, + extents: 0.25, + + samples: 50, + style: (:), + mark-style: (:), + axes: ("x", "y"), + label: none, +) = { + + (( + type: "violins", + + data: data, + x-key: x-key, + y-key: y-key, + side: side, + kernel: kernel, + bandwidth: bandwidth, + extents: extents, + + samples: samples, + style: style, + mark-style: mark-style, + axes: axes, + label: label, + + plot-prepare: _plot-prepare, + plot-stroke: _plot-stroke, + plot-fill: _plot-fill, + plot-legend-preview: _plot-legend-preview, + ),) + +} \ No newline at end of file From 7a409602039bf9a7a82276b78c155de658d04593 Mon Sep 17 00:00:00 2001 From: James R Swift Date: Thu, 1 Aug 2024 10:25:28 +0100 Subject: [PATCH 25/44] Remove old tests from PR --- tests-old/.gitignore | 3 - tests-old/axes/log-mode/ref/1.png | Bin 168341 -> 0 bytes tests-old/axes/log-mode/test.typ | 166 ----------- tests-old/axes/ref/1.png | Bin 30074 -> 0 bytes tests-old/axes/test.typ | 69 ----- tests-old/chart/boxwhisker/ref/1.png | Bin 18989 -> 0 bytes tests-old/chart/boxwhisker/test.typ | 26 -- tests-old/chart/piechart/ref/1.png | Bin 95347 -> 0 bytes tests-old/chart/piechart/test.typ | 110 -------- tests-old/chart/ref/1.png | Bin 241284 -> 0 bytes tests-old/chart/test.typ | 226 --------------- tests-old/helper.typ | 31 --- tests-old/plot/annotation/ref.png | Bin 63021 -> 0 bytes tests-old/plot/annotation/ref/1.png | Bin 18920 -> 0 bytes tests-old/plot/annotation/test.typ | 25 -- tests-old/plot/bar/ref/1.png | Bin 6987 -> 0 bytes tests-old/plot/bar/test.typ | 20 -- tests-old/plot/boxwhisker/ref/1.png | Bin 53599 -> 0 bytes tests-old/plot/boxwhisker/test.typ | 57 ---- tests-old/plot/broken-axes/ref/1.png | Bin 36355 -> 0 bytes tests-old/plot/broken-axes/test.typ | 23 -- tests-old/plot/contour/ref/1.png | Bin 83206 -> 0 bytes tests-old/plot/contour/test.typ | 130 --------- tests-old/plot/equal-axis/ref/1.png | Bin 14905 -> 0 bytes tests-old/plot/equal-axis/test.typ | 36 --- tests-old/plot/format/ref/1.png | Bin 42975 -> 0 bytes tests-old/plot/format/test.typ | 39 --- tests-old/plot/grid/ref/1.png | Bin 25662 -> 0 bytes tests-old/plot/grid/test.typ | 74 ----- tests-old/plot/hvline/ref/1.png | Bin 9648 -> 0 bytes tests-old/plot/hvline/test.typ | 61 ----- tests-old/plot/legend/ref/1.png | Bin 154553 -> 0 bytes tests-old/plot/legend/test.typ | 177 ------------ tests-old/plot/line/between/ref/1.png | Bin 52915 -> 0 bytes tests-old/plot/line/between/test.typ | 107 -------- tests-old/plot/line/fill/ref/1.png | Bin 57898 -> 0 bytes tests-old/plot/line/fill/test.typ | 178 ------------ tests-old/plot/line/line-type/ref/1.png | Bin 19091 -> 0 bytes tests-old/plot/line/line-type/test.typ | 25 -- tests-old/plot/line/linearization/ref.png | Bin 115459 -> 0 bytes tests-old/plot/line/linearization/ref/1.png | Bin 32512 -> 0 bytes tests-old/plot/line/linearization/test.typ | 28 -- tests-old/plot/line/mark/ref/1.png | Bin 13195 -> 0 bytes tests-old/plot/line/mark/test.typ | 28 -- tests-old/plot/line/spline/ref.png | Bin 65507 -> 0 bytes tests-old/plot/line/spline/ref/1.png | Bin 20868 -> 0 bytes tests-old/plot/line/spline/test.typ | 16 -- tests-old/plot/marks/ref/1.png | Bin 28285 -> 0 bytes tests-old/plot/marks/test.typ | 26 -- tests-old/plot/mirror-axes/ref/1.png | Bin 27385 -> 0 bytes tests-old/plot/mirror-axes/test.typ | 12 - tests-old/plot/parametric/ref/1.png | Bin 54120 -> 0 bytes tests-old/plot/parametric/test.typ | 94 ------- tests-old/plot/ref.png | Bin 823129 -> 0 bytes tests-old/plot/ref/1.png | Bin 264220 -> 0 bytes tests-old/plot/reverse-axis/ref.png | Bin 161152 -> 0 bytes tests-old/plot/reverse-axis/ref/1.png | Bin 36238 -> 0 bytes tests-old/plot/reverse-axis/test.typ | 18 -- tests-old/plot/sample/sample.typ | 37 --- tests-old/plot/test.typ | 288 -------------------- tests-old/plot/vertical/ref/1.png | Bin 78757 -> 0 bytes tests-old/plot/vertical/test.typ | 51 ---- 62 files changed, 2181 deletions(-) delete mode 100644 tests-old/.gitignore delete mode 100644 tests-old/axes/log-mode/ref/1.png delete mode 100644 tests-old/axes/log-mode/test.typ delete mode 100644 tests-old/axes/ref/1.png delete mode 100644 tests-old/axes/test.typ delete mode 100644 tests-old/chart/boxwhisker/ref/1.png delete mode 100644 tests-old/chart/boxwhisker/test.typ delete mode 100644 tests-old/chart/piechart/ref/1.png delete mode 100644 tests-old/chart/piechart/test.typ delete mode 100644 tests-old/chart/ref/1.png delete mode 100644 tests-old/chart/test.typ delete mode 100644 tests-old/helper.typ delete mode 100644 tests-old/plot/annotation/ref.png delete mode 100644 tests-old/plot/annotation/ref/1.png delete mode 100644 tests-old/plot/annotation/test.typ delete mode 100644 tests-old/plot/bar/ref/1.png delete mode 100644 tests-old/plot/bar/test.typ delete mode 100644 tests-old/plot/boxwhisker/ref/1.png delete mode 100644 tests-old/plot/boxwhisker/test.typ delete mode 100644 tests-old/plot/broken-axes/ref/1.png delete mode 100644 tests-old/plot/broken-axes/test.typ delete mode 100644 tests-old/plot/contour/ref/1.png delete mode 100644 tests-old/plot/contour/test.typ delete mode 100644 tests-old/plot/equal-axis/ref/1.png delete mode 100644 tests-old/plot/equal-axis/test.typ delete mode 100644 tests-old/plot/format/ref/1.png delete mode 100644 tests-old/plot/format/test.typ delete mode 100644 tests-old/plot/grid/ref/1.png delete mode 100644 tests-old/plot/grid/test.typ delete mode 100644 tests-old/plot/hvline/ref/1.png delete mode 100644 tests-old/plot/hvline/test.typ delete mode 100644 tests-old/plot/legend/ref/1.png delete mode 100644 tests-old/plot/legend/test.typ delete mode 100644 tests-old/plot/line/between/ref/1.png delete mode 100644 tests-old/plot/line/between/test.typ delete mode 100644 tests-old/plot/line/fill/ref/1.png delete mode 100644 tests-old/plot/line/fill/test.typ delete mode 100644 tests-old/plot/line/line-type/ref/1.png delete mode 100644 tests-old/plot/line/line-type/test.typ delete mode 100644 tests-old/plot/line/linearization/ref.png delete mode 100644 tests-old/plot/line/linearization/ref/1.png delete mode 100644 tests-old/plot/line/linearization/test.typ delete mode 100644 tests-old/plot/line/mark/ref/1.png delete mode 100644 tests-old/plot/line/mark/test.typ delete mode 100644 tests-old/plot/line/spline/ref.png delete mode 100644 tests-old/plot/line/spline/ref/1.png delete mode 100644 tests-old/plot/line/spline/test.typ delete mode 100644 tests-old/plot/marks/ref/1.png delete mode 100644 tests-old/plot/marks/test.typ delete mode 100644 tests-old/plot/mirror-axes/ref/1.png delete mode 100644 tests-old/plot/mirror-axes/test.typ delete mode 100644 tests-old/plot/parametric/ref/1.png delete mode 100644 tests-old/plot/parametric/test.typ delete mode 100644 tests-old/plot/ref.png delete mode 100644 tests-old/plot/ref/1.png delete mode 100644 tests-old/plot/reverse-axis/ref.png delete mode 100644 tests-old/plot/reverse-axis/ref/1.png delete mode 100644 tests-old/plot/reverse-axis/test.typ delete mode 100644 tests-old/plot/sample/sample.typ delete mode 100644 tests-old/plot/test.typ delete mode 100644 tests-old/plot/vertical/ref/1.png delete mode 100644 tests-old/plot/vertical/test.typ diff --git a/tests-old/.gitignore b/tests-old/.gitignore deleted file mode 100644 index fe5a11a..0000000 --- a/tests-old/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -**/out/* -**/diff/* -*.pdf diff --git a/tests-old/axes/log-mode/ref/1.png b/tests-old/axes/log-mode/ref/1.png deleted file mode 100644 index 6c6d2afec42c25e8ebadee6ab5c4d476e1a9cb76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 168341 zcmeFZXHZmKw=Rmd&`MO1+<=OJAUS6Q1tkke&LBBw1R88Wau7j+WDt-bNiqVBBqc}^ z2@;wNlA%dWx|7%Ne&^focWUpt_tvetb^dh0>b2&YbB-~d@r*D#9%G{TD>@m7B#0kE_^5Zy+|90K(%Kk&}AL`yM za0v;4U|~Ij;($Mi@WBTSIrtFa03RNP;9~;`K2Qj7`(HQy{n!6_1Z>RzV>@QjxTbay zcSaym=~mV(Jv)7Jx$}`FGoJscocaQLt5QAm?_g4KxnmkbAay8KU zbcE_H&-SV?`dty~JK6nq@7-{@x!=32Iqnf$;BjEk>)G@+4q>&L^AxLEu1x;e)y0yc z!w!d>BPc|y*1?P6Q_^$cCD*>v+<$j(u~o#PWZ=eV(v2IpJ(o~}ax5OJ%oinT zJ_M6px^?+N-v+8F64(C?WS8TmY5Bc6!j+D_=Jk!4J(?~fnh$&9qNaAmy2oX{{NBy` zRvM@MZx20@pVS-c)_lt+ZV_;C;69hkc=QR0h4l$Zm4Yx3Vcct?TyD;SM0GovnV3~>MA^&n0s&RUy9ioQCi(@Ttci>Qdow;|%iNqf?eS8Ynq10}M8nZ0(S z_K9L*1e&m(?XX3G^4ec7pi*f!dSZxm7e^ZBBxlU@5%a4bJD#}%8Mm1!BfeZ?kWnes zwBex~v{K7fp}%@*B+%@=%>10wQ%Tu>uqepmn=srCx30=$tK5lqs!5()Z+wVjM&4$_*BR(pKQZSNoin)}-_vWs z<9GdqnF!5H0#G5;2n6dHet4(hF@*;Qc?nCBJVQLBq}y!St&Z$7Qu|$;sn$rZRRtwJ z&WM9hNn@Q5JqYS= z8j)nWNUBkVJN?zSx8E%k2UhqnO(2oxW_>`Cg!49yzW$e>0EhLOd5_3w@5_Tq;~F-f z%3%07CZ!2adFu7oVOIj$^R~TkMf63ypAm*%zbM2ZRC1B9%Zh^zIYuZy@Q@+q3)Pz& z9;KQIYkfQs*H306B~yLr$@GSsjIRe1Mc?`?@>;#%ISFw@t~-f~;RT6aL>~g%$uPg^ zz&jPUV*r$OU3pgtU?O`;mk-AftfGYHIAZ_&EO29(Or!3@YYwU!^hTyqXk7 zH2JAueRd>QC9g%^XwM$LckVXB?4{?eYlm+R)%H!f7>Y&_vgJZ{Zq z5^4E(En&0g&iV8BQHN2lyBXXj#m943o)h4Pjqe%lN%-GTl2RTM;;`AwoI&dzQ~R!D zKX}BfAnR^*cdw`CGZPoy4$ZODhY7$4)3jXb#s{r-+7WPv z8r`yiFNO;j*YQC{I;Mp~`uVw6L!Rjko4{ez|K7Y<)wly+lv9n3Q*^gkNQ89Oxse~F+XuR* zMAR-^N!>2E1a6Tq{f9&y#^g9QdqYjfK52A!<`2f-8ArK##A$N8*X^AQIwk>}0 zIx@OS99YEcB^}IYP2|V8_^gjMmH6^|MD)&O)~DhW{Yjd30#9d#YhV@$h(dZtlaR@WPmL&(qF>i9_K= z=dqrL8Qh`-g~`fY;&Gmi6!241R zD?`bP88|sO-*`E`NoZX(G`!*OxK}Mh@WEk9ayTq3thLN-Ra;ZNSRa084>4s>KariE zAA0h__B*Z9UbIjT9_3o^hM%M^fBj`_?0o`x0{M$Kz9PRO6;yR} zboBH@P1^7+3~lI2?I*=_TN~Y0UlXTjy*CTNBE65Jl*HF<85hH-Bi&o+pc!%~?byzypB4@}5qkt6=b zy}%EfZB*4V%l8?@#ZP-P?)BZ2npy7-@w3ntyqO9mq7kv^O)o1e3-m48pMi;a?|;ox zjXrlkf3v&-r)?a?#J2qHUERpF-iep$5@xN&&-sZOJT|AhFLNkLJmsIxj3sEW6;f4C z@xxj>8S*!0JB^I5M)|o-3qH%b9l(LHaJc>SCY!eClrK$hVk(aMoe*W5ifS$lX;3Ar z#pXDf*J7i_L%H*(3aYx&kuVto7~axhCwrrz%oW`Nhw-tUSuU+w7p9_@GsTrfMRp@) zrcq*;x7Ch_@NoS5a~oIPC*gbF-subs43r+<;N`dxK$&1cSq(=iRL(}0^UkzKt@l3b z5j(_pFQH?r#$KiW5K{KWkz2Rl@bu*PWn5Q2c5Hk+KOc>NLwf0zZ9b91Du^yG2g|%n zVnS|CvR`){M&i{#c{eIzX*%Lc51x=$x7fS(qqOH@-t)OqS!HEcdq{H&`KosoqU{Yf zXvvSm(yP9S`HS^pw1(pc#j`yn&`itfQ67{7Z5WjpxOoc6$A)_yi84^KNYS&)ZMwbN*c8 zBS^vUB(zP46LYWfobSUyJj!0O*A2bs=XUm4QK_?V6e-phV+AXeed94{k}xtWW6JZ+ z?9o&ZEoIOTB#BiKlp){|gxJauy9hcjQcaOln7{KOy+^-B;>;2Vx^{piuofc@(M#LI zCQ%e^UTZbxC?rB(yV9$ACQ=y16v2~;xJE*v3?Y_$i@3zg!OOAGv|F0K`I0h$IhZnm zvYNme#cTb|kiA(Zh9O1Vx4}pxGR^lyP?sS5-ToEI0S>W~{TU+hBaW-XjkrF2`$sIA zC0MQXpI!&2Rv5S9{?y|P9727FNj$2t+VIDQutSZ{acwkTbk$?DUu~bxV4z@xR6(J( zwJ+P1`1CtfJsZYmBo{UoePR473D+`HJmO?OTI5G^Vyr}2(;iS9lN^~A$hD&GsiNuio zW9Dga(&+4#)Pw|&_4+-&_jLDOiC^yQP=uJ6_}c7ke8giXPfPMz`tCzPS{#!Jnrzc8#ITY=Tp zh93s|inP9J{Mkd|Rk%9urSroNwet*EO;i0&>{_JQ?@*JI2r3-nGu4=0M(Pc8hRJo{ z_a^pdJFU5irn?v8*Wz4=GUAjr5H%Hz@=@6Z_UE?qsfyi=mYjmg9^F^^gQAteAHUp( z6qupupi-XJBlc|HJ%Tz6oxg*+tlaGTE8{oeHy_03w+Przd;qhZs-ka!#(4-t1V=@+ z-_)rlro&uwbGz&{RHSF%d+f@HdyLsxo+1i(S2lp;<>lqrUHv}v>8Ojl2kC=BTRH~p zUcLPNB2V`YGT<^0EkRd;COYVY8oLEXa)2tXp*K8)UHz9V~`my`o`v)LPLc)nhs&^5mhCQdK10c%!fH z!%YmvlkU+sfXR7?OThmGP*!L2mT)*u-`}@Raf}X#Ru^<%OSXy@I;Og!_!-X_R-yeq z*se;5>3BRiO#j1@y+V7~cZR9GpU>d%~M6q9%R#^6FBEz~AgH z*dYcHAKs!CHS1&@I?ln#(e9?`S7nv!+DC70Pr^Uj3qCQcsHMlS^OeaMA8gH02UHdy zwzsyfOH|o@pRc|i4%7G9?&eo~|MjpLBgV16*-mQ|UvSItaCfzYMY|kW{7*653&Tr! z{W>&cUO65>uh*q#aJpL&qWJW-*Avz71l(2OErS|1s33bVFP2 zCY!9*d-0u)WAlfsZ-FD>t1zC-pzHqn>`|2kV`_Fgi|5@XeYh@KwySUG@VG`}1DJy#CB-*Xa-_W*-`JWUCV)}i;UJsVtejBSIBr82xUNDK5O`rdBap$!Q zL@f;Q&jrkNyt)(JmU{0ZG$KBU&(=vh6llgw@cM`CNTWuN&70>RMN#o9vY51mOUSbX zY-Qs7vR$CDv|JDWXfDtb65{3U9`Sa?4>1f1lpKc7$XsMy%;cLG@Onh|bobWAr*tFK z8brNzy-yAnoI&hSx=OOa^-Xa7s zs|t`#1&v^(vEzp2qB^mojeEJgh@{<`ni_g~`f7!U`2X}E=U3$ED8+=4^a#VbfeXhj1n`ZLHR)@CMuwS1bG zC81U59)dBkReHr&9T7>p{X%@GT!z+|Nb`v}H3H6?6A|Cs(`!TZgm0Ho4 ze!dQQM;6~#b-RFCgZ;K>4rz2B|LVNP-C|g4g<04c_oQbHIXXHbl)OtRz{9@S2xp}n z#%i=285yZ_TXoU}uJ7k&@Ho(1l;-eZl57t^-hof&L6OY|NSLEGFuax!gjwLoE+RtocTpn zh$?XMfK~ExWZ>9AwhhPeUjKCRSxN*VUb{9nZfC4iz{r?|l~qAO!PU46XiNj^^)%^XbS@iDRt8&D($E+t2PUz53TzR(A z*6ipJ+FhwP1SbU%)wfM;4^g5rMRMW(vUPxb(mTvls5K)*IBiWlxv0(d%OQW#n9PVF&$3JESvUKSp;? zkihW*)S|o!yi3)%+xO85YfnM?X+rlx|WvqJ{H^$t^h@%q&cH+VLtTf=~R{0Y!H z)ITY4c{z{G$ERI$Eg{7E=Yy4R$eN8=#+E;$cxS{(X!TfAUw_Tktf0K(t!{wuExuD~J|o_uixN>WFj%kdO5lW0dVuvR%5i$4EyjY|=oSC?Y<8aG8z$3#E4q?_ z1=0P?+U+mGF)=YzY&dIUHQU`u{3*9lqb=urk$t0^62yxKMCkbNk+O#xv0m&Kl63w#r?|SU zSnL*7hWXd63?s#O9cGB|zfBu&a`#_U!A^T(VYs3^+wyW}dFbAr&(UVPMzMbDJW{XL zB~j2hXTkJ%KFK~+$Tjr0GtNT@5;Xvl^=mkveahmX(ywa9`QIx-ytF!Y5A%cn! z&G7pJ$YE^RN?9SN*@wW#xZQR5_>8mkET*1qTuG@9rBuH5tm>+FpkcJjIeMiM6!F9U zVAK3Jm%X3886K8pZu-dlO0VUnRDaF=`y~%=gx@ryJ{2x?vT-K=T;io!y2qBP`6a*ca7hV=m~Dj`7XiP;;AB}%f^>?-r&{_EPdi5#K(FD2H!v; zPK+bGdKf29B8R1l;%R(B|oWU~< z(zya0g!n}IT#dC4vQ-3tqau!EgPnK;xRoRG5IIG|KVZM}WzGs>|L+V1e z8TVjOFC%}NfkBw`p6N**nX$mPua+&v;+@VUF3WocVWpP}27mPJF1awz#G3P(xYb|m zoKxXXoH3?(7ui4)e*A3iOZc&fez24QX0)6C?0+%|2GuquV(aRgVR`QxSa+s`Fh3OE zFKb6u2s|?D5+G7WyVen=9n5lF9dKD99Z?G^upP0e(AKyT;$(P?QEZ$fn-X9Dy_%>a zT7j6)x))Pq4Mp%g!JRI;l0J!cVw*Dy7jTQ4Ld3;leonO|B35k}8~0-eRZN$=e45hwk={hFD01qaiF| z55ky5)31xIz{)dN@>P-;&c6-~lsgY9U@&qZKu6TMJNNN2v+@lbprW( zW3#!tlWc2$7eiu6goQ!^34}u!8Hm26VMjnrBm_A6qQ((g7i7G!upA6YA+Q9IVF?2+Ab;tcZcCGpU1EooXPPCpHp7@`#ms`!Mau;BN51vp;pVqp>D zv)~9g+}t+Q`~8-71fm=YUSY_IM8v#?E;C!e$nicRLc|F1--rUGf`pwTO#URI`9}jC zQNAl<3ZdE(*8-;|9y`-)C0t;5@U)blDCNe;n} zZ*N@9Pjm0>&7N3(mj!Q#7sD-uVnGAVkcgzNCP&=YkoVUK3o$TqVM_D(TN?lj+dX9b-$=_CsG73x?&E}@xNZ}o3Czgl&tsKsD=WF}*4+J?`| zGYAqU3Z%;GuXOB-T3A?!?{X2U7kl=7Ro$_tWZ1Mlw*SDpA#U;b>hwFN?e+N6x$l+4 zsNzYbAs@jMMW*aE^HxH%9DL`{_vst?9Y;^EkK<-Jv9%HP>b*`oo!Lx{d#4)n+Xr%H z9OMp$Kr{j+W~goC{EfrJAQmNwr00AD)}-1yaGJjb6z@OdF5zrQ+JaC^gWOv=heTvh zIG`Au9gf)kO+paDu_uI)s=crTY)8Yv1>|Kfplud7uuw z7BuRy;br9b!I)bZl=Kh@#iJM4(Zq4L?0DpYJ2$iC85!DIsIH36PqXq1ijNQDpC%{I zKQ?0^K5hugP*v3%)H}VUMteMT0T?M)FcBOM2kDCFhi@t>NT7-lt|edo#sp;9U#83z z45_H7I5;=}VF(Ec384OfbCo%?Ac9<|3;!Sy01sdqF!BWa>E^ZZI*=MzrjCz~kAewq zfy{`&M(;a+SJGb&1i(t%UuX~`G4{5or~xFG8#^d?tzu^c{`tmx`ARrxV(>H93TSly zT3&rU9HcMf*$vd^XsYbSN9)|}?d;a#SatIA^T)Up%+1X~5On*RvG~8g7x+O3!-q?s zufqt#ta5U5SxI8fEJSWtB zI?AIYLKb?H_Q=(>wOzT|+S-;-koP}s%?vI-T?2V*e!H0AI{uRgBH|hcV#45&{;7>h~y5^_zhro{*EY+=nTOr_?XCTu9(E9K!`a!G^N|D z_uBig6+}cEdNobZ83(G!#eZs@`7c_7LIStY?BHsMN1%S*W)(>*?sF|yuk1-kesXH6 zm?wI%p+U?ZyAmuIez*(j#jRmvX`){0YUe=O)K`!5kiJ)EY`scwG~CM-);Xhm;VO-t_e8Q`u+lsP_-H7gVZU zmQ>W9g56U&netZ{T?P4WQ~KDnG+##CrA7FUDbY$#kTg5ImQ%Y)#*SBu7LS_96}f6* zB5zY2a$?BAwysJ;A-_Jw+;^&&>B|@jYA`SHx`CZyLCv^=8zFdGFik}fm&HfA4P>=h zS@&>FoHOWSo=&%fKzLhEp^6L>?h>bSw5}(qcXuK0>0*zO_tTD%KecwU3{oJ91V(OU zUs3__J=ih@k;m3o4YgE}r)4@8>qtvWOYz!dkjj4l{ykmb;>X?H-C^8XCoH}NF`uJh z@@-(}k^9EJMws!&IGAwXVx;5mZCt)D*ZG3aSYJ9V%-dhT1G^fPiO? zwsUpA{l|kwr$-@^4f8;YN}o^A3r5a}R#H}L!PO*EaMDPI<4@O8PQ`c1(&+}f+oV#c zhV!I`PWFe@Hoi*gud>q#czYhPQY!ZmeMrc7XQF$4v=lG9t>FsAll7^5d>a(CmPX0N z@>)C+c$hN;Lr`tzs=mS0CZ*?p0w+4D@9oAANR1>7}O<9?W?5$E;jvfR+IPIyM1@c=rpfT8R)HiE(+LyW(Wk#Q$ zGhp)C>fG0vb!BB`|7=jxv(A>v%E~Y@c26otsX%(?!DIIOeHIE4q=wl|KN@%1ZZ26( z-(pluenB%-s72QUebat&;q&S1Xdy;nU2J#i zXP7ZUgfGAWl$%4B(O<95&D7kySg($ck@11Pl&mS^;CP+8?<9h{onkR5lU=tu9hU@T z--6d1cYX|l3P2Q%=++%H8V#am4tDl+^wPjqtjugrn;P^doRhdU|^K-Y*Uj_c9)-s>V24 zT3B!xY<^3pxjD;#`*hk?*TqyXGq1RqpR-!{F|Sgp2q&&y5$IsJ{Z+vdU4LQ1uZO5L z@=&jr6eucwwL-xzws;OMukmjE;>O6{O^?%y?N;(=>|8VI!-q>m;qV7rVTO~zaBOOe zu**6r1>)JHj2*$)6FF=|Cv;h-T_vtQWKpoDfty}F|T zdMh$BGq<*!WC~8M`m=pq>>$mB@-4oby??0oRPxfwog>wxcXyx2;>S&g zW`<8Is>7dTX3~0a-qUHXzExE0!F$tx>5|Gvao1h+BtVM}*Ga5jnTh-FWsn3U!Ev9Go9f5;vgrdTIn4bwU^Je zg`}whDH4|R6t6TGDvG?h{bA>jJ+vdwYfW^|TTO5>4f8`E_zY6OgBnBx^zr^;=PZDo zgTMk@_U|bq#eghRw_5RO{AZ;c=`r6LM_r;3T`ZPz4H)|I{34qn@YSlO0%83^FYGPJM-r}_1;TXbWiRo*L4awD|aX981Y?n<8HF^F}VTyoQ{34So zKlct*%iZ{%o2JYd(uwTWNXNF0+(fPn(W4lq6Yg-jko5?GOPf7oqGCPw0M^A>8-p0` z8AJ!)o9O7UKAO#Zj4H1PSDUrmO0%Lj^Hgh$>hs>ORYC|?_K;~^b7}hF1?dmgPdm@3 zEh)Sa8VEu(XkaS{jChG6hkg>_FhGR=T;?K*RQt)W`xcNO&_f|h$i00C$Cg}3xrd4k ze1V5rHlc`+rak5+-o31yzvNao6Gq;cO1h~Vxs}^UM0xIHI%#cwqQFFv!<3oOGe360 z*V_6{_E(XH)N}7ceUcMZCmBqccUAWS^Tc}h^HJR`r0!hV1fk=1GHEf2A=BpK}- z%M2f?H?tl@k#ghV;lVUPgbEurM%DJxR)gyM+S=N6;D@*c3<15`^7iub+PLzuErXr| zMtHAtHXX6~;@h`xDze8u;^pQY!cm9p#7a}S29<4Uy?4azX?*>di1i7s&2uN8xbQ9R zcrstzs8)S~_D^<7jHy>Y9;#7H6^IG(b-_L^t~T z`;X;So!`=I1*r8}WvDghKPNrzxr52-TlAR+)tEm^+(T}mV*Z$l4-r0y=?U?}K+I}Q z11tU+qmY6a<yd^c|^sL_V!{84+O z9kp-ahE>P4HZ9Jy-1?^36C`8A$H%v6Z{KeeNEoq;;TjN9Kc-^nm!IE1K@Rv14H9RG zV>7qu9<8Kxh}Vb4ZK!xmh}3xPu|ea2mxWe>ShS)y?Gg4nQf=NsZHqn)Jc2*_XYe1l z^}oN{w%z}*+py|aPaO*X-Ne#uf7w!zUMGKl!lW0S9_D-c71S60@sqXA=GCC>kNT{qbDKMdb`?GO4Y zzM!7T%qRmdbDfwkl65XJ#$Vda-94!txXM_S#!2ymx3Y&(N89o597)g}JxR}11$GBx zFBPl5tkbZ)%(!v8UY{M9Y9K{{fUJ=xo+DE@-`SRH*%4>aR^^2}PIqGDROx<_6Z<;k zoxrkXJ*&wc`Eh7NtGgvkrAFi6J?-e7llA9oMm8DrCy90KyJOX5Wz#3WL1_}=yHDd` zX}RRXKqmdgR`f}spE}+;;-^Y;uAUL@uoLfV;srYaVzPEL?S zTp%?)_wN$Yt>z%d_vyB=wLSJkzA4R^TvtkM7v}~kq~4b!V`KQNQ|VKS1qB66+4{v~ zql~d`G<&CW?#mHazB_RKWVu_Zk_UsaOlVdZPx8rQK1~AyB_`e|qc%k`$lS)O85J7a zV4vZflC+MP{7Iz_`E|_-;gD!62JY;y@-~~{Ips~eyKYM?#%H!VnO$n~Q&!Cj^&2(8 zbWBw(f}h!27oI)@IeS58tlvh{wX=szQCzT@2Q2##`T>6ECVqKTyle!J&fQ$3fT{21 zw3OPqz-Y1WnB|52Fnce5veOB1Ta|g@Kpwi+vjRYo)|5g<9yE5@WEpIZe6~*rO}YR2 zGb}87vFAF5dwO%G9fYsljhN@pu?bmLL2}K8bHfWoI&?3tAbn7>AW#jD74s7T9SqM9 z@Rz9yasXHM7Xtpg3=r_lRR7^}LHb-1ECJ$d_`lx$_hp+avA+NURe2_OZJMsLapsYv z5&$6n4FA^yngB?_H$Q2_Z<98u`!9A?{-I>+LL7feO0|hUa`{}a<9PVtR zG_hHU=8*uWrPBW8IyF$OU*mvB{*12<1)g}zkPzr0cy$Tz&;ljEai?i%YkR)+kUvM9$1dVje^gzdZ>@E(<$*nqbRW5~Hdr%^Y)lvJC$*QDWkO9~|>c%T7Pd z&(EK69CZy1RoQX7y@c^V37>;;PdcOle3AevTBLFCrlUAVKEuA%`PZgNPN`C4^z!kvp;?VWxX)M)g9@S}rR5=SOrnYvL#2nb>ZqZ$b-`Cr>Bda^O+Cuo zqOpRb5u(*8x<^OmmS|Yi2wig#GUw*EpYV1^9`3C- zZ&MoYd3x6C%P!4z#xstcEYrGE0iN-FVx#r@kLZ9%MsHrgHMB)5YY76Ijb!tl7K(JB zm2zRnBSZo;cTCMh5vH#{%QNv33pZDpP7Q`G_Hdv%F+0L}X+2j#P#~D}FPI8xgxV?m z1x-YGq}u2~$*_jW-@@vZg#yy_OSV9azhrJv00A)dUqo2mk1!!A7fFBsf&~=zmxkZH z^N;c_AN-%({Ldrn{PW2FeDnYPY53R7R85}rOdJIARJ1uBp@S}Bl-NW2SBPd=n+tD$ zz34x2P2w~i|7-Z+&0GE?yB+ByQ&LUxxcE2MZgMhL&xNUty^O2ekm|eYRb>4=MVmo) zO9(Tg_u6`INbspF*LQ4!JQ;&hs=TZX2h{AJUp@RtUq3Z14K(OX zbDFe)J}!Q8^H16!tbU!8l=SkYBpr>Q)2$CZHO>nvs;YTk5+iBFyc3d>y+OB+;Vng= z=Af-tmKMx6YQ-I8D&?M=VbZ2>Y`(jkx6Jk(={P z{?IGb32RrC5;&UH_X=2x4I0&Hyjw3Myre1~l50OcK9+VusaRKOV@HUP6NWGT6dV9Z z5CxfV?w?QUHsecjc5+G}8X6jE|BMI=4gHcVNF3ghEf?>$HbxNv$<5C0PUH^a?dR|K z@KDhSws~XFOAhBi?$`~qH1QCMxc>MG8um)0^Pi@ITC~&H@UQ^rx$`vs1{SEm zKy1s(#>&bY4msUQQqA*|rosP9ThA2BlIUk`);PVC{4K)=*JbsvR!RP#CBpokg|H4p z1#bdgi5)cOss*!`qLPw%3K^^R{iFa00*l(*)HK`YGbn}!Q7nQ6x|g>=OO$D#!%SPm zZcA46S`1c?=t*JglC6?WfV|ho8K;_n%jhc6DNAc>Vo47X8X-`TJg<;**^hy=kCwi# z2j6I^Kl|k^km$ytxNtTn6jE9T|1IhOgy&5hpAdth&5y~bb)fT+^AIZzF&sAgbwA&RbT`S#7yCX1sZhzXGum7z+Ffz)h zJ(qx18^>DNK6s1!`oLiYmSF85A#sk_aGdaHx)sl9oaByrz$7%s4gTB@tcphpbQ{%@ zNNQ*d)m@;g@0UBTyWypyq|{Rv@Zi%2Igi`Dk{3zWTJG0ahAg&Uel0&Y&zvbPo9TNe zpsbARt7P-pc{4&AWUC?Q?0Zx9_)=jol_#FX3Aon+CmWu56oioVTbjd-IosqFP z2Eg0O_bU6zfF??c*C}~Ydo^xDjj|*zdNDdzD*5TK4h<>SaVd7~#(I6>`7D-4h zGhlpgGr}bRaOzCDIJ)@WVzibVOhr~7@vov{W3Pp^UftHVsDx^cQ^B$!#2|B*AmBKS zL}Gb8A|>I5x;pM}7%^S&e2wmmXBReo7$Fq22;%Usey%i?QuSg%Kb*AWv}?jH>N{&ddSi$CkT5P$S{wdy z?ZvQ$wjhcoF%is%sK|$$X#IfG%NAVEFY}bXo_y#IrAC3rQjD!cH)xghfCM zsvQvFdw_acbsit?<+WN>NITPdfKEj|iq}OK3jGwB4!`eBUjK1J882cni1y6hn3zW4ESUM0S^mHu?_aC_I_Tpr1s)u@LI?I7CqE`DEjc&M)2jg9y722*|I3cz?Qq z{80p_=_QahwH_+!{_-WqU#&oWyxz+_K^3&vO#)P|V3Yz$D`=1@=sxDT8Ic{HkeDbF z3YykcE1Cgk+FSh|pOhpglmZHIF)__2t)+y}oYKh8CAFK;^jZ1-7lt3768&+WCR{P? zPe9ww-%b?8*u1{S&&OAGdI@&ygPNW;zPiIl5!F7xcR|g+pdFkZ6EtLhYCiGNu|~I$ z-lcb1i1E_K*y)Jy|0?MV;onGH82YkCo!^?_R4gIZURbn&c~Iz zu>)dbodZ3L-3nf7+v~~e^~sZrdt*W3*{9o+b~;rfq%(^}$ci3ChL~tl!G`PbD{t^7 z%p>ll5(wnEaL9)coK_|qto^xLJn-&*l-S=+DC1q z-hBibkRl^x;kkB!CzF&w&Y><(lQ^fYm*yC7L@`vBgqR_ zl76C^k_}&#a1ewN(x@(en@w{T0mlghIDTcuUtnF`fP=1#ZqJ9`BxA&^Gi1`%ArSB- z3W*q^mnMA?RyvFLh9)gi`?4`dT=v-;VA)u*4P zB)!bd6smsA4A;PZgO_@?p%D=qGf|O|lqM@CNUdpx+wgi6vkl!+yV$cSnUSV&*s#}& z1ip zowTn5-Ms|5IibESNA-`uc}mXE25s-z?WZ#r*UR)BR_(K7S~vXq_$3zy1MN{n+Fn;0 z?2!eJ-O*7AZmQppH=a4bbDBL>a+mECthX&;DfY;G|xMji?NFoY$b9S!>z4v}|(J_vAdP~7TA z?0~M!CIOD6zjpp##i8~O*Dq&5h6V?D1uDS-tR)j;W64uAO7P3nN_-#lB7{+~rxY%h z78PL2#Zb;ZMcz&#Y?=4?HGbx!+vFxIGfxPzmWD*yln;wlgcJHAVjqgHp?MpA0QjE5 zdBFVYo;nHfwa?x8w-Qft}Sc}^~I~(-Uf?CJ7pD8 zC$Zym?n$2x*Bg7_8NW0HA_i?h3p$vXm;^N!2^LN>TYS4$M*13kCWuW=F|mm$3v27< zKdWjW(w+Mk4WESKbo|xD^!a);Vd0)%4q+QYh8D^Ihmqd~8VJQ<@P8vzQuI&P@BfGE z%wuF{b=oXYVvUTBj?Umxj|ud7HZ^5rnJD%VR+l^Kr2fpearv=SQu1S>gwIbhsfuLl z7V?kU_tvu)RLcF^ZXDvEM#f_zPcQbRAXZJj?!EBe57Kxbkly&R!G@TRC8K@Q-UQ~T z7j-rDu?w+@aLKuPLb%os-u=kpCFshsXz|+OWasHbyxUVe55gjiY5P9$@sWyg-nEhU zL5Gs6f9ox}3|Cu6rzcr(dk(*?y*<~LmWnFI99!^XSKK!^_8p|F@0S!x5z+Rz{)BF` ztZ9`|lLrrwlcqBpD^wh*Vf7NezKxu1VmRT?uBHZ`UO$^$4|Wik+91tc@#vBf1Oa)y zra>I0FsC?xR?z$CcpdIL=JW!)VlfTlbr28S>kJa$3$TEgbA&9`8O4kLD!anx{~gIp z;Jp?iKNHuPNu>jGJL@vJx`eF7{U2Nhy^8;MfBrlYbT91e><|zTfckM#LV|!W#PZq) zDtEKCOts$A{S~~YRwvM|Ij6RSzCLBkstX1Vk8+QCD1$`mUZ}R0OQ6MDM%$nHU$7w_ zGmx3|%KTyX*XEq^xp72rx*3|l2U(9q=bo-ZX$6m%PQAnf-RpCuPLg-_K7alUaO4*K|w)ykxflak`zOWMv)P!tAclsjb6FAWu=-d9cE4{f`&pVkz$oE z-6TM#T7xfp`=?Kzj_Pb+sc=Xew~IfpXsz$t2Oog!WG5lgNbV1*S9W#Zy;)y%8m#O? zyJFqz$-dC~PDYBhXcR$E{jyp=uC^|RwxoDZEngV3x7E0pEsB~H9nq_0Va4Qr*YkC6 zT$!j(I;Bnc)og%rM#-f?06B0Iuz5uR8vXvZ$U$Ro?+?v=-@XMx{wN}?!0!LM7Wtnn z^?z#FuMbZVM1pUw>!X-0exrDC-YM<=i>&&3>OW{7cbk=!wUc=mBmz(GbFY$e+r)VtEf&?vo%CD|%vjx;=9>nU| z*U?tTJ%J{$bMY?EOQqfeor<%f@Wo6GN0DaJ2crFhPq9s)1LP)9K95B!!DB1e4uSJC zVU4O__?^hd%+LQt4Lms4|7Wa3;wH7`I<^9J_!(7^#|Usd0@y}~FOL)A0tn7OT$geE z=j;FJ{{M9S|EqB{V$Q6J#_6HN%D=2nLk=@DGZO?`T?O_0Uo5ZWKIq8NQkPIwRV~$A zamB~SN5=sjn%d^K+W}e`}vS z)iqpf18@xCkNPdtuaDNRyTc6@U+wEW5t(B;7}xKwTW_c<#3%eij+c56%Rhcl!iwv6 zM;|Au{g#L-8KFUJx?AVT!%|$<;(Q5EM06{hT00K0SyK?+xt$^$PCct0W znwy(bQc}M86xYnZ?)(qtt8ote4ha%r`d>$2Y5j}>XI{Z?4Qy4~et#n>wxaPg4Zb@9 z&ZyiSr8_%L4J!UYv_2jn7zRhEbg%u}jKQe}Pyny_jC!S^uLWAF^3@6oRm2WvV;tS* zKraY54GY?tR#&Sg?RxgX!QEl$De!k?j}K-kcXtn*xnKOIV?Pd(G#{y}o&I%pKfCAT z9JxG>cZT*>MogY>cL;2NvtNfl3X0bz8z)I`zZcp7C$@^})@o&!#w)Cc6!^y<-@Zn= zhs&$hdGX%D-AVa(t!kDTV44HK95v>|?3gemZn27*n z2hR~j!ukMEoPP;P{@NlGGJXn~C~pQ*#=_vhDiiXKdg1C9%A?P@=b(53DMtZ#>dCo z61>GGb+*YL*muWtn*l^n9+mNBdSm~W3yMP%r=4sx5)P;B5RFY@wh!HTj=D~cY`c~C z2ahefU-Le`{ELqwCJ_)G%?kp<2q+GY%?vo@bBAiuv7NdSfDTK#H8{!F>2nk&M~kgd z0=mvX^0Gp9y`jEOX|uc0cXNofujl>%ZO*r2AxtUp^q6>%`spkNzcQE zvznAdcVaI(lt9REvAvxF_EO?o7>i|hvwDK7apSSV}mU*KN6)}>pO9x5br3)~=wA3JYBPCn7-gKFzH$~Kv%ixQG(rfkG#}>e`S@EGV%n;Y zog7zsPcGg0M61*2Ikv(_ttD6K)EZE4N_%Hi#OTm1B1M49=oQJ=MVH-U#IXQT_@5WP z!kL#SA*2=S`hsI^jk4g8UGQ0Y$KELdYTE5IUN#Jr36IgQjtlk?Kp{TD6xL^0K+%=&Qs@PaJn1z~9>NNT4Yc9N1 z)%g1G#e+!?se9j~<5@GSDsl+O$OvVqj%Kq?Vjd_g*E{76IdXr=pFUj;JFF@eT8lf< zVVL;fy|Q>@p2~2z`r&CW@~lM|{N5RZf2^e;&4sX_PyIv43)pp@v(XQvrKL5~3qb)R z(lQ$SI0qx45RH83C%R3~`c!f*PA$KtbS`>z@%Dx7&08ZvVPRP-BsE=h%6$23t>0CM zv>s||5okKD;`=ljP!NZ;6@ABbPusd=jgEVo<|N%`*Aq`sd2WO`qa~f9^tPdiFZdFtMf(Lnz75`VXJsv^{wt zOVH(S=txmmj9BVyj$LyQXfxJr@J>|e=;$!pqDlDpz~DtM^X0=ErDT*7?fQ!bX?u+B z=hFnZN^jJ>edK#@t4u1UE_$Z&rdNmJ%LurJG=XXQ&STvV4uXAZ zyzwq{)I@%A1`d9ahKX`pRY_E8^X)^2RAKionV-OM2vF4$(|fZB+CDkuQoYs$t)7pD z+V^pUmy*9}23>5neBU$f1*wBW!W|;la6b!U!1;2xfn>2?hF>~j3T}atm%!y4O_ZlS z$$(V7vCpo4RXK~@eO+bU_Q{nuJo?|o-cSy!`ph4z*}ojo_rCJ?j&HoULKxK9`F}C? z7I0B@+xxgWN=S#4v~;&fDj^M`#7GWE2}nzabSd2(Dvfk^Bi-H7UDEL1gYSFad++!D zKA*p+!^}Bz&OUps^{ln`UhCOiAf-npdBI^G_@2SIaq9QR##XH!k+nS=OJ=+j=OG*)ELdQqg@I-;_wPAE@KKth#Q7#~xBGojFNyf~ON8Kb!vJeP$QDGn9mOVF`h!V-{pyDN%}A4V&h-fb%)0T_mFEBN&VRc1iG&c z3e+FsJ{a*gD_2LdTASm!b>cj zl=%oz9ODhr3yk0H6-D{BM{UYQM(bKbJ1hAd+jU3VGm(rP=i3vB@i#(H4Fcv#fpg{c zHUB41^dcWU8F`95Sx{p@$CDGtzbkExGtV`e5G>8wO?P{3xA0=1!YdS>Lm;vU{SB{P z^f&BX|B!@8GzqjQy{MzN@aR4o>h7E> zAXTuw`S$$H(Um%hzsbf_`IbP%@L93+@dLEaG0;13D+P72dLcZ+?1iJbZ$%rejHz+! zGI?Yu~mVQkJym^E_s#JfG<2y+qF3g)7Sd z-Q(}#n~vOm1Y7>=r6QmzW7Mib`-lMxn};$F{ZQNB=Oz~Rm$LDS5;wMM6V-TyWXjFc zHPNE{COaojdJ2vt)^B!@3=YA|- zr3Nk5ff^cR7yg|0XJnz;w|g8;-zBFx$_a=+?6X?>UPv;XTjbEZ(7lQfd-`aVUVal% za!D7W`S*Jg5v;nyfv%5f_i2Uhi;PA2Uy~oCvZnO|cb)~R7YQ!B z)Spyi^4cSKX>2#8%gWUYfiQvT%KnBs;di;%lrXv2YJ|NUAa+g%}-ffKkY7FN*)m=s{U>Ot2Km z9k!5J`{QOP&LQdgr*;QI{n=`N-AuDO3f=ke^WS76zE=3G1f^3QJ^?752)}oCgdHM2 z%h#%@$K*fENy`}&={2ShOV^?5eU(Qyn`8W~T&+6!(kva&$>@A3>Nen^JNCrJKHlR_ z$du0ta5`0XepQqc9Ym6N(>C(NnZyo~j|;TSnCxqc--T6$BQ6t*4efdj|ts&$I`_MvPLhOU#OZKW^>$6+>pNX2~T^7LO~~M*@ViREv@!B)&Pk&oaDjB z*GMl&l9)_*OOij$rKO}rIL3%Hou}9&vo>PdOMg=BAQSm?6LOl;ks%>d|6Icet_pxN zHT>=@B)A??3k&TcGeuND6gxNxL^Nnrhqi#GWo&mp3aB>^^g zG2k6ER5n8Rdr?Tnz(r`mnFGb|*H0%Tm8hp88>8TAm+F2OZc|3EPo1D~;D;A&c5JN5 z`J{-@shi;wyj!%3MJZHG2{-IwMY8(FOAdj#QD7q|0rP~a89BQLWy7L)*eWVG zA1$v?R}2%@ezxV0Jr6DKf*V! zbUA?9oe`>yDj3e7S25%UE)V3>?wC_ZMXVY~pNEG@^CgbFpda5bF&<|gRo_;rI=<-K z9hE_oB}v+<7p-5B&+|xpIA*Af5JF`n1A~=xcVie62;Wd zQ&(QBMi0lwzB9M`E69RRWDAS8qLF#ZcyUuq`jT2dso( z*7qOx>glTh0ckir(OVP6wAlV$84Wnk1d44f14qFaR<03Gp0nm1h&P&cz7ubsKS4Wo ziDpDN+1{8rvNi9`8LFKpDV*||yY7Nn>fDSWI(6OPWYrPc(lIr?=E^fR)=gJ>LDFj+ z)n3G@yuQx%_Oco7G@;4I%$4qT9T;c*p30vkmj>RCHIO|xaXrxgIF(mK_lWJI)o4V7ZP0p^$<|0hqMJ|#ePq;{>Jdz}95TWgMw zVHpd{=IZ@>?pgi@(Hk2ZUYKU({IObKQs#%)L}nGPxr~ddB(6SpQih68!xxiOYeVyB zXI%Pmwu19xFDtkeeG?%(az8^)E~+?77!@}qrLs%^Xbjo1gGRH$)h-Q zBSAMet6QHwFOzS@kZ;Af`=O!Jv9(w+5rAUD$vXw>n*K4Y#uV<=pw~I(A#wT{4reA> z*6$AO^dn*k|M!`I18%K!3OjklrAt2T=|Y=<5$wNF6gYTJIY^E;>)aHR!r^OqgTG=) zA^6bgPsqql2-p8!Kq1$AWbCK)&oyB1yaoh>7*x`Z6L(hWJ(u~2S2u?Wbx*Y492wYt zSEY9Z@DG33E5vh``9Q?qp&z>XeE-3O?$-wg2P-Xwu2<(#QFzHz1gPh_C1qvjx&XD4 zy{@`A4{_zmG50LHUp~84yq24B{^T*9Ahu8E7=?XxzoLj;=#G93VrSCx@>aIGi`M_*zpmP47QfVOZw34&<5RAv`52l zKYuym9S9dmr8d<$r&{3~%_)6?JJ73)VVR#@7>Y?-l&2xXq~9LrL}UEmU5cFV<^YD9 z>AXWaF`W}rH-$**_EVo92FsT{L`~Us%xZ4N+G=_9XS&oM4KvZuWkJF!$S)yCSi5Kd z6RPh5K<0eAWtZ(*2e14djIkPIb(4gq`DrNd^%p{k?0 zD1G&(rZ?3#hhy4tmF;)+70l4|Wj-viDn}M|AY+pZ64HT z!WtZ5qY&Q|CFe8V9BV_TZj0-qrBMYu?_`Wl4I_hQ4*a;aVp0Nl#x9tEgAi9>5JR0l zAJSUS@|*v@%+k6k()us*5KllJ0@xX5r!M?eaF$rf43C?ydcCj_+dM%2UN8g#?h6uJ zV4ong9qu1@N640HxWEXRFI@6}T!#*k<&pYff)ndbZS(=@2V0sTklE`6!2wAD|7sDh zB6`g`r%jXX<648=*8ikRVFOetfGK8dpVVNg&-!=Xu4dd=4gUXVN{_pY_#C$OvA`L+eU0a?G%wdR<0HWUUGEEhwoDr*dkf?9=CQIqp=uL*o zGkb+b%e^27#X?c4XVHjKv$#>u!3Mf?n-9&J4F{qe3< zTY@f4Gx&pm>M;7zji~+ime;nXE#`@jy2b{bD@a|GjlaL^nR{)0vnh!8oy?1Jo`^y4 z@l)6PBbtq>Caa}6gE^f%Ls|TyA1q+-qYRJaa@I^V^c8@Y;e}SIBANTcbFt zt_?gTbGp3YJWa1BvBT_f>W)6Qpcb@UDPhWZDFGG@{xtK`af6GeTi%cJXLAR;_3UI} zmHlGL@PEe*Uj=VTcQ^?E`}~}{GV#abL`T)^A16lP|0vL3d_8OgNQkqAwbkV{)`r!? zs@FvWFMa;!Nwy^BE`Suc?%Heh3#tQHJ_!M>ZXnLpD*9^&5{)JJo45dcH_4NzT<%x%WJiJ=#`_`YlEGsB`HX{ z639{c201C{-v-4u*TR@ZNd{j$L`VNJzle{Ck0|_tzmz237njKKfx6Aw@yf}n6%s}B zfVWcQV|dACbINP>j#C&3gG>v7sQS5CX+Ops4J&bdx}-%RT%`NP}o{cO~#rMjMn14T74W)rMDavKs<~ z5zG0stJ&&!v-1&9c?8OWKvB|P4S7I(dhg=@=cmfV6{p>+saOA}%}m5(Wag%(TRS_~ zIS{uSl>+VmZeWB5$QJ+uF;G#Mm6cUkSP1fGE-3-ak=R#mRtx(O^SaBc*gjQzm+^^p z8$My--ROF4TZ9O=WbKpRgF-`&u^2M-y6YLuvKO+5j8|B)ZmZkzewkyNL|(U5b!?~g z&-nNzCnq;-s{!GE82Yp+&lAeL)!FTuE_{6XU>l=)RhJ$fBCOE5Iy$%d&v%#S44sap z#J(Q)k%hZP4zjx9j2lV1-5+uraV5gS!NK|ax|v8Z%!SULcrqj?J4vzfuv(e*+Z_Jo7>M1hZsuoXA?aj@1S5|x~^uYWR zADd2r8A(hYnEVH9N^1>XcS%6rIUDe5Br$9$@N$4Q(mOUVOavQ{s@(u+n9xz1o&^PmWmu(ykpH!B!xijn6Z)CrtSgdqIt5dwV;Si6>UJ#m>UgQjsOv zJ4{Pk8+fdo5uz_I!J`tRUpNb;g46N7yj&Kqt*#CfqZAR`w!K(eql=YYv8ohF-E$Jp zQ^DEjTs^GH2Wb}7GiDBVy|VEF#RW*+0OyOnJ=n=XgdHgG)m80)k)K;ApvRvuGdBmH zSnyI25&{vU%M#=o9!KgQEG;#g@|eV}SnzsfSIz89OvGw{rLv=oD}H|dsi~<0%}m4m02D=BEmZ3{n1Zz2Eoq2}Qt zuX;5gW16|vc2CQR%g^JX2c5K-ln0t8d!u*SVM9#0TfErNuQUsTM~~o z1hFC}(=I#}bjf)58cwOXYX3zOTHzy1sI{{FN^r zhzGL$hn1T5?-Eti)Fc?LMiZTaiVyHW56KvrP}dx^sa>YPMoK2Ic6?7q6a&dFqL?Z% zH8mn{ZnTb`9&vTGL-X{`N+p)usw%bgh=>S3NqPAoA{t>~vh*x{Gy#kG=MN4|r^>!) z3!9CKXY9`qITuB-XgY!iVU zu;^TFom=2=xT0-54-z{Dr4%7x?Me4F#2Ni+)0ui}xERQ&ir^fyxOiUDa^%ao!u_=?eC$L=Of;G(9vw#2ofAvl#}; zh+LeoZptFNT`eGpc80T`fLw}(ve>Zi_whivV9-(pf~_;V1)3@KM=8fIk_8i5Z@6|} zsP?~)WuoJkFMZfbR^;K#mhTRVcNCmb<>Gm;G`?E*eJ12|y*$-n(_^F@|NI`=6{KAQ zG9GjiKHSEatR_pe+%2C9U*;$mD2k=^*O~*CCG|4X#>zYcq@HH6+(#ZtA7HBN+3t)C z3}W~DDiJIOsVK!-TwIPm1TXRCyOU_6YCz&)pw7h953sAHsl2Us6+CVLR9v9SZM8F; z))?C{wY4VbJ2zXtCc@dg%a`O&{RG5Prh8?$Sf8u+ahHK@s+E~`hEQ>%4#i_e8OMW) z8HqHD(^9h;zU)S0R3hnx)A?ah2w;h2rW~(iHkk(`905qwCTQgLK>ip1*_4bVPz)4~ zFkPVI>ES>l7bjMG4!A537)NkuWK)u&z-CmUIPZRilrBIkq{3wkFQvv00Q-_LwvTH0 z`=K_gEgTF1>YpGx#;WMKq9C?+R4al^BPv^av0NC>HLYAzr5fM2i7`N>HG0PYDG(_X zN1NpR3Y%CfjTrVNtV6tbj9C<8)$Hig>lxUpo!4@mfg;n&yc9DGZ+c zO@_Qb5{u6-0k)od&i^5T3~U5*bON?d8`1>&C3s40u+NMfml*M|H?=zMua!YD(G4sM)XQaiHZ^^Ls=A}e6R8#9aM|}Cyypp*iGn!c0r+-vl}qb z{X~(5&}9SOdE4;U;Lq9)Cq`IT7wiuWexOVN=1DqbY42y}dLVB<2wLPqDjEC3%);sGNqq-9LXy+gBg- zaPp0hNzDD`4O$`^-)~KEwOZ?^!0PPlQ_Tq}(G!g#^!!3IV>h8))TJcD2Kx?N76@&L zJf)t!s3)wJ1#EU=6*$ABi0H&HiF|e=@5rdAHcQV*&;3@vLwnV)PA5B-S)XlQhu1h9 z`uO^xs5IwlR2FI1yGD34FIoWNA++=Ib*#Dy^%_`LozW-7j}mjK2i4uGFLhyeye%i<3EfN=xgzFl8JJ`Y^%P3^58xX!ZA7>ZOVUawEavW z#eV&hvWCWI((l4iZWQ$on7$8ZF)%W2UvT=5MM*T?%PZ3{#^-*{-3O;KV_+~s{KPnW z;E(<8ra-%_VXvrCZF55$dMskl74OFYAsBNX*S7$;jX~(L) z&ydC&kl^`Uhz0IgU_U}*3>f4I*phIVv;pBIh*&cPKcRgFC3#@@^_V~h{{9Y6!Jqf0 zFz`O*_nq;ECXn;AeYe2f`|h1jlaT*k7rbs+wukdgLUHs0@#o8wf$gP&dzKZGW7Jh>* zl;~JcMVKMmBGtI2SiH5mdVhaEDAu#jczdoXL?$l(W^a$4u3p*0Oley)!IgMha3@MA z_(bz%7#b=JGSv`9CB?%AgM?#C2_!UvH(kor%Y;C0z_mmNzq<^$ztEvJD^jsA5HY8s zz87hXGN4$bY;}|P?t$`#PE(2QrI&4}cq3CavpuBt!Aw==)BZ0=EDy~dYJFm~V<}M% z7!StPBj-pmPWKnUVCATBH56J+c(zo7MB%mL&NnB;rUxU3Jn_O5L81^tK;U4To=BK_|&70Q;TffS$Ju)c3sa}|r5C|c`H9I8vadA%9_J;MZ zt>yiJtdoYaazkRW8K%>7hKupz`Sw=zXV=I3%_cYDc74e|*>ArpQk`Y(2~z!iraBvn z8q4GW1-K1gcxQ`2F`*Ad5aGUykoLFNvHVxAa|;&3#@s6Kdh@a^ z`3w|Ji&`!G`WdtMo>z1cJqB;3;3tv88)eG?!4DMQcdAXA0OdXM8P>eSMKSQB&*9Iv zC653$tFx_kVIznER={ddKBa%VOYJ9AR6W>uA}ZLDka{QR<&)yKbhs_1Jn_ZiN;%5W zUCTCnGiK%OOV29Wc26YorKqsiZPbL8$0xxqf_U;wc$~o;`~m61CLjk%`eej0OCxLX zv()l*DCczUz(+ySV0Am~Ntxb)riVX?8G^$4EO>JdoW9Dsw5eDRt6Ff@^7PN7MK7{I z>!YWc>2)5Dv2@NYuQ=X+GuJ!l4ga{7iD6B z{g!5tM{7sxF~*YFH{9Z5J}~R_&cFmt1^(;I#`s z+WmWaFb=j5_h>zs+z{1HQ2V>$w+xXjV8_g2@rbTvc*5PCPyB7y*JQtQpyH9_NJ6Nj zNQG^B0k(Htne~vYvCV$(a}185{s$|K@dIp<*{8*O!g4fUI@veHr<{yKL;B?Bv2^js z)6g^44XkDhA3CaHQ|>A|sv@ks=ZW`u0i{l+F&3d|&&A*H`uy_AIUaMQbj4$TvEhI9 z6K%NE{K>@(a#YvyD>@sYWWlU%QLw$K8i!(EfoSx5c%X;r=^6@_NtdXA0jKG6Fjqf3{)_svaIIfl=)5B_rsnS^;H+a9Q1t!}6 zA0OS@97N1^gdH~!ZMbXO&ug$CRy$Q{B)|%x_d(hXT!bnZxumD&j)3O=2Pssr0DkU< z`v}A&6LGYl9|By)_{^2-?tL)nB0y?)hsS9iu#Q8ST(#|Z;+dnnl9vTYgTp(2*;Ze1 zfc*wyru7Heo8wHjW!|;Q}e0F&pBVUSa&*;RI7CYhHNOq|Zj6 z#PDd=?CI$`s(N0O4syqDv_c$JTVL#>K_f2I7E*c)*<+x(o`r)pUZsqD9%RS_QG3~@ z66v8&UrrH8SA40w@A5+cZ1k#{qdc1f@1!)+@eT;sLl(-^#uu>bf;oje|7H2^$%yn; zJuN1(UE$O=l}*_cGyR(Gqp^?CUEg$&XLoNJb+q?KjHw%Wll#CnnfKTd<(KJ|<_(HN z5r0`dyBC=wc26y8G_>E6c-4l_U-=dBYN7vK0eK2@KePHd88%*Wms+hrGE=$=DAO-` zvGwq!QK`1dAFC0PpG<#@nVdq1Go}MStf6A~dg>sXcSQM7=2H=fsfMI_b)1xH9d-L% zvD(is?XPu?Vjg0RCQO+6+;6$_>oD82lB`gjOgct}Ej^@Ng9ZOudk*T6Nh&CG8XCQ(w)eJV`o`QyKWKf-9~F{t6k-gut~2d=X5x zff?oS&dcgXvMi+y2@!H+W?<6sJw??t8To=|Z3rhMG|!Ud;?>Ufv`?j?7qgX9ojHP* zFf;Qvjg{$=yH9;aJr6qVag^Z7_~^Ld2X*uP&Woz#&;}wN|N9w_>%#)Y!R993#1tHB zxlyni`qyPKJBYG{zvXhi6D8|W*j8)iV1yPWxZd|urtrg^C>yNQ7@K2F68bmKSgUbe1Vqj_xH4Xjg$Lorkhv3SPK=yIbkaI; zWjewH3kD(hHa>_3r&Qms4FYt&E(j|QzC3*F%H9Nss)Ysvq&N{e4@jnV=jw&AjVbw{ z&!hG+F}fMEsd11>F4{a560GLHyF8yDxj5ns`|PEp{5-zX`JLuIk8Z#yPaWgb#%QEqx>UPNyFbzsfF6L zmQZ)7>gZzm8p&ZW;p7|?ldjH$_=``PHMOL57v6Aox_SUECMHfK?>nsz`LEIF>Diur zICC<#k)Y4wEUeHHEFguhyW=Cd(gzbjzt0+%Iz)T^m?pl*6g)x?nPa)rB>bJLEu4{rWcP@P)Sw&uc(%+LQX~(o;4!`r#)+wDxx~P*jB#-zXrmyY(xth%NnW zI|TMFF8Jj(oJ|@Xe0`U7I-Xd%PI4r~8tN8ShsiSicH^Ar)6o^5998~N@DX#yWv;bk z8>Xw6?i-Em=ud+axcQM33U7+z{9RK9B0uWccrY1KH9OBqyQU(xwl@nszx;v6MFyUc ziM-3+5Ai$a{58u9VzkQz%5kM%n(QBt2Nx3d(;Oxpe6&PW$4reuhgeti%fvuGebBz^ zwG_%Le2+iCg}W@`)r$Rx2&vyV0&FVvdvz5WIue0~l8GpClcSaW$FD_L_s;@p^Oaai z-~v5vkWTeMw51;|dx*jGL2^5HuOZ)a^2Hz3@A6iWW+@3n$&1i10Rdxsp>JKOfn;430dpP;yaSO^yoP zD#5f$1&zdD!mna@c|Vp}qp3yrxk-g4{Q(ahb9bUGZqwywoII!tDQ~7Y(-MV-(GJsz zU8levkf1X`asp-0Oyv4_oTqmS#Uwb0fRb^MNq|Kx?9~SsUs3wF>N^=^{rqPHP2sVs zGKdzbPd;QYFl;3qHjv0N1rgmETlwgTi*@avW^4&qDBEC^WXM1@n%yc=8u~FF@k34p zN$NlLYc9KKr6Wt~QpI20f4LUo0NdHZju(^Qxb%F$5mXbOTt+aDAV^nREn@FOBNWFg z#;-?8KvwGIFYYTSz=R=6>fee)^1dtRA!`6fHKXXR;muP);l5Rz6?cd4;*)q>sZ9pk z)PIZ;-Y&Wc8{Fjjy~Qi5HB)D)Q~a8Eqlxv(11>%nuvZGw(40u-A-@N z2Z99}MzK(iLZUw}wg$Qn@YQIdWOF~nLL0rK{HbwF5NV?Rw1gk@%VEP$2iUg+M3r6$ zczcVcppBdEk)%+1cxyM|76J~iG<^K6ulpcrm6!$(rGtZX!5tFLsX5JLHu39gjzlIc ziQ6-PrX}A2l<6-H*Nn^5Pl=UERX7r-r(%I}9|MRMmfwz$!JT=}Bjc z2s>l~%U!Bi04Gq0sk6n=&Fu$Up$7^xP7W{a%QpuEG?o%&8zI3l| zh!&kz()Xk-aOM4KY%tOus$AP+OTXWiK9W<^vbn1BarOiUl1wkxB-L$k%_y{`IjRE_ z6$(x2i~{xC%78`VC#va$UrN4C;SC*VY@wXE1;jq>()r(&T9y5OR5DM=6J`5#X5u3a zqiB&2LKQ(8h^3ytMBs852lCO=rH_S51Lfu@On_@T&}?3`vC90-yRy0S9}=2d7Hd}7 z3ZXhXy+6m%Zu!VEQOd%Ar|@Abv^Ute zRVpTY$A)3Lo*isF{87u(A9zQit-W`W`}eW=%(;JTO7q;6%aL^oz~Te8n7Dk+qGO4s zE&4Z*Q2DXgs;;O8(){uPK;nfWmr?EIfBaCUxj95amt8~xARCbx1;Cmu=ihEahlkEW z?kGK+c@5v=RCNx?EnmZ|CJ#=Cx#70cujqW;6c{b`H_ZNV*;k3Cua0klB?=|)mk1+( zfBMRP;0*@hy)VlIw=rOHEn&c-WPp9$esGI2WQW3Q9sjP>52N*TYwPQ~S|;87#fN_X zrO(ST1WZkEeYTE)9tyIJru|=S<|ur~pfo-r%QR?s4=3<7hnIir@t>~CMI>$bp5_j1 zN?&;)T$7LgjEqYQ?n51V+dpb@k#$zh{!%Rd>}wC>^Q0Em=r2Sw)D5)+H^A}5`P%HX zBz{>AlJ66Vu(%)je+6xAN2VKpa(=nx$`2`xW?)a@FzdM z;+Oo}buz-kA<&V3cebNBz|h)VCu`a{dCAH^gLEVlIkBvJH!{aRO7FbRd^JtIyIfqS|Nd*Qz<=6{Ok-%LkJ*UD$= z&U2b-_Cmks6350gc(yE3>mmN$O%h*@(;Ni)fcqT3Q>06@3VqJ4tzHz#^+$Q zCSYqn?B8?(_j%tI_rh2R2gRxF@azr-Ft_z7dg*e|t5>AW%?3QCi#tsYW+OybVEscmGShUR$L`Y8d?u3yHSUMxEu*=C>Lp z1J1_4SGFpi{lE)PH|uc?e4s}t+>2>v>o2!xVA*9rqbUdi4PCpDaF$-Dpf_V9qpMr>c4*(6uxx*7z50N#D2IXi* zqYeJ{LWd1-`$z=6wZpVO+s;uqE+ADAB+G;weMm)vr&SVD20#9xwNAnMP|7vZ0TfV-_t~+M@e;1D8k|NYUXp zp8|IK!=9+e9+EbBuZUFeUnG~qJ@j!p=$SV(VON+eN0*H&_SxNYmw$cwRCraN8e<+{ zhS0AXQ&+R;OC&0%_Wb;VHe?@%$Co83`7MLUss70D7{>NB^0i2PQ3i)) zpC5My_9YXN%f>%ly29+JL7Q&ApU_Mw%!|A`M$szK(rNE|6LK$nD5`!na{&k?t+S;` zzUxn5ao8aJ3?mnW3Ls)w7AW{v*!3sO`Xi#hL;RaB8Cqkdztov}`JL&4Sl3o0Dt6=3 z544$Sgjri2arJd^kS@Bvz4cem%qA?ziQ(;5lKtJi^YbgJ%&@F&8p)!2Wysl_i1o|f zYJv3)EXwu2_4VpLMz=l-0ki)@ezgdt|BA8xEwW%&KLuFJDId>Sm}!A}NJUT5^S3tZ z`^$L{7t`XPY&W!|J$@I!(cA)eGX%nkJMiJnQ$1a*)P|OyQDHz}x?{4oNqw{ysZxA+ zXY8^}D^^nOo$mwjm*E|Rbq*id{1aa63CM)xjRRn5b}LGsVtJX8zOgr35qN3-Ny`3X zczx47A1ue3isJA>Dfd;vYf|^9#2Uu$%rE#P)ofJH%(#oIL3DvhV%a1u2z}uOK>6RW zMk%SUdq)}=(v4g@sW82;I+J-#IkS<7ch98EAAdHy23qu#TaE^AjPcK~@7>Yn*blIp z>J0KsK>h$=68@zVw;vxR>dM!^I>W6^^2P#yEYd2`-%kh&jUWC;JpZm zP~nlvproYCI>7kKCXP8gILlER;CD(EBb^r3tM>X^aP-nnMg1>V3pV1eLi6!*ey3?? zGErVq{=@Ou6Wf)k#Iv1WVprrEh?4kf#86Dc{8i^&7~8m6IexMbO?aoXP&7>xxg67r z5bz?2BLjayXPV|h?{3R*t*?hNvA9L7*;aQgbKs!MVp!DtGB{m^2swR8Xx}hO9Z)k( z(Ir#+70NusIunD8_}!OqPK5_;bSJt45m~Juyf=u2e|?Q|h&6Ay!CiFn`&dhpIgV6& z1GS_&|N7Qi(HkE|x%x9BUGI|PW?|KHF!3QdcdwK|tdw?Lq$m&)2mxFCX#whC`HbQT zQGmR#S zMALRKHXF^0Qj8iAy}NQcq|Eiy5L%C!c$i1I{^kWvpxzG`1|4P{V_nPo#fHTOu%{%x zsgn-`|2{TFL#zxh z#EoQ!@z;f}6nqE0v1J7l4~+-eO)11wKRcr3@L4O$L=39YRP{4`S08B6eLL+%P8{5F z#EhvZUIvqh&I^8i8Q<9pc~jMocgbmbQPqE4vg~L)A{1E=Md!BPk|_O`tnV)E6QFC5 zzXJ@dOJ?Pa(KAD@P&~+yz+ZX_O(RR#3*zy>1q-AZ=9=lm9s>fbz`CNJj^b?lq9j3NCd-f4ZY!^C;zr3GugX{Za*d?Z+dZDX`YHzvHuAcfTM$5~{Irxk@ z0+y;&imM*=4Ou3;u%P{Fpq-@k_s)sF`ed-hjV4{wX3@0U&C^_Q^V(~Ny@P~A>-F~x zev9_e`ImR(OfT=u@Z?_-jEI-J-nYxwAi|!huv)s;$Z%Q_c}~voItxPyHwQB%;J{V* z;no>7)l;vo#AoH4sZ@gD;oqOdTK5WE8#*fRow&*EikP6~%9+5NWHN>#sx@BuYlW~x zO1H<|e-XlGNAdBai1^=smP!d%0AO)`g1(=X*=Si$s@V+()R~_I|$?4jX;nTiaOYX3XL3h1eh_J10h~3;xLa zsJs!jZ&Me~wmZMXYR&X6f$Zf2rU`9CJsK_1b|^y6RjP1fN7|1yd%TydDmPJetlmVd zh0lc~ary)|VoMU&uLQ!4c65qR^3GqXu9+WCKdqX)sxzB?^Abg6X+@mx&hSB>a`W_+ zamXuAt_=S`@yW0ypS4-;1rEm()Kkw;YJ6e)I~!7Y&v7liFIovtx7v$FIMMH>I-n7L zxH(nPV0snM5ef@h_7Ju6s=usN%$1+gJxhK7gN1q*hnVc3$y>0sBkjFNBE2JURQ+Sa})VLf9mjr)|c> zP7ha5N9t^hOj!RC{;J6u{W`C+_iP%Wra!GD^67~-)NV!O^}N?m)HT%MhJN9#M{|F- ze<`5KFWFGk)6saVvGN!)et|lrfl_x3zTTCJ(4eO&yv0ty*^NJl!s`-~1RQo6d zSNARBy<RBE}T zjx>m%CA7-0)_P657%93gN2;F69MsR0In5du8sKUhWr*Yww&G7_VVWQ)K1@I?L|8+~ zL%=hhXA-*HYnIsl!S>Vf`fT;gvknkzgEd?cZKG5Kys3ePh#~<%n3U{%(77J~a<=4C zzqsv$fMk+_7L5uE9ZqwUtZO2xS$(q7In5_6{NJvhSWrYnwdUY?wz?}(sPuJl?a)axh6j@|aqF#)Cru$IsS=8;45d9c& z{h*|WEOJiT&)GGWf?x3=4`$k2DphB$FzLGYJ=<pR4orEPh>jPu>NiD!kaK-p!Uv zZP}o@YUq!mDy=W!1xf1?oB}esH&}jjiwt#E(~P!ng}wLCPC5l3mXML6QrgLjr_JJAtfq zP^zD(L^0j4jvSFN`z?pj75~;H<(K2-w0MMC7TRMtn-y|FNl+;TCOzxvJHczl%l@sr zpLzTde9A-x3S-(_2nNvhTPV|Bp+&(C)WL8x0(xPZ9kx1!s_7U@9=>tD@z? z^&qMOWJD_JW1(&3;hDS~Xpr}YG6x`xevr&16bExrx@fN;Iqu#>_VxYvYHKLWUO-R4 z%aJ#pG3;&{5p=3H(sLHOsP98Uv@spS7yi#7&jRjcr?BMlVY9op3cNdn`NXbL1Y=j| z_jr=o5KxHnxppQ>7@3uSKGM41jqzKdY=d&vk|e00-FQX1@UM;b_xXw`%y!sSF$V7m%5kyAAIn&*VXAO#(2UP)pGp=Q?W&P zD$nZ!I-v&jg+LM%nK^DwXUgf}9zkYtBuV|>3Wbpbn(~)I0crK)$gKHOdnG|d6qvXR zh@hG7aFl?H8wNJ1hySJmLE$c_>&J-Xc#X&XmCU~?Wkp3)Z?F@KZd7glqDgT-yA>9w zS=tbctVcw<@4cc{!7oZQ9(VDqk=H(&_%0K<6C0_tjX0lTB{x=)!XgUVhl{b>F+%W9 zWy!JRvVD8Hfk;|Sii`Vo{oZBBHWNBaUmm|o5&rNj`%IitPG>X4w*A7Qlu zYLj<#OM0HvHwWz^8r(OEH*e}Qr_Qa?!>#TNs*U=iV59haC{{y7^S|%Em9dr*Jr9e( zGgbUtK%V?Oq=n4u39?7_k2nSL_cZ(7G;SneNmj*iZrzYJ>bXfvUQf_5(<}H`A~xbl z>q_4qEMew^pNfdW)%+p^kI82`8ZOdQ5AWwTk1)?#+`P6pPenLuTlZP~FVW)>8!;Y) zJz?5w@qpt5>F8}Q)#;v#zzuNR6-Kdzi1!7$>@-Bx`f-76w`tH~^!my53Q4wyjVG6h;aYy9Rk^1) zxe_`DrVUR+qdT$i>~&xKexNQjh!e=zs`3V|=kVTmJbs88iYI`&qN!r7koz^s@P1MO z%ifvarUOsGeDy?jzQH}f0yRBBG3Oxi!Ky2c)?<(5**)lUz7Ep9F#mkXeeq*`q$OOb zVh4atyY46Zyq{ayvXE1^J+S;Tc_;kyJ5)q|iB@4w&^`wb9!pYo_2l@MC+%ACydayN zd0k|P%vRJ-4f?U1KNeQnFEWn71jN?l3Ik#*y#=sa+k-2<_ba#+{*j;Ye1M$%kGhl| zdtJr3V`vf-*K^&!w1LKuGYNX*@Cp5iUWdF5_fd;(i4+%G3CU}e9*6Q@{*QzM)&_A* z+-dRfb7)n(%RB`NZObt%zG?jU>#8up!lRaKquUp?Vp^4f21i|RT{VT=GHm#3DhUcd{NKN1{ZX>pgfQ2KG?{;Yf%>Qg8z2k?cyZK4h6})46Y?fK#>h@K z_WTvzNKIYfy@Bf|ugWA4$WB@w#(5~2w8cFCZix*@CZ5xj(~Kes9@+JARtKZMKB^IAYGa?L9rkLp?3lV1?eC~dN0y@hkt|5^SxjB zuPbm4=Y(X>?C#7xyEFIrVVa*3(i5*9^u$}ESrlEVdjH$NqaURB5;z*%NEOEVVNv!1 zm7)>ffLhFlfTc}OB;nAlgQYI7IeG3*F9JVGyh6@-1tzg=#(u3X7zT|bJ=kdpkJZs? z_vJwlW4sU1hAg6s&uj7S+&*|Y*?oN;?RI+dTpDw!On|*|XbqPM6<9?#UIXFSDuaXG zNT-L1*YT3G5xd}i9~YMf_j$fUZU+P&Sf$bJE}aGk-<59aSdEv27z1PClgZ=VG5?+4 zR_UGuR_!TXbEzKI2yIB@g|Ev|b9EMcZG|S?2_K3OVe$sNO&00Ws(Yf}>4UpCyRaYj6h=XY-tq3?UOQAB z=|4#Q*D^G5ov>M?%R%V=og6wJQlGZ0;?=A?znl`r(8uMhmGDZUlL^z)>2CtB2tb&! zq?lOQfjMAg+Xt+_M~fgE=O?r{ZmLC~cG`b$vN3Gb=GLY4OLixVk3vKPiW2Y6h(^?F{8Z-M5%P)ID`AJEP*uvz$5HjfL_|=Z| zN&L*Z(ou85P|nnzTBw_cV|+GwN8XhOxLvlOzWl` zhd?j?)w5F@)R$a8@__}&SwoU?+)Cr+;*RZX03;?q*o+I*>`e|r@?QcS*7PnLm3j$d zaD_Z)C<-ndO5_g1F}?%pFRa*hJa5$R_%!{V_f+^D!J0{y^DGJyCw3pESd_Zy3@$#; z2>c5s@tBg__rdcFmV+yTjXp((8&)rlY2v$PR$ICbF3W1q&5LfUUSh~!>KxR%W4%z7x&+*xz5~p*d#-Ikn9tuo8`YYr`*J%o5{m+ zlu~*HXHOqkk}NJz*AWgr#KQD)Z;~g!ML(SZIj`O~l)&)@NL15g^`2l)zc4F_^*x8~ zuRkh3+Syqi>Wmv>V!3NN-%e9lG;9AI8m2cC=Yzyh5gDEXlz)%yc(O+9sZFg{mZA~_ zi}Z7Qz{XtPI9f4)rR^z|R*tG~2Bu&!@4d=faR+~`94WiaG&oMH6B-5}3A5WRlz?l- zvXT^L?c?Nm<(LvuDa^q8GlZ#&N9lNc3C|1=SQviRG|1p~W|yfKZ|^^QBUwM;hillj&3N9)#;1f1spBZFzPh^e0)8ythTlC? zAm{yyNAcDI7>x`fnL5V3KP>*ll`!v_!&nMnhR(1l?<+?5#lOaU8d*{V z-H52DoWA&LoG+ho^0Q|zGQTQ6`_h>;0dfb!US%Ej-;{z@h4Uy7dwKjxac4^O=1Rj@ zSRJzF(6P)Eu8>f!R`cFG2O5UYD;m~P-x_& z`+E1=vUdUr9GKd#v{W=)lD3FW=vRX)0rwxV9+sT93*%FS?K9stRlh(ObBqPOe$8j| z+bbiahri^dq{ZHUM&){7&Ed|XM#rS`$*v^C~koGN{YIzj=%A^~uKGBV&ozQ#D(k_--4>u~kqTN0&P4 zerd(KjRGPPHX&SnGVJ7Gtm;UheLdo%F4}Za8&mE2fnML&Pq$hNw7jqG`EYy4?0>5T z;=7-p!>AYVYYJccuv)JG%DD_M>LDsHp9Ovq92oa3Y!Nl#TN|tXhtA}{^gM)+f)$3p zy^RO0;QlyItrwAdufLz?XCl~P>fO%Msw+*R0=KfXg05!*03oA{eq3b}{j(a8?6h5_ zF$GZR;e!-;Z?Nkn3OewW)WU{N+w`Luo-@P>fWtaiv1e9Epl4OA@O=d0kP z%Q|nPmMA3CT*0o}O_tvE-h!yqD`$=oS2Of0nBThRU6>7AwsAMvTmdFPbI*5EojHj> zh%>+(Tz4CN%|*)>z5ek@=2tWqE&l6jIy>D%xM(A(B|MSa8F=mPwu8Hws|ekbryJZM zf|hA&@hZXp%WK@qUFDQCVZ~$mMgvhtvnxxXa;Eb-*Y5C<+G|CH#TtwHmukfK_!GdM zv8hZ_)(5;y;wyZs6*bWIa8Md|n`AC=mSr8dKe@7Y(UxG5Qc zHp<(%Uo%bzXf?RVTBahzcT)D=4#_#YfRkwEoq4H?et0W$l=l8D-i5`xJ?$<8_(dcn z8{^*i_+3+L*1^m@c}W^%8+T17SPSr)l5oT;bo|tKCI{L4^%M9Ze*s?xSNvr>?pq`2 z!-_x}8pM8K-(wf)vfG2U*p?fhVH(~*F3O_2xNO==A=Fu~NlN0NVadgmTD+d6&RV>F zzkw3Oqnk4k&MhyY<38DqLciX7oaP$1~(6a7DI z@pj&6vG1F*zaL1i1bZ@zq|*7euIg!Ul|`WctYc2n;2PJ^BcOYS^`$W}z*^%y_3J`c z+g@Bmm(kwGyS%_tUsmYOr@tP5;De;ni3H0qYKdF8n;VdVRRjTW!tp4Z^=wwZe2pw6 zn)Tie{Hr2a_-ocyN#`PM$3aHr=`hvfT%#^v5%KG9AtPX+g`8J2hO3W4b3^Muo-{+C z2wcLOOK|v%Dt(W7dVJjMno>KRzC;WoRBPuBV{|t8^YZPTMxF8m*CoU&LgnbXA7Ckt z(5?4^EN9fFW;bQ)bA|`}>iPzoJDT*$M_kJ1R11_ddE_gyo5}H`7_4jINAcVU@dA7p z?=%xOn`H86RmktCN@1u+AMFFK?BrWl=Cus*_~dd&Yqx!YOZ5&OYO8i*w~o_f{nI4E zS&nzsqCm4Ga;jJW;dRIJkiEscT0c;ZViW+T$EMG>H#=ml&`HXW_tEnQJv<@6$&(G6aV^^a|^>rjjdcq>xG3YxKg%2W5c%d8l_G38PS$v5u61dDwzV~Nv!=0EXjphk zcQe7goZW0sK^xzz8k``z->4^+U)o-g<9}UPMf_VOl`l_ z>~*y@I7JxJ>J?c^yRH}em^M20T#mD&42p^oO+5RPR-eD+HT%9i35ddJNiP{5n^ZNp z_m~~ZV;%P;cu;8)wXUO>2v>kJ+6N6&Z@vdBTKc8qXntt4))BF^?C#txPsGa8_!5dI z^k(KDlrIXz*UBh!u&B`&1ZeQWsGA`z5cSPz)UutRY*5h}yeg5|yLJ*3b_7!fH8R)> z*}S(5YET}1wN$R-0_(%TTaa5y<1cDQ${b%gJL26q64{Xxk-8ar_G_l zje><_Imdb-rsd9HHxSa3fc=Oc5FkBtQeGXTS%DWg*}y({L!4H%MeGM_mU67x`-X`B z{$vvi$bK1ShV-JJAFcoBklG{*bN`MFJQ<4*t;(9_@ZGrYl=H4dJ7A7m{+6>7!cm!t zS>;Xyh(@C9=};Vf;N0xYnOaNa->vVuKlOW%nS^eTD56z^cd#wR^@Q5F3e-L2N|`cW0%^e zP_ldDke{Xdue4vdJQRq&-vA-c!jZw~CqDYj zYU1@0XuS`2E#ELd)hyt`huae*!Bg^f@cpHm7xfaQpMiKow!Aj!|x*7jP#>!Fl3 zh{ANH@6gs_kCtp!C#}k=kxU!k?08)OAZPFgs9dbBbkuthRN$K9y&Qn4e!0j$VLzZ# zpVJjl5OY_$x@L22LV@TcvG1_BxIf9>-d@OWzys<1+QEQCbm@p$nBWF?4!+Q$`H3`0i(agGS)_hvMYrP@WHS&Jy)duv*pQh+DmF9rVX?g^v3dwG zCzD>x2fjZRN~2xg`E#mgHJC0_%o%bV3#FJ%Kx?6ScU#8duk!9Qm&CDka)CTXP9eY6Vc{MCEOjc zDhTTeF679u@&%45+HXQ}kTjHx24iAR4nziCZXz5>t%9Pi&(yC)Auu29>% zFmv=o2W&*djO(n6O4dNFc@8uhq%8|C-y>&Mb}McF^d9F#>ortx>RJ?6Nm&R4x69q! zsTY(sR~Tda^C(U$ja(5nBOs^u@*r+0N1r1rHvxn_v0K=a_FkSiDb#-T>87~*(m=g{ zB3de6D*q87G|aV;WXhS&9qNDt$wV5BMsyQdtS<_YoGVtGLMlI(LDb+0h>zJ zlm8zrmp>hedH-$rdEz&>ujR_BK_x<4Qt5F}lYBJ%gmRE7l&aNOFGW)pzwtZWklKx(Jd*wL>EbRy^&rVO0!4Z(d=tfp)KcGYFaN2nwb75&Nzpvw z%xvOxB&@TwTD~nH5$?y|&7Z()+r4kQ#+9m=Ap0a|#3`hR59B0_46DxN)%m{x7t{ED z8zyAg2`^J08I>K0IzHOSc*UKS@Uoeozep-ygc4Tvp&qt}Lqp# zqjfl30}NCl0w4`YNm-leGzl{G`2UWZf`*rH->Jm_+WQQqlF*l&9(((jdfG zAtLxnL5oPx>r0D`bvT=F(zb}j>l(Zbn;!_foYY$*3)nIz(UmwY5E!%P!S3_W$a;Vw z$a1Siw*ElQ6|QVGxPw4C;Tu>^U<<3fFXR){7zo5RjjDzvN;e19z9oE8y;e_ZwqydW zKR2Zq=IPLP_v5$Nr_d?o6Gr;}q~JzbvWOI4f*+5S)GXcDCgc^MW}|wqNK=O)u};&e z!gfhLyKAGj?CUbW21wY~R}yg*-@8$aCY7E)cZY!PZ8B>q?nv~qPGQGAsjes2H_)eu zg3Z^?WJcB%{f;X#`^p0wM%LkGsr->W3MR3&21?o1 znjRlel4bpSo#AV|eQAY!<@xaT2@Gz`s>`#9{X_hP=*(DcV09MFG3T)<>)2ol=9f1* zc;5WBss>eAmcd?{HOJ!OA}HKkyxM{U9*KRtO8|05v?_DQanki)XrA@_2OUL6?c~9o2b^zM~Z$-VTG9BSi zR!M}|oo*kq--xOuicq`S z8}C{j3yLgmU^lQaQfo2<7bS><%?G4!VcuFG+xX9ijERdAK*OwVQE=)R3YBTPW4t8G zE3ZwJ^@@ElcqGIP4O>3A_n6FxD+ob_*E`sn*N)A8Hc)SUWk#ZywZ4b1x#1dqN?9M8s`x*sXEV8?6D^RY6XGQwk z)=jfH8&P^YR5-B%&PO7nvFlPcFhh0_^6JrElobPOXAof2lKdfzt0E!Mw80~l`an** zFhLAiY^?S$Svkin-sHi$DL95nyq-;~?0hWigg+NZ|1DboaZGE%DF5!k7CzXuIhcBu zS`Ngk@|;aOE0`xJ774WO(X?vZNO{e{pRW=f3Wq3;W#V}De}0Ns;Gpxpy~ouP_@k3r z4BvVq%(X$OZZHV`=b#vOml8aL=GPrzDk>xvQLCT~`k!Anyb!lAG0#HH(T{@|rBW#M z_>}lJN%@bnAWuEmVTp_KvUeJA*H2Ox0*LNQgz?K71$%$C12_Kak0ZSPNOz?*KKU@! zu2DGxgKO}fUNi(MVJjmDrW}Vv{tLL;=2y0`9WsYYOsVN+yEt4nu=`cv2T5?US6+$m z?}4%s3JC-Mp;xTIyNSjBlP(0R4uiwsHg%BAn~C9d zlk#WxK~jfPMk9_I`qlEk&i-?a=@|re5j5Zj)c(fb5 zs7+ZMZXxS~+ z+CZVU+O~3)ao{7Lw$vEijtIpI7UV5hF@b^cx=T8US{53%8Hcy)vuJeVD(kZ7`FdCj zBr+Twc;{;@T9I5q7?bGBZiehaxlXA_2?_@QTljGbJd$JkArd*k5F?2BeCGP74uLys zQVK8T%l_(2Oo)}qg2RsS@y#m_W44HBRyi5gli>m8yt;Y0;#-*4 zqKbO2&XRpMMt4mEzk8jW%=>?o}%R)(k;owT1lQ$f^-NRqk5eQr! zw%>f&#_P}ifgoD2fl;E(7d8_Z_%&%lw=gOFfepWh zB}JfNsXn)^9w8IqxIZd8AR-covy6cjqXND+iY7$Q#?Lq5}&BD!drfi4Yb zmH|msBP|aVl?fM=;apvzKkg>=#IL%XL7IC}=*In@c(;eobG+H2o=FAR^zNTh`9Nvt z&7u^q#@NJrA@Kb){ZFSpI~du&^s+uKj)qnR-$$Th@m^dL2*7o%)r>y+OWhX)kdIqZLX5gy~UfB3oP2eh!kZ7F($(A)dZk> z8R-~HF2F-A0eoy}l#tMI2SAeDCo%)qft&O_gMYKFkW~q7RM1DGLzFr?$rYIWo@mxY zcvNf(e+3IPOyBRQ0r6e_7RDP4g&I3SOkxBb7!PFNgZ#=UwzdD$V<6f(M%E7fd0K-v zNSvthTKQuQ-e4xSoMcjCkV#ko{_C&423BUJxc?`)-yNVj|BtGE&MyMJmJbKuf*p_j z)Pb^L2Zs%8xDUco7P#0}>1#}>W1y5#S!0Trle|Vw_MqtPxY{7^M0lF}EgJf9%N#L_EcTz$_> z*RG@RO@lZ4lBV|AwBH|>&&$mXB{f!i_7R!z=qj+1UHLz5_>(UwnGYE{bM^spDyCnV zeK^bP>wbP0ji3zuM9`LT3A|dlj)yQ8JYmFLH~q;$qyo+UlT02OXqcpn$lYi3qIH71Zhq$IX#f|v2sYhd-;`)$6b;rG0uDlSG@WQ3T_m2 z83p%@!8^!<2cI-~qYXE>c*Bp_Sa@b5eqS98<6ZP4ORFAQjrXdmiMK)+L)5oIvwh=! z|9+Dxp^;CqL|;O|dx8>HPgukJiCM5iqOuB%PZO{h(?%O|!NCm@`vX!vfL<@QQ$w?MXAzhkGC?Qi?wZ3q5$9ES9a>$V`o z5}04`#0GTM(=F~4_zvOj2onWyWO360$_eQe#W`o03jgC=`x4rMzmVmMG!}fOt21Ma z%g&84MBBS$`v*gLbY=5{zNJZLIU&F7(U7J#xLb3@XMa`ZqCEx=OfN??y6paT@+bjS zEHo_A_Uv{8Df1rs_hqj5k8Exd7=E#rWVC9e{k!rl1m=C2Th_BzfDkk=9^n2It0u4X z+Cd!cQEyhWD;lb<+yFeTA6{1=L6^JqUv4wAj-s=MQX0^4;JZRW#bd*$=iZX_K6F905a_ z-B1EFw%I}^z4pig1AkU(C=GkhLVu2nb{uyBiza*lC2hib9iV7Dwpq!mI>j(cRpKe| zl77OghpF%jTzXiquO7(#-GcDr?VPD`9OGM@Ca-_Spk>77Y%})uv!0a4KXQiAPy9c) z2>Vbr{}s;cEyH6?-fh~CKzh-50A5*HF-ws?Kdm%unI`9|ji3STT$v(s&m4pkmD@qYX&P`-5iAWdYZ6sCsV}^-a*6 zla7yXA|!kzQP8jQT=Ai%>hD<@#Qu?KvDNtQT=48dR}HjmKX}IZ3)yQrMm%HAyQ@{5 z7KqOh5Qo)Ix32{HVM)nffI9*r^C5`wRciAS=!(%^nfrQb0mRz#7p)aopq2z#}zL{ zySobEks1f0+s>k{F^KLO%gnJfz{!9%);>p!K)+HmC~(CqsYF55!>WnC z5J#iV=Y~8%u=8KG;)Sp|1c;e4B-TQ3=kaV-)Ul|x)1$Gzblt%1ZTHr>P3Q8IHcOtk zpVlG;=~suAO8`-ESmPNv9FU}m^|`9fbDkOJ1w^7ybvGb9RusVs#V@zF=VT9#Ykqa! zS%&O-R|6VhBmV8&IKZ%Tbd=IEqq$nzx3~=|m?JTJhq*V8g|@wqovJ!(BR$1edsE?z z+PX=Hk?-EuOyfd5LjfgE+$Pa`#_p+YXOrU=DPD;Ls$sg+HvZlHXLIDth9xGC6eRM3 z73aiW9%dH)o{>pkt~x*A$FSiO9#J#D6~d%kwxDynl4-7x&mBJ(R2~p%WO5dRJIMfd?$Eit0y_QBWCcty8ggbCC@xgKDb@Ev599{wTctJ+#5bjz;TpxT(o zzSscN51m>c@B>K)X=x|<9z-`THE7dYmRzscxM+`qhLApf$um;!q_chwl7_7grXr9= znIJWVS%}oo?BUP(QZr6#>Ax#)AP4C-PYyN?MPZR6Eknyh9DY);Mw6Ze*J=|FKsuTv z^3;jP`xpD~Vq4%l(JuzCYnz#JQL6hpx0)oD@gx*nf!|kdhs25(XQCB9!YesxdD{2J z(JNwKuqiz(QqjRw#y2J<)>0mC_@%h+ z#!=?`yj)MLf}aN$2aE^TF(&YZHMOIbL7dOMBMg=B%qI?47@P^dr^Q6SJ{QIB>cX_= zDOK-T$jh^m03WZmH6#qvR>*+q~+1r8zjRImCUOash4%CtOhAa=K}P8Vo? zdf2l{acm_fDB8Wk5g_kTy=7ZEWacZAZZcNo`?j2n2=eOV+~z(KbJt?gS4Nn0d|t$V zU)FxYXEr2os=!2mmsR%r?O&rGUYpqEbZUru(d=&8XFRd5Yw)>h6U%)$Jkmsh7It%3 zF4qhxpth0d^Ybu6e}VbK{U%DC`>L#pNSfPb;`8JD10kmTbRPBbGoegHyVX4RT*a8W zXAc9Fv~0EypUd!WNLXFT$fz7;4d-nUG|8)7O1)*@AV{X@6P=S(Xwmd5V45h&IpDSU zUHfjnySZKLE?r+f%?A)(Cc5&x2T9-~NZfForP7kg{vakPZ&JumDY#*xk}XAi=w;8v zP0Zcy*7N+Ad1$81Om?mF`eyRJ_?bI0zI>HJ9L3o;NbJU%kx7`|@u~2NaLVi#+ZnM; zL&poo{GOBNoX@WnZ^}lwO_dLMCfR} ztA&h#_uI02<+k^TEMv97w~HPRRh|`%t)6!%HC*Tq@H{ zk#=%M{yFXXHwpiw&6)Z2@~N2I+KQic%zE-gC-4c;sF_N_UHCmKL!n&hgdD0ZYH~*E z{^C#@py9O-r3kZ`SeI_afX3{l8p+9J*mraMm|GThqRTjZf6N z?#Rf4`M>?vw`+6%b-+)}4r++Ntu=Y!P?XjuXma(dvf;~=E?SYr=G`U2t$XmnOEpIP z=`xcRO)U>QliPqZ@;X_NOliX1!VF;UJchr6tznvjTCx;g1c`xs5)0J#(W`9@^j&&BPVviVUU-e?~c2o6n@ydO}C z%;c{{w$Gcy*0hFBGw#)zXL?jJ_c}w{fyX!QQc70tBWyQrA!Np3qt6IefsnF-05y^P zC?eW!Y-R+}?Z)Juid!OU>z?=e0G}M-SxfpD7uahPI4qssapzPh3A_j*(ilu56{w8x6vLdRvpE-?4*h>x>?ET3Jjmg2tF~maq)v;&=TQ zz(U%k+qfD>qaw>9!H*SdMWkWPPiZ9-DsX$Eotf5;X-IpB-##s;H<00~?Ea+z*fA&! z5gCMn?1+60ops@7J>xCKAyI>ldybcnbMGh z2a5R>atO*ce+sR)SlrwESe#@4-X8_)*IqExwq>AE+mVSI|>a z*n^w*CP_#`A74hlb2{rM#(_M~m0VDvWVq7#JX3nhOuczGuh>rfogU z2|Gug5|BUdu+yH{8_G`JaDp14dI(7kBtaH-*NnHAx9vWc=uMaMxFLAf5DGR z2b8%)M3*X{mxwI?^xmb~Jg-3R&XT)F80J#Bl&9(DvAszYAU4vQ)?@0{>yth9sng$z z4bREIlSQltc!9R?s5jo zFNCvPAW2qTf(lwU4+n|8o{Zy+e)|&=W)shvVq*qNL@m#@B_{}&%53Y_{V+fd%TuOUAw}ox zG*(dzgsgd2B7Qn&YeV=1lybX5eas@gnnJmzOsO_nn&I6 zOED`5q=F^3n>cZkP`1i&2EYcS07)EQGi0-Vvkt7>_^P)U5K}Cr4drrj*(DFKBwAiA zy=}M`!Q;NX}@7o(nu>~$7!U@X`eR6YKAgmvQ%5gb;v$fJ(=7< zC5@&xPWH7qp4_B2MoLhLu1i**{GB2c=8Zf84D#-&7Bjb z#fBvn3&7$8h=Vxe0dWwXP-6>w>^V+K$B#?@O>|@DuKY979OAUj8&u#P4PU;(@ot); z#Y#gkWp5JPBHRMWF(Kg<{nn5Rgw!$i>IR|}C5t$rwoYCMSkMSq`1XCUMPj4#7?y~z zaUHS@r7B^7eg!;a_?=aHKzZYL45;Ub^}woHqKP$J5L5Ho(IUWMq@wY4n;W>UNhii%bDyz2-1rpTS$GO0UFHbAP;U!x1R7%k0 z`35$SzFTfHKU+$0w=_~pP&&e|-%mmlR^Scfkl}^6;=MlE$nJ&6L?<^vHdUUD+1()7 zz_JC!_`>K-0=&j)W>E_=Y#UhDo%_=$;v~4Q37$wp)RxkB>PLg*=}A8eGa>n6sdOE# z8=}SikuY|E{bQrB1@in0hFkIq`H47)@MZw(4X7OB;(c3Pbb^h9&f}?RZ74ahbK^3o0MMbl078}^ZE0&q@;wxR~>Mv470$? zW%D1b04}gY{Cf(d*YfyW z#@RQdE<5Fkk{zq|@kkdIyUV}zO494vu98jpod*+!hD>@h6)WLys60@MnxhLFd^r8s zmGED++=*Xu7e`eB8q-&oR>DOZ7H1up_?{i`QnaszUwL>D&$&`e#xEkfIaclxEYs}M zRkgzjIL!KH6TzU2K~L_kjyb$RD~`VYrEezx)bV_Ho)gC+cKEld4C$A01|f{tb50Fj z=VYl~s7B6c9}4~Fehl&G$jGb0Nkc!CIxlP7CR5OH&(|m!dq=fsV zBVCrOaydjw&`Os@?H$(=Sip`2)e8pQ5YqVu1EVTOcN+`VKxql{gz+e^agwoBq_ZGN zSQuK}jk0y`?X&ZDBwudU@z$oG8#TTCMBSHg2hN z8o(b28VsfrhCo(Gy1tLLv7_6%V2D%7u?@sq;7+bd$lQS6#}B7sK}sgd3o%$zlPJK- zbjFJ(RF(@^#K7qS%4Y}5nz9BoRBhEYFa`dq@deZwgNtK2Y;UyM2jmiw*oX+IBonVh zqI*v&>Oz}Jqhxvf1u^M3j{!qiqQHGlJz-8grj)!nYa0;MP+`Z?A3o_YET0m`{p$+! z>!Fk*UVkMEJgU%|%vq8xILhCnXv0s)dMUx$-NHXUTk~B5g6Qz*8g{Q~z}fb9KI7@M zY}++*qVR-TU@`~dIkw({9Z)<+5#M)658tW7L_%#eX|*LM?X_4USQ)u4=}W>XJ#p}s ztpX{*p2tGZL#~E5Lo2f9Q2}TU zI*CMhtS2JWOaYdd)wKikxeSt9)f*x61y7P^@Phc{I6)m0^f{QV2Lyguea3f3HaLVL%- z{sPpv^MAknH;D9;J*84ga2p2&ny*?rxFCE5B=WgV5HL?WygoUi=)PwA3qkC^fz|f^ zxrzPIehI1;fW8m-)tVtLR&~=FlbzXfs0An0%gn%@UW}n-J^JG{4wBKxgHHQni8ck)g=Xgd?4sV6a%8{#r6Fz_SW?b77PHOFJ=JE;O1qb6T^F!- zUiTV#Dz%n;$Kgz$=18ykpX~I2nNJC859{;UE88px!(5?E8SczW2A1&JSh1|* zgms%)(qh3o)YIu&aEr<#Bk~yazDGCb&%995qL9D`ZSn8NC}GOnh+X3L$8Nt|J{CUA z`W!$V{&K<6A0gAyx=S44Z;_c6wqmStM&Tw}O?=xv;|-wE1Dyvn7w9}Zz2|=s9R7Ez zLvls6P{Cey3O+$`FGZ1P0`o)KCNaWf8*LD-aPw6(wwk_`6*^~vCg$W&46=)1a^4QP zCe2ThIprEt&F0)RQ*M+Uc4R?fWOdnDA+CE+4X~!2s%~KA2ETH{f~2U2O7AMt1g*WV zP}^Akb&9a!lIkucj<8Q|nweD|IQT_m!fHQkC!SWAP07*9u@l+3sk&eFmx@&DT`(of zM#%5c(Rk9-X?b9d8mGTJA)TPD2_tPgr`J@$l+wFARlQ$Z7G@8>cRtasR!u(@tr(g7 z3LR+3?b<$r6Xh&!T^5=)IdSl9W)4Zued{xRx3O* zL2B&(>b?a0dO@?jB={=*UVQ8eh|-l=9VgKJ%M)4N*^ol?>!gA=SkKB_Fla(>s1!>) zG4+NrC2%qHbpm>EhjWG0<5R$*(x;ZvC@CIOX!czdu$M`y|Lw41S8th#Y>FYcpgbnW_otl;*`E1u>eUR* zCc$sLnNEUB3IF8>>;qsVyA_8#D1ORG3xV5l>YW0OFNtTEdMN!=HIg*5SIkKw{Mj3H zqt+w*H!0W#0Ep7(n+Ts!u(h%E|zA+cqA`52P6kKZKP@y<+K&qonO__Xb#K2#EY1^y;g% zp`yErI*(@*NP2Z+h+)3;4?=msKCtw)w71sR#OwPgC%E40x~5ZkAtZdZ@iB(Np~qu? zxK5w_%R82nNUH^QC=FU&s}A-;AF5GHTAL9-zou-~al=&{pM#W(@e7Pa8Njjl*cfj2 z;Ii^pWnHKI=EM30HX{-H^IY0BQie@9#uSYnif`X=kRChGLARL4aFkkqC$Fv*@>c7u z0dn`9Z9$Cl@7V*kXWez2B&48Ccu(LtP?xuwaP^!2(x&F-zj$#_#&1sTQuiI;pI%DK z&+5Lx)n14ik(vq$U>;AbS-td5#y`*9dkC)nV?{gVCv!gME}1!vtVsWV%;?{7cN-u! zFT`Io28@vxP-%E_S^g7a+WQ3*M#mK*Duv;OA%)p2t!C(t-q7@-Px6Xq?!{Lg3eX5< zS{q=puO>L0HGtcXlt!_1dA6eH_q)JpeI4Wd1~H|-#DK=cxlzOs2s3pms^NRE#9#9O z-HNfyc#WSkSjq#=lqGdChT<7;lDrTH#sx#pF3QuWmWIW0|F3b#TY9yRlx8*!Hd3tt_%XOyJCslpQ? zI%muP8xl{-hv#9&Uzq)enO2WS&l3T8X48I&@|mM?oBjmq81{?l!#^~se_3q*T4ub? zUqH1%D(`ex>?#3|!H2EEm#`!~KFZeqG8n%|&!no<8Yh^~Z!Jn2RU#eRqfkGS;ALSk&0E)u zg59m}^KVg03sOey@M-J(()|ndp{dnh-4K>?;*>@}b5nnVGv2S5CxRvZxw(!7di&o- zf+}HoM&%n%l2(2fT7j5V3BMyxji%0?Pg;~LGGLV$l4jjnSXx?|XHsngga3s&4}gmG z8b8IjZ1aW^$ZhG>sfes;g}g*?UPpfX5{=$Ny}fvHzOJ>^-^_-c5fqF`^d?FemK9&g zP-b;ecNmL%Q&_!9*kCZEtJvY%v6YhgpC6x z0#BKA*&384XYAo|8dKsmV$O@sh?n5dgd%Z9ObR=Vx6EAlp4?_RBbn5QJm2CeRdXuE zM1j6H=_w`S4N4P%jV490wY;k)ndt4Us*54{mQ=$g2j8tfUZi0pR?<>n{QN?WATZ%I zzn!pnaGeWc;TgTxOg+OrZBJG6&_pKNJ9 zt;1^n_u@7d1r{HtN*q6=ee1Q!>UsLezO04sGn(ekecpMy5YBiR`6E$`;|0ZH?mcgW z#)GMv5RC{FXZ(F>K@rpk*nkO?2V74o5%@yv{)UtBronGpWb^xbd1#JU=_7B%8^iZf zy3o24hUMJZMw#GhPFl{x9u^L`Izq9|8}X7Uk!f$=G#FF|eei|dBh#{wgn~&w-A#g- z-YLPUWkm<7{ssNy9VxTcecnIvJgylEowy6n8>_-di2H1jD2%lThF}`2M)B+&shzOh ztxUn(Bk}xNPFlB&sds3a+xRyMcn%i-&cEcoTRiJA6@_w||cfp)ovHV|LK=#~SfK?{#LXG1dQrw6&E1Evp$qvRIW zuQ&IoJDkT|ILhN-Op1SOEO4Lybn#@0=!yIBXUL>r{dV>EMgRCGVoaa_>7hZ9Ha7wl z+IvG>P+U-s;%F|8>*ourLWUIzP>|QV`e*I65WWK?yE{e_N8Md^{x`f zeh^xh2cZikFEYh=%kt2Vag(bvEc(B{r?%97S`iM4N6HZdCrC<0TnfviD=DA;bUk5P zyr_AleWrwdx_ism0aN>NK)sI2^!=XK0v{HvzRfF{(GxXr?I12iX}-pulVbfok@WaB zg6I|fORL*uR0qzxs?j-;*J46bVax)55=k5#(Ki|JqjjqNA1VrYl38^w=bx@IdoXx_HN@D{sHLkTV`ThcL_g$ zQdwr8UdW8J;DSgs@e31eFX!5^hk8UDkFKWs_KROB;!bf@Tce41y}{J5NGHQLxac}# z8ET>uEj;V^!7)luCdh#E=Dh221^=bfD1@mT10jR*IySIEyJg=ovF0Urmi0|8E)K3M zdb;qBQ;Zd^H<&Dc`lb#FSBOxg&(lPhCeY{S8rN<#Kfkivt}}UG$)~UXjd@v6xtGq1 z&EF$IZdbliB_;P(DNxAEc0qM%{a5xf~sc=Twj*TXuV5^>e#jVhE@n+ zd$kiN5;DykD(ZO^oi6AipOj$PHMZ}~uZdS`1w?oxNnv6%qr@jYJ6LGX?yc42Ju(PA)ETy-vTwq*>aC9&LmO zXJ3CKwvKJR67p~>FoH>p+a{yM1CR(GN&a}REV93h#HgsYSQbvqsau0;I)c9ic1Gw7 zN~WHLSuH4!RNbc-;*9V8je(HRIHf*MtWH6}tzZ9aRob@T*!xWi&$&KT?nxl6UE^2v+v@|ZqfJU!A8x9LDGV2 z_8p=jrMF;5&c4*H)Mm$Td{=A3y8q8s|F~?6DSYD3m!C&if}5y67auv?;)$(h83?iq zU#9*{dFjz6WYgo1!=1YE$K(-(*%Xh8!&vC?Z51@w>rL+YKDGYr_9CdEWbaWscFJ7{ z=M8F@a4vrXTiNzbXi+3C?8nZw6k8GKUhh#~kg+JYwkUV%)5dae?uj%mwtk1$%^PnyzbO;#$Cod+(-%IHsud zGp+2}wl`n3!7;;4)6&AA3*wk7)a(36#mBE(jH3nS96R-F0;CU9;PG#}(5DoO%5|$- z5ZeLTHOL~&_~6?;7m_yalN|_;DJSFE(+s2}scqzVFfI^%?5J&Hjrj2%4Ha=DWQ0Q# zx;;)m=Anw`gdae7i~?>Fk{`WhgtKW*! zPyU+7l36T{j6XBpoh8~vrB@H6V)D{N3>Rl1Bix1ciFb%!kEv$3$1=L=1#I&AKQg?f zJT_B8k@KQCbHwon$hk^?5_nwa0G{0qaka#&$d-97r|xK^8?SR6Vy)CHR3O^b7w~*dD64uz239e3p6(vbcrxLpUP|*A1bz>kz}Y zDb!u*6U07qxx-=<_qt9`<#hL>$-cMiLL*AtcYYBuYh1%F(cUGU`+mhbbc7%R>(j=z zj`u4eUvjBs1tZ#?A7Q&nRA;OPs<=;vmgAodLHrOhx-xOTpm_T(#3c(vha8}uAUGf` zSVGt*IBmz37Aji)8M0vm^@z(NlKhzu189|mKs0eU)9+N%ynY_l+GIgTTfX>4;YPJv z@r}BfQ`m8KMd7CN&al%7^q-gfO0+#A*Cevvblo;X#80Wyi^VqvPMS46S1nmaQGNb@ zoV|5aRPWa|u7Y%kNS6o=Mi(mK$6u>&)5J5c1(YH)W{#xCskwmGJCb!?_l^vatYi>Q2yN zGSrrJx&LzR_0{5=#}>P%OT2`CoKJ#?XbI7u-zI6-^@%{=hzqUgwx$z%hiapXKlsf^ z(vBL|x1=i<=}eA%n?DQkDyFNma&1_(7HrANc|7xQX?V;GUHnoX`RRM(X|*DF&A`Eo zIf_fD$kdn)*@A^$36dud_@YCeE?zFnUcs(=_s7jn79knAo|JZbd5@` zJeuLTe7#JE*bQ4`c8gq|lssdsxZNI_uh&_sm%Pd}prJaIPxI*X4|fE15{34DMDXSY z|1wOUIXRm1>0#N#`P|=hggx{>_-Y&U29fLKLR_C9_7I8NCRBt};uI>{6Z2<;Pfmw0 z?&A1(dPdU{fA|4Ek*O&p;NQS`Z0Q-m60eB*(d6^i_CxkpMDE5SDuH1e$L{JdwTBhT z_Ya*2#9R1fo!{?&%V!fH8`HBfj6Lg z$Kg4`Dlgz`{XuU7Jy-sxQFKVIvlS!B1#7og5I5g_xcOU}e2$O2$#`%daDsYT9bWl7 zVmot>j6r-2M$#U`Pv|hoZ$E}njbmXQcLp2W*x=2lSYTogsnlXxpr4+6$j zLHR6!pgX;cJtw90pe^&Ys_Z3v^CV!#B`cR@?2)*{eY$#6_)Uju24M$i_sg++Xm`Q2 zY5edqRc@L3+3i1n67D`9p7RCggf~)^?_D#McSy~sF#M1BLX?slzh1^OU(xFX_uprW zc-B%aoDwYj4g22NnJchhql+0O(DBKQ5GT$81)4Q!<5{Ykgo?hw0wKe`q!On=n|CXk z#8VEB%r99SO8NEO=g^eimg!w)AfVN&!(OTPr;T6f`9+eHn{PaEV6qJO{n+bv@!92yGi?S|e5%LMf|M$l;EXfzor(siwKDhuayvsR zpT-$5F4(wAvxdVVzR>x%#x4p!$PovO>o^a|e?3&P&PxmqB-ilPz4RJ_)ySt@#86e4eDD0<%J*&d;<^$y{fm1%(~2p zb&LCSUplwI)dIo}7POj!`1Fkjq$$wh72Dj;2J-f}rfq)l8=q~=dwwZ?6E)D(u1%S) zX4pfg+ggNHVcj`J0lkc84@i(MiFa1{v$%QECkQFdC5H1ThesT1)_y$LX6(rSyUaPMMSHtLkrd=^^0ZD0w>27XFN%C;k{ zv;Obebq9cv$0w02@r(2uI5eijTi>HciJs zLUdT$}SV*a_LHP<3BWF3|_w2iL3RKzC$%&Hc! z3$s9x3c|%%fcvl>4MOWs-!Pd#Qfmi?1NA$c5N5vXT4R_N`lj(_!;g$B3Q=fKDeq)7 z`x;6LmOdnmQ9E)?^rFPWYl?-#8`rPmnICK&k|3Ui!Qnh12Mmq>O!N#eVt#-O+5eIo zms6VAsT@~vcz{L&nBfB74gfwvx++vO%jJh?{Xov9BUpqpz5sf^kV{``YQh+UcXNr! zQ&UqD_s|u^DsIf1J8XhQ^Hqz#+`M4aeZs(|=Uh%`n}#?kmmDj2H*gd^Z|4!FDo!1i zWw6Jd4u7$m1ZJL`7jLqpx~C)qUva{cBZ}kHIC42wfqnzp#IxrVwAbP#jy1mAhLTfm zof}}siIR6PegRvvAjB#D%_j4h1#k9P8Xw2$;e6GMnl_2!)t65X@e8u^4^asYw5oW= z;Uw*B0^?=wc%5pHW#7OlFazhmc&mbN9-@DE*-LQ|=iqmWM6n;*>xoga&R48A`69`g z=UD=$fii_>>jtb?=5$@0(t^h4WOF*JJlhWmHa`6JcNfkvy-wUG*Fj}<0w*r*DJ$-@ zPSee1ki5SXno4mwHNhY|X#n^_U@)sVG<#?;olh!^B_21le;5QdhUKa|X!pm!8PkSN zupMG)pU3T?zeEQ=`h}mV{Go4E8ra$qmOx#--g_cDzm~-fPH7zkyf?MSNAC%YxADiE zZt`X6zhEqA@4$bPO)>#(WMo84=SC?5Cuu*5L3cG_3tR8O`lTzn_1gesir3zJ`BVyL z&M9sG*{8%{4xFwelV(B@;F`P6<7G0pW<~Z7AL-AJtbkt1Q`j1GeOEi4=wZ1&F_Y-= z6-#aPySf&otC4*RZtuxJZT0Cv))&;Uj2*l%Fd;wak4fCCi1^*&K<MWRvuIO%s zmH`TBya9ynb~X^n6b4$QrHETyKg61&Fu~7epGNj7aI;#-Zdw{c`}svD2mx`ApV+n+deRkT;dSx8KH}-F_Pg!iKMnimvJ^ z&)IV-foLm=AGb~YLB0joF)5C7jEGpe==av|k*np_f9qakZu&^pxnS-fFbq4u@~Xwb z?caCMgaomYN9^0G$e({Q{bl)X`05d^Y-?`YSnnj>0dUY!R6;ghjwXVXp*!MO>`jk- z%{n`Y-TJ`_Kt1erYR`Yb;Wc?K2o`|SF5vh73$f1ZI>YodM*i>U>VE>PYS31qK=gPL zVL~Bk0;6VQytj<4@o#=IN-ha-4iXZFP!ZfACULz&xTVZ20u0oep7oK8)znrhCj$gV zFz?g2GTtpk9)sKluK<7wgVdB}y~2h;0=xjTpVUdnTN~;)Zh>A?^MVv*&}*m9B@Nm> zpa%j`S%}$2qEFL0{P61P4xP7}_1ol|Y2sLowL~J6{S~bKo1f zTCa~NEmy*k4wWtOIwxLsvdMrD+}6VvjY)3<%1;mAlcaq+ftU|Oo`YV-Cu$R}f9zr% z4kt{GeI|sid^~a4u01@#rtOLF_L`>MKlz+Q{#-=;OFZIad%KsdIaqpsApmgsdn$QnqJi*sn=6LY++!*A78TZcX=Jh*P93F&* zpJo7VCU&g2s6gpTN|0EV1>a_{P=@(8BN%lL3Ktig>BlCVC8&r3L9fBDn-B7|@cq%O z->gdOE`zba^T8RttPHOZ`unz5>-IhGzjg3~@zru_Ay}z#z28Wf`q!_Sy@_AC;V1@3G`MRu9TA%KF}=TG`0K(U9VNZd}w- zM8Nw3c{!#>L>AZd%5U!4R)e)*4hhrxTl=9r)f){O^NxK~{UQ;E^W)JyB4}n>;o;|8 zvNueqC#UbT>c6&m&~myE6GnUnA2ctM@#Y|lkjvZ8KX*o85KV;)V0dv+!;E7^H}PA( z+cGN+V4O|)Gd1492x*xM`i#>9&opKAYP;Qh@nV0h?Y_wtdtrEvDKRQfj+da{))79P z@}P&tIoOpzzAQfr#vgB9Q<3;*K&AXo%c9B=~lHCg!JxU%TS5 zHSA!7DvbuKO@cDZ^6enXF)fU1YYe_ovaR7~Shq|ZcB}D7K_q(y3&~)eTq3#CUaWc= zTlEu6wO}F(TF3k4SYcQYL=MLw3L9oN_q&O!jV7DRLD{LmcbTD3YDNc$agx9}fDzj5dw<^G!*4g=%dS6uwBmQQ0>To`;R z^xjyP>WU;b-clc1!%;EVeXRc^bDJVsdqIsFFL95qRT}OSfso1fpBa$vHsT2WD6QJG z?Zbl1%)_7d_VoPZy3HMmqu#5GEBl#hihOV=`{%W5F16^Gz#1*TWE2ywZLW-GU}IpT zT!X^6g^}^DCtZ19dRzp9^snipYXMOhk0i`3B{C%iC2woo$9x`IDgyqipMPToZO4rU zQVw?rc61{Rc2Muy=CE5>Tq*n9FLnNTje6H>@$p{IVO`f2NQ3S~bV(Nw)nCermrJ`% zjvznmWCjN*)7Q(kz(>}W(M6ub;V3Se=f?m33&oFR(1iZSk9m)>cxc(N!nk=CS`3%n z)m!!7EpdN?aV)PylJ7f&$evF!3u&BsO#sZtBH-!gk+t2T#@f!U=Td=SOmco~-90`P z3?e{mv85!Xw0D8q!4r=oP!{(K3El+(hOig!@~uq}6lr}{jf`J$OCyASv&D>4;+_vN z$$4cAt1A|ZQErrno0>;TmsszURJIumge_Lab{Qj*^WiRh80V16vCYe}>YoQ9?}lNz zTH_@Qz8DC4jb7td9_cYVXX4xNilss~ul2o31r_&0{GY5RHA&!-}Z;Glx@f>m;tg-ua`P*#tU2}rHwWWbg4aNqEmIp^*1^?8s$Ynu_9 zAD$QZU2j8L7rRD)mq)WTNt3;#D-rcEwr0g;cKh+7hUP+1FWqh0t8~+fu+6%SPuAxv z`wIvZo}u~3jZ!CfwBYqUD3_|_(a?M?p3!7)&qIqfGQ**h>2u*h=~0gAx5~@!eN%Fb z)F9JU+9PK#JtA{t!>0+OCbQze&wu(wE7mcifPJR!t4XkiYWi>w1@c8nZ@J z5vs^i;QYC6kY41rocO3~WaP7JI1dOH;6tOMo{5rR<0&HYLZmG6+&N3*oAB{+w1v63 z+y?A&@NW}A%<{qlp;7sx%F5ExzTRGEFE3k;v%ghqhLhE^00@^mJ3CibSMBM_2KxKQ zyd`90WCR5xTTH2`sde@A)b8KsWjl8cU=a{3^JczEZ|-7EyzQIG+rM?!hYbLU8N!G+M}$>bmr3N8TuLKpagf1wMY zWf6w||M+`Q^7ZWYH^IlDb$}iR{EIxjJuSE~bo*C8tmjVeW(_XCjbjZigzhGMdU-c^ z2hzj1=H_M_5f5i)&FfckS6LheC~$Umc4$b4hw$_{9zGgN?2+{H+M1oUb@5+O3in}8HL1S-&c3~~b4hKjbZVC#;`F`Zho4N;*49Q; zsi~>0NwYG($jZt(sch@$=$M*9U3-HxG&I!FQOfGc$;lZR85xoRKCHgJ-X24GjT0Xq zpCan$=;((bMbyh0Y5T62QP>}(%*nmU-W?^xt*8FO^!)}6wc zy>jPUI2o5HJ3XmVWQ?9%Hf{5C{(YRH`mxk7hB1ezE1)%;$}`Zy?bqu%p~I5|8ylPV z8XtaGe_vm`i8=JI;prFWAgcRfMODW~6%e4z`mRDJG0Y<3x~Am0DG@8(8?sXrH%KwtG58eHYX|U9Ha{CqgHMDfpYrRXliTYFVmF$YV(nn39%gx|*fQ{gqTZ)2}3`$elYT zq~+>7B2)?y70NrvpL_9u*L1GJkT3hgBu8jc=!n{}erNo%N+2xkv$=lS-c@!l}%aIDRl51+trCB!pDjwVU z4s^z@Jx|G^M+z6p3ERJ0L#JPVE<%QfL;Je(o?#*>gi#fcv)MLG}A;zU5n7S^qDZ_r~TCm2Ftr1`1CPN{pSs>yxs9k zqN{USRIw^_`sc#(aH@L`f0NW*ch`d<7Ul~^hr#jBokN(DKTT?ZedHak;Cp!;@P(ML4N;q#Aksge=$Uc*+I{*9zbU zgJoaSr!w9nj(vxATd*6tbOnW!2pU>eF}h- z4p8i`uiHQAp1vQDQ0@zuL;LtljT@4Gt?Fk&uX+dSE%*mmXeE`{r zV0yfEoQOb`t}>xeD$4ak2&a(wmq3yv4el~hZbOsl=i7@^)bTbWUrvq_!H}`jQwnYt zKo=(?W^^1xH%OA(j%B5|XG!P{#ssvTY<^c{c@rW7=54i|bXTp_wz17ZtaCQ3`R6JT z+MWAgbOcN7B?Q++#p@C4F6vRkuitfx@UM{#gzICIoXoVTMl$xQI^N~wI+}+hHu5Ff zRPGTt^u!ENi5eVvxTk%+>!shfa@D47YEMwxb6VJ&b*iYTp6}@hV3gD`n00DcZr5-g zl06m9k}3@gI$c6psoS}6dHdqWvu9P6>8#lqwqC2=BomI@Wv`BQJ6QRwM=w&{cUF$5 zxlAHdNQcCr6Su%pZ>WWQ*=S;bD0Ds6YQXA9c}SvOwvJgn`)#nw;&UkAQ*&jpeTo!L z86(PINc7BHK;_MVY_g6S+6qquheyc%!GVSGpRGN@2|)vAds3tA*`$RilGURnTGoua zPUZ(qPv{5fZyA$|qQ1|#u+ha+wul>Lh9(Mo_DF~GFd2DG+2bw1Z>=P++}e#d;H&yX zK1Vx!rPwYNU9@so)4g0&t&qq8&9UE9G*;;Q{O5C5{xx&yO&Q~zJVAI|oPk}OFN(^= zb6SF$AbeeO1pw}w`6 ze8tBa z5Ea5f<`yLk3;3DIb}feUe(ba(<(+H-Vi(F*;2vf@-exX+pYxa#!a0Sy<`_&+7nSdn z*M(CbU9k~LvC)%O3tPR;;Q;vgtYuDhrUNU_THYxFhU;n+a=oOGxIifU!E56@!{aMp z{`qyvrC|hbzb#;giYEcn=}XVGKLo>7kC&Wh!KqghU?{MSN5)HK^r|agL*$}dFv#;4 zxovJEyd{!2M`F92de=4Tbp5F0k1U)!2S{x2v;f+y`|5skawoL1oUpEu+emBZWI-cG zYCF?4MQd>+U-5`rfEPD4j=eaV%)!9%qPX0iu{P08&s^m|U3@N9r^9b~J?th(DS>Nj zs%E6%;_LLm7kj|r0bOJD;&l#z9;)KCRCK8Abcps~dHf;L_&0$kb20f14oN~C_1g&eXt?Y#uDNDsMNKrkO4sF`#xhxl;ZQ{fuY4R|v=iCw-YF^<$x_zAfY)9U zJ93YAz+BtXyIu(!*)yKxG}&jbIYRDf2)GC$!MOXWUzcH82hXp~jh5?r?f?E|atS5H z3hYMf7G)@{WNdIF`E5QhxOWk#-Q$sc&_ULCb*l^%_NFgp&+QC7YG}7OX2DBK+T`29 z*+h&0Cb*gA3sJTzc(c%m-_i!4q86D)r>ZZ4qZs34`1@l73eMro>D!aw7zmQ_s?FD#_FiE_^(iq=n$NcD+2Af}MxzP7L#}PuK^H>GCHm~N=*yWv z{NX6WXVH;w7vcjXQ$@dF&yV0;$&T5u47)h{+IQ%dvQh)U%d-~-(v$dxg>Wr~WI5wO z)}t1zA^tNw47vl@9ne5veC?pS;nW~c6|$nc!@vhC7h709*d^rYBmBnhn81cRUA)*! zh?Z0)Y~@anM~0TLJA1!6=QU+U@H=u%vskjkNj53$6y zkY!YKvr-{C0kIlSWU)XIEqd_>#gmt%E1~4yc*2ri6Hw;ZgTB9u7^L`Ik5EyQ|NP4# z-eCoB;fg|e_n;Q6pAds=KRiciEz0k#%!O?L`oH0!RERYliA0NAxpA5BeGQq40L>-C z& zmRodTbv0bE9TfBO>%F}HF*zRTED2_4e@_)p1-SJ-uFBCuiRQ4zhIKU;k$aVrxJN2D zxbKH!l2azDkR0C>k&rrz-748B!VXxj&6vb%By=^P6u?!Fl$AhZK~zQU6Bg*%9__H7 zX*gK4KhiFpyY_$mRiXa;H2x|#ShnAjsrM_r#-}t&b&L22bk^vq?0*MFm)_*Y^XmvCc0i3xW5#R-fUj;wv zIvX#K34V0uDznV~xk|ctXU@FIeofqTmFPit+(L)`n+mHjg7mpKS* z-}>6(5cRea2v88Wvz#w>jgEY|5He(jf~?&QZCLI0m2Gx^a}ElHem=coaQqy={`aku zFA`fC*+QW@Rs)rPvUvZx?=lu-OM}MWreFl`zlFY-Ura$6`t36?bf;NQ7_;WzM*y1rt;KCvSdp!op zl>yAXo}_(&*95gu!)b~#(NmZwVCTvxLh!cg2b_xi|EqMNG3V{H|rYES$F#9weNe7>}tKmqe_L~Nt0p$y=%T@wEO}>Evu~iiOH_M>;>mCK) z&$rd{iCqw4x7-0Jj$fA%eZhP@gt+5Lcu@95D>F|EQtEi)5nu4(kc99xz~_19Wm~PU7%|{ z`dfnrWCe74`Uu{X3J~Pg>pjvTRA-o_ymgO_H|2#;kzuW;``$sLsz6aWh!U@rnF;nj zwmn$pR_1cBHbtfReToVWY$X^Sx_Yj_!KD#{j5mPww3% z)8+?V8^d=pkTX0X@P2&I4I#pJsmOV)5UeLnuc{fB#Z0vNB5Gm$7 ztpBOLr40yOll&GgEj}Cc4|fe^_@YbJJs}qUN((=EVry4We5q`uC#L!TuIt z1dE|EQl&TXf!M?66aOq_>l}<)7_{htG9W`@osq9imhYYKX3GZx766`a#2}IjrT7z% z?=U4I9u)js|2sT2F~RM;7qR@_SxWI%g&U+ZYhz{4)zik%1Ez2Fqs}{g`$(+ z?&0)?%49wjFb}8N)xf__jq3m%18Ula$iY@gPrVP(9g2QGKWSUn!t6E_wI9ML|TsS&-dwm@a?euJPn!z5D$G1hNso``d{WB^a$(L;sYt74o z)B6s1zd2BFL%mr8HjeS??KwNxQ4Qtz{h)Qu7}nf?>?eN2yTfM$^BTS7_> zT>W~LO{cpODU6hC&hr7vpRI6bal*5NMLx|;XNWeBeA-!g0R)pt#I6>Eq1IxQ#Lbgc z69n1y%Q5>8Fb3OaKqUGf%$MbFaNe}lo4w<~6`YtK?}fwfj4gvUfJyolP}`2(l=--0azfhCAV|3;ibpgit0_B^xOL!(6Z!FR<0jl!ONiB6eHg5^Thj{~AF zOZBZJex?6vu%*iMG+|0`>>gewj!R3X7ypDkjQZBlx`91zuk4SH@il@~i+Y)VH_M=g zWs`4+*dPM;F~fj6JDwu8jLE#f#tz`?pZmRcdNxdF_p4p;_{&J6U$_M_sqw#WvHWDa zFDd*SlAXGp+$7>(7}kNsdDjTS&4|e24H4S3$6PHxLK1qJdl^h3P5tZd;gFk8lQ0G? zHCz-o7yCrpe38Zvik;kRQI)OB9~I5)%^-B7i|@gJVqAu`wY7pba}_r>ZQEXbY#5fF zqW3mA#?O}5Gc!(ka^Bk%+PO!`5L_|FMQmyIoMB@FDnY+($3fu>3n;ivDA- zNR`5O*%o%4e_^8odzOlN;`lErNALnX<^r@TAc_LCDiDmjc43#hOzeJuWW#Wxzkvo5 z+xzJGE}QWH1suJ{4@W0qw|DOTyoWb?H+DJempMSH#z{&*Q=QLh&(y?aJ}!;(SWiT$ za}7UpbBDf!;4FhAELa5Mks(y;A$h!)C`?OMsEMke&_Nd$q>s9bXy~CJP>eD(#p+)G z2eKEBWA3ODh2Q2|etEO^{Z6CedEMUa7S2@0+vYxeuU8?o zCiI@lO=R+m`|pg@DPpa&0{3ABe_}5-=O44xPC+#1@78Z9*3urvIs?C-70peEsq7qY zqn@g@ca7y8e(~7x6l_PLH4fC<`OTlH{F%Bef10IoR2TunWk5^s&+1X<4T_KjF)L$` z;0-;+B!=ukk+(>o)QE-)QHm3r5FSl_XbX8avivXS2NG$2Q&nxJk?4eBjoXHwV*BP? zS54pI43p8}RHxC6K#6^=D===md(~GKXY|zBKY#FPz0h|Sx*nj_S{vToc#|hvPt)V< znGAM+vt3P@Vi=^JV9{X9i_?JSl0nr%yg|tWGV~HW9E52ghcrehSX5}+46)LAG%zx$Gz z|M+Q8aAUH;Cj)B7_VpL;jM|QmQ^k^G{Sdj|mbtHaW2GX4OF^PYlp4`_djmdGizu|k z_0=}l22?Il`wH<$$({>`-|PdK_+UN}!&m|)1TU1m1tOGJKcQoCO9aflr&$H)r;{_F0p`WFjv`;6!1VCk->o>wx2;vA#rB( z0tKfja!JgtG3w%J=-Y|^y%89k_#! zU0~8jlYJ@!(^2<`+`gIn1Q`3v5{I2HZz#aNA*f}o8Z==4XakFdQ>u_c1KTI!Ca0(U z728Kf5y|LB$M^eyAJs_6M8s#3encSzv{B;6s^JatBA3Db`T?Sw2fP>@*`Fr&-2^z z&Z{+4n9a59QWw=dO|G2e^yoo$cXAmx`5hPff-JsR^+j&xClpUOO2ikxsO*$E9gJC) zbg+TZ>sS`zi_|@_U?%WFUVx5Y@Ki-|$hb8u78+(QI4?;lqs zZsPP&(OFf|+2z(-sl7@V9eTU)=Z|mMk}zC*4IEfgy`O$lo>ErL@jib_u&OlD6h>`Y zP%6D}W5R*>CYXV}R_K0se8fP+{^Y=binK%A>N$@|?7TT@!=vio>it0HKR- zR{jlx+^<)|K>NCj*Ejv9JC11;#iQeV-V+OnyM?f+SyhP;9n*=@d)3U@{D^}$q}~RP zS(gfjSu4PFQjmZxhTX@7jzpH8fXYbn4K2B6@~D|gm}eyYppKUuzjL}R^FCQ3zWH7? ziyO$HeNwaIkR-=kublO90<%HoNNxDHaQRv#YdB_ID|a%T*bgN3-!&AGn4%g>MTNBB zHM3xxTpH*R!Y2QW@>zf3k0NR9f2uuNp!R0 zI%BH0;d5Feoq+R8OMdzR_cr&6{}G>iYsn^aBymlkL-h4C z>FuZMf};M4H%Gqu^9m!0NpVwiO?gJ>^vutfKSt;Da#U4xL*fHPtO4G}X@Fbu2rDN8 z>W9xp6GKAd{ompfUiZljM&fofSXKQ*nyA9G#>m?*HcYy|#A}i-Zq1XRxu-vZ^%z)-&|C5iGsR(qS z{qh^AIp|CSN-#)s)H7pPEC#9F*}m; z;tabg`y1tr&!o5gzpdd98v5J0cx26+#(g92y5Tj5U9m}0l}szrT zf!eFk#S=h$KLZ6&=Yo?Sp)Hw;eZVrO<%BK|m;Y;AM<$nJkCGXM%k!HC!@qYUy#50g zeSd9Nt@|AU?{iCo^3Jp8U09;_-RmWL)MoTZ_+0v}J_kCrZY-a^1)#hFqMaIMC~YsH z2iUddj11~)5A=SLThHT|2Gs%Ue}VdZ8ugTBg!yg5T?O9EjDc27E{-?E$!mq1Mv@%q z0&lx+^WQX09aE0ysKg*=!F=FA`a%79@eiDm8fy-Qcwcpw{!ob#nK7K8D0?+CqMy3q z%}KG-RQ9(%8*?;#I<$3LY*9vuC4$}HedV?7(F~IRF@Neg1GRs66%~GMrW$`nPIMTr z=!>GO9pjaIKVqqVn|5gnZ?}UcRu^y38U`u8$lrOip5$cNR;OB@NX*wBNL{CI$qK&B z$^l(`fHVFRDCy8$kw@Ir1lHYKCiKnPj)CXXVg17Gm<_(nK{kJ~!Tire9&@bTr^D$( z9lZYdSQ1Vr$heR&c3#7IUth(Xy2qw;zO?`8RmMlHRzZ8{v=zV`s6EK*{ybVEl7|E+ z?7RG_efu|Ba=&_(E1RBu^Y&wjcTEo~BjlYg{C-LJdc@6qa#NJ-&R?gr@9pRf8ad7k zm9t?JzVP(T&7QoEufESHu~Xhcn%w@;JvLR}oU3mcaLK8il|DYd>*(jTCz;lEwD@cN zJ+UYPb5!YKh}cxQuz`}(eHiumVp~Qd-}{<260edKQH~c^m(LCPV~}~3*EBGH)*_FR z5V;pCx`WB8SZSVvTr`9s=7g}2Ia5x>IYJ_iE|HSh4*1~DzH09v zzW3SuB6mnSp03gh=P*d26-Fd~Gl`Igun_SYqQ3+!_vjI-8NfpLhyH0Y@%QiG(8@vQ zCTVVuhCZDtNK!RWz5RzNa?tEyAlfry2L@?(e)tn7kh&t*!3RvRSJS59^ z(Z5IUH?Dzt8ORoDx+&jZ?9vx1io_tDyZ6>*MHJu}^@!XRFs!IqA5Ixc1x{FAz7U~Y z>eH(l0PQ?YiDoEuZE%!zH5r5a+}?1FjVNd3fBrfMU-qq?g^B=*0|ZLxA)X80+bBTv z$SiF{#!y$e1@6dfG}YgmxBt4v@N;bpINh7}k}!)uq9Y3!B%EyQ*AtIG1n*O#xNMaj zC&8k(E3s?Xd8~FsxZ>0wDyU2MZ4A3obJRXa-7AbhP~&sSAf@b3N>SC0OfHUjjy!O|8&SpVx8*>jiOg`gCq1*EOYk+_@TJ zWMeKA`0p8!{IkyBK5b=N>s956&V*Xd5CQr`2ggt@9+KsyrYF?n0n|6|9_?>tRdp4* zSJ;uSwozjOZdpHv@)=O%h2vo)fK$wJV>SOZGTb`PUd6;pVh4p|HgXGiZdhHq1ktK2)-B|K#ZQ(o6x?)_)MyFd;Uj`7okOkUd z=jJ$H$M5dC3Ka<&x4O&2s7<()c89S<^C5QNw2@(MU_e`&z=1BcDtaF{Es}Db(thBq z9K=FN2qM81@|mVhrBBPrmL%1eE!kIXxM8=2)l_<}k*?Q&tLO_uIr|J`73}%6~$nGX~^1T%vjylBjTk?tK36W-ndzhO0CtNI5^fgtve z59T|`>!XYwlZct(?~EWycrO-7lkr4Gd2IZDf0BbJP8@=sQA}F*`iMNYlN(K zlf#IYurGXxm*}-cVo9EaZLuI@h(!h83($I6EjQ7o@`%3RQN_YElCP~q@4v0DPwT0{ zj9^5C4t6$wv96BQB=?OZk9T^ydVlG1*~SKOveP`rRcDDO9Qa^(S&3PFyS+xP6QA#a zfe5RCNb08SFmvBi%-B`TOL>ENIgrE~;z#f*U(2B(w%2%BRacnthVZJyx7+(I=&k&u z!$ng8$(8P5a2;s1TmxINFH3Jl?3Dn=ofLOKgb_okP&B;#`$<$^n|^8>Hjs?O?%yR~bt?M z`dpwSW_SRYYbPb1!1@JoVOhkKp-1^Rcbhqiyok55J?9W>t){5&CyD3Rn=DWpW}+{G zFMbb!9?i|TX@2nzp{Q@XdT#!Q*@M==ew-XSH{;PYz(^Y0l9$I<_Zv(Y|Ljfu1sv{R zWYUg~yj}=YbQ`3+>HhWP5^KQXf!P()S5L+-Jk~hYA}PVZFY_8Xeqjv=v`-VSOG@UC zYFvzckz(X#ILJta=V7>}9EA9h-%Xas2hK!X7oepi^~+Z!-nw4)Q6jqGtqT6Z>TI~fL410EI8}}SFszP){24QnbOn8~`&njLdQ&C$hRg2!yk(0xPKIprwHx6HN7&;R{>}@#6^=DHx+#ptT)xGgcsL_0pO4I;2)q^ziWTO*gp< zILM%5P9jPku>D5lzQG{XPx-;Xr%xHda3R`asltRP{1*gXf0(UGnl&%@cMgw@Wilhd zdDJg(5Ar~Ioj1fQX%GCze->I9kX}1gvMl|1>w)}9JDugmjZcdVQE`Q}cX_`sADBEf zIyCSZ*2e_v46Cy0TOPZsyJlrW#(d zA0j@>mR%NpOnJGeFk#=l@CvzwhD2oPBh6U;1{^2zs_0i5$&bLKke6($Kz8hTHZZMa zgN61Q(;@f@Z&0v&pAijAE4MZOI{yp(%fx!|m&&%Xm$_dL0{(TpKR%JVC6{2-10@%{ zO{*wGzW64c8BiWzuOu02zpgbGb*H%-qI_-dM{{Yuq!g>>&)+M-H`j@Oy?{<6{6uzY zJBaj2+Y(L}<5{|~-<&KYNto#H_Y_lb=m8C%3lzx_05a+Z1CzlY-*E5vk!H6wpemaJ zz{8bZA}7huJhPsVPGCKQKho#5p9NihA)x^rR!HCQC1$EGE1E*;jO$WfSkb#Wm_x>~@HX@-KSX0`4#=1tC5|WWNQuzU zdw`Q-h>TJBgW@U2C3s4Q>gJog%)YpEq1S)Ka=XNdKDLFgoqHMk-?Y_$0{r=eyN7YZ zCYtbmNolFP0Gy{@%y8^(^BAoV0%Br~eRu!0X9_=W{^Dlvr3=p@1A82F<0Ay_IhcD3 zB4On`I--r|-&Z@aZeu;)T#}2c+g?;UxT|maP?a*V=Uad=XE47EOUzh|Qx3;avt>|i zWyEvix-uYu;h)dCua&cx*`b+Zyf*oF@|{2N9Ldlvw}G08!Vj&0*)(t|;I;`dNO|Kz zw2gJo%&+A5CnuOdEQwb4nZ+bh6Xb->K~$}~`3wxsYW zj2a$^0C<$Uir~GkwGAzQu(3xpQ}QG@;C`;%NqY6ZDp@J$_|tN{K+qCIBzH0nro19Yi%~?8; ziM@f1@??5I+vOvHLbipYiVxXK+DnH_!vyf*vv+N)--bUkL8-gz0U}x8zJNw+s>SMU zQf5_%&AmrBS$*>JqI;KApu&Ii0FZ0b-0{l?9_;!bh>l_S3mjCRn&M$EhpWDN*G z@RFq?a!ZLE+v5?uo^=EW-epBH%FzDU2x6v5i50*?3(5hCDgBbYV`QQ=O;LfD0B?Qo z^8i(11x=tudVS2wKR~qH^9@j-=X7jfB`+(HkE$SqY00Wy1@`4#6xE@V9R7@rls`ti zNt5U~oQ{rkNvJQsMBYwMW|5^*CkNJtLUFW|T(7K^tP&ufJ84paX<^T>oFv?2#Egmd z_2+P2)_<>l{2&sz7Cfe}wT$4zhD`lHK*G!4|9^0?og)&#`ycRe-=cU1-g|w+-)|ex zWSkx+{Y82FF-CGvd;4>gOJHKb?&{Bx@o`W*S?zTo!h_Y*({p?LfBQsHz#PtD#JCnn zIi+1K;1e0#C(u{|jMH;sv3Ts#1^LK8@~0Vrgdg%|?*8ITQ^&^YD90geLH|UG(d2W2 zw6;9l-@6~P;xD@f^1UT=|GEG5J`{&ISVcmwLVdl8zoL8DmFX&^%<3*%*$9}H_u9KT zPXBP8O*v3RHOBl|ahgDn<*woj`{}}8o3Fl~QU>QN>F;HF8exF%73L&owA+s&P_1V0DyL%?zn4p!b09wL8BR(Y6c)gG%dtO|F z1L}G8tsKHTN+#VDfAMVi_4ft+b8>IZh$-%BgHTa9-W0X*MfeoPm(gA#ksALiu3>h& zBMBI@KY8r^AP%ytc8S02nVX!<^nZ(QZV_!KwYx+-_lCKgjWRSTI}BLfhOjgxPw`7p z9rKp+GjNzxAtJY~Zu>k2X*1>vW^ox&m6JiCt|a_dI53&|SZ4VzA8$o>Z!Vlf`AY$c zNhAQf`9o6zQGyXrd|8u2O&NLyFAh@cKWV(Hj(VfUt2c&4Wr&YAf3?N!$EBuUmERmS z9*Yj{`>qRzwKQNs1dIB)@Avy6ctu!(f-iz&LoHVrb$rrA!J^+m zm!C@?$#4!rF%|c@?=d_4vzObipT;V$q}D}m)L=E%Tb>&uyI(V9yNRubF^HPaqJ1{A zh))hb-!Q861Xa6jfJ-*L`gr38#M2tq*q}=hH8}153rxL$YI>pH)_R7`f;=q1$0Esf z&}gTlx6TPr5tt0P(ceQ^@@jGBz2Q=8&6AX4F*^;;-2Zol4JfGjfn)a)KRo7-B;f*J zybmI8BtL9TJX9iwD(u%MAL_1F~bV`7@s?|0Mw)eFcw6dzN;&Sj&A- z;Tc^zr)l}63t{!KUB!=1gBtC&KHxfdASDFh!gPno_3j4FDSYo=szA|B-GI`qU*+OycsI6^Q{e5 z_B1v^6cc7lwO^W?0t91532aZ$Trg&Ks?M}Ux5-e;Q6F! z{`9Aw+`GBDvNCDcMW1&5?s&EP=2T;-Br73~4XEh^AEGc#sBh4HiElm>Roug&j(QxA z+=@NSlOxz#+#T`!K% zmo$mg_`9?}b2v+ii|Z3D!2p2%Jg}svT@s-39?W4$#5U9sPCKU4DZFgA_~_#2HaYe-PtN-*}!A0{7#ipp=XQSzK0z<_BkFL@AmO`Yg5 zmzHrsO(7Wcf^N}ieyP;W2SWbB8vt{K^Uf(fkfA94I@ZMmjjX#4&5Q$AR>aIgft@{DbkG&lF}doN(dq#B_N%n z44u*;Ee+CL-=4wyexCRJ-``^nkD=zet{rQgYwdNelR{H!7Cz;UhO4gbe_#?`;)+Hp$MOR{Q)K6QBsh0F*6jqR!c5PJb=rmG?2 zGas)jb8))U;x!G}VQ!dIV~+|{K)}g>FH(|$yZ<1^46Kh9`G}jB4X2&={?u&v9Za~J z_-r+y0*%YcQ1mjep^)N-9Go3YEe3y+;*a8+oSH&$!#Hrxr(cukEI#)IUl_uSeYTYU zQ~>6guGCbbd*rYH>0RzEcyZno6PIO^BH>fZ49Uk;R#@^(a@1y;DaGg{2 zZnC+!EqZ;ivm{c?F6e%}lSwA^&mW#WH*xu2a%t&tAlr{F9we6^6FOmVXpfhtCnU&PuZwh5$deJl)cjvr;#C38*gYR>+m@}6IC!(d9_T+7oj)V9+sK)bPNM%l{JC_&b z_f*gNkOvZGB^KwOyRN6nBPs9xDD(QU;v2u)xXsYb8oZ>jo_AjHQAo*6rK!F>TCMl~ zb2b^q--ZS;1ox}F&+892FGeK|QrclRBU$*Vr-=|ZRY^@pNd0H9J#Yvy6^BfjSgB*aEmES}cdtJ@w$4o^VN5(3kygNu%t+|R} z;1tG$E&QMjE9dxbiaF1WkJaGu8!l`i9{u8@u!f$lFR{FD5Yy4z91D}~{66Lr<#~RB z*9ACza@4ubh5onX#N852p89c$CTIC^sI-#x$ft!lSM5f^ozic;$1(B0(f8s#Ak9OA zRc}8{yB29?+K5mwA;uDVRMybImuAh7nwA!PECD+C=MQc7HY1##z7SIRd3GlKGaWG7 zkFV9%)y-1~iHo;|5|AV&IZ4XMWN}AFX(RZ0%!sF@5L?clH?ip2;^Ts%1hE=ayx$l4 z-Llw}bCuKhpr|%;Gd(4!%AV9x&s&d5LzaA(hrKGKKc_R0t2y)ag0Gy~3?vitOS7Mo zl%c$;zXf#IEUOSBI999Yb1BP{FDW)kUthdg%4iYb%I5Pn5WG)9r0=%!Agx2ZMN;Y3 zYop@D7aw3dOY6G4J~D*yWwUal9jF$>*2#X=nW6!Y(hYsea(>Uo3Q$LDMbb^#?^R_I zw|%zbdmMd>^Tq&Gk$`~d(9rQR`Ds>ZKv5x`WM{>TtP@9XQ1zDJZ~3_{;8DAdK%%<1 z-6v|{gobGKw6Fwo2$b`NiN8qV3mRzc`(!{QkB`5;5!T()Gxw2~kB=5dl+bQBke~lk z$L#Fvh{-I{e-rc_I80F4+}qpJq)$mpBdDJk8=FlJgexXQzJf00;8|qh2gWpOH#dk+2Vak0h0vnM3>_Vvu){-t(9Pir^&bU8D@I%x>u{lpgROdH z_U)Zm^+iMfs|$~c?9ZCQld%Ug&E0i&WAzw|vj~tnnqw99#-c&J<`x#mmC(JK0$Izf z2LHq=g|5If;rqK|+L^A%2g~IwpsCf-FSB2+YsqE=^2s@6Nx`Co^F+c+W@h3FrYu&|ohzLKFDy1uu`+U+^eprr5quB?gv ztBTd`ovQ?~S3A1a%CC_Zv9GDgzO~9H=!gQv;T>yBYxFmB6Zj153j}|>gN8GDJgFLw zO*L!^CcoCqkf@Sfi?%hs-vFaj>zo9Og4EW_tC}WNN?ICwDm5kL+T!wZXx{4&)*;-Q zG#bPx1>Z`Ap{KU7zsw)F(NjOmslU3I>?i&147)vabID2Ix~hnqIiK)4^F2k8&}Pnh zVWp@~Q-d4jzxxyk^26HbyZp6W1PR1dS3&J_^}6a1Jh(W&UgN+d$0lp#m4gw4eU=xVrhM=)e?F2^ zXGK+`(DZ;HU_9*$rz-;6*Lx$C8b9R=wl^=NZOJ;f*q7a{1~HXX7+wzT06XENJ?jZ+GzR zH24AZ1<3;*vU5>GUf<35f|EWNv4vdFqt&E#ueC z`oOp3dq#U@y_AC5sm^(3u=}%c4%WFpgl3K)z*Qz#3Ng?2udau@U&{RwxG*&EAPl;N z$Xt)sU%DlDwj%N~f+xEC4y&Z&pgTJg&{C$c`FWK=?9-Zm{GE|=g8IjJx69?o%U&;y zp@e$CS(nHNDz!4^k2{%f4!e)Sas2#Yg2jnfcVV!f@ZKPE7*7{qqxGHb(s&M!U`oJJ zgKGeVj)E&TRC%mis2F?>b6RFx=pg7>M{PC-T0)W{kT7;|3NX6{Kpgn!Slk7(tzFys zWyx%RaawhY5(W=&V%@lztj-Og=cK=Pz03-8p6C`hth2t?2QX$lD@EQ%94SrGpPZ?o z@t^Xfm$x-0uFeX2mb4*lRL2c{1cE|Q04tBjbGpWTW1BeB=Qtr@ck)nW#MCxDep{*t zytGmQRcr^PK9_fZ)rf5Wiq|d7eIEO=?|-Xl$udwgyYM>C#;ew{aa_bPczEyt_7*rH zc(Wf*oJkwTFrvQ%zUoa))T?Ktp06FprQp&P>}fyDjJ$e? zJ}Q*haArINs%e=^^Ya$x+2a)stDwYk4+d+hK(G>aw}4#ugdXbHL8ld|u)|**qNUG; z3c>}81t|zR#@yM>5?Ri}+ETUNBd``rdAQ8O8G4Y+_QQ=x2}0PkT9q*hF4{L^Eyf+= z%}bl1`WBc*S-`45x5G`a2?UFO;Tku}fP&To`0Zj4rFQsE`>uh26s$WQPt&U>Z{om_ z=c3fruTIz1AXV+(!Teu_e!-|=<(eX~<0(ajewSwmNqR#lJxMny{yZH!;#GJ-U^(h3 z%YfQR{y%SrKtZLJ*K~^abhc)i9{|_2Tzcs6_fai&7e`Q6JK`}_N)t}s7E9WCknKw@G!2c!i*6#z_GRGJx*r1yJ53Y3q#-o+|$h|I&N-bGX{e=2x|A zSlY%!4KC&Tv^@l1PbKQ}6q9s5mPFZ#*E(l1{9(jKiF$-Ng2kBoX*C;IagM)@d*=0Hzy%R>Uo^1uON^jb!bL0E5YpfbS8`PVM*OJJ<=G@Y2|H7w`m#_bu z(&9RWE%C!Fu}sMO+;13A9rZCS5&a8}jIi7$UUXP}7c;(;=xboNN9sB56)m+b9@2+{ zWxYw2IvNtQ4SJ*>;(Vtvyq%@(KW^iVGx8|}zIZXVed3dQ^5BPIMk{jG3#Eiqc9%r` z`fk6&^6abvilG_LUt9z!En=tO`aBR88d&y`)&gbgK;`qnP6WjR89${lazY>U;hid& zA8t@aU2PK^d6@7YTbF5%K@qmJ0&JZZ+S`EbmCtvWNBcFF!2^&%XI`}GjnG$gjETO! zGpDgl#*_KB==+{}etY0%#{CSe<>tFUhtTlP1}S8iT%DV;S3$94afx$SgpwX_y%Sg% z``|UKow|FES2tDd?BJ#Ln*00d0j+G!Z`!(dJOPuvk;dg0o}uJDWfxCZ|dTX{3&z?O42p{~+ za0&2RDNnh#{7l;;W(`z19@grb5Zu3_JuiMcNsqnJwNg@1&yWioL}N-YkAC@B_%*OF zl-EY5N<@1gLtJT$_)DNMtSl_pOpW8bZ1SJwA9}$F+)_Jxjov(2`Gr5<^&h}%DXrhE z2G3We6cdWwtc=Rh+P7iFC5@Q+dEB~n515lYDhjT8Y^WLH=?dEwut;4@yQsl;V?vw4 zGf!e3Qchv+Nc~dgr^rjyXQi+O35&zhTNy?9zQM7bNX|!L&<7gh9_OuB)$_B`K~6#F z4fADoj&@GL`S@VIz*L3LHoXP7KKn|N+8@5tv*8iI0k8oxP`_r)NZs{~MEz|yn3XxV zZzP>hBQigjGm5dDTrKMrw|6fg>C`%p_BE9m1c}}+1|S~}Yy_~HUecS5X%}m|EwWO+ zI}!b>%;K8h7^D>Col?OLLO}}PtU!Ws9831Ni_f7}t)BKSNaFl^jJ=ES-a%Z*RQayr zPO;UrH=M5}F?f8;A*J=tGi$H;aDcV*W!OuWzsajDz)zhh=a3hYql)`rLX?;3RDHx@z<_D5Im}%aB+w zO+ykc#)|GIqL!#E4k4m3$|MaxvYHSc}CFz52I((aITrPI}DgJgL&k*AN4=A|oc@D|sp^fgt;DUs>=qOiyzA1`^=&hyM5i^^%Ta&Dc~7nw2EAL0tpw&3tN zYoRVGEo6IuxHwuo<661ywOUXi1M*}S2TcD5dax()Imwo-ax|)CxCgvNVe;z{E$DiVu^dNHs{3%!S zmgTA)$w}1r_}ri2g@qXmRooYhtPRsN$oPEzNQvs9(W7yb{MD=!&qO^mtgH73G%VHp z`-pWxvB#M#DCi+V*Al|`siTrZ>t-dkUz+)b%-c;?Zo%TiuY;&_zFcYkuXuikCsTfl zj6L1mH%*rT1}TUOZQ4{PxHFP@D^U+<*zysNyUejNP^)(XK|s_v$CslJoT9BV*lxC< zc#I(w%&gdWz2dbaEugQ8!Xrf53}zNEdYPooa9~=|XNqIen+@mt!wEgBkBE7wytKb2 zUo$4@J})@Qzx{c@if4oiqs0m7lQ9|1|H|wdC!4==Pa50H8Y*|u&%)-#pbL@^Sh7vo zJSwG=eWnWhog&JlO|B4-O!dTAP~3-a<=uO=?2mj=TMw1Yoh>?8@Unm{!w11_eQG$ZYQ~By{0AHmqx11#^0OW4w|1*}d3pa# z)U=R1Az_djd6(vMdMFT;7#Bz3P^SNFgdtrkOm-c=Y&+o^S&kj)=yjWU?LV!44?ZM* zM9YL1;nqK7lPX#Sy#x8nflfr}s-Mn8WGx`X4~>Wi1K3 zn*uoD0y5O*FtSrHus*qS53~RW&Py7#BxyGhFWHyDDc&U(jC=}nEcGBK>GU-#kp!vM zwWwbU!`J9JUT1c;A*Pp%qh5sPbtCvc1DBvjB7+fI<|3=JRLV)Fv#G z^W`X!CKuj*{3=P&`H-$Lu0b zqt?i4vM&)DN|<{H%OtL>7oZls2PXcZ%5WNP@*c+<pUuf`B?hUZWP!ol!*DRy~z5*Yx_`n&+69)bm56nBc2i=TZ*^=6`giHNl9P@vjSO7jwgh5tmu z5{}P}WxbcEaucM3?BQwrm5Ta*06+bym4UqjHGPv zW28RlvtUfTjN^Q#{eM&hsY(Ex;Fl#cnp9ZK^6(o;_}9YGFCUNqGNUKtmG5*SKn>{G zh|xTyit}ZF4(0foD!@WN`=AjrEB((wTcBTnva%o<0Gz_VJkQH><;(F0fz)3&K6Lqu zsF11V3n&V+;Mt|INwuln z_Y3tk-R70CPtqS`qyMR0MPleO2i!y4fWp(ZAT&6oG|7BB+y5>kcM=s+px`)qjxdKo z6FIw+ScynsL3`&uS4OqOdvxXPD4~#l_NAI9gC2Ns^k8xIfFT0G1^T=OOyxg8I?AA+ zcJS1fx%(VMo_Agz^y%P7B;IYQkTZJX8_E%=9)B|r<|_i$j>as!itkqnQ!qBW$%Zkh zD$1Yy)B+(y9yE8E4sAp(vm{n({#dcRS=h{PG+pV{ociM9d@)Iq`^)9ipSn~rq*CHN zhflGM){xwia!q=D)VoPPT!lVM0W|^%Aevy#wWY($Pk?}szaym!{6bhMkhb{g&3)_Yt0kEJ z&5ReAU}=M>!od_~L4C3hT7c_&2CSRhQ^3FaY3Oj`Tq(!wso=M@s3 zRp)aM%hS1<1Y5o`dq**a*)KwP_e?>|w6mS*n^>AD?6#)$LG-~YKZ}`m{YfD<3PI@w zw$lV0FTr9y;{~Osc&eooL+l2S&goBB@l~r6At5;lwN|z+WJk!C+9jGd`^slhFj2|1 zz_*lm8U}u8n&>&?|0I^Yme0}(a%&(2#*p?qO}x|D))rc!UvL^&oo~OBjq(UtL038B zo&wACM2Vw>aR3UEBN{vdf@Y1UkVb1d41L(F2-1%-;IqHGl zjoQUyNzaqPCvV$?+{ZK>qEFUnCNhILc5Z{yK_%TTWaM8%-sgr#K_Jxw;Tan$LNC&nC)-`e z69uYMCN=Se3tbt$gH#q+@13`ZgKtEs6{5>=QH$(S!Dcw_Kg*5_@OnY@ASe_AYS5@W zi_{g0u$8Y9pv3Ji$cW8)qcUQRU>7!dPei$sA6)@$2F=;3CBR`kV4I0i*!uMSRG_8+ z8NI_vAmnVg0Fr8iUp^)30T(e_g^JVBKnS%~P>|$5J-PmNHTlQwS-`rwknNYn79v8$ zCFsfx+cNe1H8d8}$GQyc&}!A!D|=D-3PGx|!UtSmo$jDK0<4WZ%i6_=#fr=3jEb>REmbgsdp=7#B{22XbOxyBizv`xWL{={!p8`m8a0R%F!E z_>u&(<~a>288oi#Ul?h;r~eSz^z)OsT5ysc!Hk3jTUVllET|8HvG*Vq;O1Zk)}wdO zoHNM8A&A0Oa%BOFGA%AtnN1c-5jGo)8OCGSiu7O8Rzz_b+2-j=7XKFf^M)+S|FCbH zg}G_hXVu+Qv5Q!`!LuMkFWg2cLJi(j$m?CDzdM2y{8Uc zmlQ-}E1_bY9noVTy49@erIanCT)jogC%j?N0VQOsp9BdI8vUg<F12?ds2^ewE?qO7F20y_^Cr7O;NRWsNph+C%HwZ_OkA?v5u1 z@H?*Y(UE*Y=CblwJBdD{Ae)>HK6u7;SO(BQP^41806`Ohu3hqmFUi6G(oXR%-=R5~ z&>{NTUh0v_nZRbH;VA{B>5X>pbOFJ5kMw0kxPdm`E@qH)NutZg2JG72Xz7i>=Jh~YZ%07n5?vcP5^)n~yed8Q8e-zdy5|J42Ky0_JYS{kHCdqv#X( zviSz?`Q#65K4J3%z#7JZpq_tk?ve9BCo&@tJZT1PPa9HUPa4>c8=ZiD6@g~Yo(X|^ zDkh3NJNESLE+ozL2T`l>V1XYxuV-a}I^snEiZ+g7lE1WpLc&t!V{tsIJf;+t3?D2I z+R3&f)m9kaHmX5~UM1;;d6j|B2`*PJ5X|h*9mX4e0=N}Gxyk>Dl%^t7hXjbx{FZr9 zUp-B**P!7hRJ@`xa;}~)Wxsm&j;l7H2>&$!tAOXZg8(lD4+cH(;zLufFS)w^%}@dy zP#QhHU4m=~kKv}m;+aup`nItZ7=!0g0|a${0=qBXto(@Ah2>_ZIRC*7Dv#Py*bxX1 zDTM5MrymKXIm>P;gy>JQU1*cG(6wgEw%KK}#5uEJUa{2uBn}N{$ItHkuD6&*f5-Sdonjb@j)|@;)j_ z3KdDWcLje6thL@y(my;qJLnL3vnxkWC2eS#Jx)|p%5MT5rK^IKr_Whky4xpIu_hx* zsR?!?YhvVH5_py@=7n|73}&|a;e0G`XYmCrN#5wXz2~g0GJjN#scLDzzyb=~ui018 zhN3J5coKJ(t$#NUcK|8iS^D9y{Y%-q1vdJ>#J_8(6?kz%@rg>GZ#Si`R}pJ9aEQS7 z8Q*R%!Yh4GVQPp#S{N+px?cB9Ko7p04N;0b(0ztFkzhy4iF-Tl6;PIx=vB`Ld(d)Z zUhvv(g`7aqISfVGCP>nw2ewUb_6t3V`u$REA8ee%taY#`b0WKt8RK0@a+Cw08L5e> zrEUAa{|f*%0CcWEJx2hkB@Em$%4VUT3-u#ly}Z&B?2isHS~9gX%YNXZVj zKuxjOS#0+0YzDhuzQG>pjv@BI6#eqP1BKMCX4|K_e5w}AYG0Pe7!zlsAdp>$@CgGy zChrnm*nrK+xg5wasI4GT-x+-tt)8FB=D9(gQgP$&nwcp>xiEn)U`F=PwCR7$R8$)h zMn04lDxwwEi9D`U=LW1=8mn>8lCu=eUatcRQM%Wb4+TGj?u91l0qwFduK3nl%nd8|7q!`}ni z#-r%{GBp%(3d6$wxI@&N8b&KxpSF z-GlLNK$bn&8U$k=Avl(wEjwiRwRXVRY!;>ggS6d{QB7Ky{{Ia-nBieZnQ(K1l1l#5wE4aCygvAGG7;OP{UoXL09d)G z{d7M6cFiY^guPFG+!EY$s*6~_2kVZ8XW_i)n}*VBzDfJ_pIPJVMl$0p{E$N7HF~jb zY-xttCzBgb1&ier35b&PRKuFU1PB!~B!yzzJAg#wnP!_`D~xDLn87U_9t5PoDG0`Z z0%X{YCgHzLWK2P0UEpkhmMv;7|Le_XD*a0bH{)sAgGB&d;0<`8*4ibe;|5#zzhFf^ zus<%-O4ZYW&Fi3tSRa9J*W)i#UK0cyVmHn6WWrDP!FaxI5Vv zf;tvl^YEfQf_kw4Noz?}bg^opSGuS1<(0aCc(5(tL-b-k;&PHfNeP3s`Z8t$v_37~ zw}c6F0#E5*PDIU;5B8rB=sgGK6t#RsHlH5AkJ^O@69CS-vEsnQ>{m(>?sd0&UCnI; zpr_8+>KPe+o>v6scl3Wx5Ve5?cE3#{Sd zRy7cq7``MptO2X3uJ^9c$Uq*1429aMW!H=DxM4n{`I>*q&j1V^PlH}=)+kh#Y^jnct+@}!cpZFlv@XbBE`@>?ooRIy| z!D%iUuZPc;QN3ThWX7W`vJPncD!@;*;Q3qc_h)4QU7vDJhz$bSy!*WjagD`_9u=zN zE#@#H@T)^aLd70@pbR?po)ox}ptTyXt7MLsUXn++SNI2Ly&j!#0K}O?fH(PP@;LRD zpG`P+?CT#*sFJ}`Hyq&fLA9l(8248CmO-~8DYoZSZB%3ikkIynG&4HXqmJ8p%KzX_ z(#{RP=C2#ad~(ASzXqfW8%`pn*F%;Z;DAhS6HLyTcAUmbo)Q-tZx}Q#qr$d9rWE7W z=N65wG)5csF0irDmJ#K16ix{>9$cbD(SIn^m%AT0uXLRA1GeT9I2B7|`+CgCBILCh zGv>u2Hs5a4AKYe&_+9q`ASftYbb!U@RYdy-_fI+x@e1p*it8;t8lx&k4=d9R+*DIH zZR^G}w9SsiB~@{?P`v|6p-*}dlO%?>f9y|@Bxl=8FXt5I0`cI#OB+4Vg2goYpiq8v zqV~a8;8U?cBbZiD+M`vN_Z>!n+L41G?P${hYBnIY;JJguQ7)K8uo%a_MnGlc7#!Wr z0~|=L1|R|24Il3Mn$~X0q^ zpNKC5oHQaIXU;enU$ya`w;lAY;1e#Lm*EuU~t-anR0sQ8UgaR|JMzo%m=o{>x&^dTSVMC#vxLbIhP z2qt;@hEPU?M)1GaHS;snx(04!%fi6wqjf;nGZ+)!257cM_780F`LteE96Wl>l4stX zMfs{%eFL@|^>&~C749$hvYoLdqMw6~Gb~5Uuny*jN9RJ}uiei|Em)ZOR1q@P3I!xjp;#GcX4@|LhVNs2tfhRY});PX9zFd~!m z(h@_lmk(M0v?DJU0Kgd6>?xM$%yyFWT05E`VE(ApfcOK@fZF|w;|z=X8n`I(e1>Sr z@}u_I-1yTDAA&2D1G@x$39fV9WjYB3;(%w#U9?_IR_C^|g$^;JqMSclIfaDFok%Yb zGc`4UJ`E)aC_w%nw^D?{ub?Lv=)?d<__F81Wh{tl%5`b+N7VBP1jKt_kc7Q4w(a1&vFqk<$osjvSX)SbkE0C6^;$5*ge4Fy)g^F&APB>-uR%tIJDuygM~ z&OWW=q6n*Qe+GMKgq=y7q=);;fySgf5Jy5~M5;9eXntRYOm)4J5x*x?FLo4aNY*A) z-Ro(sJ<{t?i2jeyZE|h=U%30vGbMjTWd5_gn50O!!7E@99*D|HUB;Sdi;Erz^gA?! zm~Nnh8O++ea8gn7ghDP&fxzpYOH-iBgE9pmR{lH`mn^$x#1MxyH*F+qf26JSJMaBU zdm`s@2FIxNIw@#l1CkHLiS@5te8^7>P${0=bXHB&ZuLly6OXiix|=c2fG^zt?q$!{ z_F(e*03RL`?|*T`?P*Z?=>zjSI|hBH@bLtzokhaly^q#Nl9Slp-3!#|g{st7KJCW6(|5ic~Xljh>*{as{(O#$-&w%Jn8c8d& zJuPU&%?!@+!)N!7v3n#_KIWo>Ezktb$3E7~`X2J3Cq!3?|L3v;{H~9&KL0BwJNoeAe(aa}VzbDHl`8AE z;(y}*?vo*{HXhKL(Im?}sk3l2iBJ52AO(HZQn-jzW@BTV_3FHd*a0Nd)r0=iWgk|H(oQpy6(UkA- zEr&5b!nd`$`cIi{lQ9?4%7-ME&DRAlG8^5tF8bi8ySIDa+S;m-BEdupKVSXb76*j? z1`-kj0{ua`aY5nb8_}2t=M3T z*(XtSboN6;)Fw)8HP*G#Tr3xm$9d!nNGAli?^8E0>TwgZ735O)rbB0={`-!5Ql$;> zV&dXH5i2-uiR#u1DZ)<%5M2-f}$3`uJ+$Gnkab4E6@^d)4;iqK2^dBDN~vk0eX5Kr0!VhS@k`bzC=d-2H=t zmam?ko-U@SNdeyzZGq2YaiZQPPrzTlejP}vO8$|!x~hsvPfN?cA(-%ne}jloW|n3e zFKsZ@ufY8eqMxxO$J`u+=g$V;*~&fZUf~P8Khg}Jo15F>lCh1zdOAF6Tcfdr zDAD<#n|9xUYT00utFX_-6xYR+&IJ8vRJhGiiuI)*IR(!bu$h=n8vb(fBAxW=G)D=9M<*F9;MZ(w=9$)LP`eTHjW`O_=Q-KP8}-$*-qdWN-? zU0ileF7q^E)UK;TpRxMkq2DC=$+xO1E0N93?W%y@FbUtYR*jL?#Dhx_1$_zus-3Rd z@T7Mhfw$0qNsK)Xc2Xupf}cD%>v2?Pr+U?6H#gBBm5&_M*B%N93c_HpYPWTK+~zfc z!$BbNSdL|TtUyIcxoJv{0+rD;S5ZT06OTnjMaB4q;k{CC6r*hOt}4GlXFUFd{bp>B$E5V+Ns>lW zZ@S`76B?lx+m{z2mEcduildoBP%nkidEI{gD>{aNikJNjvvV|qwAW)i_(u(FiF+ip zqTzR9(W$IU6UK^L@3{jFr&7p!K1YOy*XtgDW3^`gGecQH!Jm8c^vIbT^Y%gvD|#%@ z_D>|Az7mXOwnoa2DeB#@yHOJEe@+*=XuJ65BIzY(DPCS2?-9I)|9o@|HilH}yf_x`^3b<2DPEDCKq!pBn&E-SR5DXVhS_D zEk7~;;S`%`;}5ln`|?f3=MbSLpWlMJxv@F|KEG-;m@DU{`*)X=(TjLog^HI9O42SD&1W3|^o;|MCiO#VoES5QcE^LaL`;o%dOl zjg(r)g`b`tQP`yWnQ@=oNoS=`)WiCPz1W%bDUy&z*y=~XH5EvVgWD#?9bD)>c1qeF zq}X*;2N(g;-|Ht}=CmbV$jie?F&}BPw!X$CAr*K3L+4Ik>{fQwV@%k^bk`6;$p-xK zipm_Y6X_8xN^lutv=Pz54Qj_{;?KQ9zc!ec5OV})>U@)tvq*=tq}^9waN8AZz5JH- z@rsAnwitBj4NJ`LHU<&NzC5=xmj_p8UqT?S2+Se`1O%v6Gj7yR<~(E(?sv>4%Yz}| z0w>pUS3%bR5qik4U%%8Tt@==Q6*UYmbeWK#nZUsJ>FH@8sCdRJs{;gKGuSm#r5@t` zWDcGNl2721aX#LbSs4fV3nF|jq(B`_3Jo}lgRq|ddNf{cFCxFoq1@v4M;OM3Yh3w) zDVHlt{%7r1`AtwW+Ki94)jAx8x?cAZLY3zu`x|J8V{M7!2&v2MCT0r@iwh{h$u_A`~WSp;n8I0Mk;zFRd7{S2!xMp{LReF6hay<3Xh$p7Z>OM})C1Q70ClBP^73ffms@`=Xb?(1WL zH-qr|QE-qnOp;%aSZ9=&kWD^WsE`6Y4D|4=gPE{zy0ep0_;pg|Z1>w};t}8zilYAP+>wi{3%*74sVqy8t z+BXNwzpRnQ#Nm`oL0$Z0WMp{n-n|1$Kxr9)2Aaql^kO_b_wnFQHU{N8eMo6&awDm` z8{fsperOO&9H`N{tJ&tN6Ut9zyo9BpiJyD@<41x!pGe3&DDEiT2Aj;~8SQ8wZZlTtV2 zo4GUn-)^`I?|3hJpE%zpuZfF|Wez}?5C6K3bd(Fbk<7%)t3lC1m{&QGCg!N1;^gf7 zu%xmwQ8HrW6)3Ewja0qUpzQAMPS_qt7Ovs2N#cF5VPj0!;#jNcUinG(Rf>@>)oY{C z(-A_GuzJW_EkX9So3g~jk&L0{Vl{&Nq#pu*C}=^kj>=T)H+)uIw&}Tt>ni@N3W#U- z>@H7;r;3YjY|OkkY{Q)3Ly2MgSz*O#WPj57lGV@QTW`pV^ZL6@SvzAuR)|G-*|`03 zr*4x8m*sZ2OC$DBgN-onF-$Z$EyBKurySfE29Kl?(A+r1Ry1J2hWo3ir@ozd03=Yk#c zf~0tf8#(WyT3AwiN)YoX)$n3bOsac?JK5)&4m6hzfmL8x=bG)ijc|I+kSUQqPJMKvWxZTdIGxTri`N$gn^hy3$_9`S0QXSX~AlTP=Cs*xq5+$TO((vfZEO?EJK1b$h}MiHeFEw26V-OM2BmyOR zyL_EIz@7tP$Gp?gPVr+-j`;Rvmv)&3d)JpQ8AFwjX1gb6DMY6Lr>K2KP9|$)>*US5 z{WCz*;3*^|B`F-l=^{0<8^s#%abBx^b`nhT(Z+JGnE-6xBOl&(uk}tcaQ40GVp&69x;Q)U?Cv6uFnSTFfyO}K64e^= zp7}M5fUPK#=3w+-s)F@yi5~a2QFXfr6_%)$PyNfCmd?U7z9ufuD-1sUtgWqWEivRx zFyc)ZDH*kMt1&wM-hK3qjxz9yPtg-Q(Kg&@LFxzGtyB+UGjG3@m%&pjIgBHG^91&d zIvPGg$#F{^`Xu>aSlV@`{x?PP6mrhv&(5zu+pEco=cjfR1~=Nw0kz-w>&5-j1*fVP zV##Wsp>d&vsT$e(1n|OX361HW_O1^w>qLjm$q#hgH5u-E( zn-`L|p(VtbjTp5H`dB`0FAZ#v|M!pm%lVHvlNs2a-6S2c6IFOxF&-0bN2u>i0SOEA zm~*N!|Mn!RMbVx+OMc(Zahi`@h9RnnEr*FFqIqshiFwNbEA2zi6ZgW;46Z;6o=y+E z#EVd(@K=fLglRNsPRrxwMs8myuwYXL6THE5 zo0<@h$MKDMXM9tl{kKu2A9;hEx#~(^=~8>xbk+_|dKP!i`~b;A?v9vGm7%CrZ(wh< zQPD>|Za=B@@Vi?4=citRRf9HUnOip%kJcUT#ib`Q7#>$;Kj3Vz^hLftpr`?v5K z`LpllX10>RDVdC7XDQ)$Rj;w!&R-e(2SRMjSd3qZLi|TUKU3Yr)6WSh^Ec(w3l)V) zE`AR*8_}kS-S^E+8NKmLr^55F{^%_hfp`&}9tbwS!$T>~B}wrEL$1}t2mh4n+&n+` zE7Lc7a@A-VX$PtV$YdqM*?HBL<8MwrufXFc)9|MHK3C{!s`^pvWZE z$J5hVgq$CdkU%x=Twr{fGc#uUhk`QgOB>Lx1-6%2p9b*&9M>J@%T#dYQpi*~36_o|>CL zzRozCVQ^xCzT_E~g0!rxtc(mzupW1_j(bhYaBWqUqwdwdDtkItF}f1EqsIN`Vsw_W z&+r9zX7U+aOts2-_D`ivKNRSMg(h$8-SSIwGJSkwF|ErID%UWzDDUBh z*&#InE55n;oGxTs=q7pSk~Wn>D?E@+j~LXM{MbOqy(#H|w*`t4FT0F&j5Wa>2UI@6 zvtpywYZGchmHau70Waqe=JMX71Kjt$M6 zo*cUFcJhI^^ZSC@XP5@!iseAUfbHAV`@rtaA$l@FC;k9By;k@<>d4OT4}$WeLSFlP zi8zi%D<#x#8S(g`%shhbY+g}p;aCVa%Ti=mQ&Us)Ib8tKQlX17E5mANaq;Do>1)ib zzeO|qBP&S4N9c(j6A}v}^{ql|hYJjFS>bsta2j~9_}n@ci`M{R24e*O+0|Fcjzq=} zA`-W;HFPS<%bD|@e6u8CEm4U(W~avfWLiohg|BjgXsjSfrrEjpG5S#Wjml4U8fD~7 zCOnZCmu%|TWq^r!CO5Ux!HUCI+U3@gDZ17Og+QtI=!Tg^a z{k=$e-|3JS!tys99W{uFO<&1X%4()zIEheRe`ROlEqU|E>w@Y?9Q(1L^2}N2vAohv3@peJA08F;xxbIkiDi$Rj!;zwA^E_kyS|hW84raw*+neD z$c*7KU_btY6$QS78WrYiMz{MdEX_^LO~uUe1GVMT!>>|cDP{!Vy|1hxm3@$Ki?N4{ zrR%n6kT;RAf)Y04lNCkt9SbD9I`)>bi2*HURl zA2zVO@^!|w=X}#Bc7Lq&Zhzurt=D+g22C8aNjSVmPo9?y zzIy#o)SWtRik3d-k9wC^uV?P?1H;XWD)Eh4jpd#1BRAhQa~{prJ6nDlIC8R~+iP&r z{K#`={N=n%W~YZj(o&L#I+dPgqKs4h#BGe}Wphwdm`*x$p+uW{X)PxJQ))dRTIUr* zB12*ZIMaxho_o`EwP*P>LUFjvPEaT6c_8MC0=yT?rPO;XuK$m@w+^f7>)M6UtspI+ zuxSGoq@|Hi6c8k&8)=a44mV1dfTVj>e#9@aBfi09ZX zT-}1BxOR_pWyz&V?2z#7sjg+!szrNE?d-Bh(A;{wD(JBzfxTqRJm)lI_0;_S*hTwt(NUAi%A;zUVU zPY=g?M}h6!{H*xKTN~kNBDxy6PxB>KbS$eLVf7R>M=hGgc26Ai#f0iqzhh+chOWUK z3k<)>gvP}Rz5TVLIi(qAM~_|vdGX$)vdio#L`8YZMzcNGwQ;PmN8(xn+SfVQJPahD zkIlTZstC=_p#&tRL>-y;#8xXC)uLr*H9%Xjc~&45nr%Fm{tt8Toc-fAHv zY>e>n^}5b9xmPbvRT7Veuug-7^(bTouJJMn;+C&|9IK080(Z{8j~bu{Y9uSmz3!Kf z5cXOOs+zHUt`?{#&#BE;#YJjLS7f<3Bh|H|vkncSy#3{-h*@`aCq(9kpw!RGFo_gQ z)OVTka;zlcRQTJCL>O1A5WuJTB%}T)DqoQy(!o+mCWEX=J9giXMw0%2Z4yCPj-W#yQn>BBH(Wo6)> z^0*=&%eb?smK@B4Qp<$uQ6Mr9R&O(it;Fsr^$xm@LeV88Jv@z(FLOP21Lyf=^|yjK zIW6}TBSpQv#q)1!X==W>P51~^MH|o0;FrT*DKT^}YSN932_;{WFJ6)gKUa~CB;c!b zs#Y%4H6OI^%2S4j^!`Q7#k5{SCoR4xv2fAkH8qlLmGs^tSKy_id}jIBqU(hWQ9$H! z7RZo5!O>Z8a~5CybA*g(kbxHp%2^Qh|9Q#W6G_Gx5DR?JK>c%4l+zyA?pt_ zw;EL*J()DNqp~dizC_*Hnz4_wGx7*LHePa{PBr>_xGi7Qm=EsOwlyW{X^dx76dR%; zS_Snjst|+&vAt!Wy?iuTuXCWMx0IO@Au~S}tTPqcA4EZT-}2Zc$d7*IZw zY2hW;lc?A-%s0O1`5a=XsS|4DdiwUl!oq9f;-qKS!UhMGxD%^2 zu3sh&X!!BtMbR+5DhLH0MYZbXbrtr^8ygzpGt@UW0;i_@9nc47IYFR6Pf$@+Gv7tQ z9z1w}i;H`;W?*nIDk>^C1P0JLkjJ}%7ttbkw?LWRBq$(2PEO9nHleVPorfo;p{KBa zvHtKH@{NIZ5knOg$MwfSf&e6k*ik`36_osn7Q4TKwY0f^=gystjg9wDwjw5$mW*$I zXq-wQUbE8xZ9rcI7lz4f0PoQ)-aFA`2Uh&}L=1z>Qudf&QJTNfuR4(%dH8Cx&jT}LC8I5MDG3}vPRPD)CepO=v#cI^fcQ3?pK zfy`!TcLLAvUG$fi@ke$s)jlirZDG|vF3lGPD zB_<{H8WzAk`h=>`*;rZ2i*pJJ?v3EY#>VC|Ev%zIsei>RD~9o|`bo=IMT4@J3yX{M z8Ltlz#1Spq?nj(FTM`(&Dqp1U0086`1o9~7CswN~y1a;p&*@hKAL4-t^H-=0)YR86 zH%)J3WYEE0f=-R}mPoPUjt#^-!L~FvA4iN04=2UB{J_%NQ4NUA%*+hvdsbn8uRq4t z&aS`udh89f8O7H8k?drd8BW&6@Yl?Nd0%}kWZzDyf`n@c&W$5xv3h|Lij#v^9?#Z7 zwZ>S)>a%tWeK|zwXD7Q=lcS>eUcdU-yX}e{vN&$ z=!nul#A+2(LxTMiz=u*!L&Wai4fgI|Ovao4n*NIV`CS0@s3pj!cR2&}W5WDf|DyHs z1EsCdkE#s28n?ql=?VS%S@H&#wy}MQdiUq8MQKjIaqYrIp;POTL?ML{t>&DvqEgM# zd8W(Tt%SNoHz^l)QdFO4TB%j1LACkf7t1x4jY+^t)2}onb0;>DS|P3jmx6bbc2mYb z;fs0VL~9f-$zt@i*!mgV%uik}qK|0F+J>*&$K-b-K#1>Lt;A`xB)y?y`SsJ9c&Zo}-?P=^=3I`j6R7Fcu^H zPI1X_yh!!=+ABIgwF%J((nz4(c&GIWYgGWTs6L0fUbcm~D*)zT9;UZwwxm21P&bp> z_$SOLxc>IQW}*NYQ|!^!PQhL#cbgU+S_$b}$5m4d(oT59^cp6AOqA`^gn6ZWUe=d(Ev1O|6&;}(DD2G_FBfiG=Hi2Pa`Njb53p`< z2*h54qsEPrGgg8zqGg^KgW^+V0wy|!(wC2JI$=sa*kV29Izmao(!SDts@?R}gf=&; z6>qpv8?;>c$l8B1U&}V-fx?qYLJaX6*w&}qV$2U_?;_%@pIm{UA51uT#*0?74wza@ z)|GHkaFPUs$Sa2%796auLhk+%?Ch0wIEwF6W@=Bn1Uy_MPD=r$fZn2jo?LmtY_DOj zQ3rVk_D6auG4j>%Fz&m^Wu6j2nmLj1?T<9r>$~)HjST!{u8+U-*9TrgG1nl2j&Yk;sPlJ> z+i4uVuR}qLn?S%iJWLNCQ~^6V4u1>T`g z{(=L%+weyqvrvA#7a?#z&BDX*l{4PxBi}o-F<1{C5Yo|B*&Ic)f6uyQwT$ho<%tmz z$pyP5C0P|9+N$`#`ICmAB|#o#(L|L6@O|@f$}1~ZdOifJXBScZtaLrer?#qMY_*WT ziUNmSLYr~WSSkI*Cx;JN@c_p_OTftnSjkX>IPQ!hEjLreJZ=2uNdl(l8xEt}nLvwWyuuf|bqN`m$8g zy*f*F_)j~vy0Hk(K)(#?C!}GKC4pnYJKQ26in|}jpoekYD(M70MqM{s(cWd7QD;Rg zTXTapwY7=Ihxvo4rt0|h+|~9p6ckRTarnJsa>cCh_DA;oOuMNeP;%RrIPJj;~gQ5XX2>B(l$aE9@yUh5tK$z!pcD2J% z&F=4Dv8RBZU97+7Fm9DFeA2WDKY#QQ;}%dzq3Ewt4F;^f>Odi>L=w>_IRkkrbK^q% zvTUuarhvrTC#Tjq9|o>|gq1T4%r5X?k8WY#Ct&$yCh=sI&r1K;7s~{zWWVlm#>-x2 ziue%ox4g-Ib9G-@JTVqDuB`_==Ha2sgqRr{@3HFq0zb>A0v%Qt`oWM2QFs`Z>@S~w zFWdL{_C3tFq0K< zO5c#%o0znv+`e0(ZYJ{0<+yxLc*k{|>$iY;a9hmH(qN;66u`+(32J+CLDzIoUkDB`yOr z>0Rdf^mKiph6p}p$=#Zt>ppnx#+2Hi_Ak&25JxsqgX+Q&9?HutM=`C(e5b*A56>E9 zK0h!zddyV4vtvuvco9UR)1ZCAQ zdU++Av^i-&!t^~j$aj>ARnbKka9fL@enx&o!h%}Az#92MQYqzNNv^9Fu&2HIZC6ZLP{BJ z(c@7^8GZfNUYwlu3ghDpWW`M-2i)8kE5u|Wdv>-m=JQu4T(8mGA@0_S)2FU@HvCKF z# zAfI7fa1zCM#a1oCp0lenU~D<44O)bGk>EBULI$_{*H3*9z?aW2Wn-dbHBav~d@Y{R z)6;LxNjAyZju>-j-FN1byXBnqFiz!UUNaYi&>lxK*RY|9Qae}DflcCm=n%VkN)Okl zA|2uK$l~H^#A-k}%*}aI<|Z=ZX{66d#%}st3MA%2(%bYxkBShhwTDXK_nuC~PgHxH zI=t930NInksUwpUSI#eJ{KnB=MV_vISpj=a%3KpxoGh96_>!O3c_(KewwBgrrM{Nh zjbU+{8+|24;$9HCAFui3O)^P)7x}HQhZgxBtIZ)^cfSlFS&U3l9at(k;c!wE(!ANJ*Yh4D=TA z<$?tWhzrqPeEuzI@Ufmwj~P1yb_toy#ms{kg>Jd04%L zv!f`LNuE@?Mv425m#^`!I}9tu?}=qUbW+}9<@IK!P4udmD|mV%_jHMsMklM}mD>%x z_b;AaEi)2S+7fol2#N|`%4@Paz>23%#`5Ovmq;w2)T;&Wr#cQ{x=Ds+ZfZ1AekhiUbJL zR}odO$=xo)o;-V23yR)OO-{DQa3w7~F|~4ZJlI(tW>hWC0h=$#rORJ^_f)QSZf-7& zR$*spFf<|p&1Tl|Kbkdg?RIERTk^{YQ$WJ){vr}7FV`5=-rf$-_M5cw8Lz7-cqjYl zGX&h+PTj9!f?OT7Qkm`YLT`F)TJZg zJRktX>(8$0fwi=>q=~ z%gUT4YCHst?vX(yi)yIMO-)S|r{{Z87}d+6kHy8sWxk359<|gnjZ&!Nk~!xbe2bvQ zy9>Mo;NM)k4LW1Zep*2LW>%+0CQ3Yy!-lqH0^n$l=N~wci0!n;^S{Q<10=M{XR+Z@1tgrsjeWt;rAqq#|^}bjb!3-`QNITZJQGObwnS z+KlhLhOx}ro{_W-gXFJPX>qFC64{*M#BeEC9# zEs8a6TV~PYnJEBe77}~cp^_|cO=YS#a8+ze3ZKcaN4Eu#XI`@8mYZ;DI%?``={#05tqz+rsd(dM!Nu~SV&A;? z-Q*_Voi)|`Vu0=QS?i}7zXdN-KeEM03YYdzm>`C=x_aCT-*8)v^&O2HL&k+U&B#%r z;vq)^__lgoZ%Y<|Z@F?^3r#~RcVKtld6Z-=$0(QaA?ZixU1cK^6OVWe+V<|L>D|}A3~}-8f%*(xbiWXbxkjR;^FxTyw+0fUdO{bNy+Ta$00MO zC#)uO`bJAjE4~P~e|RPFc2+l_*DA9P%qRj@-F2`a!g^eC2Cjvjvx29zf#Y9ak*C)t zDk-VJ_F0MQ#Z>i!_&QiH$@1^zMOtnL1)( zVo-Pzell8SM}UvN_J~T3a0Ew*b!i|MlA5q`9s2cvS6|;uxUd_^hIV!DovtqX zpIySOoaNFsTeElFTzu2>^OA@1aYklsyoB$H9qFk~NAUQripoW7j6}xuq4|hyVHdtQWm;!=aCk&3%B04ddT0H*3U`Sc{Zs!BXA+cD*Ixvqe0_^ ziOT{xE}*MlA)&jE%`Gjz)7OC#YhGHAatNxc`|_)NDA-jZq$UQ>@0jxPvRJvd=BwHc zHPB~Wfx7rltx8nEp>E5V??9~=t_c|KE!>|Lf4|Mb=~AquB!LRlYajKRLXqwChB{G1 z@?_vu%z;`@6cy%D3y;^;!zc|y4EOGrZb3A(w6;h;JX+3YIZhEmOB~Z08Q}$?uXiOpmcPm0mYBaTf#^WY4;TknwSaV-2^Ig^kJ z_zu*ntSML?SA`Ll7ZhEf7`wj#JjI ztj%>LrCy@ovF}{mR^|c@oQ)^N4S*@Flra|MoO*YH69^N)*z<}1(U-Ym))yqv%tG<( z^^BcO^UQ+s69N`AU8v_{qGuRpK+Az^- z?zQG-->X7!&Xc^vmvxgIaPp%TGgCm1xe zF%vdzvBg4HL=@O96L!`e(L?d`+H3d_%pWcANjL46oQ+nmF2au+zE9i$(;#La4Di3u zv>lEQwtu47>1CTMoP!5?^sI}rN`o)8GCaVcQNlSe07Yw%Wa2&xbN@jP0N`?l9sCY` zD_L<=sc)Fh^0*vqH3m^4ZW2gf5(3C$&&Rkbi1P5ul8tb9B#y=r`oLonNn-IOX@8~t zq9TF}$f+>{311)p!Zpb4Ei9yZKE&Fsj*Sw|%$yu(-*=<1k1o3i%;u_mY*xp3rQA6m zi1+OI1B&Efo1RSJ%1c2N^R!b6lT77IUm*WCot+tpp&OcWFNF-!(`DEd`p4LEHNEsD z;d~KPB3kv7GjlBlS`9wKjoSSDqya)4578FkgI&=;{t+!e{zF5`(O*f1z|w>EQ-&?N z-|~M_g%(~Fj$=8=srNskR6xDrKb(iYI2RFeL@TVnuW#(v8V}RC0`gRMK&tQ_Cc6PV z;S8dQM0gOs2g^tRw~sZYXa0-++|z_n)0RG$c|v)|3UzL>OnKTipTrl)@%1h)uzc;Bx;4H^cZ9}l|HJh_~I zCQH*OqN%H?g@uZ0JAmy3_Yod(6Itg_4%s)zYoK}Z?V%6APDT=emT^9irWBV<_0LvpBpv6EXMJ;%@bZlcF;t$q@3b{aSoJN zvQe-K!C&ED>{I>x!Oo@?vm)9_7#$R!me3EpK}}6b9a!tb#Ms(48WR<6x7sRZbup%J z0+*MUQ!jz7+&@t@Nl7GiadO`kml@6OU;J+K`eC>GtT(=3uv3nFI{7pi1qD)rQ&-;f z^?!d%<3?YUEmfX=i6q?!R$WIH-S%~bbY=ysmJgs{*yI6;{-KeXrl=zRF0o?U}C}&ip#OMB{ z!$4PBXx#eyMo5_L)_LEY8yX9MhcI&^;mhAoNAtuaD}F`YI?LmP@8dz%J$9PehCy<9#O(0$`y}QLce-odo(ql0^0*7 z`3_7gD-Qjy)1!}BdVWtS?~uTe;af1b0lY3oC$)cU|+> z!D>2SjUCe#zj+Eg(1rIzNOa07q!?#qU1hznKt#MMVw$D8OAuRpmck@mEN%9fc;Gb2 zWnNi?zfL3dq|K5}1&1KL2~68Fu#s&%m;{FY!w5Z>Qv7E~Jg}s<-l24|>j+UMLo<&_ z^!#ivcU9GU#PKRj<|nE7w}x+-3HJ<59vX0UzEWN3qrL77yw!MNLS&+EYXK?j#xI}BmXH@9xhy`Mvgc=v;&$|IZ#l@Q`Y5h>~2UI&eY@#4~n zXlXDrK{nC5qM#5KCdkVWs3KM?+*wQOeghmFgdi_qVd{#C^Z{RE$)WOr)x)vGtnce- zf!|_XTuu<{^XKwN7;z-1UHRv7Jq_j@F@NMqV#Sa;IM$IaFxFt3$Xc0SsX1$eBT-R{Olx0@E;zrK|sT{~8J}X zv$L{5us#^)%tx6;cLO6k0ubH7&*7ods?eQ(5o#yANe569+IfE(v$I6Cozs6PuVSvo46Pr6tY(m=L? zUS_Fb)KU2=u6p@^v}P)s8*4|tHFjpYb#Q$t{U9U5W4=eG-$RQQeK(J@n^Utr2kBx)HM_`+|;`Qq4>YDb@j|Ms@IS#*n;g@c30i5ajU_U`@s`O~_i zZv7?->FZj_R}`T}vAJs#al3X1QV04gt9vV_-W%fLD7lpBo1EOKnUGV`R9<;~OG{}L zw5&|tjsf0kyErp5XMa0>m&aaU#Xdkd1kef>;NMYSUXCe+fP8)up3~Dt8_;Wj^YJYv zE{@>^Rb{J(;7@iRdI1K%;oVi&txbK$%Ik;W+zn=VL)>exPj={S^z`)$Y{qwXHvI>w zn)&GdL~WVeIT_VpiOaPdLA0@ffhTv_%R-QAqOc_)fvNA%_$DG=sjI6;p-*94$3k6C z!cKmgWz#@-WT)|Ny2|;aUj7pwW1ZhOyt0J$lR#k=G(L<1k_CGn3KR0;f^8mc-ODL? zYZZ3uD-S5kC6S>Fa`BvsrA8DOlv`WYxtd{uXr2?Fb4yFPv#jLhA9KWDEu3IeSM4D zJD0$+F-1`^`lZ;ZD}KHQT64ioX+Pu|!+0z?KWcXLjxq-QkP5fWeC zvOec7ICz5h`9P5iwoqF?DA@2ZP=nXT5+}_4m@ofZCgiSdFV)>zwBR=;q_|85ovvY;5w;p;_U^E@#M<%v8Os>``7Mh zFZATZ37HL~fM)F62s!GAmjQjMo%B>xb|d?9YAOv7bwa|W68twx%F5rP%(=$?^esJC z4jp`hP)VK)%F4kpCGz6K#O9WL zS~K@5nzUEqi|Ya{vwX$7kId;kWvz;)yq*0pJSPm|lhuj$g*Zp2lyZ_kqaq_a`(Wv5 zX|0rTuc+Uz?oXIinl`zit(N{H(7J&EMPTqnM9|3q!v(Gl@V%T7*6I&HYi@&qQIwxw zhu@7rAbNY{UcEvAtMS`F6k$+J!rpZ$=~&LuY-0YaN2-;GazMTKY6#q|dB+Lg#DeQyrxF?k6#7B&jUolVYs|G^!xE5sJ#+Ws^*~m3d$HfrZmaDRhjeqJLbiHS zU7DHoAOcaOXtcI5{c`Z7eGn!lW{RUvE*L_u%WTCdGz-ehGq&XrXyMK!hnF^5d|K5l z6Pi5k6Gf}em9}~iQdTmKK#N^-JyZ?OBh+ZGeUQfkQS%h|Z}Tjv+yJ{h1OxPvhZ=^4 z3u}j;Wgq%GtuT|6eeHh=zXOMFs0_gWRH0?slqIk`=)$UxaBLL$5_(&OCS_eoWWXcM&Pirib_nJuQ-N^h~zwe+lNER|3yOQ zUhtVOjdXs`)5uv6uq8{xwcCs;L!+bgSC^KSK0Y)^e#H=x%*sy^&=zfAZM5&K?k^#l zHSB9S4)hoFg5b1JdsZp!pw131(pIos)y&hEjV44SB zZ`f+qBq;|M7dQr^t3@PP(_v>x>CW>70fOK1?I+&@X^YBLH!QFU^HA|AZNb=)0V}&v zVprx(KS5@0nt&CdscVzAUAOC$j4WP+V_P4RF#FNh1wJ{-ZFN}TBT{Zq7ewJVljK~4 zAF4;-eVeng(bB3>LR(o_=r;IK1A=S(+udyesuze2P^-KMRfFI91r+L>wZP1M7M3BM z{ohG=|3GhKy7&(`W8TJT{wPZ9>W^A&VBb&CJFlv-cgoqa=_UK{B#QtUnGpMIcMVy& zekwqe3AIkH`J>KKdJT%(%|coR(yxtIuSDkIILj`5o8%!QCAI2NI@(j=qw z9O-kP@8lx-XJ)?2y=Hk^INF8^L{#fXeCaUvhF?8UHhDL@KA7%O;cUvRL-MwN9!`10 zV&)`i#F?gzjj8*DdFq;)GT`0We#Y{@(WmhOR(cqX!}7F7epZp8c4 zCbWpqUl-683nerONlX9&)Z_aDZT-={-`Xr6`Qd$odL?M*`k$JW%(ipq`13yEn^8LO;s7e>*1jvd9uTTf$+ZXvv{DjmCHsS| z`KDO`4hy++P|9``wmMQWg^3DMAscs@NQ7+=!^4{$LhAT;g&ri^1`Pz~PD2%!XlI7@ z%KCti7wHY^mT>R#SzWb5;L>UDF7G=zjVPh=Z~02 zz*75AUR;0n-&tD{AheV4Mh>s~_a&tN@d&mRIBc@njHCqKaz(@CNj@LLOEXMARDzlp zXc8$sb`U}$qAFQVZtepuA6tJ!hAP8ZhrL{MRFfkZpv)7ZYiDSWk20}4vlf(-bG6=i zI7bLQr<=2Rs&|3)+u7MMSM2NSTfwLr+2h(z$`I#Z8wWH4PNttB5U|Dw2OC=?mEf%x zgz8l3VQpX|?Z-;k5t-@N0m^xNj8A)v(b3vUCGbU=;i840U~*;FYbEp+^JmZA z{oJc$?C=IDwP4PiBa&Obh)P_#q8{A|PI;Z35MaDP*Z&geS7FGnlzdvuY<{eTiPQz~ zo^gX}BV7=oJcl{qV}Md%Hwz*+_|m1Azi% zwLW#AU`z}K(ZMw$vQbQS*XQ&^UWDI=h7!;cyq-|cCYo0}xt>U*AkFlTz(PKKi5Y_{O92@9ctxeh#hzAN@|zE7H#i@ zd9jX%uxS}$_1#>}BJtx=oqkEg^wc0pu94w}c|1qPCtEz^Vn`PwW!cKiLpQZPv z8Rx5KwTJ*VXKg5i^<7K9Z*7vX<#vm0H8gJLM)*w^P_9v#$%& zt#9Ku#+{Hvki9%K-ap!YS%`|$7{|%40;r;^h=xIuJ^jJQQ$!pTL3wOoEAU}sb_>P( zp-tjRxMqmrG*NtrAB?2}m+XXoG(Ih@xfN_U(dNNUG^9GG&qY+4wRqS$*VAZ6Zk2Ek ziEPB7-RC~F-`X>&>%k zexnp;;LD#b=9~>|oU-Po2gO>AE-pN285{I`9_C>&((JB|-1mfoO3fTyK8Z1E#S`cK z_R}0j0fFJ2%hJ+U1kDKN31xq{UK4rMB#6#&SNH*K(ueoze9!CLi!-LajY>6|WURBX zbmfn_L1ImQ9QcxDh?0zF%uKhsC-bP)#=GGm=2L4 zWY5KY&ap()h&04uDzEnK1(vwqCt@^<_96sH+n*GSL+JKtx1DrO=oQ zfP?i~)gJ8w1EAyzk__=TpM&gMAV37}-@h-w32G)efr3NVM-JAt_(J)YaEVsT>>}d@d^~Dfut1 zLsqAWQtH$Av5+R04oU{y^oB4h=G@}q8ceYOxGzXX{v$S|xX;CpQvi7f)^{XS>s{ir zp8KH8kY60&FSH+xD%qi2>0EL>0Wd@Ws|8Fzy0w5g1NKRbpLh<^I*no133(8#>yreZ zQ&P}`yf*4*1yk0)?`#M=@a8GS+OU75T!h@R^|*AHS0{SZLD={mal^3k5GCjRMlxfq**$Zr7oD68>S`jl<32=`Xg4J?oa z2OY|GwEl{Mf*yr9jfXRaYO8cOf5Yd>63?1F*9R^AQRr0hR%ctDvI zp1aDoS)C^cvfhJr8=+#H{FeYqWZvc3y{|M;sDu#vY2D&BST(mPQ0`5VOY1){E&r(y z$A3U>Mo~Db5Qk@=QcbfCfQ$Jy0v*-0e`E|xbp(Rz^)@}INKOj3i z2Y)VuMo7~MYIxlc>AJ*iZfD0qA|oR+t*-?*N$DQp@$r;;mzf0~J5_jNtl9GTRtH*cygd_#t|Wi>}j zs|?^s*q`pr=cCg?P)i2{xxfp8(lXP5iYu+p4Rt`#xN|lG87ms0(c9oex;|92Ik>jA zcJcJ54<9s4LB#hF|4T19loBdAwqcKVGoD-E8BJ9fcMJUqp77$*(tAkKM_UEXj01+t zbm?(znqG#C0BZ48i^e)>tZMGu1$9ByC@8C{Zaw3z2S~^_mY&roO)8+o1;Dq!LZ;1l z>mz-17EPa{=-==e&JURGrEtpN%wN;S*w(qPgpxFLzZ{^Kcs{q~skH&uYZU~j(%Dr^b9eEPJy#skxWgY!BX zsQ9w4PC`ohfWHp#Oum#J$&>FJ{1|x$Ns2kL|MosL7obQUO|Rzr00wgSQ4J~)8nYHnOM8t4e{=U<$Wo1Ucx!s;gX0iGliT!{YfxMSi7oOGQ`n3k!Wqo!7#{2|1aX z=WQc~j>(iTnpf3NI__P6*JqP_Hmv!qo(aX#_qr_}j=u07?Kt`f$eC)H(?i@jjltTx zTF{0iHx&SEgRY5*A@dJ|hJ(_6@1tG>_<~AQ=d=-*65HjW-gJe<#YMA`Vi>-ek>MEK z#3xOnyzFKGK_taox|Z2N3@T8}&3!Au{mH?>Vb}9j{FVF&G2wFaE-aYQ3j-cT@K+}u z__ca~@(NWjwZ}y+MD?l$=dj$GK5qY?d4oLij0S-;|2p!Eu+4H-R#yKiT~HEH6@3Y6 z&rvjtjV8pUCh1}OMrJK4N4@(@{k`Q*(zB7>xX;y0mJ&+5Zp*TUI+`Rr3= zLr}w$hg?3?lIXosE>Xp~v8@ztw@dCyq1fQMJk+_>yRlVJ)&YXy%>I;0(cRp4r0HV#P3QFadl$=r7LFt;ARXCt|{~13>nm4j84xmw-)#3qF zd(J4xXZ3rK#d*Mqvl=}p;LKXYgHLT=@d*hDsUCmI1+{PVf|O_gSAcH&WkIgGwtk>| zrhGC?H`SK%SB?O7v#4A6@u-Bw@E~eq$|wpgyT$Lsm!7HtPIy+IIFe#Dw+f zZc^^|?~}$5P(PtXP*>t*uAQLtX^=ErX^_w!@;&Cr#ce)W`7|kme1<7+h2y&iz3zF~blQ~T2hLU>tO{YL zsp6>wx>YJxB{6x}JZx;b?Vd+DWo4g4U*ICfRc@YD=Q(3*{iU8HU{x=&_Rbp(AiT5x zp#R006D<;ZYinz|9Y8wq>!|JO3TDlrW8@11JT5<6TcrWZLVy|&`Sxa`t?+^PmU8@I z#gWzEAZn%INXe^=@Q8?5lkvxPV~@3wWm5@?%uYG2pN)y*y3`guJS*xGJLRDMa02jD z!Mcqw#T$ZQ6nWA=4cU|yA{@0V?$cn^`bq;D0W)IvtP2#pmRElmzUr%3iq~Z&2@qY< zn2kaTqso@q0F=4fs7!HN;v9xQvR&LxHT&>xBO0g=x?UT3|LJIHVd0E11nj)rT=FPo zdVQ)f^?h#eCufj^2E~sCx~d>8r{U@iUo<2Cvcs>ZRp;{Z@**Z9dgI-ox(uvmO?IFF zEPxBiAsGPwT~Yq0xKCPj@&;vVz|Z>nx>QE5A^CP>SmK~@vvid#=^~r$jDaRp6w)HS zY|6g`NSnclsJhAgEhnaB#mmNEjP%xP@SmzoCtj$7alZl(c2!N($*QonjZN<$Y8+UQ z>(?9;E|Bm+6iP}ld7%wi9BZKLBv|$Pz}C@mXr7t2&GA^;B%^;IDoId^3Y+d_LD|rN z*0|c%^`+HH`V$~FFdkDB%+|ihn29YX=eKs`zjKQ;tTIWTqE=J}5)fvz)dw1DEX_R)oytrAg z<7(pYvidRO;FBs@Bf1PUu#t2({JSigEGh1S_R-L4oCz0Dmz@(v3hm7wqhLtmnkQE* z(t(qwD?zVmaHTnoE9HDaPU3{cs5dVc;jZ1@n>dd0TEq2 zLpgJ0`?-{kD?>z!6MC4aZU+v==<hIk#ZkvnJ*ew-YPWcBP@0Dw)1>oDHLB-( zZf-E>R^pk9ZVfykSDb0#^=~?j`8Optn4j>PRG&#P6OIJpN7^5-q{!j-*pCCmiSzne zzl{O17TEvXnSZ+V-1LI70LbcTe`&yfaVCM36Vz;|7ywaAS-h8C9t}2K%Swu+gTvsl zhC)XnQc6w=xkEhGML;Pbs>uq=_kgfQPwG(Y76FR~SVLy!F7a#baDv^{(TeSg@=p|r zuuGtn0rnC_$EGwouwha+_Hzvj7Iwj~lTO`3*MuH;4k~6#x!+pDwpo)-Js%9*#%{v$ z@4m0-p3)_$XCLM^NK_-O|LZb#R+j?fsxb@Xpa@@-0UCnMziJFAI`%J_I;c|F+y~Rw ze{|M1|IV`dy#%JWQ#tmROtG=mpJKzF8F7pcjL45?q>nzOc_D5Q?tLep;nua%_v2fa z>O~@0Z<`ye3VORkJhW$BTw|VgPV@Tl?ePO5g4XzpQ#2f>l2M0=h*0|ci$B)4ivYcF z+0(YLC_KXWHz=^fa{SOulg38iK6}fYI%AeMq z(Nly=JjOVoSFufFE7kd3>Z~%CE8fejBFcvae*Zaxz*MOrMZ?S8LD6k9fN{}^$;ciQ zm&mp@v2DJQv~SWB=CoVkMwkh!9jx6P4t?F?HvX%G68Z78ls`ezb|o$)lLceExa#RM zJUIe6U2kr?CjYx1_E5@Ab)VvR>om9MCHY4g5`Mxkya$Ed;`~7V9?DWw&)#2Ic z*~Y+nesDg?yw(Sb!{t z*P&qONz_`x7;5E6g{gr8 z@{T$H2hzyJjS0F%3~(FEl98{ppV|W3@AtCj9H|+EJ`RsI9v-uvRtW#eo)g0xD`D2B ztJJ4gAXx84q}*PqL?7B@+!rQxe^aE*jmR;7qvuUtp)+6Ao6aM<1Yu!eli{qR>rVR{ z$sL_tea(P|h-^jw7+8G++y<~p;!f}fH&CHw@W$q&QT)YN7^HGa>~ zV1kl3to7{L{gQLT1l4Ac^NwzL2ZSF`6XQElR0~beCTR+=$yCInK-x5|u(=bX#W5Z& zJWnM&EDeb8Ya0DY)~*9khvr}E)~UdOE5w+O971JChR`U0f|?FLOO>^A!3mrzmmj4! zEB?|XKA*}!VHu?B|3@V{z*LQVXCB-?zCV&+jhqPnoIuV-XJ?#`-wWK1M`CsLS{8_V zud(VL8Ip3Ast<5{6is%)T}_wJdTIh`83Y;MoyD~BvWGJ)=mpqx`q})Hj8QpxgGUqT+LZQ_%^+?{e_dA|@mqlqfif244)`UJj`y z_w$IHN;Q*!`dEYlF7CZ#Dr&`I8+WJ-u~cMMD4WmC?i8X%A) zE}j4nFflVz$pY0Jc|yTmdW_z*j0e`&ZZ52B7$Id>WHOuv_|8hbgeQtXfh=uXK!?k# zMTzOz87Z4U9;?)bgE9|_14`wt%UvagMge=bjbs4YT;&-YB&2=hXgMrPys9dYCo z6ya)tQ(I#Cz3jkjrWl_+VVg7eb0IKhfm0tBH(YCDD!w2;DYQ2(+H(MN$v@+mMfP#m zFyJ}Rp<+NveT2O~39uu+cU9Yj7q|catvi20Y^0j~7y9+@-$4eQr?senb$GiUfSDx; zEXuEj;S&KHC7{sHQaPS4Q2G^#RIYX23ZNeRr7j0F%{Vp zeSPxZ8Ct%F^b)$MAQf)sOH;*Mh)r_Oyuib+g|L_N!CzVmm%j*Srn3?HrJ9TbFg!|5 zmkz|R8$jEXkphsnJS!ao=sVdJktWi^Kq64#?Ws2l5 zYAkq;O0g;(-O8QsFw!4FOED^6#BV05M9OmX-pD1W#{Ob~>;6FO1hrad3Buy;b?M%v zn>~(kMX?JpccQ%Z`H&3P92bqli!{#8{}V3UHVg)cs}3MrWfc9@HGLOsLjm7kmN{M? zW6Ts8fUhrYPpI*IH5#|yIarIHz=>ColN(5-24`Y)mCDn1<5TCY9rJq-Q-~9} zv#0sEq6KbR5}B;GHPPiO8RT{9CA0~g{!p5moA6V#wRN;KCR9c`YsQoVd>5{*%m0A0 z{5yO0+t>c%2_g^vH7|PM_7a!@?6h&JNvGy_`t*Y58?P#Lqjn;vXC4a`@{dQ~N9(kU zFFH(tW*?M9d;SDN zKN8io1|(>zt6`4M04)g^nr7cmpFRQ7FetV3PjHE>`3&spQ8Ya_A0Gu7nX0mK+E^X1 z&~W(MprDS>X$K37VDJ+i{je$^D2S1UCiw2Z+hG#L?3|n&I2;}m6N5|12P$u+pAJq> zCs$QfRcpMElsw&APg)w1QwF3WbaZs&#tqfS$1Y%9FWAcCKU+<%qM{Ox>Pias*pOsj ztE+pj+O)Cp-4PHL9|!}RTy@hDd%YwB2CU8OYDS0`tiUp* zAQw4~j*R@YYt;j;Vmw&>S@MtPab_cdpcYZX%>(@WLe=W{QIU)4j34&(z+GKkpFVvm z+5)Q07t;O}AOr6SDf1#zjoWs*lq`w_@bB4*6bj%9fD!^^NR;;0EjVBv4uW#NZ5