Skip to content

Commit

Permalink
Handle gaps better
Browse files Browse the repository at this point in the history
  • Loading branch information
zadeviggers committed Jul 15, 2023
1 parent bbdf8d8 commit 251b878
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 38 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,19 @@ Typescript definitions are included in the package.
<summary>Basic live data</summary>

```ts
import { TimeLine, Point, xAxisPlugin, yAxisPlugin } from "@crisislab/timeline";
import {
TimeLine,
Point,
xAxisPlugin,
axisLabelPlugin,
} from "@crisislab/timeline";

const data: Point[] = [];
const maxPoints = 300;
const pointGap = 50;
const chart = new TimeLine({
container: document.getElementById("chart-container") as HTMLElement,
data,
maxPoints,
pointGap,
// Note that these aren't used by the chart itself, they're just used by plugins
xLabel: "Time",
yLabel: "Random numbers",
Expand Down Expand Up @@ -72,7 +75,7 @@ setInterval(() => {

// Call chart.recompute() when you're done updating `data`
chart.recompute();
}, pointGap);
}, 50);

// Note that you need to call chart.draw() yourself
function renderLoop() {
Expand All @@ -84,6 +87,8 @@ renderLoop();

</details>

More examples in the [examples folder](./examples/).

### Plugins

There are several plugins included:
Expand Down
12 changes: 2 additions & 10 deletions examples/basic-live/basic-live.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import {
TimeLine,
Point,
xAxisPlugin,
yAxisPlugin,
axisLabelPlugin,
} from "../../src";
import { TimeLine, Point, xAxisPlugin, axisLabelPlugin } from "../../src";

const data: Point[] = [];
const maxPoints = 300;
const pointGap = 50;
const chart = new TimeLine({
container: document.getElementById("chart-container") as HTMLElement,
data,
maxPoints,
pointGap,
// Note that these aren't used by the chart itself, they're just used by plugins
xLabel: "Time",
yLabel: "Random numbers",
Expand Down Expand Up @@ -44,7 +36,7 @@ setInterval(() => {

// Call chart.recompute() when you're done updating `data`
chart.recompute();
}, pointGap);
}, 50);

// Note that you need to call chart.draw() yourself
function renderLoop() {
Expand Down
4 changes: 1 addition & 3 deletions examples/live-with-plugins/live-with-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ import {

const data: Point[] = [];
const maxPoints = 300;
const pointGap = 50;
const chart = new TimeLine({
container: document.getElementById("chart-container") as HTMLElement,
data,
maxPoints,
pointGap,
xLabel: "Time",
yLabel: "Random numbers",
plugins: [
Expand Down Expand Up @@ -50,7 +48,7 @@ setInterval(() => {

// Call chart.recompute() when you're done updating `data`
chart.recompute();
}, pointGap);
}, 50);

// Note that you need to call chart.draw() yourself
function renderLoop() {
Expand Down
13 changes: 13 additions & 0 deletions examples/random-gaps-live-with-plugins/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Live Data with lots of plugins</title>
</head>
<body>
<h1>Live Data with random gaps and lots of plugins</h1>
<div id="chart-container" height="250" width="500"></div>
<script type="module" src="random-gaps-live-with-plugins.ts"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
TimeLine,
Point,
xAxisPlugin,
yAxisPlugin,
doubleClickCopyPlugin,
highlightNearestPointPlugin,
nearestPointInfoPopupPlugin,
pointerCrosshairPlugin,
axisLabelPlugin,
} from "../../src";

const data: Point[] = [];
const maxPoints = 300;
const chart = new TimeLine({
container: document.getElementById("chart-container") as HTMLElement,
data,
maxPoints,
xLabel: "Time",
yLabel: "Random numbers",
plugins: [
xAxisPlugin((x) => new Date(x).toLocaleTimeString()),
yAxisPlugin(),
doubleClickCopyPlugin(),
highlightNearestPointPlugin(),
nearestPointInfoPopupPlugin(),
pointerCrosshairPlugin(),
axisLabelPlugin(),
],
});

let prev = 0;
function addPoint() {
setTimeout(addPoint, Math.random() * 50);

const y =
prev + Math.floor(Math.random() * 10) * (Math.random() > 0.5 ? -1 : 1);
prev = y;
data.push({
x: Date.now(),
y,
});
// This is very important!
// You can't have more points in the data array
// than chart.maxPoints, or you'll have weird
// rendering issues.
while (data.length > maxPoints) {
data.shift();
}

// Call chart.recompute() when you're done updating `data`
chart.recompute();
}
addPoint();

// Note that you need to call chart.draw() yourself
function renderLoop() {
requestAnimationFrame(renderLoop);
chart.draw();
}
renderLoop();
53 changes: 43 additions & 10 deletions src/TimeLine.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import type {
ComputedTimeLineDataPoint,
TimeLineDataPoint,
TimeLineOptions,
TimeLinePlugin,
} from "./types";

export interface TimeLineOptions {
container: HTMLElement;
data: TimeLineDataPoint[];
maxPoints: number;
yLabel: string;
xLabel: string;
lineWidth?: number;
plugins?: (TimeLinePlugin | null | undefined | false)[];
}

// NOTE: Assumes data is sorted by X value, with smallest value first in the list
export class TimeLine {
// Raw data points passed by user
Expand All @@ -18,7 +27,6 @@ export class TimeLine {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
maxPoints: number;
pointGap: number;
yLabel: string;
xLabel: string;
lineWidth = 0.8;
Expand All @@ -34,7 +42,6 @@ export class TimeLine {
this.container = options.container;
this.data = options.data;
this.maxPoints = options.maxPoints;
this.pointGap = options.pointGap;
this.xLabel = options.xLabel;
this.yLabel = options.yLabel;
this.plugins =
Expand Down Expand Up @@ -139,18 +146,43 @@ export class TimeLine {
yOffset: number;
yMultiplier: number;
} {
// Avoid throwing errors dividing by zero
if (this.savedData.length < 2) {
return {
xOffset: 0,
xMultiplier: 1,
yOffset: 0,
yMultiplier: 1,
};
}

// Calculate X and Y multipliers

// X multiplier is easy - just use the number of points and their width
// For X, we need to first figure out the total amount of space used by the points
let totalPointWidth = 0;
for (let i = 1; i < this.savedData.length; i++) {
// Calculate the gap between this point & the previous point
const previousPoint = this.savedData[i - 1];
const currentPoint = this.savedData[i];
const gap = currentPoint.x - previousPoint.x;

totalPointWidth += gap;
}

// This is what the pointGap would be if all the points were perfectly spaced
const averageSpacePerPoint = totalPointWidth / this.savedData.length;

// Calculate the X multiplier so that the data all fits in the pane
const xMultiplier =
this.widthWithoutPadding / (this.maxPoints * this.pointGap);
this.widthWithoutPadding / (this.maxPoints * averageSpacePerPoint);

// X offset is
// Calculate the X-offset so that all data is visible
// & initially the graph scrolls from the right.
const xOffset =
(this.maxPoints - this.savedData.length) * this.pointGap -
(this.maxPoints - this.savedData.length) * averageSpacePerPoint -
this.savedData[0].x;

// Y is harder - need to find the difference between the minimum and maximum points
// Y multiplier is simpler - need to find the difference between the minimum and maximum points
// Note to future self: Always use -Infinity, not Number.MIN_VALUE
let biggestYValue = -Infinity;
let smallestYValue = Infinity;
Expand All @@ -162,10 +194,11 @@ export class TimeLine {
// Get the maximum gap
const maxYGap = biggestYValue - smallestYValue;

// Now divide the available pixels by that
// Now divide the available pixels by that for the multiplier
const yMultiplier = this.heightWithoutPadding / maxYGap;

// Also calculate what we need to add to all the Y values so that they're visible
// Y offset is very easy - just the inverse of the smallest number
// since we draw from the top
const yOffset = -smallestYValue;

return {
Expand Down
11 changes: 0 additions & 11 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,6 @@ export interface ComputedTimeLineDataPoint extends TimeLineDataPoint {
renderY: number;
}

export interface TimeLineOptions {
container: HTMLElement;
data: TimeLineDataPoint[];
maxPoints: number;
pointGap: number;
yLabel: string;
xLabel: string;
lineWidth?: number;
plugins?: (TimeLinePlugin | null | undefined | false)[];
}

type TimeLinePluginHook = (chart: TimeLine) => void;
export interface TimeLinePlugin {
"draw:before"?: TimeLinePluginHook;
Expand Down

0 comments on commit 251b878

Please sign in to comment.