Published
Edited
Nov 9, 2020
Insert cell
Insert cell
Insert cell
forceTrace = d3.csvParse(await FileAttachment("senso-record-steps.csv").text())
Insert cell
rawData = forceTrace.map((row) => ({ "millis": row.millis, "force": row.up_f }))
Insert cell
chart(rawData)
Insert cell
Insert cell
chart(forceTrace.map((row) => ({ "millis": row.millis, "force": row.center_f })))
Insert cell
Insert cell
Insert cell
Insert cell
gaussianSig20 = [0.141966, 0.142856, 0.143392, 0.143572, 0.143392, 0.142856, 0.141966]
Insert cell
gaussianSig15 = [ 0.199112, 0.200443, 0.200889, 0.200443, 0.199112]
Insert cell
smoothened = convolve(rawData, gaussianSig20)
Insert cell
chart(smoothened)
Insert cell
Insert cell
Insert cell
gradient = convolve(smoothened, [-1, 0, 1])
Insert cell
chart(gradient)
Insert cell
Insert cell
function zeroOutNonEdges(dataset) {
let processed = []
for (let ix = 0; ix < dataset.length; ix++) {
if (ix === 0 || ix === dataset.length - 1) {
processed[ix] = { millis: dataset[ix].millis, force: 0 }
continue
}
processed[ix] = {
millis: dataset[ix].millis,
force: (dataset[ix - 1].force < dataset[ix].force && dataset[ix + 1].force < dataset[ix].force ? dataset[ix].force : 0)
}
}
return processed
}
Insert cell
chart(zeroOutNonEdges(gradient))
Insert cell
Insert cell
Insert cell
centerMaxima =
zeroOutNonEdges(
convolve(
convolve(forceTrace.map((row) => ({ "millis": row.millis, "force": row.center_f })), gaussianSig20),
[-1, 0, 1]
)
)
Insert cell
chart(centerMaxima)
Insert cell
Insert cell
Insert cell
Insert cell
function zeroOutBaseline(baselineSet, dataset) {
if (baselineSet.length !== dataset.length) throw "Need same-length baseline.";
let baseline = baselineSet.reduce((sum, {force}) => +force + sum, 0)/baselineSet.length

let processed = []
for (let ix = 0; ix < dataset.length; ix++) {
if (ix === 0 || ix === dataset.length - 1) {
processed[ix] = { millis: dataset[ix].millis, force: 0 }
continue
}
processed[ix] = {
millis: dataset[ix].millis,
force: (baselineSet[ix].force > 2 * baseline ? dataset[ix].force : 0)
}
}
return processed
}
Insert cell
Insert cell
chart(
zeroOutBaseline(
forceTrace.map((row) => ({ "millis": row.millis, "force": row.center_f })),
centerMaxima
)
)
Insert cell
Insert cell
chart(
zeroOutBaseline(
rawData,
zeroOutNonEdges(gradient)
)
)
Insert cell
Insert cell
Insert cell
class DirectConvolution {
constructor(size, kernel, borderType = 'CONSTANT') {
this.size = size;
this.kernelOffset = (kernel.length - 1) / 2;
this.outputSize =
borderType === 'CONSTANT' ? size : size - 2 * this.kernelOffset;
this.output = (function createArray(len) {
const array = [];
for (let i = 0; i < len; i++) { array.push(0); }
return array;
})(this.outputSize);
this.kernel = kernel;
this.kernelSize = kernel.length;
this.borderType = borderType;
}

convolve(input) {
if (input.length !== this.size) throw "Unexpected input";
this.output.fill(0);
if (this.borderType === 'CONSTANT') {
this._convolutionBorder0(input);
} else {
this._convolutionBorderCut(input);
}
return this.output;
}

_convolutionBorder0(input) {
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.kernelSize; j++) {
this.output[i] +=
interpolateInput(input, i - this.kernelOffset + j) * this.kernel[j];
}
}
}

_convolutionBorderCut(input) {
for (let i = this.kernelOffset; i < this.size - this.kernelOffset; i++) {
const index = i - this.kernelOffset;
for (let j = 0; j < this.kernelSize; j++) {
this.output[index] += input[index + j] * this.kernel[j];
}
}
}
}
Insert cell
function directConvolution(input, kernel, borderType) {
return new DirectConvolution(input.length, kernel, borderType).convolve(
input,
);
}
Insert cell
function interpolateInput(input, idx) {
if (idx < 0) return 0;
else if (idx >= input.length) return 0;
return input[idx];
}
Insert cell
function convolve(data, kernel, borderType) {
let arr = data.map(({ force }) => force);
let convolved = directConvolution(arr, kernel, borderType);
return data.map(({ millis }, ix) => ({ millis: millis, force: convolved[ix] }));
}
Insert cell
Insert cell
import {vl} from "@vega/vega-lite-api"
Insert cell
function chart(data) {
return vl
.markLine()
.data(data)
.encode(
vl.x().fieldT('millis'),
vl.y().fieldQ('force')
)
.render()
}
Insert cell
d3 = require('d3')
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