Initial commit
This commit is contained in:
commit
8b0701b2e5
0
bbtimeline.css
Normal file
0
bbtimeline.css
Normal file
378
bbtimeline.js
Normal file
378
bbtimeline.js
Normal file
@ -0,0 +1,378 @@
|
||||
/**
|
||||
* BBTimeline
|
||||
* @version v0.1.0.076 (2023/09/30 1432)
|
||||
*/
|
||||
class BBTimeline {
|
||||
constructor(el) {
|
||||
const a = this;
|
||||
|
||||
a.Container = document.getElementById(el);
|
||||
|
||||
a.Padding = {
|
||||
Left: 20,
|
||||
Top: 20,
|
||||
Right: 20,
|
||||
Bottom: 40
|
||||
};
|
||||
a.Size = {
|
||||
Width: a.Container.innerWidth || a.Container.clientWidth,
|
||||
Height: a.Container.innerHeight || a.Container.clientHeight
|
||||
};
|
||||
a.Axis = {
|
||||
LineColour1: "#CFCFCF",
|
||||
LineWidth: 1,
|
||||
Font: "8pt Arial",
|
||||
LabelColour: "#000000",
|
||||
X: {
|
||||
NoPartPerDay: 4,
|
||||
HourLineSpace: 6,
|
||||
HourLineHeight: 10,
|
||||
HourLineColour: "#A6A6A6",
|
||||
DayLineHeight: 20,
|
||||
DayLineColour: "#282828"
|
||||
}
|
||||
};
|
||||
a.Marker = {
|
||||
BorderColour: "#3A5D9C",
|
||||
BorderWidth: 2,
|
||||
BackColour: "#D4DEEF",
|
||||
Width: 10
|
||||
};
|
||||
a.HighlightLine = {
|
||||
Colour: "#A6A6A6",
|
||||
Width: 1,
|
||||
};
|
||||
a.StartDate = new Date();
|
||||
a.ClientRectangle = a.getClientRectangle();
|
||||
|
||||
a.ctx = a.Container.getContext("2d");
|
||||
a.ctx.canvas.width = a.Size.Width;
|
||||
a.ctx.canvas.height = a.Size.Height;
|
||||
}
|
||||
|
||||
Load(startDate) {
|
||||
const a = this;
|
||||
|
||||
a.StartDate = ((typeof(startDate) == "undefined") ? new Date() : startDate);
|
||||
|
||||
a.invalidate();
|
||||
a.initialiseComponents();
|
||||
}
|
||||
|
||||
Clear(all) {
|
||||
const a = this;
|
||||
|
||||
if (all) {
|
||||
a.ctx.clearRect(0, 0, a.ctx.canvas.width, a.ctx.canvas.height);
|
||||
} else {
|
||||
const rect = a.getChartCoords();
|
||||
|
||||
a.ctx.clearRect((rect.X1 + a.Axis.LineWidth), rect.Y1, (rect.X2 - rect.X1 - a.Axis.LineWidth), (rect.Y2 - rect.Y1 - a.Axis.LineWidth));
|
||||
}
|
||||
}
|
||||
|
||||
initialiseComponents() {
|
||||
const a = this;
|
||||
const coords = a.getChartCoords();
|
||||
|
||||
// Vertical highlight line
|
||||
a.ctx.canvas.addEventListener('mousemove', function (e) {
|
||||
a.Clear(false);
|
||||
|
||||
if ((e.offsetX > (coords.X1 + a.Axis.LineWidth)) && (e.offsetX < coords.X2) && (e.offsetY >= coords.Y1) && (e.offsetY < (coords.Y2 - a.Axis.LineWidth))){
|
||||
a.drawVerticalLine(e.offsetX);
|
||||
}
|
||||
});
|
||||
|
||||
a.ctx.canvas.addEventListener('mousedown', function (e) {
|
||||
console.log('mousedown');
|
||||
console.log(e);
|
||||
});
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
const a = this;
|
||||
|
||||
a.Clear(true);
|
||||
|
||||
a.drawAxis();
|
||||
a.drawXAxis();
|
||||
a.drawXAxisLabels();
|
||||
}
|
||||
|
||||
|
||||
drawAxis() {
|
||||
const a = this;
|
||||
const coords = a.getChartCoords();
|
||||
if (coords == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.moveTo(coords.X1, coords.Y1);
|
||||
a.ctx.lineTo(coords.X1, coords.Y2);
|
||||
a.ctx.lineTo(coords.X2, coords.Y2);
|
||||
a.ctx.lineWidth = a.Axis.LineWidth;
|
||||
a.ctx.strokeStyle = a.Axis.LineColour1;
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
drawXAxis() {
|
||||
const a = this;
|
||||
const coords = a.getChartCoords();
|
||||
if (coords == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let x = coords.X1;
|
||||
let y = coords.Y2 + a.Axis.LineWidth;
|
||||
|
||||
let i = 0;
|
||||
|
||||
while (true) {
|
||||
if (x >= coords.X2) {
|
||||
break;
|
||||
}
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.moveTo(x, y);
|
||||
|
||||
if ((i % a.Axis.X.NoPartPerDay) == 0) {
|
||||
a.ctx.lineTo(x, (y + a.Axis.X.DayLineHeight));
|
||||
a.ctx.strokeStyle = a.Axis.X.DayLineColour;
|
||||
} else {
|
||||
a.ctx.lineTo(x, (y + a.Axis.X.HourLineHeight));
|
||||
a.ctx.strokeStyle = a.Axis.X.HourLineColour;
|
||||
}
|
||||
|
||||
a.ctx.lineWidth = a.Axis.LineWidth;
|
||||
a.ctx.stroke();
|
||||
|
||||
x += a.Axis.X.HourLineSpace;
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
drawXAxisLabels() {
|
||||
const a = this;
|
||||
const coords = a.getChartCoords();
|
||||
if (coords == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = a.getXAxis();
|
||||
|
||||
const y = coords.Y2 + a.Axis.LineWidth;
|
||||
|
||||
result.forEach(function(e, i) {
|
||||
const date = a.stringToDate(e.Date);
|
||||
|
||||
let writeLabel = false;
|
||||
if ((i == 0)) {
|
||||
// Don't label first entry if too close to the next month
|
||||
if (date.getDate() < 25) {
|
||||
writeLabel = true;
|
||||
}
|
||||
} else if (date.getDate() == 1) {
|
||||
writeLabel = true;
|
||||
}
|
||||
|
||||
// if (i == 0) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
const labelSize = a.drawText(e.X, (y + a.Axis.X.DayLineHeight), a.dateToString(date, "dd"), "center");
|
||||
const label2Spacing = 6;
|
||||
|
||||
// Write month on first of the month
|
||||
if (writeLabel) {
|
||||
a.drawText(e.X, (y + a.Axis.X.DayLineHeight + labelSize.Height + label2Spacing), a.dateToString(date, "MMMM yyyy"), "left");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
drawMarker(x, y) {
|
||||
const a = this;
|
||||
const coords = a.getChartCoords();
|
||||
if (coords == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const width = a.Marker.Width - (a.Marker.BorderWidth * 2);
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.arc(x, y, width, 0, 2 * Math.PI, false);
|
||||
a.ctx.fillStyle = a.Marker.BackColour;
|
||||
a.ctx.fill();
|
||||
a.ctx.lineWidth = a.Marker.BorderWidth;
|
||||
a.ctx.strokeStyle = a.Marker.BorderColour;
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
drawText(x, y, label, align) {
|
||||
const a = this;
|
||||
|
||||
a.ctx.font = a.Axis.Font;
|
||||
a.ctx.fillStyle = a.Axis.LabelColour;
|
||||
|
||||
const size = a.measureText(label);
|
||||
|
||||
switch (align) {
|
||||
case "center":
|
||||
x = (x - size.OffsetLeft);
|
||||
break;
|
||||
case "right":
|
||||
x = (x - size.Width);
|
||||
break;
|
||||
case "left":
|
||||
default:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
a.ctx.fillText(label, x, (y + size.Height));
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
drawVerticalLine(x) {
|
||||
const a = this;
|
||||
const coords = a.getChartCoords();
|
||||
if (coords == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
a.ctx.beginPath();
|
||||
a.ctx.moveTo(x, (coords.Y1 + a.HighlightLine.Width));
|
||||
a.ctx.lineTo(x, (coords.Y2 - a.HighlightLine.Width));
|
||||
a.ctx.lineWidth = a.HighlightLine.Width;
|
||||
a.ctx.strokeStyle = a.HighlightLine.Colour;
|
||||
a.ctx.stroke();
|
||||
}
|
||||
|
||||
getChartCoords() {
|
||||
const a = this;
|
||||
|
||||
if (a.ClientRectangle == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
X1: a.ClientRectangle.X,
|
||||
Y1: a.ClientRectangle.Y,
|
||||
X2: (a.ClientRectangle.Width - a.ClientRectangle.X),
|
||||
Y2: (a.ClientRectangle.Height - a.ClientRectangle.Y)
|
||||
};
|
||||
}
|
||||
|
||||
getClientRectangle() {
|
||||
const a = this;
|
||||
|
||||
return {
|
||||
X: a.Padding.Left,
|
||||
Y: a.Padding.Top,
|
||||
Width: (a.Size.Width - a.Padding.Right),
|
||||
Height: (a.Size.Height - a.Padding.Bottom)
|
||||
};
|
||||
}
|
||||
|
||||
getXAxis() {
|
||||
const a = this;
|
||||
const coords = a.getChartCoords();
|
||||
if (coords == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let result = [];
|
||||
let x = coords.X1;
|
||||
let date = a.stringToDate(a.dateToString(a.StartDate, "yyyy-MM-dd"));
|
||||
|
||||
// Rollback one day
|
||||
date.setDate(date.getDate() - 1);
|
||||
|
||||
while (true) {
|
||||
if (x >= coords.X2) {
|
||||
break;
|
||||
}
|
||||
|
||||
result.push({
|
||||
Date: a.dateToString(date, "yyyy-MM-dd"),
|
||||
X: x
|
||||
});
|
||||
|
||||
x += (a.Axis.X.HourLineSpace * a.Axis.X.NoPartPerDay);
|
||||
date.setDate(date.getDate() + 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
half(value) {
|
||||
return (value / 2);
|
||||
}
|
||||
|
||||
measureText(value) {
|
||||
const a = this;
|
||||
|
||||
const size = a.ctx.measureText(value);
|
||||
|
||||
return {
|
||||
Width: size.width,
|
||||
Height: size.fontBoundingBoxAscent,
|
||||
OffsetLeft: a.half(size.width),
|
||||
OffsetTop: a.half(size.fontBoundingBoxAscent)
|
||||
};
|
||||
}
|
||||
|
||||
dateToString(date, pattern) {
|
||||
let result = pattern;
|
||||
|
||||
result = result.replace("fffffff", date.getMilliseconds().toString().padStart(7, '0'));
|
||||
result = result.replace("ffffff", date.getMilliseconds().toString().padStart(6, '0'));
|
||||
result = result.replace("fffff", date.getMilliseconds().toString().padStart(5, '0'));
|
||||
result = result.replace("yyyy", date.getFullYear().toString().padStart(4, '0'));
|
||||
result = result.replace("MMMM", "{1}");
|
||||
result = result.replace("dddd", "{2}");
|
||||
result = result.replace("ffff", date.getMilliseconds().toString().padStart(4, '0'));
|
||||
result = result.replace("yyy", date.getFullYear().toString().padStart(3, '0'));
|
||||
result = result.replace("MMM", "{3}");
|
||||
result = result.replace("ddd", "{4}");
|
||||
result = result.replace("fff", date.getMilliseconds().toString().padStart(3, '0'));
|
||||
result = result.replace("zzz", "");
|
||||
result = result.replace("yy", date.getFullYear().toString().slice(-2));
|
||||
result = result.replace("MM", (date.getMonth() + 1).toString().padStart(2, '0'));
|
||||
result = result.replace("dd", date.getDate().toString().padStart(2, '0'));
|
||||
result = result.replace("HH", date.getHours().toString().padStart(2, '0'));
|
||||
result = result.replace("hh", (date.getHours() > 12 ? (date.getHours() - 12) : date.getHours()).toString().padStart(2, '0'));
|
||||
result = result.replace("mm", date.getMinutes().toString().padStart(2, '0'));
|
||||
result = result.replace("ss", date.getSeconds().toString().padStart(2, '0'));
|
||||
result = result.replace("ff", date.getMilliseconds().toString().padStart(2, '0'));
|
||||
result = result.replace("tt", "{5}");
|
||||
result = result.replace("zz", "");
|
||||
result = result.replace("y", date.getFullYear().toString());
|
||||
result = result.replace("M", (date.getMonth() + 1).toString());
|
||||
result = result.replace("d", date.getDate().toString());
|
||||
result = result.replace("H", date.getHours().toString());
|
||||
result = result.replace("h", (date.getHours() > 12 ? (date.getHours() - 12) : date.getHours()).toString());
|
||||
result = result.replace("m", date.getMinutes().toString());
|
||||
result = result.replace("s", date.getSeconds().toString());
|
||||
result = result.replace("z", "");
|
||||
result = result.replace("t", "{6}");
|
||||
result = result.replace("Z", "");
|
||||
|
||||
result = result.replace("{1}", date.toLocaleString('default', { month: 'long' }));
|
||||
result = result.replace("{2}", date.toLocaleString('default', { weekday: 'long' }));
|
||||
result = result.replace("{3}", date.toLocaleString('default', { month: 'short' }));
|
||||
result = result.replace("{4}", date.toLocaleString('default', { weekday: 'short' }));
|
||||
result = result.replace("{5}", (date.getHours() >= 12 ? "PM" : "AM"));
|
||||
result = result.replace("{6}", (date.getHours() >= 12 ? "P" : "A"));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
stringToDate(value) {
|
||||
return new Date(Date.parse(value));
|
||||
}
|
||||
|
||||
}
|
66
demo-test.html
Normal file
66
demo-test.html
Normal file
@ -0,0 +1,66 @@
|
||||
<!doctype html>
|
||||
<html lang="en-GB">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="content-type" content="text/html" charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="" />
|
||||
<meta name="keyword" content="" />
|
||||
|
||||
<!-- <script src="http://cdn.hiimray.co.uk/8206c600-707c-469e-8d49-a76ae35782af/bootstrap/5.3.0/dist/js/bootstrap.bundle.min.js"></script> -->
|
||||
<!-- <link href="http://cdn.hiimray.co.uk/8206c600-707c-469e-8d49-a76ae35782af/bootstrap/5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" /> -->
|
||||
|
||||
<link href="bbtimeline.css" rel="stylesheet" />
|
||||
<script src="bbtimeline.js"></script>
|
||||
<!-- <link href="bbtimeline.min.css" rel="stylesheet" /> -->
|
||||
<!-- <script src="bbtimeline.min.js"></script> -->
|
||||
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<canvas id="myCanvas"></canvas>
|
||||
|
||||
<p>
|
||||
<button onclick="Clear()">Clear</button>
|
||||
|
||||
</p>
|
||||
|
||||
<style>
|
||||
|
||||
body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #000000;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
var timeline1 = new BBTimeline("myCanvas");
|
||||
|
||||
timeline1.Load();
|
||||
|
||||
|
||||
|
||||
|
||||
function Clear()
|
||||
{
|
||||
timeline1.Clear();
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user