diff --git a/e2e/tests/ci_snaps/featured.spec.ts-snapshots/WovenResidues-chromium-linux.png b/e2e/tests/ci_snaps/featured.spec.ts-snapshots/WovenResidues-chromium-linux.png index 10746f82..02344fb9 100644 Binary files a/e2e/tests/ci_snaps/featured.spec.ts-snapshots/WovenResidues-chromium-linux.png and b/e2e/tests/ci_snaps/featured.spec.ts-snapshots/WovenResidues-chromium-linux.png differ diff --git a/e2e/tests/ci_snaps/featured.spec.ts-snapshots/WovenResidues-firefox-linux.png b/e2e/tests/ci_snaps/featured.spec.ts-snapshots/WovenResidues-firefox-linux.png index eb9fa271..88c76a63 100644 Binary files a/e2e/tests/ci_snaps/featured.spec.ts-snapshots/WovenResidues-firefox-linux.png and b/e2e/tests/ci_snaps/featured.spec.ts-snapshots/WovenResidues-firefox-linux.png differ diff --git a/e2e/tests/ci_snaps/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-chromium-linux.png b/e2e/tests/ci_snaps/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-chromium-linux.png index b3858f5f..2b86979d 100644 Binary files a/e2e/tests/ci_snaps/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-chromium-linux.png and b/e2e/tests/ci_snaps/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-chromium-linux.png differ diff --git a/e2e/tests/ci_snaps/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-firefox-linux.png b/e2e/tests/ci_snaps/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-firefox-linux.png index 2d45fc95..2170290e 100644 Binary files a/e2e/tests/ci_snaps/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-firefox-linux.png and b/e2e/tests/ci_snaps/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-firefox-linux.png differ diff --git a/e2e/tests/featured.spec.ts-snapshots/WovenResidues-chromium-linux.png b/e2e/tests/featured.spec.ts-snapshots/WovenResidues-chromium-linux.png index 10746f82..02344fb9 100644 Binary files a/e2e/tests/featured.spec.ts-snapshots/WovenResidues-chromium-linux.png and b/e2e/tests/featured.spec.ts-snapshots/WovenResidues-chromium-linux.png differ diff --git a/e2e/tests/featured.spec.ts-snapshots/WovenResidues-firefox-linux.png b/e2e/tests/featured.spec.ts-snapshots/WovenResidues-firefox-linux.png index eb9fa271..88c76a63 100644 Binary files a/e2e/tests/featured.spec.ts-snapshots/WovenResidues-firefox-linux.png and b/e2e/tests/featured.spec.ts-snapshots/WovenResidues-firefox-linux.png differ diff --git a/e2e/tests/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-chromium-linux.png b/e2e/tests/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-chromium-linux.png index b3858f5f..2b86979d 100644 Binary files a/e2e/tests/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-chromium-linux.png and b/e2e/tests/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-chromium-linux.png differ diff --git a/e2e/tests/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-firefox-linux.png b/e2e/tests/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-firefox-linux.png index c7cfb652..bccaf715 100644 Binary files a/e2e/tests/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-firefox-linux.png and b/e2e/tests/idiot.spec.ts-snapshots/Stress-test-Numberscope-usage-Way-too-big-a-number-1-firefox-linux.png differ diff --git a/src/shared/defineFeatured.ts b/src/shared/defineFeatured.ts index 0d4a3814..864a0bba 100644 --- a/src/shared/defineFeatured.ts +++ b/src/shared/defineFeatured.ts @@ -22,49 +22,46 @@ const featuredSIMs = [ 'Dance no. 163', 'ModFill', 'Formula', - 'modDimension=600&fillColor=a51d2d&alpha=min(0.4%2C+0.01*m)' - + '&highlightFormula=n+mod+163+%3E+81&highColor=ff7800', + 'modDimension=600&fillColor=min(0.4%2C+m%2F100)' + + '+*+(n+mod+163+>+81+%3F+%23ff7800+%3A+%23a51d2d)', 'formula=163n' ), specimenQuery( "Virahanka's Prime Construct", 'ModFill', 'OEIS A000045', - 'modDimension=130&backgroundColor=62a0ea&fillColor=613583' - + '&alpha=0.05&aspectRatio=false&highlightFormula=isPrime%28n%29' - + '&highColor=e5a50a', - '' + 'modDimension=130&backgroundColor=62a0ea' + + '&fillColor=isPrime(n)+%3F+%23e5a50a0d+%3A+%236135830d' ), specimenQuery( 'Prime Residues', 'ModFill', 'Formula', - 'fillColor=1a5fb4&alpha=0.1&highlightFormula=isPrime%28n%29' - + '&highColor=f66151', - 'formula=n' + 'fillColor=isPrime(n)+%3F+%23f661511a+%3A+%231a5fb41a' ), specimenQuery( 'Baffling Beatty Bars', 'ModFill', 'Formula', - 'modDimension=350&fillColor=26a269&alpha=0.3' - + '&highlightFormula=floor%28sqrt%283%29n%29&highColor=1a5fb4', - 'formula=floor%28sqrt%282%29*n%29' + 'modDimension=350' + + '&fillColor=floor(sqrt(3)n)+%25+2' + + '+%3F+%231a5fb44d+%3A+%2326a2694d', + 'formula=floor(sqrt(2)n)' ), specimenQuery( 'Woven Residues', 'ModFill', 'Random', - 'modDimension=5000', + 'modDimension=5000&fillColor=rainbow(n%2F2)', 'min=10000&max=100000' ), specimenQuery( "Picasso's Periods", 'ModFill', 'Formula', - 'modDimension=100&backgroundColor=000000&fillColor=1a5fb4' - + '&alpha=0.15&aspectRatio=false&highlightFormula=isPrime%28a%29' - + '&highColor=bf8383&alphaHigh=0.4&sunzi=0.03&frameRate=24', + 'modDimension=100&backgroundColor=00000008' + + '&fillColor=isPrime(a)+%3F+%23bf838366+%3A+%231a5fb424' + + '&sunzi=true&frameRate=24', 'formula=n%5E3%2B2n%2B1' ), specimenQuery( diff --git a/src/visualizers/ModFill.ts b/src/visualizers/ModFill.ts index 58b1f224..eb298651 100644 --- a/src/visualizers/ModFill.ts +++ b/src/visualizers/ModFill.ts @@ -7,11 +7,6 @@ import type {GenericParamDescription} from '@/shared/Paramable' import {ParamType} from '@/shared/ParamType' import {ValidationStatus} from '@/shared/ValidationStatus' -/* Helper for parameter specifications: */ -function nontrivialFormula(fmla: string) { - return fmla !== '' && fmla !== '0' && fmla !== 'false' -} - /** md # Mod Fill Visualizer @@ -31,8 +26,9 @@ each time the corresponding residue modulo _m_ occurs for some entry of the sequence. The sequence terms _a_(_n_) are considered in order, filling the corresponding cells in turn, so you can get an idea of when various residues occur by watching the order -the cells are filled in as the diagram is drawn. There are options -to control color and transparency of the fill. +the cells are filled in as the diagram is drawn. You specify how to compute +the color used for filling each cell with the formula in the `Fill color` +option. ## Parameters **/ @@ -55,43 +51,34 @@ to the largest modulus to consider. - Background color: The color of the background **/ backgroundColor: { - default: '#FFFFFF', + default: '#FFFFFFFF', type: ParamType.COLOR, displayName: 'Background color', required: true, }, /** md -- Fill color: The color used to fill each cell by default. +- Fill color: A formula which computes the color used to fill each cell as it +is drawn. See the [Chroma](../shared/Chroma.md) documentation for ways to +specify colors in a formula. The formula may use any of the following variables, +the values of which will be predefined for you: + - `a`, the current entry of the sequence, + - `n`, the current index in the sequence, + - `m`, the modulus represented by the current cell. + +To recover the residue of the current cell being filled, use the expression +`a % m`. + **/ fillColor: { - default: '#000000', - type: ParamType.COLOR, - displayName: 'Fill color', - required: true, - }, - /** md -- Opacity: The rate at which cells darken with repeated drawing. This -should be set between 0 (transparent) and 1 (solid), typically as a constant, -but can be set as a function of _n_, the sequence index, _a_, the sequence -entry, and/or _m_, the modulus. -If the function evaluates to a number less than 0, it will behave as 0; if it - evaluates to more than 1, it will behave as 1. Default: - **/ - alpha: { - default: new MathFormula( - /** md */ - `1` - /* **/ - ), + default: new MathFormula('black'), type: ParamType.FORMULA, symbols: ['n', 'a', 'm'], - displayName: 'Opacity', + displayName: 'Fill color', description: - 'The opacity of each new rectangle (rate at which cells' - + ' darken with repeated drawing). Between 0 ' - + '(transparent) and 1 (solid). ' - + "Can be a function in 'n' (index), 'a' (entry) " - + "and 'm' (modulus).", + 'A formula to compute the color that each cell relating ' + + 'to a given sequence entry will be filled with. May use ' + + 'variables `a` for the current sequence entry, `n` for the ' + + 'sequence index, and/or `m` for the modulus.', required: false, }, /** md @@ -105,106 +92,26 @@ Defaults to false. required: false, }, /** md -- Highlight formula: A formula whose output, modulo 2, determines whether -to apply the highlight color (residue 0) or fill color (residue 1). -Note that a boolean `true` value counts as 1 and `false` as 0. As with -Opacity, the formula can involve variables _n_ (index), _a_ (entry) and/or -_m_ (modulus). Default: -**/ - highlightFormula: { - default: new MathFormula( - // Note: he markdown comment closed with */ means to include code - // into the docs, until mkdocs reaches a comment ending with **/ - /** md */ - `false` - /* **/ - ), - type: ParamType.FORMULA, - symbols: ['n', 'a', 'm'], - displayName: 'Highlighting', - description: - "A function in 'n' (index), 'a' (entry) " - + "and 'm' (modulus); " - + 'when output is odd ' - + '(number) or true (boolean), draws residue of ' - + 'a(n) in the highlight color.' - /** md -{! ModFill.ts extract: - start: '[*] EXAMPLES [*]' - stop: 'required[:]' - replace: [['^\s*[+]\s"(.*)"[\s,]*$', ' \1']] -!} - **/ - /* EXAMPLES */ - + 'Examples: `isPrime(n)` highlights entries with prime index; ' - + '`a` highlights entries with odd value; and `m == 30` ' - + 'highlights the modulus 30 column.', - required: false, - }, - /** md -- Highlight color: The color used for highlighting. - **/ - highColor: { - default: '#c98787', - type: ParamType.COLOR, - displayName: 'Highlight color', - required: true, - visibleDependency: 'highlightFormula', - visiblePredicate: (dependentValue: MathFormula) => - nontrivialFormula(dependentValue.source), - }, - /** md -- Highlight opacity: The rate at which cells darken with repeated -highlighting. This should be set between 0 (transparent) and 1 (opaque), -and has the analogous meaning and may use the same variables as Opacity. -Default: if this parameter is not specified, the same value/formula for -Opacity as described above will be used. - **/ - alphaHigh: { - default: new MathFormula(''), - type: ParamType.FORMULA, - symbols: ['n', 'a', 'm'], - displayName: 'Highlight opacity', - description: - 'The opacity of each new rectangle (rate at which cells' - + ' darken with repeated drawing). Between 0' - + '(transparent) and 1 (opaque). ' - + "Can be a function in 'n' (index), 'a' (value) " - + "and 'm' (modulus).", - placeholder: '[same as Opacity]', - required: false, - visibleDependency: 'highlightFormula', - visiblePredicate: (dependentValue: MathFormula) => - nontrivialFormula(dependentValue.source), - }, - /** md -- Sunzi mode: Warning: can create a stroboscopic effect. -This sets the opacity of the background color -overlay added at each step. If 0, there is no effect. -If 1, then the canvas completely blanks between terms, -allowing you to see each term of the sequence individually. -In that case, it helps to turn down the Frame rate (it -can create quite a stroboscopic effect). If -set in the region of 0.05, it has a "history fading effect" -in that the contribution of long past terms fades into the background. -This parameter is named for Sunzi's Theorem (also known as the -Chinese Remainder Theorem). +- Sunzi mode: Warning: can create a stroboscopic effect. If true, the +background is redrawn on every frame. If the background color is opaque or +nearly so, this mode creates a stroboscopic effect in which only the residues +of the current sequence element are visible in each frame. (It can help to +turn down the Frame rate if the visual effect is too jarring.) If the opacity +of the background is roughly 0.05, turning on the Sunzi mode has a +"history fading effect," in that the contribution of long past terms fades +into the background. This parameter is named for Sunzi's Theorem (also known +as the Chinese Remainder Theorem). **/ sunzi: { default: 0, - type: ParamType.NUMBER, + type: ParamType.BOOLEAN, displayName: 'Sunzi effect', description: - 'The canvas background colour is painted at this ' - + 'opacity between ' - + 'each term of the ' - + 'sequence. ' - + 'If 0, no effect. If 1, canvas completely ' - + 'blanks between terms (warning! can be ' - + 'stroboscopic), so the residues of only a ' - + 'single term are shown ' - + 'in each frame. ' - + 'Otherwise a history fading effect (try 0.05).', + 'If true, the canvas background colour is re-painted between ' + + 'each term of the sequence. Warning: a true setting can ' + + 'create a stroboscopic effect. With a nearly transparent ' + + 'background color, this creates an effect of fading the ' + + 'residues displayed by earlier sequence values over time.', required: false, validate: function (n: number, status: ValidationStatus) { if (n < 0 || n > 1) status.addError('Must be between 0 and 1.') @@ -212,7 +119,7 @@ Chinese Remainder Theorem). }, /** md - Frame rate: Entries displayed per second. Can be useful in combination with -Sunzi mode. Only visible when Sunzi mode is nonzero. +Sunzi mode. Only visible when Sunzi mode is true. **/ frameRate: { default: 60, @@ -220,7 +127,7 @@ Sunzi mode. Only visible when Sunzi mode is nonzero. displayName: 'Frame rate', required: false, visibleDependency: 'sunzi', - visiblePredicate: s => s !== 0, + visiblePredicate: s => s, validate: function (n: number, status: ValidationStatus) { if (n < 0 || n > 100) status.addError('Must be between 0 and 100.') @@ -237,8 +144,6 @@ class ModFill extends P5Visualizer(paramDesc) { rectWidth = 0 rectHeight = 0 useMod = 0 - useFillColor = INVALID_COLOR - useHighColor = INVALID_COLOR useBackColor = INVALID_COLOR i = 0n @@ -255,14 +160,11 @@ class ModFill extends P5Visualizer(paramDesc) { } drawNew(num: bigint) { - let drawColor = this.useFillColor - let alphaFormula = this.alpha - let alphaStatus = this.statusOf.alpha - let alphaVars = this.alpha.freevars + const sketch = this.sketch const value = this.seq.getElement(num) - // determine alpha - const vars = this.highlightFormula.freevars + // determine color + const vars = this.fillColor.freevars let useNum = 0 let useValue = 0 @@ -273,43 +175,21 @@ class ModFill extends P5Visualizer(paramDesc) { let x = 0 for (let mod = 1; mod <= this.useMod; mod++) { // needs to take BigInt when implemented - const highValue = this.highlightFormula.computeWithStatus( - this.statusOf.highlightFormula, + const clr = this.fillColor.computeWithStatus( + this.statusOf.fillColor, useNum, useValue, mod ) - let high = false - if (typeof highValue === 'boolean') high = highValue - else if ( - typeof highValue === 'number' - || typeof highValue === 'bigint' - ) { - high = math.modulo(highValue, 2) === 1n - } - // set color - if (high) { - drawColor = this.useHighColor - if (this.alphaHigh.source !== '') { - alphaFormula = this.alphaHigh - alphaStatus = this.statusOf.alphaHigh - alphaVars = this.alphaHigh.freevars - } - } - if (alphaVars.has('n')) useNum = this.trySafeNumber(num) - if (alphaVars.has('a')) useValue = this.trySafeNumber(value) - const alphaValue = alphaFormula.computeWithStatus( - alphaStatus, - useNum, - useValue, - mod - ) - if (typeof alphaValue === 'number') { - drawColor.setAlpha(255 * alphaValue) - } - + if (this.statusOf.fillColor.invalid()) return // draw rectangle - this.sketch.fill(drawColor) + const sketchColor = + typeof clr === 'string' + ? sketch.color(clr) + : math.isChroma(clr) + ? sketch.color(clr.hex()) + : sketch.color('black') + this.sketch.fill(sketchColor) const y = this.sketch.height - Number(math.modulo(value, mod) + 1n) * this.rectHeight @@ -360,16 +240,15 @@ class ModFill extends P5Visualizer(paramDesc) { // set color info this.useBackColor = this.sketch.color(this.backgroundColor) - this.useFillColor = this.sketch.color(this.fillColor) - this.useHighColor = this.sketch.color(this.highColor) + const opaqueBack = this.sketch.color(this.backgroundColor) + opaqueBack.setAlpha(255) // Set up to draw: this.sketch .frameRate(this.frameRate) .noStroke() - .background(this.useBackColor) + .background(opaqueBack) this.i = this.seq.first - this.useBackColor.setAlpha(255 * this.sunzi) } draw() { @@ -378,8 +257,10 @@ class ModFill extends P5Visualizer(paramDesc) { return } // sunzi effect - this.sketch.fill(this.useBackColor) - this.sketch.rect(0, 0, this.sketch.width, this.sketch.height) + if (this.sunzi) { + this.sketch.fill(this.useBackColor) + this.sketch.rect(0, 0, this.sketch.width, this.sketch.height) + } // draw residues this.drawNew(this.i)