Skip to content

Commit

Permalink
Pass a Float32 array back and forth between the main thread and workers
Browse files Browse the repository at this point in the history
  • Loading branch information
mauriciopoppe committed Dec 18, 2023
1 parent 7bcd588 commit f472e9f
Show file tree
Hide file tree
Showing 22 changed files with 345 additions and 239 deletions.
6 changes: 3 additions & 3 deletions site/playground.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
width: window.innerWidth,
height: window.innerHeight,
data: [
{ fn: 'x^2', nSamples: window.innerWidth*5 },
{ fn: 'x^2+1', nSamples: window.innerWidth*5 },
{ fn: 'x^2+2', nSamples: window.innerWidth*5 },
{ fn: 'x^2', nSamples: window.innerWidth*5, sampler: 'asyncInterval' },
{ fn: 'x^2+1', nSamples: window.innerWidth*5, sampler: 'asyncInterval' },
{ fn: 'x^2+2', nSamples: window.innerWidth*5, sampler: 'asyncInterval' },
]
})
</script>
Expand Down
2 changes: 1 addition & 1 deletion src/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import annotations from './helpers/annotations'
import mousetip from './tip'
import helpers from './helpers'
import datumDefaults from './datum-defaults'
import globals from './globals'
import globals from './globals.mjs'

