Published
Edited
Dec 5, 2019
Importers
Insert cell
md`# Step Renderer`
Insert cell
class StepRenderer {
constructor(renderers) {
this.container = DOM.element('div')
this.buttons = DOM.element('div')
this.container.append(this.buttons)
this.nextButton = DOM.element('button')
this.nextButton.innerText = 'Next'
this.nextButton.addEventListener('click', () => this.next())
this.prevButton = DOM.element('button')
this.prevButton.addEventListener('click', () => this.prev())
this.prevButton.innerText = 'Previous'
this.playButton = DOM.element('button')
this.tooltip = DOM.element('div')
this.tooltip.className = 'tooltip'
this.container.append(this.tooltip)
this.buttons.append(this.prevButton)
this.buttons.append(this.nextButton)
this.buttons.append(this.playButton)
this.playButton.addEventListener('click', () => {
if (this.editing) this.endEdit()
if (this.step != this.maxStep) this.togglePlay()
else this.reset()
})
this.playButton.innerText = 'Play'
this.renderers = {}
for (let key in renderers) {
const renderer = renderers[key]
this.renderers[key] = renderer
this.container.append(renderer.container)
if (renderer.tooltip) {
renderer.tooltip.remove()
renderer.tooltip = this.tooltip
}
}
this.cache = []
}
prerender() {
if (this.running) this.togglePlay()
this.step = 0
this.cache = []
this.maxStep = Number.MAX_SAFE_INTEGER
return this.container
}
render(generator) {
this.prerender()
this.iter = generator(this.data)
this.renderStep()
return this.container
}
renderStep() {
if (this.editing) this.endEdit()
if (!this.cache[this.step]) {
const { value, done } = this.iter.next()
if (done) {
this.step--
this.maxStep = this.cache.length - 1
} else this.cache[this.step] = value
if (done && this.running) this.togglePlay()
}
for (let key in this.cache[this.step]) {
const renderer = this.renderers[key]
this.cache[this.step][key](renderer)
}
this.playButton.innerText = this.step == this.maxStep ? 'Restart' : (this.running ? 'Stop' : 'Play')
}
next() {
if (this.step != this.maxStep) {
this.step += 1
this.renderStep()
} else {
this.running = false
}
}
prev() {
if (this.step != 0) {
this.step = Math.max(0, this.step - 1)
this.renderStep()
}
}
reset() {
this.step = 0
this.running = false
this.renderStep()
}
set running(value) {
if (value == false) {
clearInterval(this.interval)
this.playButton.innerText = this.step == this.maxStep ? 'Restart' : 'Play'
} else {
this.playButton.innerText = 'Stop'
}
this.interval = value
}
get running() {
return Boolean(this.interval)
}
togglePlay() {
if (this.running) {
clearInterval(this.running)
this.running = false
} else {
this.running = setInterval(() => {
this.next()
}, 500)
this.next()
}
}

editor(editor) {
this.edit = DOM.element('button')
this.edit.innerText = 'Edit'
this.editing = true
let i = 0
let generator = null
this.edit.addEventListener('click', () => {
if (i == 0) {
this.edit.innerText = 'Done'
editor.edit(this.data, this.renderers)
}
else this.endEdit()
i++
})
this.buttons.append(this.edit)
editor.prerender(this.data, this.renderers)
this.endEdit = () => {
this.edit.disabled = true
this.editing = false
this.data = editor.done(this.renderers)
this.render(generator)
}
return {
render: (gen) => {
generator = gen
return this.prerender()
}
}
}
init(data) {
this.data = data
return this
}
}
Insert cell
viewof sr = new StepRenderer({
log: (() => {
const div = DOM.element('div')
div.innerText = 'test'
return {
container: div,
render(v) {
div.innerText = v
}
}
})()
})
.init(10)
.editor({
prerender() {},
edit(n, { log }) {
log.n = 20
},
done({ log }) {
return log.n || 10
}
})
.render(function*(n) {
console.log(n)
for (let i = 0; i < n; i++) {
console.log('before i')
yield {log: log => log.render(i)}
}
})
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more