간트차트로는 https://dhtmlx.com/docs/products/dhtmlxGantt/ 가 문서 양이 많긴 하지만
그만큼 기능이 잘 되어 있다는 의미이기도 하닝, 쓸만해 보인당.
조금 느린거시 아닌가 하는 생각이 쪼메씩 고개 들기도 하지만,
현재로선 따로 좋은 대안을 찾지 못했당.(직접 맹글깡?~~)
pro(유료말공) standard 다운로드(무료) 하거나
https://dhtmlx.com/x/download/regular/dhtmlxGantt.zip
그냥 아래 코드 복사/붙여넣기로 넣장 (추가 필요한 라이브러리가 있다면 알아서 추강!)
<link rel="stylesheet" href="https://docs.dhtmlx.com/gantt/codebase/dhtmlxgantt.css"> <script src="https://docs.dhtmlx.com/gantt/codebase/dhtmlxgantt.js"></script>
포맷설명: https://docs.dhtmlx.com/gantt/desktop__date_format.html
사용가능 skin은 아래와 같당. 골라서 붙인당!
<!-- available skins <link rel="stylesheet" href="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css"> <link rel="stylesheet" href="https://docs.dhtmlx.com/gantt/codebase/skins/dhtmlxgantt.css"> <link rel="stylesheet" href="https://docs.dhtmlx.com/gantt/codebase/skins/dhtmlxgantt_skyblue.css"> <link rel="stylesheet" href="https://docs.dhtmlx.com/gantt/codebase/skins/dhtmlxgantt_meadow.css"> <link rel="stylesheet" href="https://docs.dhtmlx.com/gantt/codebase/skins/dhtmlxgantt_broadway.css"> <link rel="stylesheet" href="https://docs.dhtmlx.com/gantt/codebase/skins/dhtmlxgantt_contrast_black.css"> <link rel="stylesheet" href="https://docs.dhtmlx.com/gantt/codebase/skins/dhtmlxgantt_contrast_white.css"> <link rel="stylesheet" href="https://docs.dhtmlx.com/gantt/codebase/skins/dhtmlxgantt_material.css"> -->
홈페이지의 샘플데이터의 날짜 포맷이 영어권 포맷이어서 우리 스타일로 바꾼 샘플데이터
코드로 붙이자닝 역시 너무 기럭지가 김! (그냥 다운받장!)
위 json 파일은 아래 html문서와 같은 폴더에 넣고, 서버를 실행하장.
억지로 합쳐버린 sampleGantt.html
<!DOCTYPE html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>Gantt chart with resource panel</title> <link rel="stylesheet" href="https://docs.dhtmlx.com/gantt/codebase/dhtmlxgantt.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/spectrum/1.8.1/spectrum.min.css"> <script src="https://docs.dhtmlx.com/gantt/codebase/dhtmlxgantt.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/spectrum/1.8.1/spectrum.min.js"></script> <style> html, body { padding: 0px; margin: 0px; height: 100%; } .gantt_grid_scale .gantt_grid_head_cell, .gantt_task .gantt_task_scale .gantt_scale_cell { font-weight: bold; font-size: 14px; color: rgba(0, 0, 0, 0.7); } .task-color-cell { vertical-align: middle; width: 12px; height: 12px; border: 1px solid #cecece; display: inline-block; border-radius: 6px; } </style> </head> <body> <h1 style="text-align: center;background-color: black;color:yellow">DHTMLX GANTT 예젱</h1> <div id="e7eGantt" style='width:99vw; height:93vh;'></div> <script> function calculateResourceLoad(tasks, scale) { var step = scale.unit; var timegrid = {}; for (var i = 0; i < tasks.length; i++) { var task = tasks[i]; var currDate = gantt.date[step + "_start"](new Date(task.start_date)); while (currDate < task.end_date) { var date = currDate; currDate = gantt.date.add(currDate, 1, step); if (!gantt.isWorkTime({ date: date, task: task })) { continue; } var timestamp = date.valueOf(); if (!timegrid[timestamp]) timegrid[timestamp] = 0; timegrid[timestamp] += 8; } } var timetable = []; var start, end; for (var i in timegrid) { start = new Date(i * 1); end = gantt.date.add(start, 1, step); timetable.push({ start_date: start, end_date: end, value: timegrid[i] }); } return timetable; } var renderResourceLine = function (resource, timeline) { var tasks = gantt.getTaskBy("user", resource.id); var timetable = calculateResourceLoad(tasks, timeline.getScale()); var row = document.createElement("div"); for (var i = 0; i < timetable.length; i++) { var day = timetable[i]; var css = ""; if (day.value <= 8) { css = "gantt_resource_marker gantt_resource_marker_ok"; } else { css = "gantt_resource_marker gantt_resource_marker_overtime"; } var sizes = timeline.getItemPosition(resource, day.start_date, day.end_date); var el = document.createElement('div'); el.className = css; el.style.cssText = [ 'left:' + sizes.left + 'px', 'width:' + sizes.width + 'px', 'position:absolute', 'height:' + (gantt.config.row_height - 1) + 'px', 'line-height:' + sizes.height + 'px', 'top:' + sizes.top + 'px' ].join(";"); el.innerHTML = day.value; row.appendChild(el); } return row; }; var resourceLayers = [ renderResourceLine, "taskBg" ]; // 색깔 에디터 추가 let editor; gantt.config.editor_types.color = { show: function (id, column, config, placeholder) { var html = "<div><input type='color' name='" + column.name + "'></div>"; placeholder.innerHTML = html; editor = $(placeholder).find("input").spectrum({ change: () => { gantt.ext.inlineEditors.save(); } }); setTimeout(() => { editor.spectrum("show") }) }, hide: function () { if (editor) { editor.spectrum("destroy"); editor = null; } }, set_value: function (value, id, column, node) { editor.spectrum("set", value); }, get_value: function (id, column, node) { return editor.spectrum("get").toHexString(); }, is_changed: function (value, id, column, node) { // console.log("THIS: ", this); var newValue = this.get_value(id, column, node); return newValue !== value; }, is_valid: function (value, id, column, node) { var newValue = this.get_value(id, column, node); return !!newValue; }, save: function (id, column, node) { // only for inputs with map_to:auto. complex save behavior goes here }, focus: function (node) { editor.spectrum("show"); } } const colorEditor = { type: "color", map_to: "color" }; var mainGridConfig = { columns: [ { name: "text", tree: true, width: 200, resize: true }, { name: "start_date", align: "center", width: 80, resize: true }, { name: "owner", align: "center", width: 60, label: "담당자", template: function (task) { var store = gantt.getDatastore("resources"); var owner = store.getItem(task.user); if (owner) { return owner.label; } else { return "디폴트"; } } }, { name: "duration", width: 50, align: "center" }, { name: "color", label: "색깔", align: "center", width: 50, resize: true, editor: colorEditor, template: (task) => { if (!task.color) { if (task.type == "project") task.color = "#7fbc64"; if (task.type == "milestone") task.color = "#d18c7f"; } task.color = task.color || "#7e9667"; return `<div class="task-color-cell" style="background:${task.color}"></div>`; } }, { name: "add", width: 44 } ] }; var resourcePanelConfig = { columns: [ { name: "name", label: "Name", align: "center", template: function (resource) { //console.log("체킁:", resource); return resource.label; } }, { name: "workload", label: "Workload", align: "center", template: function (resource) { var tasks = gantt.getTaskBy("user", resource.id); var totalDuration = 0; for (var i = 0; i < tasks.length; i++) { totalDuration += tasks[i].duration; } return (totalDuration || 0) * 8 + ""; } } ] }; gantt.config.layout = { css: "gantt_container", rows: [ { cols: [ { view: "grid", group: "grids", config: mainGridConfig, scrollY: "scrollVer" }, { resizer: true, width: 1, group: "vertical" }, { view: "timeline", id: "timeline", scrollX: "scrollHor", scrollY: "scrollVer" }, { view: "scrollbar", id: "scrollVer", group: "vertical" } ] }, { resizer: true, width: 1 }, { config: resourcePanelConfig, cols: [ { view: "grid", id: "resourceGrid", group: "grids", bind: "resources", scrollY: "resourceVScroll" }, { resizer: true, width: 1, group: "vertical" }, { view: "timeline", id: "resourceTimeline", bind: "resources", bindLinks: null, layers: resourceLayers, scrollX: "scrollHor", scrollY: "resourceVScroll" }, { view: "scrollbar", id: "resourceVScroll", group: "vertical" } ] }, { view: "scrollbar", id: "scrollHor" } ] }; var resourcesStore = gantt.createDatastore({ name: "resources", initItem: function (item) { item.id = item.key || gantt.uid(); return item; } }); var tasksStore = gantt.getDatastore("task"); //console.log("체킁taskStore:", tasksStore); tasksStore.attachEvent("onStoreUpdated", function (id, item, mode) { resourcesStore.refresh(); }); // 기본 설정 gantt.i18n.setLocale("kr"); gantt.config.scales = [ { unit: "month", step: 1, format: "%Y, %F" }, { unit: "day", step: 1, format: "%j, %D" } ]; //날짜 형식 gantt.config.date_format = "%Y-%m-%d %H:%i"; // 실제 전달되는 데이타의 start_date등의 포맷 gantt.config.task_date = "%Y년 %m월 %d일"; gantt.init("e7eGantt"); resourcesStore.parse([// resources { key: '0', label: "없음" }, { key: '1', label: "E7E" }, { key: '2', label: "메롱" }, { key: '3', label: "황당" }, { key: '4', label: "당황" }, { key: '5', label: "우앙" }, { key: '6', label: "오호" }, { key: '7', label: "눈물" } ]); // 비동기롱 데이터 가져오깅, 괘니 fetch 써보깅 const fData = async () => { let response = await fetch("./sampleData.json"); let taskData = await response.json(); gantt.parse(taskData); } fData(); </script>
실행하닝, 아래와 같은 결과가 나왔당. 그대로 쓰거낭,
커스터마이징을 원한다면 문서를 쪼메 욜씨미 읽는 시간 투자가 필요하당!
쌀쌀함의 그늘아래 그리움의 씨앗이 알아서 싹을 연다.
그립다. 멀어지는 뒷모습
추앙한다. 쫓아가고 싶은 걸음걸이
그 꽃이 피어야 , 지고, 어떤 꽃이 필텐데.....
달아나는 눈물 보다 뒷모습에 그려진 찬란한 걸음걸이
DNA의 오류는 급발진을 부른당.
연신 고개만 돌려질 뿐, 딴 곳을 재촉하는 내 걸음 걸음
그저 볼 수 없으닝, 그립당
그....녀가 원한다면 그럴 것이다.
https://www.youtube.com/watch?v=5MyP1LrGAEU
이벤트 동적 바인딩 당신만 몰라용!! (0) | 2023.11.23 |
---|---|
검색 드롭다운 원리 (Search Drop Down) (4) | 2023.11.06 |
GridStack 가안딴 사용법 (1) | 2023.09.08 |
FullCalendar(풀캘린더) 어거지 사용법 (6) | 2023.09.06 |
브라우져에서 자바스크립트 Module 사용하당 (0) | 2023.09.06 |