Skip to content

Instantly share code, notes, and snippets.

@jrr6
Last active May 21, 2021 13:23
Show Gist options
  • Select an option

  • Save jrr6/a4556759fed1eb9e982a3bc3c0fd1713 to your computer and use it in GitHub Desktop.

Select an option

Save jrr6/a4556759fed1eb9e982a3bc3c0fd1713 to your computer and use it in GitHub Desktop.
A userscript that adds animation and custom parameter inputs to shad.io's matrix visualizer
// ==UserScript==
// @name Interactive Matrix Featurifier
// @author aaplmath
// @version 1.0.0
// @description Adds ability to run animation and manually set variables on shad.io 3b1b-style matrix visualizer.
// @match https://shad.io/MatVis/
// @downloadURL https://gist.github.com/jrr6/a4556759fed1eb9e982a3bc3c0fd1713/raw/InteractiveMatrixFeaturifier.js
// @updateURL https://gist.github.com/jrr6/a4556759fed1eb9e982a3bc3c0fd1713/raw/InteractiveMatrixFeaturifier.js
// @grant none
// ==/UserScript==
// ------------- VAR INPUT -------------
function setStateVar (varName, val) {
let panel = document.getElementsByClassName('panel-body')[0]
let key = Object.keys(panel)[0] // name of React internals object—changes on page load
let instance = panel[key]._renderedChildren['.$.0']._instance
instance.updater.enqueueSetState(instance, {[varName]: val})
}
function setPropVar (varName, val) {
let panel = document.getElementsByClassName('panel-body')[0]
let key = Object.keys(panel)[0] // name of React internals object—changes on page load
let instance = panel[key]._renderedChildren['.$.0']._instance
instance.props[varName] = val
instance.updater.enqueueForceUpdate(instance)
}
let inputsDiv = document.createElement('div')
inputsDiv.innerHTML = `
<input type="number" id="a" placeholder="a">
<input type="number" id="b" placeholder="b">
<input type="number" id="x" placeholder="x">
<input type="number" id="c" placeholder="c">
<input type="number" id="d" placeholder="d">
<input type="number" id="y" placeholder="y">
`
inputsDiv.style.display = 'grid'
inputsDiv.style.gridTemplateColumns = 'repeat(3, 1fr)'
inputsDiv.addEventListener('change', (e) => {
let id = e.target.getAttribute('id')
let val = e.target.value
if (val !== '' && val !== undefined && val !== null) {
setStateVar(e.target.getAttribute('id'), parseFloat(e.target.value))
e.target.value = ''
}
}, true)
document.getElementById('mainContainer').insertBefore(inputsDiv, document.getElementsByClassName('panel')[0])
// ------------- ANIMATION -------------
/**
* Runs the simulation automatically.
* @param {boolean} inc whether to increment time (play forward; true) or decrement time (play backward; false).
* @param {number} iterDelta how much to progress time by (where total time = 0–1) on each iteration.
* @param {number} updateFreqMS how frequently to update.
*/
function runSim (inc) {
let iterDelta = parseFloat(document.getElementById('delta').value)
let updateFreqMS = parseFloat(document.getElementById('freq').value)
let slide = document.querySelector('input.form-control')
let val = parseFloat(slide.value)
if ((inc && val < 1) || (!inc && val > 0)) {
let newVal = parseFloat(slide.value) + (inc ? iterDelta : -iterDelta)
// setting it to 1 or 0 means we never actually get to the end…idek this website is weird
if (newVal >= 1) newVal = 0.99999999
if (newVal <= 0) newVal = 1e-10
slide.value = newVal
Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(slide, slide.value)
slide.dispatchEvent(new Event('input', { bubbles: true }))
let obj = window.setTimeout(() => { inc ? runSim(true) : runSim(false) }, updateFreqMS)
ref = {
cancel: () => { clearTimeout(obj) }
}
}
}
function safeCancel () {
if (ref && ref.cancel) ref.cancel()
}
let ref
let slide = document.querySelector('input.form-control')
slide.style.display = 'inline'
slide.style.width = '70%'
// move the t label out of the way
let container = document.querySelector('.col-sm-11')
let label = document.querySelector('label.control-label')
container.insertBefore(label, document.querySelector('.col-sm-11 div:last-child'))
label.style.maxWidth = 'unset'
label.style.position = 'absolute'
let div = slide.parentNode
let buttonDiv = document.createElement('div')
buttonDiv.style.display = 'inline'
buttonDiv.innerHTML = `
<button id="play-reverse">◄</button>
<button id="pause">&#9612;&#9612;</button>
<button id="play-forward">►</button>
`
div.insertBefore(buttonDiv, div.firstChild)
let inputDiv = document.createElement('div')
inputDiv.innerHTML = `
Time 𝚫 per iteration: <input type="number" value="0.0125" id="delta">
Iteration freq (ms): <input type="number" value="25" id="freq">
Scale (temp): <input type="number" placeholder="60" id="scale">
`
div.appendChild(inputDiv)
document.getElementById('play-reverse').addEventListener('click', (e) => { e.preventDefault(); safeCancel(); runSim(false) }, false)
document.getElementById('play-forward').addEventListener('click', (e) => { e.preventDefault(); safeCancel(); runSim(true) }, false)
document.getElementById('pause').addEventListener('click', (e) => { e.preventDefault(); safeCancel() }, false)
// TODO: scale doesn't take upon animating
let scale = document.getElementById('scale')
scale.addEventListener('change', (e) => { e.preventDefault(); setPropVar('scale', parseFloat(scale.value)); scale.value = '' }, false)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment