|
| 1 | +// Compute list of linear ticks for axis |
| 2 | +// |
| 3 | +// - axis (axis): Axis |
| 4 | +#let compute-linear-ticks(axis, style, add-zero: true) = { |
| 5 | + let (min, max) = (axis.min, axis.max) |
| 6 | + let dt = max - min; if (dt == 0) { dt = 1 } |
| 7 | + let ticks = axis.ticks |
| 8 | + let ferr = util.float-epsilon |
| 9 | + let tick-limit = style.tick-limit |
| 10 | + let minor-tick-limit = style.minor-tick-limit |
| 11 | + |
| 12 | + let l = () |
| 13 | + if ticks != none { |
| 14 | + let major-tick-values = () |
| 15 | + if "step" in ticks and ticks.step != none { |
| 16 | + assert(ticks.step >= 0, |
| 17 | + message: "Axis tick step must be positive and non 0.") |
| 18 | + if axis.min > axis.max { ticks.step *= -1 } |
| 19 | + |
| 20 | + let s = 1 / ticks.step |
| 21 | + |
| 22 | + let num-ticks = int(max * s + 1.5) - int(min * s) |
| 23 | + assert(num-ticks <= tick-limit, |
| 24 | + message: "Number of major ticks exceeds limit " + str(tick-limit)) |
| 25 | + |
| 26 | + let n = range(int(min * s), int(max * s + 1.5)) |
| 27 | + for t in n { |
| 28 | + let v = (t / s - min) / dt |
| 29 | + if t / s == 0 and not add-zero { continue } |
| 30 | + |
| 31 | + if v >= 0 - ferr and v <= 1 + ferr { |
| 32 | + l.push((v, format-tick-value(t / s, ticks), true)) |
| 33 | + major-tick-values.push(v) |
| 34 | + } |
| 35 | + } |
| 36 | + } |
| 37 | + |
| 38 | + if "minor-step" in ticks and ticks.minor-step != none { |
| 39 | + assert(ticks.minor-step >= 0, |
| 40 | + message: "Axis minor tick step must be positive") |
| 41 | + if axis.min > axis.max { ticks.minor-step *= -1 } |
| 42 | + |
| 43 | + let s = 1 / ticks.minor-step |
| 44 | + |
| 45 | + let num-ticks = int(max * s + 1.5) - int(min * s) |
| 46 | + assert(num-ticks <= minor-tick-limit, |
| 47 | + message: "Number of minor ticks exceeds limit " + str(minor-tick-limit)) |
| 48 | + |
| 49 | + let n = range(int(min * s), int(max * s + 1.5)) |
| 50 | + for t in n { |
| 51 | + let v = (t / s - min) / dt |
| 52 | + if v in major-tick-values { |
| 53 | + // Prefer major ticks over minor ticks |
| 54 | + continue |
| 55 | + } |
| 56 | + |
| 57 | + if v != none and v >= 0 and v <= 1 + ferr { |
| 58 | + l.push((v, none, false)) |
| 59 | + } |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + } |
| 64 | + |
| 65 | + return l |
| 66 | +} |
| 67 | + |
| 68 | +// Compute list of linear ticks for axis |
| 69 | +// |
| 70 | +// - axis (axis): Axis |
| 71 | +#let compute-logarithmic-ticks(axis, style, add-zero: true) = { |
| 72 | + let ferr = util.float-epsilon |
| 73 | + let (min, max) = ( |
| 74 | + calc.log(calc.max(axis.min, ferr), base: axis.base), |
| 75 | + calc.log(calc.max(axis.max, ferr), base: axis.base) |
| 76 | + ) |
| 77 | + let dt = max - min; if (dt == 0) { dt = 1 } |
| 78 | + let ticks = axis.ticks |
| 79 | + |
| 80 | + let tick-limit = style.tick-limit |
| 81 | + let minor-tick-limit = style.minor-tick-limit |
| 82 | + let l = () |
| 83 | + |
| 84 | + if ticks != none { |
| 85 | + let major-tick-values = () |
| 86 | + if "step" in ticks and ticks.step != none { |
| 87 | + assert(ticks.step >= 0, |
| 88 | + message: "Axis tick step must be positive and non 0.") |
| 89 | + if axis.min > axis.max { ticks.step *= -1 } |
| 90 | + |
| 91 | + let s = 1 / ticks.step |
| 92 | + |
| 93 | + let num-ticks = int(max * s + 1.5) - int(min * s) |
| 94 | + assert(num-ticks <= tick-limit, |
| 95 | + message: "Number of major ticks exceeds limit " + str(tick-limit)) |
| 96 | + |
| 97 | + let n = range( |
| 98 | + int(min * s), |
| 99 | + int(max * s + 1.5) |
| 100 | + ) |
| 101 | + |
| 102 | + for t in n { |
| 103 | + let v = (t / s - min) / dt |
| 104 | + if t / s == 0 and not add-zero { continue } |
| 105 | + |
| 106 | + if v >= 0 - ferr and v <= 1 + ferr { |
| 107 | + l.push((v, format-tick-value( calc.pow(axis.base, t / s), ticks), true)) |
| 108 | + major-tick-values.push(v) |
| 109 | + } |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + if "minor-step" in ticks and ticks.minor-step != none { |
| 114 | + assert(ticks.minor-step >= 0, |
| 115 | + message: "Axis minor tick step must be positive") |
| 116 | + if axis.min > axis.max { ticks.minor-step *= -1 } |
| 117 | + |
| 118 | + let s = 1 / ticks.step |
| 119 | + let n = range(int(min * s)-1, int(max * s + 1.5)+1) |
| 120 | + |
| 121 | + for t in n { |
| 122 | + for vv in range(1, int(axis.base / ticks.minor-step)) { |
| 123 | + |
| 124 | + let v = ( (calc.log(vv * ticks.minor-step, base: axis.base) + t)/ s - min) / dt |
| 125 | + if v in major-tick-values {continue} |
| 126 | + |
| 127 | + if v != none and v >= 0 and v <= 1 + ferr { |
| 128 | + l.push((v, none, false)) |
| 129 | + } |
| 130 | + |
| 131 | + } |
| 132 | + |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + return l |
| 138 | +} |
| 139 | + |
| 140 | +// Get list of fixed axis ticks |
| 141 | +// |
| 142 | +// - axis (axis): Axis object |
| 143 | +#let fixed-ticks(axis) = { |
| 144 | + let l = () |
| 145 | + if "list" in axis.ticks { |
| 146 | + for t in axis.ticks.list { |
| 147 | + let (v, label) = (none, none) |
| 148 | + if type(t) in (float, int) { |
| 149 | + v = t |
| 150 | + label = format-tick-value(t, axis.ticks) |
| 151 | + } else { |
| 152 | + (v, label) = t |
| 153 | + } |
| 154 | + |
| 155 | + v = value-on-axis(axis, v) |
| 156 | + if v != none and v >= 0 and v <= 1 { |
| 157 | + l.push((v, label, true)) |
| 158 | + } |
| 159 | + } |
| 160 | + } |
| 161 | + return l |
| 162 | +} |
| 163 | + |
| 164 | +// Compute list of axis ticks |
| 165 | +// |
| 166 | +// A tick triple has the format: |
| 167 | +// (rel-value: float, label: content, major: bool) |
| 168 | +// |
| 169 | +// - axis (axis): Axis object |
| 170 | +#let compute-ticks(axis, style, add-zero: true) = { |
| 171 | + let find-max-n-ticks(axis, n: 11) = { |
| 172 | + let dt = calc.abs(axis.max - axis.min) |
| 173 | + let scale = calc.floor(calc.log(dt, base: 10) - 1) |
| 174 | + if scale > 5 or scale < -5 {return none} |
| 175 | + |
| 176 | + let (step, best) = (none, 0) |
| 177 | + for s in style.auto-tick-factors { |
| 178 | + s = s * calc.pow(10, scale) |
| 179 | + |
| 180 | + let divs = calc.abs(dt / s) |
| 181 | + if divs >= best and divs <= n { |
| 182 | + step = s |
| 183 | + best = divs |
| 184 | + } |
| 185 | + } |
| 186 | + return step |
| 187 | + } |
| 188 | + |
| 189 | + if axis == none or axis.ticks == none { return () } |
| 190 | + if axis.ticks.step == auto { |
| 191 | + axis.ticks.step = find-max-n-ticks(axis, n: style.auto-tick-count) |
| 192 | + } |
| 193 | + if axis.ticks.minor-step == auto { |
| 194 | + axis.ticks.minor-step = if axis.ticks.step != none { |
| 195 | + axis.ticks.step / 5 |
| 196 | + } else { |
| 197 | + none |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + let ticks = if axis.mode == "log" { |
| 202 | + compute-logarithmic-ticks(axis, style, add-zero: add-zero) |
| 203 | + } else { |
| 204 | + compute-linear-ticks(axis, style, add-zero: add-zero) |
| 205 | + } |
| 206 | + ticks += fixed-ticks(axis) |
| 207 | + return ticks |
| 208 | +} |
0 commit comments