diff --git a/examples/article.html b/examples/article.html index aaa2100..45b8ccc 100644 --- a/examples/article.html +++ b/examples/article.html @@ -42,6 +42,11 @@
+ +

We often think of Momentum as a means of dampening oscillations and speeding up the iterations, leading to faster convergence. But it has other interesting behavior. It allows a larger range of step-sizes to be used, and creates its own oscillations. What is going on?

diff --git a/package-lock.json b/package-lock.json index a61c6e0..932eedd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1281,6 +1281,67 @@ "cssom": "0.3.2" } }, + "d3-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz", + "integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw==" + }, + "d3-collection": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.4.tgz", + "integrity": "sha1-NC39EoN8kJdPM/HMCnha6lcNzcI=" + }, + "d3-color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.0.3.tgz", + "integrity": "sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs=" + }, + "d3-dispatch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz", + "integrity": "sha1-RuFJHqqbWMNY/OW+TovtYm54cfg=" + }, + "d3-drag": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.1.tgz", + "integrity": "sha512-Cg8/K2rTtzxzrb0fmnYOUeZHvwa4PHzwXOLZZPwtEs2SKLLKLXeYwZKBB+DlOxUvFmarOnmt//cU4+3US2lyyQ==", + "requires": { + "d3-dispatch": "1.0.3", + "d3-selection": "1.1.0" + } + }, + "d3-format": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.2.0.tgz", + "integrity": "sha1-a0gLqohohdRlHcJIqPSsnaFtsHo=" + }, + "d3-interpolate": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.1.5.tgz", + "integrity": "sha1-aeCZ/zkhRxblY8muw+qdHqS4p58=", + "requires": { + "d3-color": "1.0.3" + } + }, + "d3-scale": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.6.tgz", + "integrity": "sha1-vOGdqA06DPQiyVQ64zIghiILNO0=", + "requires": { + "d3-array": "1.2.1", + "d3-collection": "1.0.4", + "d3-color": "1.0.3", + "d3-format": "1.2.0", + "d3-interpolate": "1.1.5", + "d3-time": "1.0.7", + "d3-time-format": "2.0.5" + } + }, + "d3-selection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.1.0.tgz", + "integrity": "sha1-GZhoSJZIj4OcoDchI9o08dMYgJw=" + }, "d3-time": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.7.tgz", diff --git a/package.json b/package.json index ef9d5b5..2ef483d 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,9 @@ "dependencies": { "@webcomponents/webcomponentsjs": "^1.0.7", "assert": "^1.4.1", + "d3-drag": "^1.2.1", + "d3-scale": "^1.0.6", + "d3-selection": "^1.1.0", "d3-time-format": "^2.0.3", "intersection-observer": "^0.4.0", "commander": "^2.9.0", diff --git a/src/components.js b/src/components.js index 67ec771..32eb8f0 100644 --- a/src/components.js +++ b/src/components.js @@ -21,10 +21,12 @@ import { TOC } from './components/d-toc'; import { Figure } from './components/d-figure'; import { Interstitial } from './components/d-interstitial'; +import { Slider } from './ui/d-slider'; + const components = [ Abstract, Appendix, Article, Bibliography, Byline, Cite, CitationList, Code, Footnote, FootnoteList, FrontMatter, Title, DMath, References, TOC, Figure, - Interstitial, + Slider, Interstitial ]; /* Distill website specific components */ diff --git a/src/ui/d-slider.js b/src/ui/d-slider.js new file mode 100644 index 0000000..be8c0c0 --- /dev/null +++ b/src/ui/d-slider.js @@ -0,0 +1,248 @@ +import { Template } from '../mixins/template'; +import { scaleLinear } from 'd3-scale'; +import { drag } from 'd3-drag'; +import { select } from 'd3-selection'; +import {event as currentEvent } from 'd3-selection'; + +const T = Template('d-slider', ` + + +
+
+
+
+
+
+
+
+
+`); + +// Events +// change, only on commit +// input, onslide + +// Properties +// min +// max +// step [float | "any"] +// + +// ARIA +// The element serving as the focusable slider control has role slider. +// The slider element has the aria-valuenow property set to a decimal value representing the current value of the slider. +// The slider element has the aria-valuemin property set to a decimal value representing the minimum allowed value of the slider. +// The slider element has the aria-valuemax property set to a decimal value representing the maximum al +// If the slider has a visible label, it is referenced by aria-labelledby on the slider element. Otherwise, the slider element has a label provided by aria-label. +// If the slider is vertically oriented, it has aria-orientation set to vertical. The default value of aria-orientation for a slider is horizontal. + +const keyCodes = { + left: 37, + up: 38, + right: 39, + down: 40, + pageUp: 33, + pageDown: 34, + end: 35, + home: 36 +}; + +export class Slider extends T(HTMLElement) { + connectedCallback() { + if (!this.hasAttribute("tabindex")) { + this.setAttribute("tabindex", 0); + } + this.knob = this.root.querySelector(".knob-container"); + this.background = this.root.querySelector(".background"); + this.trackFill = this.root.querySelector(".track-fill"); + this.track = this.root.querySelector(".track"); + this.min = this.hasAttribute("min") ? this.getAttribute("min") : 0; + this.max = this.hasAttribute("max") ? this.getAttribute("max") : 1; + this.value = this.hasAttribute("value") ? this.getAttribute("value") : 0; + this.step = this.hasAttribute("step") ? this.getAttribute("step") : 0.1; + this.scale = scaleLinear().domain([this.min, this.max]).range([0, 1]).clamp(true); + this.drag = drag() + .container(this.track) + .on("start", () => { + this.background.classList.add("mousedown"); + this.dragUpdate(); + }) + .on("drag", () => { + this.dragUpdate(); + }) + .on("end", () => { + this.background.classList.remove("mousedown"); + }); + this.drag(select(this.background)); + this.renderTicks(5); + this.addEventListener("keydown", (e) => { + console.log("keydown", e); + let stopPropagation = false; + switch (event.keyCode) { + case keyCodes.left: + case keyCodes.down: + this.update(this.value - this.step) + stopPropagation = true; + break; + case keyCodes.right: + case keyCodes.up: + this.update(this.value + this.step) + stopPropagation = true; + break; + case keyCodes.pageUp: + this.update(this.value + this.step * 10) + stopPropagation = true; + break; + + case keyCodes.pageDown: + this.update(this.value + this.step * 10) + stopPropagation = true; + break; + case keyCodes.home: + this.update(this.min); + stopPropagation = true; + break; + case keyCodes.end: + this.update(this.max); + stopPropagation = true; + break; + default: + break; + } + if (stopPropagation) { + e.preventDefault(); + e.stopPropagation(); + } + }); + this.addEventListener("focus", () => { + console.log("focus"); + }); + this.addEventListener("blur", () => { + console.log("blur"); + }) + } + + dragUpdate() { + const bbox = this.background.getBoundingClientRect(); + const left = bbox.left; + const x = event.x - left; + const width = bbox.width; + this.update(this.scale.invert(x / width)); + } + + update(value) { + let v = value; + if (this.step !== "any") { + v = Math.round(value / this.step) * this.step; + } + v = Math.max(Math.min(this.max, v), this.min); + this.knob.style.left = this.scale(v) * 100 + "%"; + this.trackFill.style.width = this.scale(v) * 100 + "%"; + if (this.value !== v) { + this.value = v; + this.dispatchInput(); + // TODO change should only update on commit. + this.dispatchChange(); + } + } + + dispatchChange() { + const e = new Event("change"); + this.dispatchEvent(e, {}); + } + + dispatchInput() { + const e = new Event("input"); + this.dispatchEvent(e, {}); + } + + renderTicks(numTicks) { + const ticksContainer = this.root.querySelector(".ticks"); + const tickData = this.scale.ticks(numTicks); + tickData.forEach(d => { + const tick = document.createElement("div"); + tick.classList.add("tick"); + tick.style.left = this.scale(d) * 100 + "%"; + ticksContainer.appendChild(tick) + }); + } +} \ No newline at end of file