245 lines
6.8 KiB
JavaScript
245 lines
6.8 KiB
JavaScript
class RyzGanttChart {
|
|
constructor(el, options) {
|
|
const a = this;
|
|
|
|
a.Canvas = new Canvas(el);
|
|
a.Options = Object.assign(a.DefaultOptions, options);
|
|
|
|
a.Debug = false;
|
|
a.Project = null;
|
|
|
|
a.StartDate = null;
|
|
|
|
// a.initialiseComponents();
|
|
}
|
|
|
|
// initialiseComponents() {
|
|
// const a = this;
|
|
|
|
// }
|
|
|
|
|
|
get DefaultOptions() {
|
|
return {
|
|
DayWidth: 24,
|
|
HeaderRow: {
|
|
Height: [ 21, 21 ]
|
|
},
|
|
Row: {
|
|
Height: 28,
|
|
Task: {
|
|
Height: 13,
|
|
PaddingTop: 6,
|
|
BorderColour: "#555555",
|
|
FillColour: "#9CC2E6"
|
|
},
|
|
CollatedTask: {
|
|
Height: 3,
|
|
PaddingTop: 11,
|
|
BorderColour: "#555555",
|
|
FillColour: "#555555"
|
|
}
|
|
},
|
|
Line: {
|
|
Margin: 5,
|
|
Colour: "#555555",
|
|
Width: 1,
|
|
ArrowSize: 5
|
|
},
|
|
DateFont: "7pt sans-serif",
|
|
DateForeColour: "#636363",
|
|
BorderWidth: 1,
|
|
BorderColour: "#B8B8B8",
|
|
BorderDashPattern: [1, 3],
|
|
};
|
|
}
|
|
|
|
get HeaderHeight() {
|
|
const a = this;
|
|
|
|
return a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1];
|
|
}
|
|
|
|
Invalidate() {
|
|
const a = this;
|
|
|
|
a.Canvas.Clear();
|
|
|
|
if (a.Project == null) {
|
|
return;
|
|
}
|
|
|
|
const tasks = a.Project.ExportTasks();
|
|
const width = ((a.Project.Duration + 2) * a.Options.DayWidth);
|
|
const height = (tasks.length * a.Options.Row.Height) + a.HeaderHeight;
|
|
|
|
a.AutoSize = false;
|
|
a.ClientWidth = width;
|
|
a.ClientHeight = height;
|
|
|
|
a.Canvas.Invalidate();
|
|
|
|
a.drawChartLabel(a.Project);
|
|
a.drawTasks(tasks);
|
|
a.drawLines(tasks);
|
|
}
|
|
|
|
Load(project) {
|
|
const a = this;
|
|
|
|
a.Canvas.Clear();
|
|
|
|
if (project == null) {
|
|
return;
|
|
}
|
|
|
|
a.Project = project;
|
|
a.StartDate = new Date(project.StartDate);
|
|
|
|
a.Invalidate();
|
|
}
|
|
|
|
drawChartLabel(project) {
|
|
const a = this;
|
|
|
|
const width = a.Canvas.ClientWidth;
|
|
const height = a.Canvas.ClientHeight;
|
|
const displayDays = project.Duration + 2;
|
|
|
|
let startDate = new Date(a.StartDate);
|
|
startDate.addDays(-1);
|
|
|
|
// Draw vertical lines
|
|
for (let i=1; i<displayDays; i++) {
|
|
a.Canvas.DrawVerticalLine((a.Options.DayWidth * i), (a.Options.HeaderRow.Height[0] + a.Options.BorderWidth), (a.Options.HeaderRow.Height[1] - (a.Options.BorderWidth * 2)), a.Options.BorderColour, {});
|
|
}
|
|
|
|
a.Canvas.DrawHorizontalLine(0, a.Options.HeaderRow.Height[0], width, a.Options.BorderColour);
|
|
a.Canvas.DrawHorizontalLine(0, (a.HeaderHeight - a.Options.BorderWidth), width, a.Options.BorderColour);
|
|
|
|
// Write dates
|
|
for (let i=0; i<displayDays; i++) {
|
|
const date = Date.addDays(startDate, i);
|
|
const x = (a.Options.DayWidth * i);
|
|
|
|
// Draw month
|
|
if (date.getDate() == 1) {
|
|
const size = a.Canvas.MeasureText(a.Options.DateFont, "#");
|
|
const monthPoint = {
|
|
X: (x + 2),
|
|
Y: Math.half(a.Options.HeaderRow.Height[0] - size.H)
|
|
};
|
|
|
|
a.Canvas.DrawText(monthPoint.X, monthPoint.Y, date.toCString("MMMM"), a.Options.DateFont, a.Options.DateForeColour);
|
|
}
|
|
|
|
// Draw day
|
|
const dateRectangle = {
|
|
X: x,
|
|
Y: a.Options.HeaderRow.Height[1],
|
|
W: a.Options.DayWidth - a.Options.BorderWidth,
|
|
H: (a.Options.HeaderRow.Height[1] - (a.Options.BorderWidth * 2))
|
|
};
|
|
|
|
a.Canvas.FillText(dateRectangle, date.getDate(), a.Options.DateFont, a.Options.DateForeColour);
|
|
|
|
if (a.Debug) a.Canvas.DrawRectangle(dateRectangle, "red", {});
|
|
|
|
// Draw day-of-week guideline
|
|
const guidelineHeight = height - a.Options.BorderWidth;
|
|
|
|
if (project.Project.StartOfWeek == date.getDay()) {
|
|
// Draw start-of-the-week guideline
|
|
a.Canvas.DrawVerticalLine(x, a.HeaderHeight, guidelineHeight, a.Options.BorderColour, {});
|
|
} else {
|
|
a.Canvas.DrawVerticalLine(x, a.HeaderHeight, guidelineHeight, a.Options.BorderColour, { LineDash: a.Options.BorderDashPattern });
|
|
}
|
|
}
|
|
}
|
|
|
|
drawTasks(tasks) {
|
|
const a = this;
|
|
|
|
for (let i=0; i<tasks.length; i++) {
|
|
const style = ((tasks[i].IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
|
|
const rectangle = a.getTaskRectangle(tasks[i], style);
|
|
|
|
if (tasks[i].Duration <= 0) {
|
|
a.Canvas.DrawDiamond(rectangle, style.BorderColour, { FillColour: style.FillColour });
|
|
} else {
|
|
a.Canvas.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.Canvas.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.Canvas.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;
|
|
}
|
|
|
|
} |