Skip to content

Commit c37c2d9

Browse files
committed
2 parents 07591c9 + 3238ce2 commit c37c2d9

5 files changed

Lines changed: 121 additions & 22 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Plugins w/ axis swap</title>
7+
</head>
8+
<body>
9+
<h1>Plugins w/ axis swap</h1>
10+
<div id="chart-container" height="250" width="500"></div>
11+
<script type="module" src="plugins-axis-swapped.ts"></script>
12+
</body>
13+
</html>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {
2+
TimeLine,
3+
Point,
4+
xAxisPlugin,
5+
yAxisPlugin,
6+
doubleClickCopyPlugin,
7+
highlightNearestPointPlugin,
8+
nearestPointInfoPopupPlugin,
9+
pointerCrosshairPlugin,
10+
axisLabelPlugin,
11+
} from "../../src";
12+
13+
const data: Point[] = [];
14+
const maxPoints = 300;
15+
const chart = new TimeLine({
16+
container: document.getElementById("chart-container") as HTMLElement,
17+
data,
18+
maxPoints,
19+
// Note that these aren't used by the chart itself, they're just used by plugins
20+
xLabel: "Time",
21+
yLabel: "Random numbers",
22+
plugins: [
23+
xAxisPlugin((x) => new Date(x).toLocaleTimeString(), 5, "top"),
24+
yAxisPlugin((a) => a + "", 5, "right"),
25+
doubleClickCopyPlugin(),
26+
highlightNearestPointPlugin(),
27+
nearestPointInfoPopupPlugin(),
28+
pointerCrosshairPlugin(),
29+
axisLabelPlugin(true, true, "top", "right"),
30+
],
31+
});
32+
33+
let prev = 0;
34+
setInterval(() => {
35+
const y =
36+
prev + Math.floor(Math.random() * 10) * (Math.random() > 0.5 ? -1 : 1);
37+
prev = y;
38+
data.push({
39+
x: Date.now(),
40+
y,
41+
});
42+
// This is very important!
43+
// You can't have more points in the data array
44+
// than chart.maxPoints, or you'll have weird
45+
// rendering issues.
46+
while (data.length > maxPoints) {
47+
data.shift();
48+
}
49+
50+
// Call chart.recompute() when you're done updating `data`
51+
chart.recompute();
52+
}, 50);
53+
54+
// Note that you need to call chart.draw() yourself
55+
function renderLoop() {
56+
requestAnimationFrame(renderLoop);
57+
chart.draw();
58+
}
59+
renderLoop();

src/TimeLine.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {
55
} from "./types";
66
import { isPointInBox } from "./utils";
77

