@arijs/transfer-speed-graph
v0.1.3
Published
A lightweight, modular transfer speed graph inspired by the Windows 8 file transfer dialog, with support for: Real transfer integration (download/upload), Deterministic and random fake transfer simulation, Pause/cancel/finish states, and Highly configurab
Downloads
722
Readme
Transfer Speed Graph

Transfer Speed Graph homepage / NPM Package
A lightweight, modular transfer-speed graph inspired by the Windows 8 file transfer dialog, with support for:
- Real transfer integration (download/upload)
- Deterministic and random fake transfer simulation
- Pause/cancel/finish states
- Highly configurable graph smoothing and dynamic Y-scale behavior
Real Example

Quick Usage
Install
npm install @arijs/transfer-speed-graphThen use the object:
import { TransferGraph } from '@arijs/transfer-speed-graph'
const graph = new TransferGraph()CDN
If you prefer not to use a bundler, you can use either ES module imports or the UMD bundle.
ES module import via jsDelivr:
import { TransferGraph } from 'https://cdn.jsdelivr.net/npm/@arijs/transfer-speed-graph@latest/+esm'
const graph = new TransferGraph()ES module import via unpkg:
import { TransferGraph } from 'https://unpkg.com/@arijs/transfer-speed-graph@latest?module'
const graph = new TransferGraph()UMD script via jsDelivr:
<script src="https://cdn.jsdelivr.net/npm/@arijs/transfer-speed-graph@latest/dist/arijs-transfer-speed-graph.umd.js"></script>UMD script via unpkg:
<script src="https://unpkg.com/@arijs/transfer-speed-graph@latest/dist/arijs-transfer-speed-graph.umd.js"></script>HTML Script Tag
After loading one of the CDN scripts above, you can initialize the graph with a minimal HTML snippet:
<canvas id="graph" width="416" height="72"></canvas>
<script>
const graph = new TransferSpeedGraph.TransferGraph()
const canvasCtx = document.getElementById('graph').getContext('2d')
graph.setRendererOptions({
canvasCtx,
canvasWidth: 416,
canvasHeight: 72,
})
graph.startTransfer({ totalSize: 12 * 1024 * 1024 })
</script>Download Example
import { TransferGraph } from '@arijs/transfer-speed-graph'
const controller = new TransferGraph()
controller.setOnFrame((view) => {
// update your UI with view.progressInt, view.speedBps, etc.
})
const downloadUrl = 'https://httpbin.org/bytes/12582912?seed=2026'
const fallbackTotalSize = 12 * 1024 * 1024
controller.setRendererOptions({
canvasCtx,
canvasWidth: 416,
canvasHeight: 72,
})
fetch(downloadUrl).then(async (res) => {
if (!res.ok || !res.body) throw new Error('Download failed: ' + res.status)
const totalHeader = parseInt(res.headers.get('content-length') || '0', 10)
const totalSize = Number.isFinite(totalHeader) && totalHeader > 0
? totalHeader
: fallbackTotalSize
const reader = res.body.getReader()
let transferredBytes = 0
controller.startTransfer({ totalSize })
while (true) {
const chunk = await reader.read()
if (chunk.done) break
transferredBytes += chunk.value.byteLength
controller.pushProgress({ transferredBytes, totalSize })
}
controller.finishTransfer({ transferredBytes, totalSize })
}).catch((err) => {
console.warn(err)
controller.cancel()
})Source example: real/download-example.js
Upload Example
import { TransferGraph } from '@arijs/transfer-speed-graph'
const controller = new TransferGraph()
const uploadUrl = 'https://httpbin.org/post'
const uploadSize = 10 * 1024 * 1024
const xhr = new XMLHttpRequest()
const payload = new Blob([new Uint8Array(uploadSize)])
controller.setRendererOptions({
canvasCtx,
canvasWidth: 416,
canvasHeight: 72,
})
controller.startTransfer({ totalSize: uploadSize })
xhr.upload.addEventListener('progress', (ev) => {
if (!ev.lengthComputable) return
controller.pushProgress({
transferredBytes: ev.loaded,
totalSize: ev.total,
})
})
xhr.addEventListener('load', () => {
controller.finishTransfer({
transferredBytes: uploadSize,
totalSize: uploadSize,
})
})
xhr.addEventListener('error', () => {
console.warn('upload failed')
controller.cancel()
})
xhr.addEventListener('abort', () => {
controller.cancel()
})
xhr.open('POST', uploadUrl)
xhr.send(payload)Source example: real/upload-example.js
State Showcase
Paused state:

Cancelled state:

