Added gantt chart and canvas extensions

This commit is contained in:
Ray 2024-01-06 17:55:37 +00:00
parent d6048a20be
commit a51b73ee5d
3 changed files with 276 additions and 43 deletions

187
canvas.js
View File

@ -185,6 +185,126 @@ class Canvas {
a._ctx.clearRect(0, 0, a._ctx.canvas.width, a._ctx.canvas.height);
}
DrawArrowS(rectangle, penColour, options) {
const a = this;
if (a._ctx == null) {
return;
}
const opt = Object.assign({
LineWidth: 1,
LineDash: [],
FillColour: null
}, options);
// Adjust for pen discrepancy
rectangle.X += 0.5;
rectangle.Y += 0.5;
rectangle.W -= opt.LineWidth;
rectangle.H -= opt.LineWidth;
a._ctx.beginPath();
a._ctx.strokeStyle = penColour;
a._ctx.lineWidth = opt.LineWidth;
a._ctx.setLineDash(opt.LineDash);
a._ctx.moveTo(rectangle.X, rectangle.Y);
a._ctx.lineTo((rectangle.X + rectangle.W), rectangle.Y);
a._ctx.lineTo((rectangle.X + Math.half(rectangle.W)), (rectangle.Y + rectangle.H));
a._ctx.closePath();
if (opt.FillColour != null) {
a._ctx.fillStyle = opt.FillColour;
a._ctx.fill();
}
a._ctx.stroke();
}
DrawCircle(x, y, width, penColour, options) {
const a = this;
if (a._ctx == null) {
return;
}
const opt = Object.assign({
LineWidth: 1,
LineDash: [],
FillColour: null
}, options);
let rectangle = {
X: x + Math.half(width),
Y: y + Math.half(width),
W: Math.half(width),
H: Math.half(width)
};
// Adjust for pen discrepancy
rectangle.X += 0.5;
rectangle.Y += 0.5;
rectangle.W -= Math.half(opt.LineWidth);
rectangle.H -= Math.half(opt.LineWidth);
a._ctx.beginPath();
a._ctx.strokeStyle = penColour;
a._ctx.lineWidth = opt.LineWidth;
a._ctx.setLineDash(opt.LineDash);
a._ctx.arc(rectangle.X, rectangle.Y, rectangle.W, 0, 2 * Math.PI, false);
a._ctx.closePath();
if (opt.FillColour != null) {
a._ctx.fillStyle = opt.FillColour;
a._ctx.fill();
}
a._ctx.stroke();
}
DrawDiamond(rectangle, penColour, options) {
const a = this;
if (a._ctx == null) {
return;
}
const opt = Object.assign({
LineWidth: 1,
LineDash: [],
FillColour: null
}, options);
// Adjust for pen discrepancy
rectangle.X += 0.5;
rectangle.Y += 0.5;
rectangle.W -= opt.LineWidth;
rectangle.H -= opt.LineWidth;
a._ctx.beginPath();
a._ctx.strokeStyle = penColour;
a._ctx.lineWidth = opt.LineWidth;
a._ctx.setLineDash(opt.LineDash);
a._ctx.moveTo((rectangle.X + Math.half(rectangle.W)), rectangle.Y);
a._ctx.lineTo((rectangle.X + rectangle.W), (rectangle.Y + Math.half(rectangle.H)));
a._ctx.lineTo((rectangle.X + Math.half(rectangle.W)), (rectangle.Y + rectangle.H));
a._ctx.lineTo(rectangle.X, (rectangle.Y + Math.half(rectangle.H)));
a._ctx.closePath();
if (opt.FillColour != null) {
a._ctx.fillStyle = opt.FillColour;
a._ctx.fill();
}
a._ctx.stroke();
}
DrawRectangle(rectangle, penColour, options) {
const a = this;
@ -215,21 +335,69 @@ class Canvas {
a._ctx.fill();
}
a._ctx.closePath();
a._ctx.stroke();
return rectangle;
}
// DrawLine(x1, y1, x2, y2, width, colour) {
// const a = this;
DrawLine(startX, startY, finishX, finishY, penColour, options) {
const a = this;
// a.CTX.beginPath();
// a.CTX.moveTo(x1, y1);
// a.CTX.lineTo(x2, (y2 - width));
// a.CTX.lineWidth = width;
// a.CTX.strokeStyle = colour;
// a.CTX.stroke();
// }
if (a._ctx == null) {
return;
}
const opt = Object.assign({
LineWidth: 1,
LineDash: []
}, options);
startY -= 0.5;
// y -= penWidth;
a._ctx.beginPath();
a._ctx.strokeStyle = penColour;
a._ctx.lineWidth = opt.LineWidth;
a._ctx.setLineDash(opt.LineDash);
a._ctx.moveTo(startX, startY);
a._ctx.lineTo(finishX, finishY);
a._ctx.stroke();
}
DrawLines(points, penColour, options) {
const a = this;
if (a._ctx == null) {
return;
}
if (points == null) {
return;
}
if (points.length <= 0) {
return;
}
const opt = Object.assign({
LineWidth: 1,
LineDash: []
}, options);
a._ctx.beginPath();
a._ctx.strokeStyle = penColour;
a._ctx.lineWidth = opt.LineWidth;
a._ctx.setLineDash(opt.LineDash);
a._ctx.moveTo(points[0].X, points[0].Y);
for (let i=1; i<points.length; i++) {
a._ctx.lineTo(points[i].X, points[i].Y);
}
a._ctx.stroke();
}
DrawHorizontalLine(x, y, width, penColour, options) {
const a = this;
@ -373,5 +541,4 @@ class Canvas {
return result;
}
}

