Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Formula grid #542

Draft
wants to merge 14 commits into
base: ui2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/FormulaGrid/Hexagons.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/FormulaGrid/Triangles.png
76 changes: 54 additions & 22 deletions src/components/ParamField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
:class="!status.isValid() ? 'error-field' : ''"
:value="value"
:placeholder="placehold(param)"
@keyup.enter="growArea($event)"
@input="updateString($event)" />
<input
v-else
Expand All @@ -52,10 +51,15 @@
<div
v-if="param.hideDescription && param.description"
class="desc-tooltip">
<span class="material-icons-sharp">help</span>
<div class="desc-tooltip-text shadowed">
{{ param.description }}
</div>
<span
class="material-icons-sharp"
@mouseenter="popHelp"
@mouseleave="hideHelp">help</span>
<Teleport to="body">
<div ref="helpText" class="desc-tooltip-text shadowed">
{{ param.description }}
</div>
</Teleport>
</div>
</div>
<p
Expand All @@ -76,7 +80,7 @@
</template>

<script setup lang="ts">
import {ref, watch} from 'vue'
import {ref, onMounted, useTemplateRef, watch} from 'vue'
import PickColors from 'vue-pick-colors'

import type {ParamInterface} from '../shared/Paramable'
Expand All @@ -91,6 +95,33 @@
status: ValidationStatus
}>()

const helpPopup = useTemplateRef('helpText')
function popHelp(e: Event) {
const help = helpPopup?.value
if (!help) return
help.style.opacity = '1'
help.style.visibility = 'visible'
if (e.target instanceof HTMLSpanElement) {
const rect = e.target.getBoundingClientRect()
const popHeight = help.offsetHeight
if (rect.top > popHeight) {
help.style.top = rect.top - popHeight - 4 + 'px'
help.style.right = '8px'
} else if (rect.bottom + popHeight + 4 < window.innerHeight) {
help.style.top = rect.bottom + 4 + 'px'
help.style.right = '8px'
} else {
help.style.top = '0px'
help.style.right = '42px'
}
}
}
function hideHelp() {
if (!helpPopup?.value) return
helpPopup.value.style.opacity = '0'
helpPopup.value.style.visibility = 'hidden'
}

const emit = defineEmits(['updateParam'])
const isColorful =
props.param.type === ParamType.COLOR
Expand All @@ -112,14 +143,20 @@
const field = e.target
if (field instanceof HTMLElement) field.blur()
}
function growArea(e: Event) {
const area = e.target
if (area instanceof HTMLTextAreaElement) {
const curheight = area.getBoundingClientRect().height
area.style.height = `${curheight + 14}px`

function growArea(area: HTMLTextAreaElement) {
if (area.scrollHeight > area.offsetHeight) {
area.style.height = `${area.scrollHeight + 3}px`
}
}

onMounted(() => {
if (props.param.type === ParamType.FORMULA) {
const field = document.getElementById(props.paramName)
if (field instanceof HTMLTextAreaElement) growArea(field)
}
})

function updateBoolean(e: Event) {
blurField(e)
const inp = e.target as HTMLInputElement
Expand All @@ -134,6 +171,7 @@
|| t instanceof HTMLTextAreaElement
) {
emit('updateParam', t.value)
if (t instanceof HTMLTextAreaElement) growArea(t)
}
}

Expand Down Expand Up @@ -327,11 +365,10 @@
}

right: 4px;
bottom: 0px;
top: 4px;
}