export interface ChartMetaMargin {
left?: number
Expand Down
2 changes: 1 addition & 1 deletion src/evaluate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import globals from './globals'
import globals from './globals.mjs'
import { syncSamplerInterval, asyncSamplerInterval } from './samplers/interval'
import builtIn from './samplers/builtIn'

Expand Down
48 changes: 24 additions & 24 deletions src/globals.ts → src/globals.mjs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { hsl as d3Hsl, HSLColor } from 'd3-color'
import { hsl as d3Hsl } from 'd3-color'

import { GraphTypeBuilder } from './graph-types/types'
import { IntervalWorkerPool } from './samplers/interval_worker_pool'

export type TGlobals = {
COLORS: Array<HSLColor>
DEFAULT_WIDTH: number
DEFAULT_HEIGHT: number
DEFAULT_ITERATIONS: number
MAX_ITERATIONS: number
TIP_X_EPS: number

hiddenWorkerPool?: IntervalWorkerPool
workerPool: IntervalWorkerPool

/**
* graphTypes are the graph types registered in functionPlot,
* to register a new graphType use `registerGraphType`
*/
graphTypes: { [key: string]: GraphTypeBuilder }
}

const Globals: TGlobals = {
// import { GraphTypeBuilder } from './graph-types/types'
// import { IntervalWorkerPool } from './samplers/interval_worker_pool'
//
// export type TGlobals = {
// COLORS: Array<HSLColor>
// DEFAULT_WIDTH: number
// DEFAULT_HEIGHT: number
// DEFAULT_ITERATIONS: number
// MAX_ITERATIONS: number
// TIP_X_EPS: number
//
// hiddenWorkerPool?: IntervalWorkerPool
// workerPool: IntervalWorkerPool
//
// /**
// * graphTypes are the graph types registered in functionPlot,
// * to register a new graphType use `registerGraphType`
// */
// graphTypes: { [key: string]: GraphTypeBuilder }
// }
//
const Globals = {
COLORS: [
'steelblue',
'red',
Expand Down Expand Up @@ -57,7 +57,7 @@ const Globals: TGlobals = {

Globals.MAX_ITERATIONS = Globals.DEFAULT_WIDTH * 10

function registerGraphType(graphType: string, graphTypeBulder: GraphTypeBuilder) {
function registerGraphType(graphType, graphTypeBulder) {
if (Object.hasOwn(Globals.graphTypes, graphType)) {
throw new Error(`registerGraphType: graphType ${graphType} is already registered.`)
}
Expand Down
8 changes: 4 additions & 4 deletions src/graph-types/interval.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { select as d3Select, Selection } from 'd3-selection'

import { asyncIntervalEvaluate, intervalEvaluate } from '../evaluate'
import utils from '../utils'
import { infinity, color } from '../utils.mjs'

import { Chart } from '../index'
import { Interval, FunctionPlotDatum, FunctionPlotScale } from '../types'
Expand Down Expand Up @@ -62,8 +62,8 @@ export function createPathD(
minWidthHeight,
minY,
maxY,
isFinite(yHi) ? yScale(yHi) : -utils.infinity(),
isFinite(yLo) ? yScale(yLo) : utils.infinity()
isFinite(yHi) ? yScale(yHi) : -infinity(),
isFinite(yLo) ? yScale(yLo) : infinity()
)
const vLo = viewportY[0]
const vHi = viewportY[1]
Expand Down Expand Up @@ -101,7 +101,7 @@ export default function interval(chart: Chart) {
const selection = innerSelection
.merge(innerSelectionEnter)
.attr('stroke-width', minWidthHeight)
.attr('stroke', utils.color(d, index) as any)
.attr('stroke', color(d, index) as any)
.attr('opacity', closed ? 0.5 : 1)
.attr('d', function (d: Array<[Interval, Interval]>) {
return createPathD(xScale, yScale, minWidthHeight, d, closed)
Expand Down
14 changes: 7 additions & 7 deletions src/graph-types/polyline.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { select as d3Select, Selection } from 'd3-selection'
import { line as d3Line, area as d3Area, curveLinear as d3CurveLinear } from 'd3-shape'

import utils from '../utils'
import { color, infinity, clamp } from '../utils.mjs'
import { builtInEvaluate } from '../evaluate'

import { Chart } from '../index'
Expand All @@ -13,7 +13,7 @@ export default function polyline(chart: Chart) {
const el = ((plotLine as any).el = d3Select(this))
const index = d.index
const evaluatedData = builtInEvaluate(chart, d)
const color = utils.color(d, index)
const computedColor = color(d, index)

// join
const innerSelection = el.selectAll(':scope > path.line').data(evaluatedData)
Expand All @@ -26,12 +26,12 @@ export default function polyline(chart: Chart) {
yMax += diff * 1e6
yMin -= diff * 1e6
if (d.skipBoundsCheck) {
yMax = utils.infinity()
yMin = -utils.infinity()
yMax = infinity()
yMin = -infinity()
}

function y(d: number[]) {
return utils.clamp(chart.meta.yScale(d[1]), yMin, yMax)
return clamp(chart.meta.yScale(d[1]), yMin, yMax)
}

const line = d3Line()
Expand Down Expand Up @@ -60,15 +60,15 @@ export default function polyline(chart: Chart) {
const path = d3Select(this)
let pathD
if (d.closed) {
path.attr('fill', color)
path.attr('fill', computedColor)
path.attr('fill-opacity', 0.3)
pathD = area
} else {
path.attr('fill', 'none')
pathD = line
}
path
.attr('stroke', color)
.attr('stroke', computedColor)
.attr('marker-end', function () {
// special marker for vectors
return d.fnType === 'vector' ? 'url(#' + chart.markerId + ')' : null
Expand Down
8 changes: 4 additions & 4 deletions src/graph-types/scatter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { select as d3Select, Selection } from 'd3-selection'
import { hsl as d3Hsl } from 'd3-color'

import utils from '../utils'
import { color } from '../utils.mjs'
import { builtInEvaluate } from '../evaluate'

import { Chart } from '../index'
Expand All @@ -15,7 +15,7 @@ export default function Scatter(chart: Chart) {
selection.each(function (d) {
let i, j
const index = d.index
const color = utils.color(d, index)
const computedColor = color(d, index)
const evaluatedData = builtInEvaluate(chart, d)

// scatter doesn't need groups, therefore each group is
Expand All @@ -34,8 +34,8 @@ export default function Scatter(chart: Chart) {

const selection = innerSelection
.merge(innerSelectionEnter)
.attr('fill', d3Hsl(color.toString()).brighter(1.5).formatHex())
.attr('stroke', color)
.attr('fill', d3Hsl(computedColor.toString()).brighter(1.5).formatHex())
.attr('stroke', computedColor)
.attr('opacity', 0.7)
.attr('r', 1)
.attr('cx', function (d) {
Expand Down
6 changes: 3 additions & 3 deletions src/graph-types/text.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { select as d3Select, Selection } from 'd3-selection'
import { hsl as d3Hsl } from 'd3-color'

import utils from '../utils'
import { color } from '../utils.mjs'

import { Chart } from '../index'
import { FunctionPlotDatum } from '../types'
Expand All @@ -19,12 +19,12 @@ export default function Text(chart: Chart) {
const innerSelection = d3Select(this).selectAll(':scope > text.fn-text').data([d.location])
const innerSelectionEnter = innerSelection.enter().append('text').attr('class', `fn-text fn-text-${d.index}`)

const color = utils.color(d, d.index)
const computeColor = color(d, d.index)

// enter + update
const selection = innerSelection
.merge(innerSelectionEnter)
.attr('fill', d3Hsl(color.toString()).brighter(1.5).formatHex())
.attr('fill', d3Hsl(computeColor.toString()).brighter(1.5).formatHex())
.attr('x', (d) => xScale(d[0]))
.attr('y', (d) => yScale(d[1]))
.text(() => d.text)
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/derivative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { select as d3Select, Selection } from 'd3-selection'
import { polyline } from '../graph-types/'
import { builtIn as builtInEvaluator } from './eval.mjs'
import datumDefaults from '../datum-defaults'
import utils from '../utils'
import { infinity } from '../utils.mjs'

import { Chart } from '../index'
import { FunctionPlotDatum } from '../types'
Expand All @@ -21,7 +21,7 @@ export default function derivative(chart: Chart) {
if (!d.derivative) {
return []
}
const x0 = typeof d.derivative.x0 === 'number' ? d.derivative.x0 : utils.infinity()
const x0 = typeof d.derivative.x0 === 'number' ? d.derivative.x0 : infinity()
derivativeDatum.index = d.index
derivativeDatum.scope = {
m: builtInEvaluator(d.derivative, 'fn', { x: x0 }),
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/secant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { select as d3Select, Selection } from 'd3-selection'
import { builtIn as builtInEvaluator } from './eval.mjs'
import datumDefaults from '../datum-defaults'
import { polyline } from '../graph-types/'
import utils from '../utils'
import { infinity } from '../utils.mjs'

import { Chart } from '../index'
import { FunctionPlotDatumScope, FunctionPlotDatum, FunctionPlotDatumSecant } from '../types'
Expand All @@ -28,7 +28,7 @@ export default function secant(chart: Chart) {
secant.scope = secant.scope || {}

const x0 = secant.x0
const x1 = typeof secant.x1 === 'number' ? secant.x1 : utils.infinity()
const x1 = typeof secant.x1 === 'number' ? secant.x1 : infinity()
Object.assign(secant.scope, {
x0,
x1,
Expand Down
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { IntervalWorkerPool } from './samplers/interval_worker_pool'
import { FunctionPlotOptions } from './types'
import { Chart, ChartMeta, ChartMetaMargin } from './chart'

import globals, { registerGraphType } from './globals'
import globals, { registerGraphType } from './globals.mjs'
import { polyline, interval, scatter, text } from './graph-types'
import { interval as intervalEval, builtIn as builtInEval } from './helpers/eval.mjs'

Expand Down Expand Up @@ -52,7 +52,6 @@ export * from './types'
export { Chart, ChartMeta, ChartMetaMargin }
export { registerGraphType, withWebWorkers }
export { builtIn as EvalBuiltIn, interval as EvalInterval } from './helpers/eval.mjs'
export { TGlobals } from './globals'
export {
interval as GraphTypeInterval,
polyline as GraphTypePolyline,
Expand Down
2 changes: 1 addition & 1 deletion src/perf/interval-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { Bench } from 'tinybench'
import { scaleLinear } from 'd3-scale'

import globals from '../globals'
import globals from '../globals.mjs'
import { IntervalWorkerPool } from '../samplers/interval_worker_pool'
import { FunctionPlotDatum, FunctionPlotOptionsAxis } from '../types'
import { createPathD } from '../graph-types/interval'
Expand Down
34 changes: 17 additions & 17 deletions src/samplers/builtIn.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import utils from '../utils'
import { linspace, sgn, infinity, clamp, space, isValidNumber } from '../utils.mjs'
import { builtIn as evaluate } from '../helpers/eval.mjs'

import { FunctionPlotDatum, FunctionPlotScale } from '../types'
Expand Down Expand Up @@ -27,15 +27,15 @@ function checkAsymptote(
const n = 10
const x0 = d0[0]
const x1 = d1[0]
const samples = utils.linspace(x0, x1, n)
const samples = linspace(x0, x1, n)
let oldY: number, oldX: number
for (let i = 0; i < n; i += 1) {
const x = samples[i]
const y = evaluate(d, 'fn', { x })

if (oldY) {
const deltaY = y - oldY
const newSign = utils.sgn(deltaY)
const newSign = sgn(deltaY)
if (newSign === sign) {
return checkAsymptote([oldX, oldY], [x, y], d, sign, level - 1)
}
Expand All @@ -53,23 +53,23 @@ function checkAsymptote(
function split(d: FunctionPlotDatum, data: SamplerResultGroup, yScale: FunctionPlotScale): SamplerResult {
let oldSign: number
const samplerResult: SamplerResult = []
const yMin = yScale.domain()[0] - utils.infinity()
const yMax = yScale.domain()[1] + utils.infinity()
const yMin = yScale.domain()[0] - infinity()
const yMax = yScale.domain()[1] + infinity()

let samplerGroup: SamplerResultGroup = [data[0]]

let i = 1
let deltaX = utils.infinity()
let deltaX = infinity()
while (i < data.length) {
const yOld = data[i - 1][1]
const yNew = data[i][1]
const deltaY = yNew - yOld
const newSign = utils.sgn(deltaY)
const newSign = sgn(deltaY)
// make a new set if:
if (
// we have at least 2 entries (so that we can compute deltaY)
samplerGroup.length >= 2 &&
// utils.sgn(y1) * utils.sgn(y0) < 0 && // there's a change in the evaluated values sign
// sgn(y1) * sgn(y0) < 0 && // there's a change in the evaluated values sign
// there's a change in the slope sign
oldSign !== newSign &&
// the slope is bigger to some value (according to the current zoom scale)
Expand All @@ -81,12 +81,12 @@ function split(d: FunctionPlotDatum, data: SamplerResultGroup, yScale: FunctionP
// data[i-1] has an updated [x,y], it was already added to a group (in a previous iteration)
// we just need to update the yCoordinate
data[i - 1][0] = check.d0[0]
data[i - 1][1] = utils.clamp(check.d0[1], yMin, yMax)
data[i - 1][1] = clamp(check.d0[1], yMin, yMax)
samplerResult.push(samplerGroup)

// data[i] has an updated [x,y], create a new group with it.
data[i][0] = check.d1[0]
data[i][1] = utils.clamp(check.d1[1], yMin, yMax)
data[i][1] = clamp(check.d1[1], yMin, yMax)
samplerGroup = [data[i]]
} else {
// false alarm, it's not an asymptote
Expand All @@ -112,17 +112,17 @@ function split(d: FunctionPlotDatum, data: SamplerResultGroup, yScale: FunctionP
}

function linear(samplerParams: SamplerParams): SamplerResult {
const allX = utils.space(samplerParams.xAxis, samplerParams.range, samplerParams.nSamples)
const allX = space(samplerParams.xAxis, samplerParams.range, samplerParams.nSamples)
const yDomain = samplerParams.yScale.domain()
// const yDomainMargin = yDomain[1] - yDomain[0]
const yMin = yDomain[0] - utils.infinity()
const yMax = yDomain[1] + utils.infinity()
const yMin = yDomain[0] - infinity()
const yMax = yDomain[1] + infinity()
const data: Array<[number, number]> = []
for (let i = 0; i < allX.length; i += 1) {
const x = allX[i]
let y = evaluate(samplerParams.d, 'fn', { x })
if (utils.isValidNumber(x) && utils.isValidNumber(y)) {
y = utils.clamp(y, yMin, yMax)
if (isValidNumber(x) && isValidNumber(y)) {
y = clamp(y, yMin, yMax)
data.push([x, y])
}
}
Expand All @@ -134,7 +134,7 @@ function parametric(samplerParams: SamplerParams): SamplerResult {
// range is mapped to canvas coordinates from the input
// for parametric plots the range will tell the start/end points of the `t` param
const parametricRange = samplerParams.d.range || [0, 2 * Math.PI]
const tCoords = utils.space(samplerParams.xAxis, parametricRange, samplerParams.nSamples)
const tCoords = space(samplerParams.xAxis, parametricRange, samplerParams.nSamples)
const samples: SamplerResultGroup = []
for (let i = 0; i < tCoords.length; i += 1) {
const t = tCoords[i]
Expand All @@ -149,7 +149,7 @@ function polar(samplerParams: SamplerParams): SamplerResult {
// range is mapped to canvas coordinates from the input
// for polar plots the range will tell the start/end points of the `theta` param
const polarRange = samplerParams.d.range || [-Math.PI, Math.PI]
const thetaSamples = utils.space(samplerParams.xAxis, polarRange, samplerParams.nSamples)
const thetaSamples = space(samplerParams.xAxis, polarRange, samplerParams.nSamples)
const samples: SamplerResultGroup = []
for (let i = 0; i < thetaSamples.length; i += 1) {
const theta = thetaSamples[i]
Expand Down
Loading

0 comments on commit f472e9f

Please sign in to comment.