View File

@ -40,6 +40,12 @@ class RyzGanttChart extends Canvas {
FillColour: "#555555"
}
},
Line: {
Margin: 5,
Colour: "#555555",
Width: 1,
ArrowSize: 5
},
DateFont: "7pt sans-serif",
DateForeColour: "#636363",
BorderWidth: 1,
@ -48,6 +54,11 @@ class RyzGanttChart extends Canvas {
};
}
get HeaderHeight() {
const a = this;
return a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1];
}
// Invalidate() {
// const a = this;
@ -70,8 +81,7 @@ class RyzGanttChart extends Canvas {
const tasks = project.ExportTasks();
const width = ((project.Duration + 2) * a.Options.DayWidth);
const headerHeight = (a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1]);
const height = (tasks.length * a.Options.Row.Height) + headerHeight;
const height = (tasks.length * a.Options.Row.Height) + a.HeaderHeight;
a.AutoSize = false;
a.ClientWidth = width;
@ -81,6 +91,7 @@ class RyzGanttChart extends Canvas {
a.#drawChartLabel(project);
a.#drawTasks(tasks);
a.#drawLines(tasks);
}
#drawChartLabel(project) {
@ -89,7 +100,6 @@ class RyzGanttChart extends Canvas {
const width = a.ClientWidth;
const height = a.ClientHeight;
const displayDays = project.Duration + 2;
const headerHeight = (a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1]);
let startDate = new Date(a.StartDate);
startDate.addDays(-1);
@ -100,7 +110,7 @@ class RyzGanttChart extends Canvas {
}
a.DrawHorizontalLine(0, a.Options.HeaderRow.Height[0], width, a.Options.BorderColour);
a.DrawHorizontalLine(0, (headerHeight - a.Options.BorderWidth), width, a.Options.BorderColour);
a.DrawHorizontalLine(0, (a.HeaderHeight - a.Options.BorderWidth), width, a.Options.BorderColour);
// Write dates
for (let i=0; i<displayDays; i++) {
@ -135,9 +145,9 @@ class RyzGanttChart extends Canvas {
if (project.Project.StartOfWeek == date.getDay()) {
// Draw start-of-the-week guideline
a.DrawVerticalLine(x, headerHeight, guidelineHeight, a.Options.BorderColour, {});
a.DrawVerticalLine(x, a.HeaderHeight, guidelineHeight, a.Options.BorderColour, {});
} else {
a.DrawVerticalLine(x, headerHeight, guidelineHeight, a.Options.BorderColour, { LineDash: a.Options.BorderDashPattern });
a.DrawVerticalLine(x, a.HeaderHeight, guidelineHeight, a.Options.BorderColour, { LineDash: a.Options.BorderDashPattern });
}
}
}
@ -145,30 +155,86 @@ class RyzGanttChart extends Canvas {
#drawTasks(tasks) {
const a = this;
const labelHeight = a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1];
for (let i=0; i<tasks.length; i++) {
if (tasks[i].IsCollated == true) {
const rectangle = {
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, tasks[i].StartDate) + 1)),
Y: (labelHeight + (a.Options.Row.Height * i) + a.Options.Row.CollatedTask.PaddingTop),
W: (a.Options.DayWidth * tasks[i].Duration),
H: a.Options.Row.CollatedTask.Height
};
const style = ((tasks[i].IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
const rectangle = a.#getTaskRectangle(tasks[i], style);
a.DrawRectangle(rectangle, a.Options.Row.CollatedTask.BorderColour, { FillColour: a.Options.Row.CollatedTask.FillColour });
if (tasks[i].Duration <= 0) {
a.DrawDiamond(rectangle, style.BorderColour, { FillColour: style.FillColour });
} else {
const rectangle = {
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, tasks[i].StartDate) + 1)),
Y: (labelHeight + (a.Options.Row.Height * i) + a.Options.Row.Task.PaddingTop),
W: (a.Options.DayWidth * tasks[i].Duration),
H: a.Options.Row.Task.Height
};
a.DrawRectangle(rectangle, a.Options.Row.Task.BorderColour, { FillColour: a.Options.Row.Task.FillColour });
a.DrawRectangle(rectangle, style.BorderColour, { FillColour: style.FillColour });
}
}
}
#drawLines(tasks) {
const a = this;
for (let i=0; i<tasks.length; i++) {
if (tasks[i].PredecessorTaskID == null) {
continue;
}
if (tasks[i].PredecessorTaskNo == null) {
continue;
}
const style = ((tasks[i].IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
const rectangle = a.#getTaskRectangle(tasks[i], style);
const predecessorTask = tasks.first("Order", tasks[i].PredecessorTaskNo);
if (predecessorTask == null) {
continue;
}
const predecessorStyle = ((predecessorTask.IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
const predecessorRectangle = a.#getTaskRectangle(predecessorTask, predecessorStyle);
let points = [];
points.push({ X: (predecessorRectangle.X + predecessorRectangle.W), Y: (predecessorRectangle.Y + Math.half(predecessorRectangle.H)) });
points.push({ X: (rectangle.X + a.Options.Line.Margin), Y: (predecessorRectangle.Y + Math.half(predecessorRectangle.H)) });
points.push({ X: (rectangle.X + a.Options.Line.Margin), Y: (rectangle.Y - a.Options.Line.Margin) });
points.push({ X: (rectangle.X + a.Options.Line.Margin), Y: rectangle.Y });
a.DrawLines(points, a.Options.Line.Colour, { LineWidth: a.Options.Line.Width });
const arrowRectangle = {
X: (rectangle.X + a.Options.Line.Margin - Math.half(a.Options.Line.ArrowSize)),
Y: (rectangle.Y - a.Options.Line.ArrowSize),
W: a.Options.Line.ArrowSize,
H: a.Options.Line.ArrowSize
}
a.DrawArrowS(arrowRectangle, a.Options.Line.Colour, { FillColour: a.Options.Line.Colour });
}
}
#getTaskRectangle(task, style) {
const a = this;
const i = (task.Order - 1);
let rectangle = {};
if (task.Duration <= 0) {
rectangle = {
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, task.StartDate) + 1)),
Y: (a.HeaderHeight + (a.Options.Row.Height * i) + style.PaddingTop),
W: style.Height,
H: style.Height
};
rectangle.X -= Math.half(rectangle.W);
} else {
rectangle = {
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, task.StartDate) + 1)),
Y: (a.HeaderHeight + (a.Options.Row.Height * i) + style.PaddingTop),
W: (a.Options.DayWidth * task.Duration),
H: style.Height
};
}
return rectangle;
}
}

