今回はガントチャートをWebアプリとしてつくることを考えます。ここではJavaScriptをつかってチャートだけ表示させます。
GanttChartクラスをつくります。あとはHTML側でGanttChartクラスのインスタンスを生成して表示させます。
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ガントチャートの実験</title> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <div id = "main"></div> <script src='./gantt-chart.js'></script> <script> let main_element = document.getElementById('main'); let ganttChart = new GanttChart(main_element); ganttChart.SetGanttChartFirstDay(2021,12,1); ganttChart.SetGanttChartEndDay(2021,12,31); ganttChart.DrawChart(); </script> </body> </html> |
GanttChartクラス
ではGanttChartクラスを示します。
コンストラクタ
最初にコンストラクタを示します。
コンストラクタの引数にHTML側で実行したdocument.getElementById関数の戻り値を渡します。すると必要な要素が追加されてチャートが描画されるという感じです。
gantt-chart.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class GanttChart{ constructor(mainElement){ this.MainElement = mainElement; this.DayElements = []; // 「日」の要素 this.TotalWidth = 0; // 全体の幅 this.DayWidth = 25; // 「日」の幅 this.yPos = 40; // タスクが追加されるときのY座標 this.CategoryWidth = 120; // カテゴリ項目の幅 this.TaskWidth = 120; // タスク項目の幅 this.ManagerWidth = 100; // 担当者項目の幅 this.PeriodWidth = 100; // 期間項目の幅 this.ItemsWidth = this.CategoryWidth + this.TaskWidth + this.ManagerWidth + this.PeriodWidth; // 左側に表示される項目全体の幅(カテゴリ、タスク、担当者、期間の幅の合計) this.DayCount = 0; // 何日分表示させるか } } |
表示させたい最初の日と最終日を指定する
SetGanttChartFirstDay関数とSetGanttChartEndDay関数はチャートに表示させたい日付の最初の日と最終日を指定するときに使います。Dateクラスのコンストラクタには年と日はそのまま渡しますが、月は1少ない値を指定しなければなりません。2021年11月3日であればnew Date(2021, 10, 3)としなければなりません。
1 2 3 4 5 6 7 8 9 |
class GanttChart{ SetGanttChartFirstDay(firstYear, firstMonth, firstDay){ this.GanttChartFirstDay = new Date(firstYear, firstMonth-1, firstDay); } SetGanttChartEndDay(endYear, endMonth, endDay){ this.GanttChartEndDay = new Date(endYear, endMonth-1, endDay); } } |
チャートを描画します。関数が長くなるので項目の部分と日付の部分をわけて処理をしています。各要素はthis.MainElementの内部に絶対配置していきます。
描画する
1 2 3 4 5 6 7 8 9 10 |
class GanttChart{ DrawChart(){ this.MainElement.style.position = 'relative'; this.DrawItemsHeader(); this.DrawDays(); this.MainElement.style.height = '400px'; this.MainElement.style.width = this.TotalWidth + 'px'; this.MainElement.style.border = '1px solid #cccccc'; } } |
各項目のヘッダー部分を描画する処理を示します。最後にMoveTextCellCenter関数を呼び出していますが、これは要素内の文字を中央に表示させるためのものです。通常はtextAlignスタイルとverticalAlignスタイルを設定すればいいのですが、positionがabsoluteだとうまくいかないので別に処理をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class GanttChart{ DrawItemsHeader(){ let widths =[this.CategoryWidth, this.TaskWidth, this.ManagerWidth, this.PeriodWidth]; let texts =['カテゴリ', 'タスク', '担当者', '期間']; let left = 0; for(let i=0; i<4; i++){ let newElement = document.createElement('div'); newElement.style.fontSize = '12px'; newElement.textContent = texts[i]; newElement.style.position = 'absolute'; newElement.style.width = widths[i] + 'px'; newElement.style.height = '40px'; newElement.style.top = '0px'; newElement.style.left = left + 'px'; newElement.style.borderRight = "solid 1px"; newElement.style.borderRightColor = '#cccccc'; newElement.style.borderBottom = "solid 1px"; newElement.style.borderBottomColor = '#cccccc'; this.MoveTextCellCenter(newElement); this.MainElement.appendChild(newElement); left += widths[i]; } } } |
要素内の文字を中央に表示させるMoveTextCellCenter関数を示します。positionがabsoluteだとtextAlignスタイルとverticalAlignスタイルを設定してもうまくいきません。そこで要素のなかに別の要素を追加して文字列を設定しなおしています。またdisplayスタイルはtable-cellにしなければなりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class GanttChart{ MoveTextCellCenter(element){ let innerElement = document.createElement('div'); innerElement.innerHTML = element.innerHTML; // element.innerHTMLを入れ替える innerElement.style.width = element.style.width; innerElement.style.height = element.style.height; innerElement.style.fontSize = element.style.fontSize; innerElement.style.display = "table-cell"; innerElement.style.textAlign = "center"; innerElement.style.verticalAlign = "middle"; element.innerHTML = ''; element.appendChild(innerElement); } } |
チャートの日付部分を描画するための処理を示します。まず最初に表示される日にちと曜日を調べます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
class GanttChart{ DrawDays(){ let ms = this.GanttChartEndDay.getTime() - this.GanttChartFirstDay.getTime(); this.DayCount = Math.round(ms / (1000*60*60*24)) + 1; // 最初に表示される日は何日?何曜日? let firstyear = this.GanttChartFirstDay.getFullYear(); let firstmonth = this.GanttChartFirstDay.getMonth(); let firstday = this.GanttChartFirstDay.getDate(); let firstdate = new Date(firstyear, firstmonth, firstday); let weeks = ['日','月','火','水','木','金','土',]; for(let i=0; i<this.DayCount; i++){ // 表示させようとしている日の曜日を取得 let day = i+firstdate.getDay(); let week = weeks[day%7]; // 日の表示色 let colorText1 = 'rgba(255, 0, 0, 0.4)'; let colorText2 = 'rgba(255, 0, 0, 0.2)'; if(day%7 == 0){ // 日曜日の表示色は赤 colorText1 = 'rgba(255, 0, 0, 0.4)'; colorText2 = 'rgba(255, 0, 0, 0.2)'; } else if(day%7 == 6){ // 土曜日の表示色は赤 colorText1 = 'rgba(0, 0, 255, 0.4)'; colorText2 = 'rgba(0, 0, 255, 0.2)'; } else{ // それ以外の曜日の表示色は黄色 colorText1 = 'rgba(255, 255, 0, 0.1)'; colorText2 = 'rgba(255, 255, 0, 0.05)'; } let dayElement1 = document.createElement('div'); dayElement1.textContent = i+firstday; dayElement1.style.position = 'absolute'; dayElement1.style.width = this.DayWidth + 'px'; dayElement1.style.height = '20px'; dayElement1.style.backgroundColor = colorText1; dayElement1.style.top = '0px'; dayElement1.style.left = this.ItemsWidth + (this.DayWidth + 1)* i + 'px'; dayElement1.style.fontSize = '14px'; dayElement1.style.textAlign = 'center'; this.MainElement.appendChild(dayElement1); let dayElement2 = document.createElement('div'); dayElement2.textContent = week; dayElement2.style.position = 'absolute'; dayElement2.style.width = this.DayWidth + 'px'; dayElement2.style.height = '20px'; dayElement2.style.backgroundColor = colorText1; dayElement2.style.top = '20px'; dayElement2.style.left = this.ItemsWidth + (this.DayWidth + 1)* i + 'px'; dayElement2.style.fontSize = '14px'; dayElement2.style.textAlign = 'center'; this.MainElement.appendChild(dayElement2); let dayElement3 = document.createElement('div'); dayElement3.textContent = ''; dayElement3.style.position = 'absolute'; dayElement3.style.width = this.DayWidth + 'px'; dayElement3.style.height = '400px'; dayElement3.style.backgroundColor = colorText2; dayElement3.style.top = '40px'; dayElement3.style.left = this.ItemsWidth + (this.DayWidth + 1)* i + 'px'; dayElement3.style.fontSize = '14px'; dayElement3.style.textAlign = 'center'; this.MainElement.appendChild(dayElement3); // 各日の下に縦長の帯のように表示させる部分はあとで高さ変更が必要 // なので保存しておく this.DayElements.push(dayElement3); } // すべての日を表示しおわったときに全体の幅が決まる this.TotalWidth = this.ItemsWidth + (this.DayWidth + 1)* this.DayCount - 1; } } |