8-
interface TimeLinePadding {
8+
interface TimeLineSides {
99
left: number;
1010
right: number;
1111
top: number;
@@ -19,7 +19,7 @@ export interface TimeLineOptions {
1919
yLabel: string;
2020
xLabel: string;
2121
lineWidth?: number;
22-
padding?: Partial<TimeLinePadding>;
22+
padding?: Partial<TimeLineSides>;
2323
plugins?: (TimeLinePlugin | null | undefined | false)[];
2424
}
2525

@@ -50,7 +50,7 @@ export class TimeLine {
5050
xLabel: string;
5151
lineWidth = 0.8;
5252
paused = false;
53-
padding: TimeLinePadding;
53+
padding: TimeLineSides;
5454

5555
helpfulInfo: TimeLineHelpfulInfo = {
5656
cursor: {

src/plugins/axis-labels.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import { TimeLinePlugin } from "../types";
44
* This plugin adds text labels to the X and Y axis.
55
* @param showX Weather or not to add the x-axis label
66
* @param showY Weather or not to add the y-axis label
7+
* @param xSide The side to position the x-axis label
8+
* @param ySide The side to position the y-axis label
79
* @returns {TimeLinePlugin}
810
*/
911
export const axisLabelPlugin = (
1012
showX = true,
1113
showY = true,
14+
xSide: "top" | "bottom" = "bottom",
15+
ySide: "left" | "right" = "left",
1216
): TimeLinePlugin => ({
1317
data: {
1418
xLabelEl: document.createElement("p"),
@@ -17,10 +21,10 @@ export const axisLabelPlugin = (
1721
},
1822
construct: function (chart) {
1923
if (showY) {
20-
chart.padding.left += 20;
24+
chart.padding[ySide] += 20;
2125
}
2226
if (showX) {
23-
chart.padding.bottom += 10;
27+
chart.padding[xSide] += 10;
2428
}
2529

2630
this.data.styleTag.innerText = `.crisislab-timeline-axis-label {
@@ -32,17 +36,21 @@ export const axisLabelPlugin = (
3236
.crisislab-timeline-axis-label.crisislab-timeline-x-axis {
3337
left: 50%;
3438
transform: translateX(-50%);
35-
bottom: 0px;
36-
margin-bottom: 2px;
39+
${xSide}: 0px;
40+
margin-${xSide}: 2px;
3741
3842
}
3943
4044
.crisislab-timeline-axis-label.crisislab-timeline-y-axis {
41-
left: 0px;
45+
${ySide}: 0px;
4246
top: 50%;
43-
writing-mode: vertical-rl;
44-
transform: rotate(180deg) translateY(50%);
45-
margin-left: 1px;
47+
writing-mode: vertical-lr;
48+
transform: ${
49+
ySide === "left"
50+
? "rotate(180deg) translateY(50%)"
51+
: "translateY(-50%)"
52+
};
53+
margin-${ySide}: 1px;
4654
}`;
4755
chart.container.appendChild(this.data.styleTag);
4856

src/plugins/axis.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,45 @@ const labelFont = `${labelFontSize}px Arial`;
1010
* This plugin draws an x-axis on the chart.
1111
* @param formatLabel A function that converts an x-axis value to a human-readable format
1212
* @param xMarks The number of markers to show on the x-axis
13+
* @param side The side of the graph to render the axis on
1314
* @returns {TimeLinePlugin}
1415
*/
1516
export const xAxisPlugin = (
1617
formatLabel: (x: number) => string = (x) => x + "",
1718
xMarks = 5,
19+
side: "top" | "bottom" = "bottom",
1820
): TimeLinePlugin => ({
1921
construct: (chart) => {
20-
chart.padding.bottom += 30;
22+
chart.padding[side] += 30;
2123
},
2224
"draw:after": (chart) => {
25+
const onBottom = side === "bottom";
2326
// Set font properties
2427
chart.ctx.font = labelFont;
2528
chart.ctx.fillStyle = chart.foregroundColour;
2629
chart.ctx.textAlign = "start";
27-
chart.ctx.textBaseline = "top";
30+
chart.ctx.textBaseline = onBottom ? "top" : "bottom";
2831

2932
const xPointGap = Math.floor(chart.maxPoints / xMarks);
3033

3134
for (let i = 0; i < xMarks; i++) {
3235
const point = chart.computedData[i * xPointGap];
3336
if (!point) continue;
34-
const renderY = chart.height - chart.padding.bottom;
37+
const renderY = onBottom
38+
? chart.heightInsidePadding + chart.padding.top
39+
: chart.padding.top;
3540

3641
const label = formatLabel(point.x);
3742
const textX = point.renderX + 5;
38-
const textY = renderY + axisGap;
43+
const textY = renderY + (onBottom ? axisGap : -axisGap);
3944

4045
// Marker
4146
chart.ctx.beginPath();
4247
chart.ctx.moveTo(point.renderX, renderY);
43-
chart.ctx.lineTo(point.renderX, renderY + tickLength);
48+
chart.ctx.lineTo(
49+
point.renderX,
50+
renderY + (onBottom ? tickLength : -tickLength),
51+
);
4452
chart.ctx.stroke();
4553

4654
// Label
@@ -53,39 +61,50 @@ export const xAxisPlugin = (
5361
* This plugin draws a y-axis on the chart.
5462
* @param formatLabel A function that converts an y-axis value to a human-readable format
5563
* @param yMarks The number of markers to show on the y-axis
64+
* @param side The side of the graph to render the axis on
5665
* @returns {TimeLinePlugin}
5766
*/
5867
export const yAxisPlugin = (
5968
formatLabel: (y: number) => string = (y) => y + "",
6069
yMarks = 5,
70+
side: "left" | "right" = "left",
6171
): TimeLinePlugin => ({
6272
construct: (chart) => {
63-
chart.padding.left += 40;
73+
chart.padding[side] += 40;
6474
},
6575
"draw:after": (chart) => {
76+
const onLeft = side === "left";
6677
const { yOffset, yMultiplier } = chart.getRenderOffsetsAndMultipliers();
6778

6879
// Set font properties
6980
chart.ctx.font = labelFont;
7081
chart.ctx.fillStyle = chart.foregroundColour;
71-
chart.ctx.textAlign = "right";
82+
chart.ctx.textAlign = onLeft ? "right" : "left";
7283
chart.ctx.textBaseline = "top";
7384
chart.ctx.fillStyle = chart.foregroundColour;
7485

86+
const relevantChartContentEdgeX = onLeft
87+
? chart.padding.left
88+
: chart.padding.left + chart.widthInsidePadding;
89+
7590
for (let i = 0; i < yMarks; i++) {
7691
const yValue = (i * chart.heightInsidePadding) / (yMarks - 1);
77-
const yRenderPosition = yValue + chart.padding.top;
92+
const yRenderPosition = yValue + chart.padding.top + 1;
7893
const yDataValue =
7994
(chart.heightInsidePadding - yValue) / yMultiplier - yOffset;
8095

81-
const textX = chart.padding.left - axisGap;
96+
const textX =
97+
relevantChartContentEdgeX + (onLeft ? -axisGap : axisGap);
8298
const textY = yRenderPosition + axisGap; // Move down so it doesn't overlap the line
8399
const label = formatLabel(yDataValue);
84100

85101
//Marker
86102
chart.ctx.beginPath();
87-
chart.ctx.moveTo(chart.padding.left - tickLength, yRenderPosition);
88-
chart.ctx.lineTo(chart.padding.left, yRenderPosition);
103+
chart.ctx.moveTo(
104+
relevantChartContentEdgeX + (onLeft ? -tickLength : tickLength),
105+
yRenderPosition,
106+
);
107+
chart.ctx.lineTo(relevantChartContentEdgeX, yRenderPosition);
89108
chart.ctx.stroke();
90109

91110
// Label

0 commit comments

Comments
 (0)