View File

@ -62,7 +62,7 @@ body {
ID: 1,
Name: "Task A",
StartDelay: 0,
Duration: Math.randomN(1, 30),
Duration: Math.randomN(0, 28),
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: null
@ -72,7 +72,7 @@ body {
ID: 2,
Name: "Task B",
StartDelay: Math.randomN(0, 5),
Duration: Math.randomN(1, 30),
Duration: Math.randomN(0, 28),
PredecessorTaskID: 1,
IsCollated: false,
CollatedTaskID: null
@ -82,7 +82,7 @@ body {
ID: 5,
Name: "Task B1",
StartDelay: Math.randomN(0, 5),
Duration: Math.randomN(1, 30),
Duration: Math.randomN(0, 28),
PredecessorTaskID: 2,
IsCollated: true,
CollatedTaskID: null
@ -92,7 +92,7 @@ body {
ID: 6,
Name: "Task B11",
StartDelay: Math.randomN(0, 5),
Duration: Math.randomN(1, 30),
Duration: Math.randomN(0, 28),
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: 5
@ -102,7 +102,7 @@ body {
ID: 7,
Name: "Task B12",
StartDelay: Math.randomN(0, 5),
Duration: Math.randomN(1, 30),
Duration: Math.randomN(0, 28),
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: 5,
@ -113,7 +113,7 @@ body {
ID: 8,
Name: "Task E",
StartDelay: Math.randomN(0, 5),
Duration: Math.randomN(1, 30),
Duration: Math.randomN(0, 28),
PredecessorTaskID: null,
IsCollated: true,
CollatedTaskID: null
@ -123,7 +123,7 @@ body {
ID: 9,
Name: "Task E1",
StartDelay: Math.randomN(0, 5),
Duration: Math.randomN(1, 30),
Duration: Math.randomN(0, 28),
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: 8
@ -133,7 +133,7 @@ body {
ID: 3,
Name: "Task C",
StartDelay: Math.randomN(0, 5),
Duration: Math.randomN(1, 30),
Duration: Math.randomN(0, 28),
PredecessorTaskID: 8,
IsCollated: false,
CollatedTaskID: null
@ -143,7 +143,7 @@ body {
ID: 4,
Name: "Task D",
StartDelay: Math.randomN(0, 5),
Duration: Math.randomN(1, 30),
Duration: Math.randomN(0, 28),
PredecessorTaskID: 3,
IsCollated: false,
CollatedTaskID: null