Skip to content

Commit e2aa3ca

Browse files
committed
FactorHistogram overhaul (#568)
* fix: don't post popup when mouse button is down * fix: correctly position labels when zooming * refactor: move factoring/bin setup to presketch to get a jump on it * fix: post warning about terms used for infinite sequence * fix: don't block the browser when the number of terms is huge * doc: explain hashing of first bar * test: changes to ensure full suite of tests pass * fix: show finer-grained progress on factoring long sequence * fix: make sure the factor counts are recomputed when parameter change * chore: reword hover box caption * chore: remaining PR feedback, remove now-detrimental hover scaling * fix: don't draw a y-axis tick mark that will get cut off; revert ci.yaml
1 parent 875e373 commit e2aa3ca

22 files changed

Lines changed: 333 additions & 206 deletions

doc/visualizer-in-depth.md

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,14 @@ fills it in from the `category`, and makes it read-only.
298298

299299
### Other properties
300300

301+
#### Status of mouse primary button
302+
303+
A P5Visualizer automatically maintains a property `mousePrimaryDown` that is
304+
true when the primary mouse button is in its pressed/down state. This property
305+
is in essence identical to `sketch.mouseIsPressed` but (a) it specifically
306+
only pays attention to the primary mouse button, and (b) has its value
307+
maintained more reliably in the face of events outside the sketch.
308+
301309
#### Vue and reactive objects
302310

303311
It is important to know that Vue will instrument (i.e., insert code into) your
@@ -390,17 +398,20 @@ opportunity to do pre-computation as well. That is the `presketch()` method,
390398
which runs asynchronously, meaning that the browser will not be blocked while
391399
this function completes. This facility is not a part of p5.js, but a part of
392400
the P5Visualizer design. The `presketch()` method is called by the framework
393-
with one argument, representing the size of the canvas to be created as a
394-
ViewSize object with number fields `width` and `height`.
401+
with two boolean arguments: the first specifies whether the sequence has
402+
changed since the last `presketch()`, and the second specifies whether the
403+
size has changed. (Both arguments are true for initialization.) You can obtain
404+
the size of the canvas to be created via the `this.size` property, a ViewSize
405+
object with number fields `width` and `height`.
395406

396407
If you implement `presketch()`, begin by calling
397-
`await super.presketch(size)`, which will initialize the sequence that the
398-
visualizer is viewing. After this call, you have access to the values of the
399-
sequence, so you can do sequence-dependent initialization here. It is OK to
400-
set up internal data variables in this method. For example, this is a good
401-
place to populate an array with time-consuming precomputed values you will use
402-
repeatedly during the sketch. However, in `presketch()` you still have no
403-
access to the p5 canvas or the `this.sketch` object.
408+
`await super.presketch(seqChanged, sizeChanged)`, which will initialize the
409+
sequence that the visualizer is viewing. After this call, you have access to
410+
the values of the sequence, so you can do sequence-dependent initialization
411+
here. It is OK to set up internal data variables in this method. For example,
412+
this is a good place to populate an array with time-consuming precomputed
413+
values you will use repeatedly during the sketch. However, in `presketch()`
414+
you still have no access to the p5 canvas or the `this.sketch` object.
404415

405416
Note also that `presketch()` is called when there is a new visualizer, when
406417
the sequence changes, when the canvas size changes, and when you reload the
@@ -409,6 +420,12 @@ parameters change. So if there is initialization you want to do only on these
409420
more signifcant changes but not on parameter changes, then `presketch()` is a
410421
good method.
411422

423+
Note that since `presketch()` is called asynchronously, you cannot assume it
424+
has completed by the time any other method has been called, e.g. `setup()` or
425+
`draw()`. Therefore, P5Visualizer provides a boolean property
426+
`this.presketchComplete` that you can test to see if the presketch()
427+
initialization is done.
428+
412429
When a visualizer is resized, or the restart button on Numberscope is pressed,
413430
the class function `reset()` is called. By default, a new canvas is created on
414431
a `reset()` (and so `setup()` will be called). In this event, `presketch()`

e2e/tests/featured.spec.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ test.describe('Featured gallery images', () => {
99
const featProps = parseSpecimenQuery(feature.query)
1010
const details = {}
1111
if (
12-
featProps.visualizerKind === 'Histogram'
12+
featProps.visualizerKind === 'FactorHistogram'
1313
|| featProps.visualizerKind === 'Turtle'
1414
|| featProps.visualizerKind === 'Chaos'
1515
) {
1616
details.tag = '@webGL'
1717
}
18-
test(featProps.name, details, async ({page, browserName}) => {
18+
test(featProps.name, details, async ({page}) => {
1919
const short = encodeURIComponent(
2020
featProps.name.replaceAll(' ', '')
2121
)
@@ -27,9 +27,7 @@ test.describe('Featured gallery images', () => {
2727
timeout: featProps.visualizerKind === 'Chaos' ? 60000 : 30000,
2828
})
2929
const matchParams =
30-
browserName === 'firefox' && details.tag === '@webGL'
31-
? {maxDiffPixelRatio: 0.01}
32-
: {}
30+
details.tag === '@webGL' ? {maxDiffPixelRatio: 0.01} : {}
3331
expect(
3432
await page.locator('#canvas-container').screenshot()
3533
).toMatchSnapshot(`${short}.png`, matchParams)

e2e/tests/transversal.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ test.describe('Visualizer-sequence challenges', () => {
6969
for (const viz of vizKeys) {
7070
const vizPar = viz === 'Chaos' ? 'circSize=5' : '' // ow tough to see
7171
const details = {}
72-
if (viz === 'Histogram' || viz === 'Turtle' || viz === 'Chaos') {
72+
if (
73+
viz === 'FactorHistogram'
74+
|| viz === 'Turtle'
75+
|| viz === 'Chaos'
76+
) {
7377
details.tag = '@webGL'
7478
}
7579
for (const seq of vizSeqs[viz]) {
4.15 KB
Loading
16.6 KB
Loading
11.7 KB
Loading
29.4 KB
Loading

src/components/SwitcherModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ click on the trash button on its preview card.
7777
addSequence,
7878
deleteSequence,
7979
} from '@/shared/browserCaching'
80-
import {isMobile} from '@/shared/layoutUtilities'
80+
import {isMobile} from '@/shared/layout'
8181
import {Specimen} from '@/shared/Specimen'
8282
import {
8383
specimenQuery,

src/sequences/Cached.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {Factorization, SequenceInterface} from './SequenceInterface'
22
import simpleFactor from './simpleFactor'
33

4+
import {yieldExecution} from '@/shared/asynchronous'
45
import {math, CachingError} from '@/shared/math'
56
import type {ExtendedBigint} from '@/shared/math'
67
import {Paramable, paramClone} from '@/shared/Paramable'
@@ -151,7 +152,7 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
151152
firstValueCached: ExtendedBigint = math.posInfinity
152153
lastFactorCached = -1024n // dummy value
153154
firstFactorCached: ExtendedBigint = math.posInfinity
154-
valueCachingPromise = Promise.resolve()
155+
valueCachingPromise: Promise<void> | undefined = undefined
155156
factorCachingPromise = Promise.resolve()
156157

157158
/**
@@ -256,6 +257,7 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
256257
async fillValueCache(n: bigint) {
257258
const start = this.lastValueCached + 1n
258259
for (let i = start; i <= n; i++) {
260+
if (i % 10000n === 0n) await yieldExecution()
259261
const key = i.toString()
260262
// trust values we find; hopefully we have cleared when
261263
// needed, so that we can presume they are from some
@@ -269,16 +271,13 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
269271
}
270272

271273
async cacheValues(n: bigint) {
272-
// Let any existing value caching complete
273-
await this.valueCachingPromise
274-
// Let any pending parameter changes complete
275274
if (this.parChangePromise) await this.parChangePromise
275+
if (this.valueCachingPromise) await this.valueCachingPromise
276276
if (n > this.lastValueCached) {
277277
this.valueCachingPromise = this.fillValueCache(n)
278278
await this.valueCachingPromise
279-
} else {
280-
this.valueCachingPromise = Promise.resolve()
281279
}
280+
this.valueCachingPromise = undefined
282281
}
283282

284283
getElement(n: bigint): bigint {
@@ -299,6 +298,8 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
299298
await this.cacheValues(n)
300299
const start = this.lastFactorCached + 1n
301300
for (let i = start; i <= n; ++i) {
301+
// Can we yield execution?
302+
if (i % 10000n === 0n) await yieldExecution()
302303
const key = i.toString()
303304
// trust values we find
304305
if (!(key in this.factorCache)) {
@@ -393,10 +394,13 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
393394
// interval from the _previous_ value of `this.first`
394395
// to `this.lastValueCached` is full.
395396
if (
396-
this.first < this.firstValueCached
397-
|| this.first > this.lastValueCached + 1n
397+
!needsReset
398+
&& (this.first < this.firstValueCached
399+
|| this.first > this.lastValueCached + 1n)
398400
) {
399-
await this.valueCachingPromise
401+
if (this.valueCachingPromise) {
402+
await this.valueCachingPromise
403+
}
400404
this.firstValueCached = math.posInfinity
401405
this.lastValueCached = this.first - 1n
402406
// Get the new cache rolling:
@@ -406,8 +410,9 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
406410
// kick off the factoring process because we don't
407411
// even know if this sequence is being factored.
408412
if (
409-
this.first < this.firstFactorCached
410-
|| this.first > this.lastFactorCached + 1n
413+
!needsReset
414+
&& (this.first < this.firstFactorCached
415+
|| this.first > this.lastFactorCached + 1n)
411416
) {
412417
await this.factorCachingPromise
413418
this.firstFactorCached = math.posInfinity
@@ -441,7 +446,9 @@ export function Cached<PD extends GenericParamDescription>(desc: PD) {
441446
}
442447
if (needsReset) {
443448
this.ready = false
444-
await this.valueCachingPromise
449+
if (this.valueCachingPromise) {
450+
await this.valueCachingPromise
451+
}
445452
await this.factorCachingPromise
446453
this.initialize()
447454
}

src/sequences/OEIS.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {Factorization} from './SequenceInterface'
55
import simpleFactor from './simpleFactor'
66

77
import {alertMessage} from '@/shared/alertMessage'
8-
import {breakableString} from '@/shared/layoutUtilities'
8+
import {breakableString} from '@/shared/layout'
99
import {math} from '@/shared/math'
1010
import type {ExtendedBigint} from '@/shared/math'
1111
import type {GenericParamDescription} from '@/shared/Paramable'

0 commit comments

Comments
 (0)