Why This Library Is Strong
Source Files
The published package is split by concern:
- Graph core:
- core/main.js
public
TransferGraphfacade that coordinates model, renderer, and runtime APIs. - core/model.js transfer state machine plus series, timing, and control state management.
- core/renderer.js renderer configuration layer that resolves options and delegates frame drawing.
- core/frame.js canvas frame rendering, graph geometry, and draw-hook execution.
- core/speed-series.js series utilities for averaging, speed calculations, and coordinate transforms.
- core/main.js
public
- Fake transfer subsystem:
- fake/progress-source.js controller bridge that drives fake progress updates into the graph.
- fake/simulation.js deterministic/random simulation engine for timing and size progression.
- fake/series.js fake-series helpers and presets used by the simulator.
- Real transfer examples:
- real/download-example.js streaming download example wired to the graph controller.
- real/upload-example.js upload progress example wired to the graph controller.
That separation keeps rendering, state management, fake generation, and transport examples independent and easy to evolve.
High Configurability
Graph behavior can be tuned at runtime with controls such as:
pixelAverageWindowmaxSpeedDecaymaxSpeedHeadroomignoreTrailingSpeedSample
Fake simulation is configurable through simulationOptions, including:
minFrame,maxFrameminFrameRepeat,maxFrameRepeatminSizeInc,maxSizeIncdeterministicSeed
This allows both realistic noisy profiles and stable deterministic test profiles.
Practical Runtime API
TransferGraph gives a straightforward app-facing API:
startTransferStart a new transfer and reset the runtime state.
Signature:
startTransfer(config)config.totalSize: required positive total size for the transfer.config.nowMs: optional timestamp used as the transfer start time.
Example:
const graph = new TransferGraph() graph.startTransfer({ totalSize: 12 * 1024 * 1024 })pushProgressAppend a new progress update to the active transfer.
Signature:
pushProgress(update)update.transferredBytes: current transferred byte count.update.totalSize: optional total size override.update.elapsedMs: optional elapsed time override.update.nowMs: optional timestamp used to resolve elapsed time.
Example:
const graph = new TransferGraph() graph.pushProgress({ transferredBytes: 4 * 1024 * 1024 })finishTransferMark the transfer as finished and capture the final point.
Signature:
finishTransfer(update)update.transferredBytes: final transferred byte count.update.totalSize: optional total size override.update.elapsedMs: optional elapsed time override.update.nowMs: optional timestamp used to resolve elapsed time.
Example:
const graph = new TransferGraph() graph.finishTransfer({ transferredBytes: 12 * 1024 * 1024 })cancelCancel the active transfer and freeze it in a finished state.
Signature:
cancel()Example:
const graph = new TransferGraph() graph.cancel()resetReset the model back to its initial state.
Signature:
reset()Example:
const graph = new TransferGraph() graph.reset()pausePause elapsed-time tracking without clearing transfer progress.
Signature:
pause(nowMs)nowMs: optional timestamp used to freeze elapsed-time calculations.
Example:
const graph = new TransferGraph() graph.pause()resumeResume elapsed-time tracking after a pause.
Signature:
resume(nowMs)nowMs: optional timestamp used to resume elapsed-time calculations.
Example:
const graph = new TransferGraph() graph.resume()toggleFinishedPauseVisualToggle the paused visual state that is shown after a transfer finishes.
Signature:
toggleFinishedPauseVisual()Example:
const graph = new TransferGraph() graph.toggleFinishedPauseVisual()refreshGraphScaleForce the renderer to recalculate the speed scale on the next frame.
Signature:
refreshGraphScale()Example:
const graph = new TransferGraph() graph.refreshGraphScale()setPixelAverageWindowChange the rolling average window used by the renderer.
Signature:
setPixelAverageWindow(nextWindow)nextWindow: smoothing window size in rendered points.
Example:
const graph = new TransferGraph() graph.setPixelAverageWindow(16)setMaxSpeedDecayChange the decay factor used for the dynamic speed scale.
Signature:
setMaxSpeedDecay(nextValue)nextValue: new decay value, typically between0.5and0.999.
Example:
const graph = new TransferGraph() graph.setMaxSpeedDecay(0.95)setMaxSpeedHeadroomChange the headroom factor used for the dynamic speed scale.
Signature:
setMaxSpeedHeadroom(nextValue)nextValue: new headroom value, typically between1and2.
Example:
const graph = new TransferGraph() graph.setMaxSpeedHeadroom(1.08)setOnFrameRegister a callback that receives the computed frame view model.
Signature:
setOnFrame(onFrame)onFrame(view): receives the computed frame view model.
Example:
const graph = new TransferGraph() graph.setOnFrame((view) => { console.log(view.progressInt) })setOnControlsRegister a callback that receives the current controls state.
Signature:
setOnControls(onControls)onControls(view): receives the current controls state.
Example:
const graph = new TransferGraph() graph.setOnControls((controls) => { console.log(controls.pixelAverageWindow) })setOnStateChangeRegister a callback that receives the raw state snapshot.
Signature:
setOnStateChange(onStateChange)onStateChange(state): receives the raw state snapshot.
Example:
const graph = new TransferGraph() graph.setOnStateChange((state) => { console.log(state.started, state.finished) })
Canvas Drawing Hooks
TransferGraphRenderer accepts optional draw hooks that let you customize the canvas output without replacing the whole renderer. If a hook is omitted, the default implementation reproduces the current visuals.
Provide them through new TransferGraph({ ... }) or setRendererOptions({ ... }):
Common argument properties
Every draw hook receives these shared properties:
canvasCtx: the active 2D canvas context for the current frame.canvasWidth: the canvas width in pixels.canvasHeight: the canvas height in pixels.drawProgressBarSignature:
drawProgressBar({ canvasCtx, canvasWidth, canvasHeight, lastX, backgroundValue, createDefaultPath })lastX: the resolved filled width of the progress bar in canvas coordinates.backgroundValue: the value used to compute the background fill width.createDefaultPath(): builds the current progress-bar path withbeginPath()+rect().
Default: the renderer calls
createDefaultPath(), thenfill()andstroke().const graph = new TransferGraph({ drawProgressBar({ canvasCtx, createDefaultPath }) { createDefaultPath() canvasCtx.fill() canvasCtx.stroke() }, })drawGridSignature:
drawGrid({ canvasCtx, canvasWidth, canvasHeight, gridCols, gridRows, createDefaultPath })gridCols: number of vertical grid divisions.gridRows: number of horizontal grid divisions.createDefaultPath(): builds the current grid paths for both directions.
Default: the renderer calls
createDefaultPath(), thenstroke().const graph = new TransferGraph({ drawGrid({ canvasCtx, createDefaultPath }) { createDefaultPath() canvasCtx.stroke() }, })drawSpeedOverlaySignature:
drawSpeedOverlay({ canvasCtx, canvasWidth, canvasHeight, lastX, endX, avgWithSpeeds, renderPointCount, pixelsPerValue, maxAvgSpeed, createDefaultPath })lastX: the resolved filled width of the progress area.endX: the right edge where the overlay path should continue until the filled progress ends.avgWithSpeeds: the calculated speed points used to draw the overlay.renderPointCount: number of points used when rendering the visible speed curve.pixelsPerValue: the conversion factor from value units to canvas X pixels.maxAvgSpeed: the resolved speed scale used to normalize the overlay height.createDefaultPath(): builds the current speed-area path and lets the default implementation resolve the current point position.
Default: the renderer calls
createDefaultPath(), thenfill().const graph = new TransferGraph({ drawSpeedOverlay({ canvasCtx, createDefaultPath }) { createDefaultPath() canvasCtx.fill() }, })drawSpeedLineLabelSignature:
drawSpeedLineLabel({ canvasCtx, canvasWidth, canvasHeight, guideY, textX, labelBottomY, labelTopY, labelLeftX, labelWidth, labelPaddingX, labelPaddingY, speedLabel, speedLabelColor, speedLabelBackgroundColor, speedGuideColor, createDefaultLinePath, createDefaultLabelBackgroundPath, fillDefaultLabelText })guideY: the Y position of the horizontal guide line.textX: the right-aligned X position used for the label text.labelBottomY: the baseline Y position for the label text.labelTopY: the top Y position of the background box.labelLeftX: the left X position of the background box.labelWidth: measured width of the label text.labelPaddingX: horizontal padding used by the default label box.labelPaddingY: vertical padding used by the default label box.speedLabel: the formatted label string.speedLabelColor: the current text color.speedLabelBackgroundColor: the current background fill color.speedGuideColor: the current guide-line stroke color.createDefaultLinePath(): builds the current guide-line path.createDefaultLabelBackgroundPath(): builds the current label background path withbeginPath()+rect().fillDefaultLabelText(options): draws the current text usingfillText(). Optionaloptionscan override fill style and text alignment for custom drawing.
Default: the renderer strokes the line path, fills the background path, and calls
fillDefaultLabelText().const graph = new TransferGraph({ drawSpeedLineLabel({ canvasCtx, createDefaultLinePath, createDefaultLabelBackgroundPath, fillDefaultLabelText, }) { createDefaultLinePath() canvasCtx.stroke() createDefaultLabelBackgroundPath() canvasCtx.fill() fillDefaultLabelText() }, })drawBorderSignature:
drawBorder({ canvasCtx, canvasWidth, canvasHeight, borderColor, createDefaultPath })borderColor: the current border stroke color.createDefaultPath(): builds the current canvas border path withbeginPath()+rect().
Default: the renderer calls
createDefaultPath(), thenstroke().const graph = new TransferGraph({ drawBorder({ canvasCtx, createDefaultPath }) { createDefaultPath() canvasCtx.stroke() }, })
Development
See repo at https://github.com/rhengles/transfer-speed-graph