.desc-tooltip .desc-tooltip-text {
visibility: hidden;
.desc-tooltip-text {
width: 240px;
background-color: var(--ns-color-white);
color: var(--ns-color-black);
Expand All @@ -340,20 +377,15 @@
padding: 8px;
font-size: 12px;

position: absolute;
z-index: 1;
bottom: 125%;
position: fixed;
z-index: 120;
right: 0;
margin-left: -120px;

opacity: 0;
visibility: hidden;
transition:
opacity 0.2s,
visibility 0.2s;
}

.desc-tooltip:hover .desc-tooltip-text {
visibility: visible;
opacity: 1;
}
</style>
39 changes: 31 additions & 8 deletions src/shared/Chroma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ the [chroma-js api](https://www.vis4.net/chromajs/). Additional functions
and facilities for manipulating Chroma colors are documented below.

All of the chroma-js api and operations documented here are also available
in [mathjs formulas](math.md). In addition, all of the named colors (like
`red` or `chartreuse`) are available as pre-defined constant symbols, as
are the color brewer palettes, like `RdBu` or `Set1`. Note the palettes are
arrays of colors, so to get a specific color from them in a formula you
need to index them with a 1-based index, e.g., `Set1[5]`.
in [mathjs formulas](math.md). For example, you can darken a color `x` an
amount controlled by a number `x` by writing `c.darken(x)`, or desaturate it
by writing `c.desaturate(x)`, etc.

In addition, all of the named colors (like
`red` or `chartreuse`, including all CSS (Cascading Style Sheets) named colors)
are available as pre-defined constant symbols, as are the color brewer
palettes, like `RdBu` or `Set1`. Note the palettes are arrays of colors,
so to get a specific color from them in a formula you need to index them
with a 1-based index, e.g., `Set1[5]`.
**/
import type {Color as Chroma} from 'chroma-js'
import chromaRaw from 'chroma-js'
Expand Down Expand Up @@ -59,11 +64,29 @@ export const chroma = function (...args: unknown[]) {
if (arg instanceof Array && arg.length === 4) {
return chromaRaw(...(arg as Quad), 'gl')
}
if (typeof arg === 'boolean') {
return arg ? chromaRaw('white') : chromaRaw('black').alpha(0)
}
if (typeof arg === 'number') {
if (arg <= 1.0) {
return chromaRaw(arg, arg, arg, 1, 'gl')
// Can't think of any natural meaning for negative numbers,
// so just ignore sign
const n: number = Math.abs(arg)
if (n <= 1.0) {
return chromaRaw(n, n, n, 1, 'gl')
}
if (n > 0xffffff) {
// largest number chroma interprets
if (n < 0xffffffff) {
// interpret last two digits as alpha
const alpha = n % 0x100
const rest = Math.floor(n / 0x100)
return chromaRaw(rest).alpha(alpha / 0xff)
} else {
// what color should huge numbers be?
return chromaRaw('white')
}
}
return chromaRaw(arg)
return chromaRaw(n)
}
}
if (
Expand Down
12 changes: 8 additions & 4 deletions src/shared/ParamType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ function validateExtInt(value: string, status: ValidationStatus) {

//Helper function for color types:
function isColor(value: string) {
return value.trim().match(/^(#[0-9A-Fa-f]{3})|(#[0-9A-Fa-f]{6})$/)
return value
.trim()
.match(/^#?([0-9A-Fa-f]{3,4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/)
}

export const typeFunctions: {
Expand Down Expand Up @@ -228,15 +230,17 @@ export const typeFunctions: {
)
return
}
const freeVars = fmla.freevars.difference(new Set(inputSymbols))
const knownSymbols = new Set(inputSymbols)
const freeVars = fmla.freevars.difference(knownSymbols)
status.forbid(
freeVars.size,
`free variables limited to ${inputSymbols}; `
+ `please remove '${Array.from(freeVars).join(', ')}'`
)
const freeFuncs = fmla.freefuncs.difference(knownSymbols)
status.forbid(
fmla.freefuncs.size,
`unknown functions '${fmla.freefuncs}'`
freeFuncs.size,
`unknown functions '${Array.from(freeFuncs).join(', ')}'`
)
},
realize: function (value) {
Expand Down
2 changes: 1 addition & 1 deletion src/shared/Paramable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ export class Paramable implements ParamableInterface {
const param = this.params[key]
if (
param.type === ParamType.COLOR
&& value.match(/^[0-9a-fA-F]{6}$/)
&& value.match(/^[0-9a-fA-F]{3,8}$/)
)
this.tentativeValues[key] = '#' + value
else this.tentativeValues[key] = value
Expand Down
56 changes: 56 additions & 0 deletions src/shared/defineFeatured.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,62 @@ const featuredSIMs = [
'domain=0+1&angles=15+-165&steps=2+1'
+ '&speed=10&bgColor=e0def7&strokeColor=%231E90FF'
),
specimenQuery(
"Pascal's Triangle",
'FormulaGrid',
'OEIS A007318',
'&aspect=r&fillOrder=2&pathFormula=%5Bceil%28c%2F2%29+-+invTriangular'
+ '%28k-1%29+%2B+2*%28k+-+triangular%28invTriangular%28k-1%29%29+'
+ '-+1%29%2CinvTriangular%28k-1%29+%2B+1%5D'
+ '&backgroundColor=21218FFF'
+ '&fillFormula=%7Bhexagon%3A+rainbow%2872a-100%29.desaturate'
+ '%281.2%29.brighten%280.8%29%7D',
''
),
specimenQuery(
'Lattice Murmuration',
'FormulaGrid',
'OEIS A293773',
'&dimensions=200+5000&fillOrder=3&pathFormula=%5Bk%2C+floor'
+ '%28r%2F2%29+-+a%5D&backgroundColor=D2E1FBFF'
+ '&fillFormula=%7B+circle%3A+%27black%27%7D'
+ '&inset=12',
''
),
specimenQuery(
'Modular Multiplication Table',
'FormulaGrid',
'Formula',
'&dimensions=200+200&aspect=1&backgroundColor=0B207CFF'
+ '&speed=100&fillFormula=%7B+square%3A+chroma%28%27%233bbf7d%27%29'
+ '.alpha%28abs%28%28x*y%29+%25+200-100%29%2F100%29%2C%0Amouseover'
+ '%3A+%5B+x%2C+%27x%27%2C+y%2C+%27%3D%27%2C+mod%28x*y%2Cc%29%5D'
+ '+%7D&inset=1',
''
),
specimenQuery(
'Ulam Divisors',
'FormulaGrid',
'OEIS A000005',
'&dimensions=200+200&aspect=1&fillOrder=1&pathFormula=spiral%28k%29'
+ '&backgroundColor=000000FF&speed=100&fillFormula=%7B+circle%3A'
+ '+chroma.mix%28%27%230571b0%27%2C+%27darkseagreen%27%0A%2C0.125'
+ '*%28a-10%29%29%2C+mouseover%3A+%5B%0A++++%27d%28%27%2C+n%2C+'
+ '%27%29+%3D+%27%2C+a%0A%5D%7D&inset=0.8',
''
),
specimenQuery(
'Integerstellar',
'FormulaGrid',
'Formula',
'&dimensions=200&backgroundColor=000000FF&speed=1024'
+ '&fillFormula=%7Bcircle%3A+chroma.mix%28%27yellow'
+ '%27%2C%27black%27%2C1-gcd%28x%2Cy%29%2Fsqrt%28x'
+ '*y%29%29%2C%0A+mouseover%3A+%5B%0A++++%27gcd'
+ '%28%27%2C+x%2C+%27%2C+%27%2C+y%2C+%27%29+%3D'
+ '+%27%2C+gcd%28x%2Cy%29%0A%5D%7D&inset=0.8',
''
),
]

// Is there any reason for us to associate dates with featured specimens? Do
Expand Down
16 changes: 13 additions & 3 deletions src/shared/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export type TposInfinity = 1e999 // since that's above range for number,
export type TnegInfinity = -1e999 // similarly
export type ExtendedBigint = bigint | TposInfinity | TnegInfinity

type ExtendedMathJs = Omit<MathJsInstance, 'hasNumericValue'> & {
type ExtendedMathJs = Omit<MathJsInstance, 'hasNumericValue' | 'add'> & {
negInfinity: TnegInfinity
posInfinity: TposInfinity
safeNumber(n: MathTypeTemp): number
Expand All @@ -134,10 +134,15 @@ type ExtendedMathJs = Omit<MathJsInstance, 'hasNumericValue'> & {
bigabs(a: Integer): bigint
bigmax(...args: Integer[]): ExtendedBigint
bigmin(...args: Integer[]): ExtendedBigint
triangular(n: number): number
invTriangular(t: number): number
chroma: typeof chroma
rainbow(a: Integer): Chroma
isChroma(a: unknown): a is Chroma
add: MathJsInstance['add'] & ((c: Chroma, d: Chroma) => Chroma)
add: ((c: Chroma, d: Chroma) => Chroma) &
((v: number[], a: number) => number[]) &
((v: number[], w: number[]) => number[]) &
((a: MathType, b: MathType) => MathType)
hasNumericValue(x: unknown): x is MathScalarType
multiply: MathJsInstance['multiply'] &
((s: number, c: Chroma) => Chroma) &
Expand All @@ -154,6 +159,11 @@ math.typed.addType({
test: isChroma,
})

const numberTheory: Record<string, unknown> = {
triangular: (n: number) => (n * (n + 1)) / 2,
invTriangular: (t: number) => Math.floor((Math.sqrt(1 + 8 * t) - 1) / 2),
}

const colorStuff: Record<string, unknown> = {
chroma,
rainbow: (h: MathScalarType | bigint) => {
Expand Down Expand Up @@ -182,7 +192,7 @@ for (palette in chroma.brewer) {
colorStuff[palette] = factory(palette, [], () => clrs)
}

math.import(colorStuff)
math.import({...numberTheory, ...colorStuff})

math.negInfinity = -Infinity as TnegInfinity
math.posInfinity = Infinity as TposInfinity
Expand Down
Loading
Loading