- ): void;
133 | setOptions(options: Options): void;
134 | render(): string;
135 | }
136 | ```
137 |
138 | ## Report a issue
139 |
140 | * [All issues](https://github.com/d-band/gantt/issues)
141 | * [New issue](https://github.com/d-band/gantt/issues/new)
142 |
143 | ## License
144 |
145 | Gantt is available under the terms of the MIT License.
146 |
--------------------------------------------------------------------------------
/demo/.doolrc:
--------------------------------------------------------------------------------
1 | {
2 | entry: {
3 | bundle: './index.js'
4 | },
5 | outputPath: '.',
6 | babelPlugins: [
7 | ['@babel/plugin-transform-react-jsx', {
8 | 'pragma': 'h'
9 | }]
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | Gantt Demo
2 | ==========
3 |
4 | ```
5 | npm i dool -g
6 |
7 | dool server
8 | dool build
9 | ```
--------------------------------------------------------------------------------
/demo/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d-band/gantt/2a842e03c9356011122b42b0f68ed504262ab8d9/demo/image.png
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Gantt Chart Demo
6 |
24 |
25 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | SVGGantt,
3 | CanvasGantt,
4 | StrGantt,
5 | utils
6 | } from '../src';
7 | import { getData, formatXML } from './utils';
8 |
9 | const $ = s => document.querySelector(s);
10 | const { tasks, links } = getData();
11 |
12 | const data = utils.formatData(tasks, links);
13 | const opts = {
14 | viewMode: 'week',
15 | onClick: v => console.log(v)
16 | };
17 | const svgGantt = new SVGGantt('#svg', data, opts);
18 | const canvasGantt = new CanvasGantt('#canvas', data, opts);
19 | const strGantt = new StrGantt(data, opts);
20 |
21 | function renderStr() {
22 | $('#str').textContent = formatXML(strGantt.render());
23 | }
24 |
25 | renderStr();
26 |
27 | function changeOptions(options) {
28 | svgGantt.setOptions(options);
29 | canvasGantt.setOptions(options);
30 | strGantt.setOptions(options);
31 | renderStr();
32 | }
33 |
34 | function changeData() {
35 | const list = utils.formatData(tasks, links);
36 | svgGantt.setData(list);
37 | canvasGantt.setData(list);
38 | strGantt.setData(list);
39 | renderStr();
40 | }
41 | $('#viewMode').onchange = e => {
42 | const viewMode = e.target.value;
43 | changeOptions({ viewMode });
44 | };
45 | $('#showLinks').onchange = () => {
46 | const showLinks = $('#showLinks').checked;
47 | changeOptions({ showLinks });
48 | };
49 | $('#showDelay').onchange = () => {
50 | const showDelay = $('#showDelay').checked;
51 | changeOptions({ showDelay });
52 | };
53 | $('#autoSchedule').onclick = () => {
54 | utils.autoSchedule(tasks, links);
55 | changeData();
56 | };
57 |
58 | function addLink(s, e) {
59 | const sid = parseInt(s.dataset['id']);
60 | const eid = parseInt(e.dataset['id']);
61 | const snode = tasks.find(t => t.id === sid);
62 | const enode = tasks.find(t => t.id === eid);
63 | let stype = isStart(s) ? 'S' : 'F';
64 | let etype = isStart(e) ? 'S' : 'F';
65 | if (snode.type === 'milestone') {
66 | stype = 'F';
67 | }
68 | if (enode.type === 'milestone') {
69 | etype = 'S';
70 | }
71 | links.push({ source: sid, target: eid, type: `${stype}${etype}` });
72 | changeData();
73 | }
74 |
75 | const NS = 'http://www.w3.org/2000/svg';
76 |
77 | let $svg = null;
78 | let moving = false;
79 | let $start = null;
80 | let $line = null;
81 |
82 | function isStart(el) {
83 | return el.classList.contains('gantt-ctrl-start');
84 | }
85 |
86 | function isFinish(el) {
87 | return el.classList.contains('gantt-ctrl-finish');
88 | }
89 |
90 | document.onmousedown = (e) => {
91 | $svg = $('svg');
92 | if (!isStart(e.target) && !isFinish(e.target)) {
93 | return;
94 | }
95 | e.preventDefault();
96 | $start = e.target;
97 | document.querySelectorAll('.gantt-ctrl-start,.gantt-ctrl-finish').forEach(elem => {
98 | elem.style['display'] = 'block';
99 | });
100 | moving = true;
101 | $line = document.createElementNS(NS, 'line');
102 | const x = $start.getAttribute('cx');
103 | const y = $start.getAttribute('cy');
104 | $line.setAttribute('x1', x);
105 | $line.setAttribute('y1', y);
106 | $line.setAttribute('x2', x);
107 | $line.setAttribute('y2', y);
108 | $line.style['stroke'] = '#ffa011';
109 | $line.style['stroke-width'] = '2';
110 | $line.style['stroke-dasharray'] = '5';
111 | $svg.appendChild($line);
112 | };
113 |
114 | document.onmousemove = (e) => {
115 | if (!moving) return;
116 | e.preventDefault();
117 | if (isStart(e.target) || isFinish(e.target)) {
118 | const x = e.target.getAttribute('cx');
119 | const y = e.target.getAttribute('cy');
120 | $line.setAttribute('x2', x);
121 | $line.setAttribute('y2', y);
122 | } else {
123 | const x = e.clientX;
124 | const y = e.clientY;
125 | const rect = $svg.getBoundingClientRect();
126 | $line.setAttribute('x2', x - rect.left);
127 | $line.setAttribute('y2', y - rect.top);
128 | }
129 | };
130 |
131 | document.onmouseup = (e) => {
132 | if (!moving) return;
133 | e.preventDefault();
134 | const isCtrl = isStart(e.target) || isFinish(e.target);
135 | if ($start && isCtrl) {
136 | addLink($start, e.target);
137 | }
138 |
139 | document.querySelectorAll('.gantt-ctrl-start,.gantt-ctrl-finish').forEach(elem => {
140 | elem.style['display'] = 'none';
141 | });
142 | moving = false;
143 | if ($start) {
144 | $start.style['display'] = 'none';
145 | $start = null;
146 | }
147 | if ($line) {
148 | $svg.removeChild($line);
149 | $line = null;
150 | }
151 | };
152 |
--------------------------------------------------------------------------------
/demo/utils.js:
--------------------------------------------------------------------------------
1 | function rand(begin) {
2 | let date;
3 | let days;
4 | if (begin) {
5 | days = Math.random() * 60 + 5;
6 | date = new Date(begin);
7 | } else {
8 | days = Math.random() * 60 - 60;
9 | date = new Date();
10 | }
11 | date.setDate(date.getDate() + days);
12 | return date;
13 | }
14 |
15 | export function getData() {
16 | const tasks = [{
17 | id: 1,
18 | name: 'Waterfall model'
19 | }, {
20 | id: 11,
21 | parent: 1,
22 | name: 'Requirements'
23 | }, {
24 | id: 12,
25 | parent: 1,
26 | name: 'Design'
27 | }, {
28 | id: 13,
29 | parent: 1,
30 | name: 'Implement',
31 | type: 'milestone'
32 | }, {
33 | id: 14,
34 | parent: 1,
35 | name: 'Verification'
36 | }, {
37 | id: 2,
38 | name: 'Development'
39 | }, {
40 | id: 21,
41 | parent: 2,
42 | name: 'Preliminary'
43 | }, {
44 | id: 22,
45 | parent: 2,
46 | name: 'Systems design'
47 | }, {
48 | id: 23,
49 | parent: 2,
50 | name: 'Development'
51 | }, {
52 | id: 24,
53 | parent: 2,
54 | name: 'Integration'
55 | }];
56 | tasks.forEach((v) => {
57 | v.start = rand();
58 | v.duration = Math.random() * 90;
59 | v.percent = Math.random();
60 | });
61 | const links = [{
62 | source: 11,
63 | target: 12,
64 | type: 'FS',
65 | lag: 3
66 | }, {
67 | source: 12,
68 | target: 13,
69 | type: 'FS',
70 | lag: 5
71 | }, {
72 | source: 13,
73 | target: 14,
74 | type: 'FS'
75 | }, {
76 | source: 13,
77 | target: 21,
78 | type: 'FS'
79 | }, {
80 | source: 23,
81 | target: 24,
82 | type: 'SF'
83 | }];
84 | return { tasks, links };
85 | }
86 |
87 | export function formatXML (xml) {
88 | var reg = /(>)\s*(<)(\/*)/g; // updated Mar 30, 2015
89 | var wsexp = / *(.*) +\n/g;
90 | var contexp = /(<.+>)(.+\n)/g;
91 | xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
92 | var pad = 0;
93 | var formatted = '';
94 | var lines = xml.split('\n');
95 | var indent = 0;
96 | var lastType = 'other';
97 | // 4 types of tags - single, closing, opening, other (text, doctype, comment) - 4*4 = 16 transitions
98 | var transitions = {
99 | 'single->single': 0,
100 | 'single->closing': -1,
101 | 'single->opening': 0,
102 | 'single->other': 0,
103 | 'closing->single': 0,
104 | 'closing->closing': -1,
105 | 'closing->opening': 0,
106 | 'closing->other': 0,
107 | 'opening->single': 1,
108 | 'opening->closing': 0,
109 | 'opening->opening': 1,
110 | 'opening->other': 1,
111 | 'other->single': 0,
112 | 'other->closing': -1,
113 | 'other->opening': 0,
114 | 'other->other': 0
115 | };
116 |
117 | for (var i = 0; i < lines.length; i++) {
118 | var ln = lines[i];
119 |
120 | // Luca Viggiani 2017-07-03: handle optional declaration
121 | if (ln.match(/\s*<\?xml/)) {
122 | formatted += ln + '\n';
123 | continue;
124 | }
125 |
126 | var single = Boolean(ln.match(/<.+\/>/)); // is this line a single tag? ex.
127 | var closing = Boolean(ln.match(/<\/.+>/)); // is this a closing tag? ex.
128 | var opening = Boolean(ln.match(/<[^!].*>/)); // is this even a tag (that's not )
129 | var type = single ? 'single' : closing ? 'closing' : opening ? 'opening' : 'other';
130 | var fromTo = lastType + '->' + type;
131 | lastType = type;
132 | var padding = '';
133 |
134 | indent += transitions[fromTo];
135 | for (var j = 0; j < indent; j++) {
136 | padding += ' ';
137 | }
138 | if (fromTo == 'opening->closing')
139 | formatted = formatted.substr(0, formatted.length - 1) + ln + '\n'; // substr removes line break (\n) from prev loop
140 | else
141 | formatted += padding + ln + '\n';
142 | }
143 |
144 | return formatted;
145 | }
146 |
--------------------------------------------------------------------------------
/dist/gantt.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3 | typeof define === 'function' && define.amd ? define(['exports'], factory) :
4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Gantt = {}));
5 | }(this, (function (exports) { 'use strict';
6 |
7 | function getDefaultExportFromCjs (x) {
8 | return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
9 | }
10 |
11 | function createCommonjsModule(fn, basedir, module) {
12 | return module = {
13 | path: basedir,
14 | exports: {},
15 | require: function (path, base) {
16 | return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
17 | }
18 | }, fn(module, module.exports), module.exports;
19 | }
20 |
21 | function commonjsRequire () {
22 | throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
23 | }
24 |
25 | var _extends_1 = createCommonjsModule(function (module) {
26 | function _extends() {
27 | module.exports = _extends = Object.assign || function (target) {
28 | for (var i = 1; i < arguments.length; i++) {
29 | var source = arguments[i];
30 |
31 | for (var key in source) {
32 | if (Object.prototype.hasOwnProperty.call(source, key)) {
33 | target[key] = source[key];
34 | }
35 | }
36 | }
37 |
38 | return target;
39 | };
40 |
41 | module.exports["default"] = module.exports, module.exports.__esModule = true;
42 | return _extends.apply(this, arguments);
43 | }
44 |
45 | module.exports = _extends;
46 | module.exports["default"] = module.exports, module.exports.__esModule = true;
47 | });
48 |
49 | var _extends = /*@__PURE__*/getDefaultExportFromCjs(_extends_1);
50 |
51 | var defineProperty = createCommonjsModule(function (module) {
52 | function _defineProperty(obj, key, value) {
53 | if (key in obj) {
54 | Object.defineProperty(obj, key, {
55 | value: value,
56 | enumerable: true,
57 | configurable: true,
58 | writable: true
59 | });
60 | } else {
61 | obj[key] = value;
62 | }
63 |
64 | return obj;
65 | }
66 |
67 | module.exports = _defineProperty;
68 | module.exports["default"] = module.exports, module.exports.__esModule = true;
69 | });
70 |
71 | var _defineProperty = /*@__PURE__*/getDefaultExportFromCjs(defineProperty);
72 |
73 | var classCallCheck = createCommonjsModule(function (module) {
74 | function _classCallCheck(instance, Constructor) {
75 | if (!(instance instanceof Constructor)) {
76 | throw new TypeError("Cannot call a class as a function");
77 | }
78 | }
79 |
80 | module.exports = _classCallCheck;
81 | module.exports["default"] = module.exports, module.exports.__esModule = true;
82 | });
83 |
84 | var _classCallCheck = /*@__PURE__*/getDefaultExportFromCjs(classCallCheck);
85 |
86 | var createClass = createCommonjsModule(function (module) {
87 | function _defineProperties(target, props) {
88 | for (var i = 0; i < props.length; i++) {
89 | var descriptor = props[i];
90 | descriptor.enumerable = descriptor.enumerable || false;
91 | descriptor.configurable = true;
92 | if ("value" in descriptor) descriptor.writable = true;
93 | Object.defineProperty(target, descriptor.key, descriptor);
94 | }
95 | }
96 |
97 | function _createClass(Constructor, protoProps, staticProps) {
98 | if (protoProps) _defineProperties(Constructor.prototype, protoProps);
99 | if (staticProps) _defineProperties(Constructor, staticProps);
100 | return Constructor;
101 | }
102 |
103 | module.exports = _createClass;
104 | module.exports["default"] = module.exports, module.exports.__esModule = true;
105 | });
106 |
107 | var _createClass = /*@__PURE__*/getDefaultExportFromCjs(createClass);
108 |
109 | function ownKeys$5(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
110 |
111 | function _objectSpread$5(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys$5(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys$5(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
112 |
113 | function addChild(c, childNodes) {
114 | if (c === null || c === undefined) return;
115 |
116 | if (typeof c === 'string' || typeof c === 'number') {
117 | childNodes.push(c.toString());
118 | } else if (Array.isArray(c)) {
119 | for (var i = 0; i < c.length; i++) {
120 | addChild(c[i], childNodes);
121 | }
122 | } else {
123 | childNodes.push(c);
124 | }
125 | }
126 |
127 | function h(tag, props) {
128 | var childNodes = [];
129 |
130 | for (var _len = arguments.length, children = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
131 | children[_key - 2] = arguments[_key];
132 | }
133 |
134 | addChild(children, childNodes);
135 |
136 | if (typeof tag === 'function') {
137 | return tag(_objectSpread$5(_objectSpread$5({}, props), {}, {
138 | children: childNodes
139 | }));
140 | }
141 |
142 | return {
143 | tag: tag,
144 | props: props,
145 | children: childNodes
146 | };
147 | }
148 |
149 | function ownKeys$4(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
150 |
151 | function _objectSpread$4(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys$4(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys$4(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
152 |
153 | var DAY = 24 * 3600 * 1000;
154 | function addDays(date, days) {
155 | var d = new Date(date.valueOf());
156 | d.setDate(d.getDate() + days);
157 | return d;
158 | }
159 | function getDates(begin, end) {
160 | var dates = [];
161 | var s = new Date(begin);
162 | s.setHours(24, 0, 0, 0);
163 |
164 | while (s.getTime() <= end) {
165 | dates.push(s.getTime());
166 | s = addDays(s, 1);
167 | }
168 |
169 | return dates;
170 | }
171 | var ctx = null;
172 | function textWidth(text, font, pad) {
173 | ctx = ctx || document.createElement('canvas').getContext('2d');
174 | ctx.font = font;
175 | return ctx.measureText(text).width + pad;
176 | }
177 | function formatMonth(date) {
178 | var y = date.getFullYear();
179 | var m = date.getMonth() + 1;
180 | return "".concat(y, "/").concat(m > 9 ? m : "0".concat(m));
181 | }
182 | function formatDay(date) {
183 | var m = date.getMonth() + 1;
184 | var d = date.getDate();
185 | return "".concat(m, "/").concat(d);
186 | }
187 | function minDate(a, b) {
188 | if (a && b) {
189 | return a > b ? b : a;
190 | }
191 |
192 | return a || b;
193 | }
194 | function maxDate(a, b) {
195 | if (a && b) {
196 | return a < b ? b : a;
197 | }
198 |
199 | return a || b;
200 | }
201 | function max(list, defaultValue) {
202 | if (list.length) {
203 | return Math.max.apply(null, list);
204 | }
205 |
206 | return defaultValue;
207 | }
208 | function p2s(arr) {
209 | return arr.map(function (p) {
210 | return "".concat(p[0], ",").concat(p[1]);
211 | }).join(' ');
212 | }
213 | function s2p(str) {
214 | return str.split(' ').map(function (s) {
215 | var p = s.split(',');
216 | return [parseFloat(p[0]), parseFloat(p[1])];
217 | });
218 | }
219 |
220 | function walkLevel(nodes, level) {
221 | for (var i = 0; i < nodes.length; i++) {
222 | var node = nodes[i];
223 | node.level = "".concat(level).concat(i + 1);
224 | node.text = "".concat(node.level, " ").concat(node.name);
225 | walkLevel(node.children, "".concat(node.level, "."));
226 | }
227 | }
228 |
229 | function walkDates(nodes) {
230 | var start = null;
231 | var end = null;
232 | var percent = 0;
233 |
234 | for (var i = 0; i < nodes.length; i++) {
235 | var node = nodes[i];
236 |
237 | if (node.children.length) {
238 | var tmp = walkDates(node.children);
239 | node.start = tmp.start;
240 | node.end = tmp.end;
241 | node.percent = tmp.percent;
242 |
243 | if (tmp.start && tmp.end) {
244 | node.duration = (tmp.end - tmp.start) / DAY;
245 | } else {
246 | node.duration = 0;
247 | }
248 | } else {
249 | node.percent = node.percent || 0;
250 |
251 | if (node.start) {
252 | node.end = addDays(node.start, node.duration || 0);
253 | }
254 |
255 | if (node.type === 'milestone') {
256 | node.end = node.start;
257 | }
258 | }
259 |
260 | start = minDate(start, node.start);
261 | end = maxDate(end, node.end);
262 | percent += node.percent;
263 | }
264 |
265 | if (nodes.length) {
266 | percent /= nodes.length;
267 | }
268 |
269 | return {
270 | start: start,
271 | end: end,
272 | percent: percent
273 | };
274 | }
275 |
276 | function formatData(tasks, links, walk) {
277 | var map = {};
278 | var tmp = tasks.map(function (t, i) {
279 | map[t.id] = i;
280 | return _objectSpread$4(_objectSpread$4({}, t), {}, {
281 | children: [],
282 | links: []
283 | });
284 | });
285 | var roots = [];
286 | tmp.forEach(function (t) {
287 | var parent = tmp[map[t.parent]];
288 |
289 | if (parent) {
290 | parent.children.push(t);
291 | } else {
292 | roots.push(t);
293 | }
294 | });
295 | links.forEach(function (l) {
296 | var s = tmp[map[l.source]];
297 | var t = tmp[map[l.target]];
298 |
299 | if (s && t) {
300 | s.links.push(l);
301 | }
302 | });
303 | walkLevel(roots, '');
304 | walkDates(roots);
305 |
306 | if (walk) {
307 | walk(roots);
308 | }
309 |
310 | var list = [];
311 | roots.forEach(function (r) {
312 | var stack = [];
313 | stack.push(r);
314 |
315 | while (stack.length) {
316 | var node = stack.pop();
317 | var len = node.children.length;
318 |
319 | if (len) {
320 | node.type = 'group';
321 | }
322 |
323 | list.push(node);
324 |
325 | for (var i = len - 1; i >= 0; i--) {
326 | stack.push(node.children[i]);
327 | }
328 | }
329 | });
330 | return list;
331 | }
332 | function hasPath(vmap, a, b) {
333 | var stack = [];
334 | stack.push(vmap[a]);
335 |
336 | while (stack.length) {
337 | var v = stack.pop();
338 |
339 | if (v.id === b) {
340 | return true;
341 | }
342 |
343 | for (var i = 0; i < v.links.length; i++) {
344 | stack.push(v.links[i]);
345 | }
346 | }
347 |
348 | return false;
349 | }
350 | function toposort(links) {
351 | var vmap = {};
352 | links.forEach(function (l) {
353 | var init = function init(id) {
354 | return {
355 | id: id,
356 | out: [],
357 | "in": 0
358 | };
359 | };
360 |
361 | vmap[l.source] = init(l.source);
362 | vmap[l.target] = init(l.target);
363 | });
364 |
365 | for (var i = 0; i < links.length; i++) {
366 | var l = links[i];
367 | vmap[l.target]["in"]++;
368 | vmap[l.source].out.push(i);
369 | }
370 |
371 | var s = Object.keys(vmap).map(function (k) {
372 | return vmap[k].id;
373 | }).filter(function (id) {
374 | return !vmap[id]["in"];
375 | });
376 | var sorted = [];
377 |
378 | while (s.length) {
379 | var id = s.pop();
380 | sorted.push(id);
381 |
382 | for (var _i = 0; _i < vmap[id].out.length; _i++) {
383 | var index = vmap[id].out[_i];
384 | var v = vmap[links[index].target];
385 | v["in"]--;
386 |
387 | if (!v["in"]) {
388 | s.push(v.id);
389 | }
390 | }
391 | }
392 |
393 | return sorted;
394 | }
395 | function autoSchedule(tasks, links) {
396 | var lockMilestone = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
397 | var vmap = {};
398 | links.forEach(function (l) {
399 | vmap[l.source] = {
400 | id: l.source,
401 | links: []
402 | };
403 | vmap[l.target] = {
404 | id: l.target,
405 | links: []
406 | };
407 | });
408 | var dag = [];
409 | links.forEach(function (l) {
410 | var source = l.source,
411 | target = l.target;
412 |
413 | if (!hasPath(vmap, target, source)) {
414 | dag.push(l);
415 | vmap[source].links.push(vmap[target]);
416 | }
417 | });
418 | var sorted = toposort(dag);
419 | var tmap = {};
420 |
421 | for (var i = 0; i < tasks.length; i++) {
422 | var task = tasks[i];
423 |
424 | if (task.type === 'milestone') {
425 | task.duration = 0;
426 | }
427 |
428 | tmap[task.id] = i;
429 | }
430 |
431 | var ins = {};
432 | sorted.forEach(function (id) {
433 | ins[id] = [];
434 | });
435 | dag.forEach(function (l) {
436 | ins[l.target].push(l);
437 | });
438 | sorted.forEach(function (id) {
439 | var task = tasks[tmap[id]];
440 | if (!task) return;
441 | var days = task.duration || 0;
442 |
443 | if (lockMilestone && task.type === 'milestone') {
444 | return;
445 | }
446 |
447 | var start = null;
448 | var end = null;
449 |
450 | for (var _i2 = 0; _i2 < ins[id].length; _i2++) {
451 | var l = ins[id][_i2];
452 | var v = tasks[tmap[l.source]];
453 |
454 | if (v && v.start) {
455 | var s = addDays(v.start, l.lag || 0);
456 | var e = addDays(s, v.duration || 0);
457 |
458 | if (l.type === 'SS') {
459 | start = maxDate(start, s);
460 | }
461 |
462 | if (l.type === 'FS') {
463 | start = maxDate(start, e);
464 | }
465 |
466 | if (l.type === 'SF') {
467 | end = maxDate(end, s);
468 | }
469 |
470 | if (l.type === 'FF') {
471 | end = maxDate(end, e);
472 | }
473 | }
474 | }
475 |
476 | if (end) {
477 | task.start = addDays(end, -days);
478 | }
479 |
480 | if (start) {
481 | task.start = start;
482 | }
483 | });
484 | }
485 |
486 | var utils = /*#__PURE__*/Object.freeze({
487 | __proto__: null,
488 | DAY: DAY,
489 | addDays: addDays,
490 | getDates: getDates,
491 | textWidth: textWidth,
492 | formatMonth: formatMonth,
493 | formatDay: formatDay,
494 | minDate: minDate,
495 | maxDate: maxDate,
496 | max: max,
497 | p2s: p2s,
498 | s2p: s2p,
499 | formatData: formatData,
500 | hasPath: hasPath,
501 | toposort: toposort,
502 | autoSchedule: autoSchedule
503 | });
504 |
505 | function Layout(_ref) {
506 | var styles = _ref.styles,
507 | width = _ref.width,
508 | height = _ref.height,
509 | offsetY = _ref.offsetY,
510 | thickWidth = _ref.thickWidth,
511 | maxTextWidth = _ref.maxTextWidth;
512 | var x0 = thickWidth / 2;
513 | var W = width - thickWidth;
514 | var H = height - thickWidth;
515 | return h("g", null, h("rect", {
516 | x: x0,
517 | y: x0,
518 | width: W,
519 | height: H,
520 | style: styles.box
521 | }), h("line", {
522 | x1: 0,
523 | x2: width,
524 | y1: offsetY - x0,
525 | y2: offsetY - x0,
526 | style: styles.bline
527 | }), h("line", {
528 | x1: maxTextWidth,
529 | x2: width,
530 | y1: offsetY / 2,
531 | y2: offsetY / 2,
532 | style: styles.line
533 | }));
534 | }
535 |
536 | function YearMonth(_ref) {
537 | var styles = _ref.styles,
538 | dates = _ref.dates,
539 | unit = _ref.unit,
540 | offsetY = _ref.offsetY,
541 | minTime = _ref.minTime,
542 | maxTime = _ref.maxTime,
543 | maxTextWidth = _ref.maxTextWidth;
544 | var months = dates.filter(function (v) {
545 | return new Date(v).getDate() === 1;
546 | });
547 | months.unshift(minTime);
548 | months.push(maxTime);
549 | var ticks = [];
550 | var x0 = maxTextWidth;
551 | var y2 = offsetY / 2;
552 | var len = months.length - 1;
553 |
554 | for (var i = 0; i < len; i++) {
555 | var cur = new Date(months[i]);
556 | var str = formatMonth(cur);
557 | var x = x0 + (months[i] - minTime) / unit;
558 | var t = (months[i + 1] - months[i]) / unit;
559 | ticks.push(h("g", null, h("line", {
560 | x1: x,
561 | x2: x,
562 | y1: 0,
563 | y2: y2,
564 | style: styles.line
565 | }), t > 50 ? h("text", {
566 | x: x + t / 2,
567 | y: offsetY * 0.25,
568 | style: styles.text3
569 | }, str) : null));
570 | }
571 |
572 | return h("g", null, ticks);
573 | }
574 |
575 | function DayHeader(_ref) {
576 | var styles = _ref.styles,
577 | unit = _ref.unit,
578 | minTime = _ref.minTime,
579 | maxTime = _ref.maxTime,
580 | height = _ref.height,
581 | offsetY = _ref.offsetY,
582 | maxTextWidth = _ref.maxTextWidth;
583 | var dates = getDates(minTime, maxTime);
584 | var ticks = [];
585 | var x0 = maxTextWidth;
586 | var y0 = offsetY / 2;
587 | var RH = height - y0;
588 | var len = dates.length - 1;
589 |
590 | for (var i = 0; i < len; i++) {
591 | var cur = new Date(dates[i]);
592 | var day = cur.getDay();
593 | var x = x0 + (dates[i] - minTime) / unit;
594 | var t = (dates[i + 1] - dates[i]) / unit;
595 | ticks.push(h("g", null, day === 0 || day === 6 ? h("rect", {
596 | x: x,
597 | y: y0,
598 | width: t,
599 | height: RH,
600 | style: styles.week
601 | }) : null, h("line", {
602 | x1: x,
603 | x2: x,
604 | y1: y0,
605 | y2: offsetY,
606 | style: styles.line
607 | }), h("text", {
608 | x: x + t / 2,
609 | y: offsetY * 0.75,
610 | style: styles.text3
611 | }, cur.getDate()), i === len - 1 ? h("line", {
612 | x1: x + t,
613 | x2: x + t,
614 | y1: y0,
615 | y2: offsetY,
616 | style: styles.line
617 | }) : null));
618 | }
619 |
620 | return h("g", null, h(YearMonth, {
621 | styles: styles,
622 | unit: unit,
623 | dates: dates,
624 | offsetY: offsetY,
625 | minTime: minTime,
626 | maxTime: maxTime,
627 | maxTextWidth: maxTextWidth
628 | }), ticks);
629 | }
630 |
631 | function WeekHeader(_ref) {
632 | var styles = _ref.styles,
633 | unit = _ref.unit,
634 | minTime = _ref.minTime,
635 | maxTime = _ref.maxTime,
636 | height = _ref.height,
637 | offsetY = _ref.offsetY,
638 | maxTextWidth = _ref.maxTextWidth;
639 | var dates = getDates(minTime, maxTime);
640 | var weeks = dates.filter(function (v) {
641 | return new Date(v).getDay() === 0;
642 | });
643 | weeks.push(maxTime);
644 | var ticks = [];
645 | var x0 = maxTextWidth;
646 | var y0 = offsetY;
647 | var RH = height - y0;
648 | var d = DAY / unit;
649 | var len = weeks.length - 1;
650 |
651 | for (var i = 0; i < len; i++) {
652 | var cur = new Date(weeks[i]);
653 | var x = x0 + (weeks[i] - minTime) / unit;
654 | var curDay = cur.getDate();
655 | var prevDay = addDays(cur, -1).getDate();
656 | ticks.push(h("g", null, h("rect", {
657 | x: x - d,
658 | y: y0,
659 | width: d * 2,
660 | height: RH,
661 | style: styles.week
662 | }), h("line", {
663 | x1: x,
664 | x2: x,
665 | y1: offsetY / 2,
666 | y2: offsetY,
667 | style: styles.line
668 | }), h("text", {
669 | x: x + 3,
670 | y: offsetY * 0.75,
671 | style: styles.text2
672 | }, curDay), x - x0 > 28 ? h("text", {
673 | x: x - 3,
674 | y: offsetY * 0.75,
675 | style: styles.text1
676 | }, prevDay) : null));
677 | }
678 |
679 | return h("g", null, h(YearMonth, {
680 | styles: styles,
681 | unit: unit,
682 | dates: dates,
683 | offsetY: offsetY,
684 | minTime: minTime,
685 | maxTime: maxTime,
686 | maxTextWidth: maxTextWidth
687 | }), ticks);
688 | }
689 |
690 | function Year(_ref) {
691 | var styles = _ref.styles,
692 | months = _ref.months,
693 | unit = _ref.unit,
694 | offsetY = _ref.offsetY,
695 | minTime = _ref.minTime,
696 | maxTime = _ref.maxTime,
697 | maxTextWidth = _ref.maxTextWidth;
698 | var years = months.filter(function (v) {
699 | return new Date(v).getMonth() === 0;
700 | });
701 | years.unshift(minTime);
702 | years.push(maxTime);
703 | var ticks = [];
704 | var x0 = maxTextWidth;
705 | var y2 = offsetY / 2;
706 | var len = years.length - 1;
707 |
708 | for (var i = 0; i < len; i++) {
709 | var cur = new Date(years[i]);
710 | var x = x0 + (years[i] - minTime) / unit;
711 | var t = (years[i + 1] - years[i]) / unit;
712 | ticks.push(h("g", null, h("line", {
713 | x1: x,
714 | x2: x,
715 | y1: 0,
716 | y2: y2,
717 | style: styles.line
718 | }), t > 35 ? h("text", {
719 | x: x + t / 2,
720 | y: offsetY * 0.25,
721 | style: styles.text3
722 | }, cur.getFullYear()) : null));
723 | }
724 |
725 | return h("g", null, ticks);
726 | }
727 |
728 | function MonthHeader(_ref) {
729 | var styles = _ref.styles,
730 | unit = _ref.unit,
731 | minTime = _ref.minTime,
732 | maxTime = _ref.maxTime,
733 | offsetY = _ref.offsetY,
734 | maxTextWidth = _ref.maxTextWidth;
735 | var MONTH = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
736 | var dates = getDates(minTime, maxTime);
737 | var months = dates.filter(function (v) {
738 | return new Date(v).getDate() === 1;
739 | });
740 | months.unshift(minTime);
741 | months.push(maxTime);
742 | var ticks = [];
743 | var x0 = maxTextWidth;
744 | var y0 = offsetY / 2;
745 | var len = months.length - 1;
746 |
747 | for (var i = 0; i < len; i++) {
748 | var cur = new Date(months[i]);
749 | var month = cur.getMonth();
750 | var x = x0 + (months[i] - minTime) / unit;
751 | var t = (months[i + 1] - months[i]) / unit;
752 | ticks.push(h("g", null, h("line", {
753 | x1: x,
754 | x2: x,
755 | y1: y0,
756 | y2: offsetY,
757 | style: styles.line
758 | }), t > 30 ? h("text", {
759 | x: x + t / 2,
760 | y: offsetY * 0.75,
761 | style: styles.text3
762 | }, MONTH[month]) : null));
763 | }
764 |
765 | return h("g", null, h(Year, {
766 | styles: styles,
767 | unit: unit,
768 | months: months,
769 | offsetY: offsetY,
770 | minTime: minTime,
771 | maxTime: maxTime,
772 | maxTextWidth: maxTextWidth
773 | }), ticks);
774 | }
775 |
776 | function Grid(_ref) {
777 | var styles = _ref.styles,
778 | data = _ref.data,
779 | width = _ref.width,
780 | height = _ref.height,
781 | offsetY = _ref.offsetY,
782 | rowHeight = _ref.rowHeight,
783 | maxTextWidth = _ref.maxTextWidth;
784 | return h("g", null, data.map(function (v, i) {
785 | var y = (i + 1) * rowHeight + offsetY;
786 | return h("line", {
787 | key: i,
788 | x1: 0,
789 | x2: width,
790 | y1: y,
791 | y2: y,
792 | style: styles.line
793 | });
794 | }), h("line", {
795 | x1: maxTextWidth,
796 | x2: maxTextWidth,
797 | y1: 0,
798 | y2: height,
799 | style: styles.bline
800 | }));
801 | }
802 |
803 | function Labels(_ref) {
804 | var styles = _ref.styles,
805 | data = _ref.data,
806 | _onClick = _ref.onClick,
807 | rowHeight = _ref.rowHeight,
808 | offsetY = _ref.offsetY;
809 | return h("g", null, data.map(function (v, i) {
810 | return h("text", {
811 | key: i,
812 | x: 10,
813 | y: (i + 0.5) * rowHeight + offsetY,
814 | "class": "gantt-label",
815 | style: styles.label,
816 | onClick: function onClick() {
817 | return _onClick(v);
818 | }
819 | }, v.text);
820 | }));
821 | }
822 |
823 | function LinkLine(_ref) {
824 | var styles = _ref.styles,
825 | data = _ref.data,
826 | unit = _ref.unit,
827 | offsetY = _ref.offsetY,
828 | minTime = _ref.minTime,
829 | rowHeight = _ref.rowHeight,
830 | barHeight = _ref.barHeight,
831 | maxTextWidth = _ref.maxTextWidth;
832 | var x0 = maxTextWidth;
833 | var y0 = rowHeight / 2 + offsetY;
834 | var map = {};
835 | data.forEach(function (v, i) {
836 | map[v.id] = i;
837 | });
838 | return h("g", null, data.map(function (s, i) {
839 | if (!s.end || !s.start || !s.links) {
840 | return null;
841 | }
842 |
843 | return s.links.map(function (l) {
844 | var j = map[l.target];
845 | var e = data[j];
846 | if (!e || !e.start || !e.end) return null;
847 | var gap = 12;
848 | var arrow = 6;
849 | var mgap = e.type === 'milestone' ? barHeight / 2 : 0;
850 | var y1 = y0 + i * rowHeight;
851 | var y2 = y0 + j * rowHeight;
852 | var vgap = barHeight / 2 + 4;
853 |
854 | if (y1 > y2) {
855 | vgap = -vgap;
856 | }
857 |
858 | if (l.type === 'FS') {
859 | var x1 = x0 + (s.end - minTime) / unit;
860 | var x2 = x0 + (e.start - minTime) / unit - mgap;
861 | var p1 = [[x1, y1], [x1 + gap, y1]];
862 |
863 | if (x2 - x1 >= 2 * gap) {
864 | p1.push([x1 + gap, y2]);
865 | } else {
866 | p1.push([x1 + gap, y2 - vgap]);
867 | p1.push([x2 - gap, y2 - vgap]);
868 | p1.push([x2 - gap, y2]);
869 | }
870 |
871 | p1.push([x2 - arrow, y2]);
872 | var p2 = [[x2 - arrow, y2 - arrow], [x2, y2], [x2 - arrow, y2 + arrow]];
873 | return h("g", null, h("polyline", {
874 | points: p2s(p1),
875 | style: styles.link
876 | }), h("polygon", {
877 | points: p2s(p2),
878 | style: styles.linkArrow
879 | }));
880 | }
881 |
882 | if (l.type === 'FF') {
883 | var _x = x0 + (s.end - minTime) / unit;
884 |
885 | var _x2 = x0 + (e.end - minTime) / unit + mgap;
886 |
887 | var _p = [[_x, y1], [_x + gap, y1]];
888 |
889 | if (_x2 <= _x) {
890 | _p.push([_x + gap, y2]);
891 | } else {
892 | _p.push([_x + gap, y2 - vgap]);
893 |
894 | _p.push([_x2 + gap, y2 - vgap]);
895 |
896 | _p.push([_x2 + gap, y2]);
897 | }
898 |
899 | _p.push([_x2 + arrow, y2]);
900 |
901 | var _p2 = [[_x2 + arrow, y2 - arrow], [_x2, y2], [_x2 + arrow, y2 + arrow]];
902 | return h("g", null, h("polyline", {
903 | points: p2s(_p),
904 | style: styles.link
905 | }), h("polygon", {
906 | points: p2s(_p2),
907 | style: styles.linkArrow
908 | }));
909 | }
910 |
911 | if (l.type === 'SS') {
912 | var _x3 = x0 + (s.start - minTime) / unit;
913 |
914 | var _x4 = x0 + (e.start - minTime) / unit - mgap;
915 |
916 | var _p3 = [[_x3, y1], [_x3 - gap, y1]];
917 |
918 | if (_x3 <= _x4) {
919 | _p3.push([_x3 - gap, y2]);
920 | } else {
921 | _p3.push([_x3 - gap, y2 - vgap]);
922 |
923 | _p3.push([_x4 - gap, y2 - vgap]);
924 |
925 | _p3.push([_x4 - gap, y2]);
926 | }
927 |
928 | _p3.push([_x4 - arrow, y2]);
929 |
930 | var _p4 = [[_x4 - arrow, y2 - arrow], [_x4, y2], [_x4 - arrow, y2 + arrow]];
931 | return h("g", null, h("polyline", {
932 | points: p2s(_p3),
933 | style: styles.link
934 | }), h("polygon", {
935 | points: p2s(_p4),
936 | style: styles.linkArrow
937 | }));
938 | }
939 |
940 | if (l.type === 'SF') {
941 | var _x5 = x0 + (s.start - minTime) / unit;
942 |
943 | var _x6 = x0 + (e.end - minTime) / unit + mgap;
944 |
945 | var _p5 = [[_x5, y1], [_x5 - gap, y1]];
946 |
947 | if (_x5 - _x6 >= 2 * gap) {
948 | _p5.push([_x5 - gap, y2]);
949 | } else {
950 | _p5.push([_x5 - gap, y2 - vgap]);
951 |
952 | _p5.push([_x6 + gap, y2 - vgap]);
953 |
954 | _p5.push([_x6 + gap, y2]);
955 | }
956 |
957 | _p5.push([_x6 + arrow, y2]);
958 |
959 | var _p6 = [[_x6 + arrow, y2 - arrow], [_x6, y2], [_x6 + arrow, y2 + arrow]];
960 | return h("g", null, h("polyline", {
961 | points: p2s(_p5),
962 | style: styles.link
963 | }), h("polygon", {
964 | points: p2s(_p6),
965 | style: styles.linkArrow
966 | }));
967 | }
968 |
969 | return null;
970 | });
971 | }));
972 | }
973 |
974 | function Bar(_ref) {
975 | var styles = _ref.styles,
976 | data = _ref.data,
977 | unit = _ref.unit,
978 | height = _ref.height,
979 | offsetY = _ref.offsetY,
980 | minTime = _ref.minTime,
981 | showDelay = _ref.showDelay,
982 | rowHeight = _ref.rowHeight,
983 | barHeight = _ref.barHeight,
984 | maxTextWidth = _ref.maxTextWidth,
985 | current = _ref.current,
986 | onClick = _ref.onClick;
987 | var x0 = maxTextWidth;
988 | var y0 = (rowHeight - barHeight) / 2 + offsetY;
989 | var cur = x0 + (current - minTime) / unit;
990 | return h("g", null, current > minTime ? h("line", {
991 | x1: cur,
992 | x2: cur,
993 | y1: offsetY,
994 | y2: height,
995 | style: styles.cline
996 | }) : null, data.map(function (v, i) {
997 | if (!v.end || !v.start) {
998 | return null;
999 | }
1000 |
1001 | var handler = function handler() {
1002 | return onClick(v);
1003 | };
1004 |
1005 | var x = x0 + (v.start - minTime) / unit;
1006 | var y = y0 + i * rowHeight;
1007 | var cy = y + barHeight / 2;
1008 |
1009 | if (v.type === 'milestone') {
1010 | var size = barHeight / 2;
1011 | var points = [[x, cy - size], [x + size, cy], [x, cy + size], [x - size, cy]].map(function (p) {
1012 | return "".concat(p[0], ",").concat(p[1]);
1013 | }).join(' ');
1014 | return h("g", {
1015 | key: i,
1016 | "class": "gantt-bar",
1017 | style: {
1018 | cursor: 'pointer'
1019 | },
1020 | onClick: handler
1021 | }, h("polygon", {
1022 | points: points,
1023 | style: styles.milestone,
1024 | onClick: handler
1025 | }), h("circle", {
1026 | "class": "gantt-ctrl-start",
1027 | "data-id": v.id,
1028 | cx: x,
1029 | cy: cy,
1030 | r: 6,
1031 | style: styles.ctrl
1032 | }));
1033 | }
1034 |
1035 | var w1 = (v.end - v.start) / unit;
1036 | var w2 = w1 * v.percent;
1037 | var bar = v.type === 'group' ? {
1038 | back: styles.groupBack,
1039 | front: styles.groupFront
1040 | } : {
1041 | back: styles.taskBack,
1042 | front: styles.taskFront
1043 | };
1044 |
1045 | if (showDelay) {
1046 | if (x + w2 < cur && v.percent < 0.999999) {
1047 | bar.back = styles.warning;
1048 | }
1049 |
1050 | if (x + w1 < cur && v.percent < 0.999999) {
1051 | bar.back = styles.danger;
1052 | }
1053 | }
1054 |
1055 | return h("g", {
1056 | key: i,
1057 | "class": "gantt-bar",
1058 | style: {
1059 | cursor: 'pointer'
1060 | },
1061 | onClick: handler
1062 | }, h("text", {
1063 | x: x - 4,
1064 | y: cy,
1065 | style: styles.text1
1066 | }, formatDay(v.start)), h("text", {
1067 | x: x + w1 + 4,
1068 | y: cy,
1069 | style: styles.text2
1070 | }, formatDay(v.end)), h("rect", {
1071 | x: x,
1072 | y: y,
1073 | width: w1,
1074 | height: barHeight,
1075 | rx: 1.8,
1076 | ry: 1.8,
1077 | style: bar.back,
1078 | onClick: handler
1079 | }), w2 > 0.000001 ? h("rect", {
1080 | x: x,
1081 | y: y,
1082 | width: w2,
1083 | height: barHeight,
1084 | rx: 1.8,
1085 | ry: 1.8,
1086 | style: bar.front
1087 | }) : null, v.type === 'group' ? null : h("g", null, h("circle", {
1088 | "class": "gantt-ctrl-start",
1089 | "data-id": v.id,
1090 | cx: x - 12,
1091 | cy: cy,
1092 | r: 6,
1093 | style: styles.ctrl
1094 | }), h("circle", {
1095 | "class": "gantt-ctrl-finish",
1096 | "data-id": v.id,
1097 | cx: x + w1 + 12,
1098 | cy: cy,
1099 | r: 6,
1100 | style: styles.ctrl
1101 | })));
1102 | }));
1103 | }
1104 |
1105 | function ownKeys$3(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
1106 |
1107 | function _objectSpread$3(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys$3(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys$3(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
1108 |
1109 | var SIZE = '14px';
1110 | var TYPE = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
1111 | function getFont(_ref) {
1112 | var _ref$fontSize = _ref.fontSize,
1113 | fontSize = _ref$fontSize === void 0 ? SIZE : _ref$fontSize,
1114 | _ref$fontFamily = _ref.fontFamily,
1115 | fontFamily = _ref$fontFamily === void 0 ? TYPE : _ref$fontFamily;
1116 | return "bold ".concat(fontSize, " ").concat(fontFamily);
1117 | }
1118 | function getStyles(_ref2) {
1119 | var _ref2$bgColor = _ref2.bgColor,
1120 | bgColor = _ref2$bgColor === void 0 ? '#fff' : _ref2$bgColor,
1121 | _ref2$lineColor = _ref2.lineColor,
1122 | lineColor = _ref2$lineColor === void 0 ? '#eee' : _ref2$lineColor,
1123 | _ref2$redLineColor = _ref2.redLineColor,
1124 | redLineColor = _ref2$redLineColor === void 0 ? '#f04134' : _ref2$redLineColor,
1125 | _ref2$groupBack = _ref2.groupBack,
1126 | groupBack = _ref2$groupBack === void 0 ? '#3db9d3' : _ref2$groupBack,
1127 | _ref2$groupFront = _ref2.groupFront,
1128 | groupFront = _ref2$groupFront === void 0 ? '#299cb4' : _ref2$groupFront,
1129 | _ref2$taskBack = _ref2.taskBack,
1130 | taskBack = _ref2$taskBack === void 0 ? '#65c16f' : _ref2$taskBack,
1131 | _ref2$taskFront = _ref2.taskFront,
1132 | taskFront = _ref2$taskFront === void 0 ? '#46ad51' : _ref2$taskFront,
1133 | _ref2$milestone = _ref2.milestone,
1134 | milestone = _ref2$milestone === void 0 ? '#d33daf' : _ref2$milestone,
1135 | _ref2$warning = _ref2.warning,
1136 | warning = _ref2$warning === void 0 ? '#faad14' : _ref2$warning,
1137 | _ref2$danger = _ref2.danger,
1138 | danger = _ref2$danger === void 0 ? '#f5222d' : _ref2$danger,
1139 | _ref2$link = _ref2.link,
1140 | link = _ref2$link === void 0 ? '#ffa011' : _ref2$link,
1141 | _ref2$textColor = _ref2.textColor,
1142 | textColor = _ref2$textColor === void 0 ? '#222' : _ref2$textColor,
1143 | _ref2$lightTextColor = _ref2.lightTextColor,
1144 | lightTextColor = _ref2$lightTextColor === void 0 ? '#999' : _ref2$lightTextColor,
1145 | _ref2$lineWidth = _ref2.lineWidth,
1146 | lineWidth = _ref2$lineWidth === void 0 ? '1px' : _ref2$lineWidth,
1147 | _ref2$thickLineWidth = _ref2.thickLineWidth,
1148 | thickLineWidth = _ref2$thickLineWidth === void 0 ? '1.4px' : _ref2$thickLineWidth,
1149 | _ref2$fontSize = _ref2.fontSize,
1150 | fontSize = _ref2$fontSize === void 0 ? SIZE : _ref2$fontSize,
1151 | _ref2$smallFontSize = _ref2.smallFontSize,
1152 | smallFontSize = _ref2$smallFontSize === void 0 ? '12px' : _ref2$smallFontSize,
1153 | _ref2$fontFamily = _ref2.fontFamily,
1154 | fontFamily = _ref2$fontFamily === void 0 ? TYPE : _ref2$fontFamily,
1155 | _ref2$whiteSpace = _ref2.whiteSpace,
1156 | whiteSpace = _ref2$whiteSpace === void 0 ? 'pre' : _ref2$whiteSpace;
1157 | var line = {
1158 | stroke: lineColor,
1159 | 'stroke-width': lineWidth
1160 | };
1161 | var redLine = {
1162 | stroke: redLineColor,
1163 | 'stroke-width': lineWidth
1164 | };
1165 | var thickLine = {
1166 | stroke: lineColor,
1167 | 'stroke-width': thickLineWidth
1168 | };
1169 | var text = {
1170 | fill: textColor,
1171 | 'dominant-baseline': 'central',
1172 | 'font-size': fontSize,
1173 | 'font-family': fontFamily,
1174 | 'white-space': whiteSpace
1175 | };
1176 | var smallText = {
1177 | fill: lightTextColor,
1178 | 'font-size': smallFontSize
1179 | };
1180 | return {
1181 | week: {
1182 | fill: 'rgba(252, 248, 227, .6)'
1183 | },
1184 | box: _objectSpread$3(_objectSpread$3({}, thickLine), {}, {
1185 | fill: bgColor
1186 | }),
1187 | line: line,
1188 | cline: redLine,
1189 | bline: thickLine,
1190 | label: text,
1191 | groupLabel: _objectSpread$3(_objectSpread$3({}, text), {}, {
1192 | 'font-weight': '600'
1193 | }),
1194 | text1: _objectSpread$3(_objectSpread$3(_objectSpread$3({}, text), smallText), {}, {
1195 | 'text-anchor': 'end'
1196 | }),
1197 | text2: _objectSpread$3(_objectSpread$3({}, text), smallText),
1198 | text3: _objectSpread$3(_objectSpread$3(_objectSpread$3({}, text), smallText), {}, {
1199 | 'text-anchor': 'middle'
1200 | }),
1201 | link: {
1202 | stroke: link,
1203 | 'stroke-width': '1.5px',
1204 | fill: 'none'
1205 | },
1206 | linkArrow: {
1207 | fill: link
1208 | },
1209 | milestone: {
1210 | fill: milestone
1211 | },
1212 | groupBack: {
1213 | fill: groupBack
1214 | },
1215 | groupFront: {
1216 | fill: groupFront
1217 | },
1218 | taskBack: {
1219 | fill: taskBack
1220 | },
1221 | taskFront: {
1222 | fill: taskFront
1223 | },
1224 | warning: {
1225 | fill: warning
1226 | },
1227 | danger: {
1228 | fill: danger
1229 | },
1230 | ctrl: {
1231 | display: 'none',
1232 | fill: '#f0f0f0',
1233 | stroke: '#929292',
1234 | 'stroke-width': '1px'
1235 | }
1236 | };
1237 | }
1238 |
1239 | var UNIT = {
1240 | day: DAY / 28,
1241 | week: 7 * DAY / 56,
1242 | month: 30 * DAY / 56
1243 | };
1244 |
1245 | function NOOP() {}
1246 |
1247 | function Gantt(_ref) {
1248 | var _ref$data = _ref.data,
1249 | data = _ref$data === void 0 ? [] : _ref$data,
1250 | _ref$onClick = _ref.onClick,
1251 | onClick = _ref$onClick === void 0 ? NOOP : _ref$onClick,
1252 | _ref$viewMode = _ref.viewMode,
1253 | viewMode = _ref$viewMode === void 0 ? 'week' : _ref$viewMode,
1254 | _ref$maxTextWidth = _ref.maxTextWidth,
1255 | maxTextWidth = _ref$maxTextWidth === void 0 ? 140 : _ref$maxTextWidth,
1256 | _ref$offsetY = _ref.offsetY,
1257 | offsetY = _ref$offsetY === void 0 ? 60 : _ref$offsetY,
1258 | _ref$rowHeight = _ref.rowHeight,
1259 | rowHeight = _ref$rowHeight === void 0 ? 40 : _ref$rowHeight,
1260 | _ref$barHeight = _ref.barHeight,
1261 | barHeight = _ref$barHeight === void 0 ? 16 : _ref$barHeight,
1262 | _ref$thickWidth = _ref.thickWidth,
1263 | thickWidth = _ref$thickWidth === void 0 ? 1.4 : _ref$thickWidth,
1264 | _ref$styleOptions = _ref.styleOptions,
1265 | styleOptions = _ref$styleOptions === void 0 ? {} : _ref$styleOptions,
1266 | _ref$showLinks = _ref.showLinks,
1267 | showLinks = _ref$showLinks === void 0 ? true : _ref$showLinks,
1268 | _ref$showDelay = _ref.showDelay,
1269 | showDelay = _ref$showDelay === void 0 ? true : _ref$showDelay,
1270 | start = _ref.start,
1271 | end = _ref.end;
1272 | var unit = UNIT[viewMode];
1273 | var minTime = start.getTime() - unit * 48;
1274 | var maxTime = end.getTime() + unit * 48;
1275 | var width = (maxTime - minTime) / unit + maxTextWidth;
1276 | var height = data.length * rowHeight + offsetY;
1277 | var box = "0 0 ".concat(width, " ").concat(height);
1278 | var current = Date.now();
1279 | var styles = getStyles(styleOptions);
1280 | return h("svg", {
1281 | width: width,
1282 | height: height,
1283 | viewBox: box
1284 | }, h(Layout, {
1285 | styles: styles,
1286 | width: width,
1287 | height: height,
1288 | offsetY: offsetY,
1289 | thickWidth: thickWidth,
1290 | maxTextWidth: maxTextWidth
1291 | }), viewMode === 'day' ? h(DayHeader, {
1292 | styles: styles,
1293 | unit: unit,
1294 | height: height,
1295 | offsetY: offsetY,
1296 | minTime: minTime,
1297 | maxTime: maxTime,
1298 | maxTextWidth: maxTextWidth
1299 | }) : null, viewMode === 'week' ? h(WeekHeader, {
1300 | styles: styles,
1301 | unit: unit,
1302 | height: height,
1303 | offsetY: offsetY,
1304 | minTime: minTime,
1305 | maxTime: maxTime,
1306 | maxTextWidth: maxTextWidth
1307 | }) : null, viewMode === 'month' ? h(MonthHeader, {
1308 | styles: styles,
1309 | unit: unit,
1310 | offsetY: offsetY,
1311 | minTime: minTime,
1312 | maxTime: maxTime,
1313 | maxTextWidth: maxTextWidth
1314 | }) : null, h(Grid, {
1315 | styles: styles,
1316 | data: data,
1317 | width: width,
1318 | height: height,
1319 | offsetY: offsetY,
1320 | rowHeight: rowHeight,
1321 | maxTextWidth: maxTextWidth
1322 | }), maxTextWidth > 0 ? h(Labels, {
1323 | styles: styles,
1324 | data: data,
1325 | onClick: onClick,
1326 | offsetY: offsetY,
1327 | rowHeight: rowHeight
1328 | }) : null, showLinks ? h(LinkLine, {
1329 | styles: styles,
1330 | data: data,
1331 | unit: unit,
1332 | height: height,
1333 | current: current,
1334 | offsetY: offsetY,
1335 | minTime: minTime,
1336 | rowHeight: rowHeight,
1337 | barHeight: barHeight,
1338 | maxTextWidth: maxTextWidth
1339 | }) : null, h(Bar, {
1340 | styles: styles,
1341 | data: data,
1342 | unit: unit,
1343 | height: height,
1344 | current: current,
1345 | offsetY: offsetY,
1346 | minTime: minTime,
1347 | onClick: onClick,
1348 | showDelay: showDelay,
1349 | rowHeight: rowHeight,
1350 | barHeight: barHeight,
1351 | maxTextWidth: maxTextWidth
1352 | }));
1353 | }
1354 |
1355 | var _typeof_1 = createCommonjsModule(function (module) {
1356 | function _typeof(obj) {
1357 | "@babel/helpers - typeof";
1358 |
1359 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
1360 | module.exports = _typeof = function _typeof(obj) {
1361 | return typeof obj;
1362 | };
1363 |
1364 | module.exports["default"] = module.exports, module.exports.__esModule = true;
1365 | } else {
1366 | module.exports = _typeof = function _typeof(obj) {
1367 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
1368 | };
1369 |
1370 | module.exports["default"] = module.exports, module.exports.__esModule = true;
1371 | }
1372 |
1373 | return _typeof(obj);
1374 | }
1375 |
1376 | module.exports = _typeof;
1377 | module.exports["default"] = module.exports, module.exports.__esModule = true;
1378 | });
1379 |
1380 | var _typeof = /*@__PURE__*/getDefaultExportFromCjs(_typeof_1);
1381 |
1382 | var NS = 'http://www.w3.org/2000/svg';
1383 | var doc = document;
1384 |
1385 | function applyProperties(node, props) {
1386 | Object.keys(props).forEach(function (k) {
1387 | var v = props[k];
1388 |
1389 | if (k === 'style' && _typeof(v) === 'object') {
1390 | Object.keys(v).forEach(function (sk) {
1391 | // eslint-disable-next-line
1392 | node.style[sk] = v[sk];
1393 | });
1394 | } else if (k === 'onClick') {
1395 | if (typeof v === 'function') {
1396 | node.addEventListener('click', v);
1397 | }
1398 | } else {
1399 | node.setAttribute(k, v);
1400 | }
1401 | });
1402 | }
1403 |
1404 | function render$2(vnode, ctx) {
1405 | var tag = vnode.tag,
1406 | props = vnode.props,
1407 | children = vnode.children;
1408 | var node = doc.createElementNS(NS, tag);
1409 |
1410 | if (props) {
1411 | applyProperties(node, props);
1412 | }
1413 |
1414 | children.forEach(function (v) {
1415 | node.appendChild(typeof v === 'string' ? doc.createTextNode(v) : render$2(v));
1416 | });
1417 | return node;
1418 | }
1419 |
1420 | function ownKeys$2(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
1421 |
1422 | function _objectSpread$2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys$2(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys$2(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
1423 |
1424 | var SVGGantt = /*#__PURE__*/function () {
1425 | function SVGGantt(element, data) {
1426 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1427 |
1428 | _classCallCheck(this, SVGGantt);
1429 |
1430 | this.dom = typeof element === 'string' ? document.querySelector(element) : element;
1431 | this.format(data);
1432 | this.options = options;
1433 | this.render();
1434 | }
1435 |
1436 | _createClass(SVGGantt, [{
1437 | key: "format",
1438 | value: function format(data) {
1439 | this.data = data;
1440 | var start = null;
1441 | var end = null;
1442 | data.forEach(function (v) {
1443 | start = minDate(start, v.start);
1444 | end = maxDate(end, v.end);
1445 | });
1446 | this.start = start || new Date();
1447 | this.end = end || new Date();
1448 | }
1449 | }, {
1450 | key: "setData",
1451 | value: function setData(data) {
1452 | this.format(data);
1453 | this.render();
1454 | }
1455 | }, {
1456 | key: "setOptions",
1457 | value: function setOptions(options) {
1458 | this.options = _objectSpread$2(_objectSpread$2({}, this.options), options);
1459 | this.render();
1460 | }
1461 | }, {
1462 | key: "render",
1463 | value: function render() {
1464 | var data = this.data,
1465 | start = this.start,
1466 | end = this.end,
1467 | options = this.options;
1468 |
1469 | if (this.tree) {
1470 | this.dom.removeChild(this.tree);
1471 | }
1472 |
1473 | if (options.maxTextWidth === undefined) {
1474 | var font = getFont(options.styleOptions || {});
1475 |
1476 | var w = function w(v) {
1477 | return textWidth(v.text, font, 20);
1478 | };
1479 |
1480 | options.maxTextWidth = max(data.map(w), 0);
1481 | }
1482 |
1483 | var props = _objectSpread$2(_objectSpread$2({}, options), {}, {
1484 | start: start,
1485 | end: end
1486 | });
1487 |
1488 | this.tree = render$2(h(Gantt, _extends({
1489 | data: data
1490 | }, props)));
1491 | this.dom.appendChild(this.tree);
1492 | }
1493 | }]);
1494 |
1495 | return SVGGantt;
1496 | }();
1497 |
1498 | function render$1(vnode, ctx, e) {
1499 | var tag = vnode.tag,
1500 | props = vnode.props,
1501 | children = vnode.children;
1502 |
1503 | if (tag === 'svg') {
1504 | var width = props.width,
1505 | height = props.height;
1506 | ctx.width = width;
1507 | ctx.height = height;
1508 | }
1509 |
1510 | if (tag === 'line') {
1511 | var x1 = props.x1,
1512 | x2 = props.x2,
1513 | y1 = props.y1,
1514 | y2 = props.y2,
1515 | _props$style = props.style,
1516 | style = _props$style === void 0 ? {} : _props$style;
1517 |
1518 | if (style.stroke) {
1519 | ctx.strokeStyle = style.stroke;
1520 | ctx.lineWidth = parseFloat(style['stroke-width'] || 1);
1521 | }
1522 |
1523 | ctx.beginPath();
1524 | ctx.moveTo(x1, y1);
1525 | ctx.lineTo(x2, y2);
1526 | ctx.stroke();
1527 | }
1528 |
1529 | if (tag === 'polyline' || tag === 'polygon') {
1530 | var points = props.points,
1531 | _props$style2 = props.style,
1532 | _style = _props$style2 === void 0 ? {} : _props$style2;
1533 |
1534 | var p = s2p(points);
1535 |
1536 | if (_style.stroke) {
1537 | ctx.strokeStyle = _style.stroke;
1538 | ctx.lineWidth = parseFloat(_style['stroke-width'] || 1);
1539 | }
1540 |
1541 | if (_style.fill) {
1542 | ctx.fillStyle = _style.fill;
1543 | }
1544 |
1545 | ctx.beginPath();
1546 | ctx.moveTo(p[0][0], p[0][1]);
1547 |
1548 | for (var i = 1; i < p.length; i++) {
1549 | ctx.lineTo(p[i][0], p[i][1]);
1550 | }
1551 |
1552 | if (tag === 'polyline') {
1553 | ctx.stroke();
1554 | } else {
1555 | ctx.fill();
1556 | }
1557 | }
1558 |
1559 | if (tag === 'rect') {
1560 | var x = props.x,
1561 | y = props.y,
1562 | _width = props.width,
1563 | _height = props.height,
1564 | _props$rx = props.rx,
1565 | rx = _props$rx === void 0 ? 0 : _props$rx,
1566 | _props$ry = props.ry,
1567 | ry = _props$ry === void 0 ? 0 : _props$ry,
1568 | onClick = props.onClick,
1569 | _props$style3 = props.style,
1570 | _style2 = _props$style3 === void 0 ? {} : _props$style3; // From https://github.com/canvg/canvg
1571 |
1572 |
1573 | ctx.beginPath();
1574 | ctx.moveTo(x + rx, y);
1575 | ctx.lineTo(x + _width - rx, y);
1576 | ctx.quadraticCurveTo(x + _width, y, x + _width, y + ry);
1577 | ctx.lineTo(x + _width, y + _height - ry);
1578 | ctx.quadraticCurveTo(x + _width, y + _height, x + _width - rx, y + _height);
1579 | ctx.lineTo(x + rx, y + _height);
1580 | ctx.quadraticCurveTo(x, y + _height, x, y + _height - ry);
1581 | ctx.lineTo(x, y + ry);
1582 | ctx.quadraticCurveTo(x, y, x + rx, y);
1583 |
1584 | if (e && onClick && ctx.isPointInPath(e.x, e.y)) {
1585 | onClick();
1586 | }
1587 |
1588 | ctx.closePath();
1589 |
1590 | if (_style2.fill) {
1591 | ctx.fillStyle = _style2.fill;
1592 | }
1593 |
1594 | ctx.fill();
1595 |
1596 | if (_style2.stroke) {
1597 | ctx.strokeStyle = _style2.stroke;
1598 | ctx.lineWidth = parseFloat(_style2['stroke-width'] || 1);
1599 | ctx.stroke();
1600 | }
1601 | }
1602 |
1603 | if (tag === 'text') {
1604 | var _x = props.x,
1605 | _y = props.y,
1606 | _style3 = props.style;
1607 |
1608 | if (_style3) {
1609 | ctx.fillStyle = _style3.fill;
1610 | var BL = {
1611 | central: 'middle',
1612 | middle: 'middle',
1613 | hanging: 'hanging',
1614 | alphabetic: 'alphabetic',
1615 | ideographic: 'ideographic'
1616 | };
1617 | var AL = {
1618 | start: 'start',
1619 | middle: 'center',
1620 | end: 'end'
1621 | };
1622 | ctx.textBaseline = BL[_style3['dominant-baseline']] || 'alphabetic';
1623 | ctx.textAlign = AL[_style3['text-anchor']] || 'start';
1624 | ctx.font = "".concat(_style3['font-weight'] || '', " ").concat(_style3['font-size'], " ").concat(_style3['font-family']);
1625 | }
1626 |
1627 | ctx.fillText(children.join(''), _x, _y);
1628 | }
1629 |
1630 | children.forEach(function (v) {
1631 | if (typeof v !== 'string') {
1632 | render$1(v, ctx, e);
1633 | }
1634 | });
1635 | }
1636 |
1637 | function createContext(dom) {
1638 | var canvas = typeof dom === 'string' ? document.querySelector(dom) : dom;
1639 | var ctx = canvas.getContext('2d');
1640 | var backingStore = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
1641 | var ratio = (window.devicePixelRatio || 1) / backingStore;
1642 | ['width', 'height'].forEach(function (key) {
1643 | Object.defineProperty(ctx, key, {
1644 | get: function get() {
1645 | return canvas[key] / ratio;
1646 | },
1647 | set: function set(v) {
1648 | canvas[key] = v * ratio;
1649 | canvas.style[key] = "".concat(v, "px");
1650 | ctx.scale(ratio, ratio);
1651 | },
1652 | enumerable: true,
1653 | configurable: true
1654 | });
1655 | });
1656 | canvas.addEventListener('click', function (e) {
1657 | if (!ctx.onClick) return;
1658 | var rect = canvas.getBoundingClientRect();
1659 | ctx.onClick({
1660 | x: (e.clientX - rect.left) * ratio,
1661 | y: (e.clientY - rect.top) * ratio
1662 | });
1663 | });
1664 | return ctx;
1665 | }
1666 |
1667 | function ownKeys$1(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
1668 |
1669 | function _objectSpread$1(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys$1(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys$1(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
1670 |
1671 | var CanvasGantt = /*#__PURE__*/function () {
1672 | function CanvasGantt(element, data) {
1673 | var _this = this;
1674 |
1675 | var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1676 |
1677 | _classCallCheck(this, CanvasGantt);
1678 |
1679 | this.ctx = createContext(element);
1680 | this.format(data);
1681 | this.options = options;
1682 | this.render();
1683 |
1684 | this.ctx.onClick = function (e) {
1685 | return _this.render(e);
1686 | };
1687 | }
1688 |
1689 | _createClass(CanvasGantt, [{
1690 | key: "format",
1691 | value: function format(data) {
1692 | this.data = data;
1693 | var start = null;
1694 | var end = null;
1695 | data.forEach(function (v) {
1696 | start = minDate(start, v.start);
1697 | end = maxDate(end, v.end);
1698 | });
1699 | this.start = start || new Date();
1700 | this.end = end || new Date();
1701 | }
1702 | }, {
1703 | key: "setData",
1704 | value: function setData(data) {
1705 | this.format(data);
1706 | this.render();
1707 | }
1708 | }, {
1709 | key: "setOptions",
1710 | value: function setOptions(options) {
1711 | this.options = _objectSpread$1(_objectSpread$1({}, this.options), options);
1712 | this.render();
1713 | }
1714 | }, {
1715 | key: "render",
1716 | value: function render(e) {
1717 | var data = this.data,
1718 | start = this.start,
1719 | end = this.end,
1720 | options = this.options;
1721 |
1722 | if (options.maxTextWidth === undefined) {
1723 | var font = getFont(options.styleOptions || {});
1724 |
1725 | var w = function w(v) {
1726 | return textWidth(v.text, font, 20);
1727 | };
1728 |
1729 | options.maxTextWidth = max(data.map(w), 0);
1730 | }
1731 |
1732 | var props = _objectSpread$1(_objectSpread$1({}, options), {}, {
1733 | start: start,
1734 | end: end
1735 | });
1736 |
1737 | render$1(h(Gantt, _extends({
1738 | data: data
1739 | }, props)), this.ctx, e);
1740 | }
1741 | }]);
1742 |
1743 | return CanvasGantt;
1744 | }();
1745 |
1746 | function attrEscape(str) {
1747 | return String(str).replace(/&/g, '&').replace(//g, '>').replace(/\r/g, '
');
1752 | }
1753 |
1754 | function render(vnode, ctx) {
1755 | var tag = vnode.tag,
1756 | props = vnode.props,
1757 | children = vnode.children;
1758 | var tokens = [];
1759 | tokens.push("<".concat(tag));
1760 | Object.keys(props || {}).forEach(function (k) {
1761 | var v = props[k];
1762 | if (k === 'onClick') return;
1763 |
1764 | if (k === 'style' && _typeof(v) === 'object') {
1765 | v = Object.keys(v).map(function (i) {
1766 | return "".concat(i, ":").concat(v[i], ";");
1767 | }).join('');
1768 | }
1769 |
1770 | tokens.push(" ".concat(k, "=\"").concat(attrEscape(v), "\""));
1771 | });
1772 |
1773 | if (!children || !children.length) {
1774 | tokens.push(' />');
1775 | return tokens.join('');
1776 | }
1777 |
1778 | tokens.push('>');
1779 | children.forEach(function (v) {
1780 | if (typeof v === 'string') {
1781 | tokens.push(escape(v));
1782 | } else {
1783 | tokens.push(render(v));
1784 | }
1785 | });
1786 | tokens.push("".concat(tag, ">"));
1787 | return tokens.join('');
1788 | }
1789 |
1790 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
1791 |
1792 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
1793 |
1794 | var StrGantt = /*#__PURE__*/function () {
1795 | function StrGantt(data) {
1796 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1797 |
1798 | _classCallCheck(this, StrGantt);
1799 |
1800 | this.format(data);
1801 | this.options = options;
1802 | }
1803 |
1804 | _createClass(StrGantt, [{
1805 | key: "format",
1806 | value: function format(data) {
1807 | this.data = data;
1808 | var start = null;
1809 | var end = null;
1810 | data.forEach(function (v) {
1811 | start = minDate(start, v.start);
1812 | end = maxDate(end, v.end);
1813 | });
1814 | this.start = start || new Date();
1815 | this.end = end || new Date();
1816 | }
1817 | }, {
1818 | key: "setData",
1819 | value: function setData(data) {
1820 | this.format(data);
1821 | }
1822 | }, {
1823 | key: "setOptions",
1824 | value: function setOptions(options) {
1825 | this.options = _objectSpread(_objectSpread({}, this.options), options);
1826 | }
1827 | }, {
1828 | key: "render",
1829 | value: function render$1() {
1830 | var data = this.data,
1831 | start = this.start,
1832 | end = this.end,
1833 | options = this.options;
1834 |
1835 | var props = _objectSpread(_objectSpread({}, options), {}, {
1836 | start: start,
1837 | end: end
1838 | });
1839 |
1840 | return render(h(Gantt, _extends({
1841 | data: data
1842 | }, props)));
1843 | }
1844 | }]);
1845 |
1846 | return StrGantt;
1847 | }();
1848 |
1849 | exports.CanvasGantt = CanvasGantt;
1850 | exports.SVGGantt = SVGGantt;
1851 | exports.StrGantt = StrGantt;
1852 | exports.default = CanvasGantt;
1853 | exports.utils = utils;
1854 |
1855 | Object.defineProperty(exports, '__esModule', { value: true });
1856 |
1857 | })));
1858 |
--------------------------------------------------------------------------------
/dist/gantt.min.js:
--------------------------------------------------------------------------------
1 | (function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory(exports):typeof define==="function"&&define.amd?define(["exports"],factory):(global=typeof globalThis!=="undefined"?globalThis:global||self,factory(global.Gantt={}))})(this,function(exports){"use strict";function getDefaultExportFromCjs(x){return x&&x.__esModule&&Object.prototype.hasOwnProperty.call(x,"default")?x["default"]:x}function createCommonjsModule(fn,basedir,module){return module={path:basedir,exports:{},require:function(path,base){return commonjsRequire(path,base===undefined||base===null?module.path:base)}},fn(module,module.exports),module.exports}function commonjsRequire(){throw new Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs")}var _extends_1=createCommonjsModule(function(module){function _extends(){module.exports=_extends=Object.assign||function(target){for(var i=1;i2?_len-2:0),_key=2;_key<_len;_key++){children[_key-2]=arguments[_key]}addChild(children,childNodes);if(typeof tag==="function"){return tag(_objectSpread$5(_objectSpread$5({},props),{},{children:childNodes}))}return{tag:tag,props:props,children:childNodes}}function ownKeys$4(object,enumerableOnly){var keys=Object.keys(object);if(Object.getOwnPropertySymbols){var symbols=Object.getOwnPropertySymbols(object);if(enumerableOnly){symbols=symbols.filter(function(sym){return Object.getOwnPropertyDescriptor(object,sym).enumerable})}keys.push.apply(keys,symbols)}return keys}function _objectSpread$4(target){for(var i=1;i9?m:"0".concat(m))}function formatDay(date){var m=date.getMonth()+1;var d=date.getDate();return"".concat(m,"/").concat(d)}function minDate(a,b){if(a&&b){return a>b?b:a}return a||b}function maxDate(a,b){if(a&&b){return a=0;i--){stack.push(node.children[i])}}});return list}function hasPath(vmap,a,b){var stack=[];stack.push(vmap[a]);while(stack.length){var v=stack.pop();if(v.id===b){return true}for(var i=0;i2&&arguments[2]!==undefined?arguments[2]:false;var vmap={};links.forEach(function(l){vmap[l.source]={id:l.source,links:[]};vmap[l.target]={id:l.target,links:[]}});var dag=[];links.forEach(function(l){var source=l.source,target=l.target;if(!hasPath(vmap,target,source)){dag.push(l);vmap[source].links.push(vmap[target])}});var sorted=toposort(dag);var tmap={};for(var i=0;i50?h("text",{x:x+t/2,y:offsetY*.25,style:styles.text3},str):null))}return h("g",null,ticks)}function DayHeader(_ref){var styles=_ref.styles,unit=_ref.unit,minTime=_ref.minTime,maxTime=_ref.maxTime,height=_ref.height,offsetY=_ref.offsetY,maxTextWidth=_ref.maxTextWidth;var dates=getDates(minTime,maxTime);var ticks=[];var x0=maxTextWidth;var y0=offsetY/2;var RH=height-y0;var len=dates.length-1;for(var i=0;i28?h("text",{x:x-3,y:offsetY*.75,style:styles.text1},prevDay):null))}return h("g",null,h(YearMonth,{styles:styles,unit:unit,dates:dates,offsetY:offsetY,minTime:minTime,maxTime:maxTime,maxTextWidth:maxTextWidth}),ticks)}function Year(_ref){var styles=_ref.styles,months=_ref.months,unit=_ref.unit,offsetY=_ref.offsetY,minTime=_ref.minTime,maxTime=_ref.maxTime,maxTextWidth=_ref.maxTextWidth;var years=months.filter(function(v){return new Date(v).getMonth()===0});years.unshift(minTime);years.push(maxTime);var ticks=[];var x0=maxTextWidth;var y2=offsetY/2;var len=years.length-1;for(var i=0;i35?h("text",{x:x+t/2,y:offsetY*.25,style:styles.text3},cur.getFullYear()):null))}return h("g",null,ticks)}function MonthHeader(_ref){var styles=_ref.styles,unit=_ref.unit,minTime=_ref.minTime,maxTime=_ref.maxTime,offsetY=_ref.offsetY,maxTextWidth=_ref.maxTextWidth;var MONTH=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];var dates=getDates(minTime,maxTime);var months=dates.filter(function(v){return new Date(v).getDate()===1});months.unshift(minTime);months.push(maxTime);var ticks=[];var x0=maxTextWidth;var y0=offsetY/2;var len=months.length-1;for(var i=0;i30?h("text",{x:x+t/2,y:offsetY*.75,style:styles.text3},MONTH[month]):null))}return h("g",null,h(Year,{styles:styles,unit:unit,months:months,offsetY:offsetY,minTime:minTime,maxTime:maxTime,maxTextWidth:maxTextWidth}),ticks)}function Grid(_ref){var styles=_ref.styles,data=_ref.data,width=_ref.width,height=_ref.height,offsetY=_ref.offsetY,rowHeight=_ref.rowHeight,maxTextWidth=_ref.maxTextWidth;return h("g",null,data.map(function(v,i){var y=(i+1)*rowHeight+offsetY;return h("line",{key:i,x1:0,x2:width,y1:y,y2:y,style:styles.line})}),h("line",{x1:maxTextWidth,x2:maxTextWidth,y1:0,y2:height,style:styles.bline}))}function Labels(_ref){var styles=_ref.styles,data=_ref.data,_onClick=_ref.onClick,rowHeight=_ref.rowHeight,offsetY=_ref.offsetY;return h("g",null,data.map(function(v,i){return h("text",{key:i,x:10,y:(i+.5)*rowHeight+offsetY,class:"gantt-label",style:styles.label,onClick:function onClick(){return _onClick(v)}},v.text)}))}function LinkLine(_ref){var styles=_ref.styles,data=_ref.data,unit=_ref.unit,offsetY=_ref.offsetY,minTime=_ref.minTime,rowHeight=_ref.rowHeight,barHeight=_ref.barHeight,maxTextWidth=_ref.maxTextWidth;var x0=maxTextWidth;var y0=rowHeight/2+offsetY;var map={};data.forEach(function(v,i){map[v.id]=i});return h("g",null,data.map(function(s,i){if(!s.end||!s.start||!s.links){return null}return s.links.map(function(l){var j=map[l.target];var e=data[j];if(!e||!e.start||!e.end)return null;var gap=12;var arrow=6;var mgap=e.type==="milestone"?barHeight/2:0;var y1=y0+i*rowHeight;var y2=y0+j*rowHeight;var vgap=barHeight/2+4;if(y1>y2){vgap=-vgap}if(l.type==="FS"){var x1=x0+(s.end-minTime)/unit;var x2=x0+(e.start-minTime)/unit-mgap;var p1=[[x1,y1],[x1+gap,y1]];if(x2-x1>=2*gap){p1.push([x1+gap,y2])}else{p1.push([x1+gap,y2-vgap]);p1.push([x2-gap,y2-vgap]);p1.push([x2-gap,y2])}p1.push([x2-arrow,y2]);var p2=[[x2-arrow,y2-arrow],[x2,y2],[x2-arrow,y2+arrow]];return h("g",null,h("polyline",{points:p2s(p1),style:styles.link}),h("polygon",{points:p2s(p2),style:styles.linkArrow}))}if(l.type==="FF"){var _x=x0+(s.end-minTime)/unit;var _x2=x0+(e.end-minTime)/unit+mgap;var _p=[[_x,y1],[_x+gap,y1]];if(_x2<=_x){_p.push([_x+gap,y2])}else{_p.push([_x+gap,y2-vgap]);_p.push([_x2+gap,y2-vgap]);_p.push([_x2+gap,y2])}_p.push([_x2+arrow,y2]);var _p2=[[_x2+arrow,y2-arrow],[_x2,y2],[_x2+arrow,y2+arrow]];return h("g",null,h("polyline",{points:p2s(_p),style:styles.link}),h("polygon",{points:p2s(_p2),style:styles.linkArrow}))}if(l.type==="SS"){var _x3=x0+(s.start-minTime)/unit;var _x4=x0+(e.start-minTime)/unit-mgap;var _p3=[[_x3,y1],[_x3-gap,y1]];if(_x3<=_x4){_p3.push([_x3-gap,y2])}else{_p3.push([_x3-gap,y2-vgap]);_p3.push([_x4-gap,y2-vgap]);_p3.push([_x4-gap,y2])}_p3.push([_x4-arrow,y2]);var _p4=[[_x4-arrow,y2-arrow],[_x4,y2],[_x4-arrow,y2+arrow]];return h("g",null,h("polyline",{points:p2s(_p3),style:styles.link}),h("polygon",{points:p2s(_p4),style:styles.linkArrow}))}if(l.type==="SF"){var _x5=x0+(s.start-minTime)/unit;var _x6=x0+(e.end-minTime)/unit+mgap;var _p5=[[_x5,y1],[_x5-gap,y1]];if(_x5-_x6>=2*gap){_p5.push([_x5-gap,y2])}else{_p5.push([_x5-gap,y2-vgap]);_p5.push([_x6+gap,y2-vgap]);_p5.push([_x6+gap,y2])}_p5.push([_x6+arrow,y2]);var _p6=[[_x6+arrow,y2-arrow],[_x6,y2],[_x6+arrow,y2+arrow]];return h("g",null,h("polyline",{points:p2s(_p5),style:styles.link}),h("polygon",{points:p2s(_p6),style:styles.linkArrow}))}return null})}))}function Bar(_ref){var styles=_ref.styles,data=_ref.data,unit=_ref.unit,height=_ref.height,offsetY=_ref.offsetY,minTime=_ref.minTime,showDelay=_ref.showDelay,rowHeight=_ref.rowHeight,barHeight=_ref.barHeight,maxTextWidth=_ref.maxTextWidth,current=_ref.current,onClick=_ref.onClick;var x0=maxTextWidth;var y0=(rowHeight-barHeight)/2+offsetY;var cur=x0+(current-minTime)/unit;return h("g",null,current>minTime?h("line",{x1:cur,x2:cur,y1:offsetY,y2:height,style:styles.cline}):null,data.map(function(v,i){if(!v.end||!v.start){return null}var handler=function handler(){return onClick(v)};var x=x0+(v.start-minTime)/unit;var y=y0+i*rowHeight;var cy=y+barHeight/2;if(v.type==="milestone"){var size=barHeight/2;var points=[[x,cy-size],[x+size,cy],[x,cy+size],[x-size,cy]].map(function(p){return"".concat(p[0],",").concat(p[1])}).join(" ");return h("g",{key:i,class:"gantt-bar",style:{cursor:"pointer"},onClick:handler},h("polygon",{points:points,style:styles.milestone,onClick:handler}),h("circle",{class:"gantt-ctrl-start","data-id":v.id,cx:x,cy:cy,r:6,style:styles.ctrl}))}var w1=(v.end-v.start)/unit;var w2=w1*v.percent;var bar=v.type==="group"?{back:styles.groupBack,front:styles.groupFront}:{back:styles.taskBack,front:styles.taskFront};if(showDelay){if(x+w21e-6?h("rect",{x:x,y:y,width:w2,height:barHeight,rx:1.8,ry:1.8,style:bar.front}):null,v.type==="group"?null:h("g",null,h("circle",{class:"gantt-ctrl-start","data-id":v.id,cx:x-12,cy:cy,r:6,style:styles.ctrl}),h("circle",{class:"gantt-ctrl-finish","data-id":v.id,cx:x+w1+12,cy:cy,r:6,style:styles.ctrl})))}))}function ownKeys$3(object,enumerableOnly){var keys=Object.keys(object);if(Object.getOwnPropertySymbols){var symbols=Object.getOwnPropertySymbols(object);if(enumerableOnly){symbols=symbols.filter(function(sym){return Object.getOwnPropertyDescriptor(object,sym).enumerable})}keys.push.apply(keys,symbols)}return keys}function _objectSpread$3(target){for(var i=1;i0?h(Labels,{styles:styles,data:data,onClick:onClick,offsetY:offsetY,rowHeight:rowHeight}):null,showLinks?h(LinkLine,{styles:styles,data:data,unit:unit,height:height,current:current,offsetY:offsetY,minTime:minTime,rowHeight:rowHeight,barHeight:barHeight,maxTextWidth:maxTextWidth}):null,h(Bar,{styles:styles,data:data,unit:unit,height:height,current:current,offsetY:offsetY,minTime:minTime,onClick:onClick,showDelay:showDelay,rowHeight:rowHeight,barHeight:barHeight,maxTextWidth:maxTextWidth}))}var _typeof_1=createCommonjsModule(function(module){function _typeof(obj){"@babel/helpers - typeof";if(typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"){module.exports=_typeof=function _typeof(obj){return typeof obj};module.exports["default"]=module.exports,module.exports.__esModule=true}else{module.exports=_typeof=function _typeof(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj};module.exports["default"]=module.exports,module.exports.__esModule=true}return _typeof(obj)}module.exports=_typeof;module.exports["default"]=module.exports,module.exports.__esModule=true});var _typeof=getDefaultExportFromCjs(_typeof_1);var NS="http://www.w3.org/2000/svg";var doc=document;function applyProperties(node,props){Object.keys(props).forEach(function(k){var v=props[k];if(k==="style"&&_typeof(v)==="object"){Object.keys(v).forEach(function(sk){node.style[sk]=v[sk]})}else if(k==="onClick"){if(typeof v==="function"){node.addEventListener("click",v)}}else{node.setAttribute(k,v)}})}function render$2(vnode,ctx){var tag=vnode.tag,props=vnode.props,children=vnode.children;var node=doc.createElementNS(NS,tag);if(props){applyProperties(node,props)}children.forEach(function(v){node.appendChild(typeof v==="string"?doc.createTextNode(v):render$2(v))});return node}function ownKeys$2(object,enumerableOnly){var keys=Object.keys(object);if(Object.getOwnPropertySymbols){var symbols=Object.getOwnPropertySymbols(object);if(enumerableOnly){symbols=symbols.filter(function(sym){return Object.getOwnPropertyDescriptor(object,sym).enumerable})}keys.push.apply(keys,symbols)}return keys}function _objectSpread$2(target){for(var i=1;i2&&arguments[2]!==undefined?arguments[2]:{};_classCallCheck(this,SVGGantt);this.dom=typeof element==="string"?document.querySelector(element):element;this.format(data);this.options=options;this.render()}_createClass(SVGGantt,[{key:"format",value:function format(data){this.data=data;var start=null;var end=null;data.forEach(function(v){start=minDate(start,v.start);end=maxDate(end,v.end)});this.start=start||new Date;this.end=end||new Date}},{key:"setData",value:function setData(data){this.format(data);this.render()}},{key:"setOptions",value:function setOptions(options){this.options=_objectSpread$2(_objectSpread$2({},this.options),options);this.render()}},{key:"render",value:function render(){var data=this.data,start=this.start,end=this.end,options=this.options;if(this.tree){this.dom.removeChild(this.tree)}if(options.maxTextWidth===undefined){var font=getFont(options.styleOptions||{});var w=function w(v){return textWidth(v.text,font,20)};options.maxTextWidth=max(data.map(w),0)}var props=_objectSpread$2(_objectSpread$2({},options),{},{start:start,end:end});this.tree=render$2(h(Gantt,_extends({data:data},props)));this.dom.appendChild(this.tree)}}]);return SVGGantt}();function render$1(vnode,ctx,e){var tag=vnode.tag,props=vnode.props,children=vnode.children;if(tag==="svg"){var width=props.width,height=props.height;ctx.width=width;ctx.height=height}if(tag==="line"){var x1=props.x1,x2=props.x2,y1=props.y1,y2=props.y2,_props$style=props.style,style=_props$style===void 0?{}:_props$style;if(style.stroke){ctx.strokeStyle=style.stroke;ctx.lineWidth=parseFloat(style["stroke-width"]||1)}ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke()}if(tag==="polyline"||tag==="polygon"){var points=props.points,_props$style2=props.style,_style=_props$style2===void 0?{}:_props$style2;var p=s2p(points);if(_style.stroke){ctx.strokeStyle=_style.stroke;ctx.lineWidth=parseFloat(_style["stroke-width"]||1)}if(_style.fill){ctx.fillStyle=_style.fill}ctx.beginPath();ctx.moveTo(p[0][0],p[0][1]);for(var i=1;i2&&arguments[2]!==undefined?arguments[2]:{};_classCallCheck(this,CanvasGantt);this.ctx=createContext(element);this.format(data);this.options=options;this.render();this.ctx.onClick=function(e){return _this.render(e)}}_createClass(CanvasGantt,[{key:"format",value:function format(data){this.data=data;var start=null;var end=null;data.forEach(function(v){start=minDate(start,v.start);end=maxDate(end,v.end)});this.start=start||new Date;this.end=end||new Date}},{key:"setData",value:function setData(data){this.format(data);this.render()}},{key:"setOptions",value:function setOptions(options){this.options=_objectSpread$1(_objectSpread$1({},this.options),options);this.render()}},{key:"render",value:function render(e){var data=this.data,start=this.start,end=this.end,options=this.options;if(options.maxTextWidth===undefined){var font=getFont(options.styleOptions||{});var w=function w(v){return textWidth(v.text,font,20)};options.maxTextWidth=max(data.map(w),0)}var props=_objectSpread$1(_objectSpread$1({},options),{},{start:start,end:end});render$1(h(Gantt,_extends({data:data},props)),this.ctx,e)}}]);return CanvasGantt}();function attrEscape(str){return String(str).replace(/&/g,"&").replace(//g,">").replace(/\r/g,"
")}function render(vnode,ctx){var tag=vnode.tag,props=vnode.props,children=vnode.children;var tokens=[];tokens.push("<".concat(tag));Object.keys(props||{}).forEach(function(k){var v=props[k];if(k==="onClick")return;if(k==="style"&&_typeof(v)==="object"){v=Object.keys(v).map(function(i){return"".concat(i,":").concat(v[i],";")}).join("")}tokens.push(" ".concat(k,'="').concat(attrEscape(v),'"'))});if(!children||!children.length){tokens.push(" />");return tokens.join("")}tokens.push(">");children.forEach(function(v){if(typeof v==="string"){tokens.push(escape(v))}else{tokens.push(render(v))}});tokens.push("".concat(tag,">"));return tokens.join("")}function ownKeys(object,enumerableOnly){var keys=Object.keys(object);if(Object.getOwnPropertySymbols){var symbols=Object.getOwnPropertySymbols(object);if(enumerableOnly){symbols=symbols.filter(function(sym){return Object.getOwnPropertyDescriptor(object,sym).enumerable})}keys.push.apply(keys,symbols)}return keys}function _objectSpread(target){for(var i=1;i1&&arguments[1]!==undefined?arguments[1]:{};_classCallCheck(this,StrGantt);this.format(data);this.options=options}_createClass(StrGantt,[{key:"format",value:function format(data){this.data=data;var start=null;var end=null;data.forEach(function(v){start=minDate(start,v.start);end=maxDate(end,v.end)});this.start=start||new Date;this.end=end||new Date}},{key:"setData",value:function setData(data){this.format(data)}},{key:"setOptions",value:function setOptions(options){this.options=_objectSpread(_objectSpread({},this.options),options)}},{key:"render",value:function render$1(){var data=this.data,start=this.start,end=this.end,options=this.options;var props=_objectSpread(_objectSpread({},options),{},{start:start,end:end});return render(h(Gantt,_extends({data:data},props)))}}]);return StrGantt}();exports.CanvasGantt=CanvasGantt;exports.SVGGantt=SVGGantt;exports.StrGantt=StrGantt;exports.default=CanvasGantt;exports.utils=utils;Object.defineProperty(exports,"__esModule",{value:true})});
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gantt",
3 | "version": "3.1.1",
4 | "description": "Gantt chart library using jsx support SVG, Canvas and SSR",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "jsnext:main": "es/index.js",
8 | "files": [
9 | "lib",
10 | "es",
11 | "src",
12 | "dist"
13 | ],
14 | "scripts": {
15 | "lint": "eslint src",
16 | "clean": "rimraf lib es dist",
17 | "build:lib": "cross-env BABEL_ENV=cjs babel src -d lib",
18 | "build:es": "cross-env BABEL_ENV=es babel src -d es",
19 | "build:dist": "cross-env NODE_ENV=production rollup -c && uglifyjs -o dist/gantt.min.js dist/gantt.js",
20 | "build": "npm run clean && npm run build:lib && npm run build:es && npm run build:dist",
21 | "prepare": "npm run build",
22 | "pages": "cd demo && dool build && gh-pages -d ."
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/d-band/gantt.git"
27 | },
28 | "keywords": [
29 | "gantt",
30 | "chart",
31 | "jsx",
32 | "svg",
33 | "canvas",
34 | "vdom",
35 | "ssr"
36 | ],
37 | "author": "d-band",
38 | "license": "MIT",
39 | "bugs": {
40 | "url": "https://github.com/d-band/gantt/issues"
41 | },
42 | "homepage": "https://github.com/d-band/gantt#readme",
43 | "devDependencies": {
44 | "@babel/cli": "^7.10.5",
45 | "@babel/core": "^7.11.4",
46 | "@babel/plugin-external-helpers": "^7.10.4",
47 | "@babel/plugin-transform-react-jsx": "^7.10.4",
48 | "@babel/plugin-transform-runtime": "^7.11.0",
49 | "@babel/preset-env": "^7.11.0",
50 | "@babel/runtime": "^7.11.2",
51 | "@rollup/plugin-babel": "^5.2.0",
52 | "@rollup/plugin-commonjs": "^15.0.0",
53 | "@rollup/plugin-node-resolve": "^9.0.0",
54 | "babel-eslint": "^10.1.0",
55 | "cross-env": "^7.0.2",
56 | "eslint": "^7.7.0",
57 | "eslint-config-airbnb": "^18.2.0",
58 | "eslint-plugin-import": "^2.22.0",
59 | "eslint-plugin-jsx-a11y": "^6.3.1",
60 | "eslint-plugin-react": "^7.20.6",
61 | "eslint-plugin-react-hooks": "^4.1.0",
62 | "gh-pages": "^3.1.0",
63 | "rimraf": "^3.0.2",
64 | "rollup": "^2.0.0",
65 | "uglify-js": "^3.10.3"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel';
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import nodeResolve from '@rollup/plugin-node-resolve';
4 |
5 | export default {
6 | output: {
7 | file: 'dist/gantt.js',
8 | format: 'umd',
9 | name: 'Gantt',
10 | exports: 'named'
11 | },
12 | input: 'src/index.js',
13 | plugins: [
14 | nodeResolve(),
15 | commonjs({
16 | include: 'node_modules/**'
17 | }),
18 | babel({
19 | babelrc: false,
20 | presets: [
21 | ['@babel/preset-env', {
22 | modules: false
23 | }]
24 | ],
25 | plugins: [
26 | ['@babel/plugin-transform-react-jsx', {
27 | 'pragma': 'h'
28 | }],
29 | '@babel/plugin-external-helpers',
30 | '@babel/plugin-transform-runtime'
31 | ],
32 | babelHelpers: 'runtime',
33 | exclude: 'node_modules/**'
34 | })
35 | ]
36 | };
37 |
--------------------------------------------------------------------------------
/src/CanvasGantt.js:
--------------------------------------------------------------------------------
1 | import h from './h';
2 | import Gantt from './gantt';
3 | import render from './render/canvas';
4 | import createContext from './context';
5 | import { getFont } from './gantt/styles';
6 | import {
7 | minDate, maxDate, textWidth, max
8 | } from './utils';
9 |
10 | export default class CanvasGantt {
11 | constructor(element, data, options = {}) {
12 | this.ctx = createContext(element);
13 | this.format(data);
14 | this.options = options;
15 | this.render();
16 | this.ctx.onClick = (e) => this.render(e);
17 | }
18 | format(data) {
19 | this.data = data;
20 | let start = null;
21 | let end = null;
22 | data.forEach((v) => {
23 | start = minDate(start, v.start);
24 | end = maxDate(end, v.end);
25 | });
26 | this.start = start || new Date();
27 | this.end = end || new Date();
28 | }
29 | setData(data) {
30 | this.format(data);
31 | this.render();
32 | }
33 | setOptions(options) {
34 | this.options = { ...this.options, ...options };
35 | this.render();
36 | }
37 | render(e) {
38 | const {
39 | data, start, end, options
40 | } = this;
41 | if (options.maxTextWidth === undefined) {
42 | const font = getFont(options.styleOptions || {});
43 | const w = (v) => textWidth(v.text, font, 20);
44 | options.maxTextWidth = max(data.map(w), 0);
45 | }
46 | const props = { ...options, start, end };
47 | render(, this.ctx, e);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/SVGGantt.js:
--------------------------------------------------------------------------------
1 | import h from './h';
2 | import Gantt from './gantt';
3 | import render from './render/svg';
4 | import { getFont } from './gantt/styles';
5 | import {
6 | minDate, maxDate, textWidth, max
7 | } from './utils';
8 |
9 | export default class SVGGantt {
10 | constructor(element, data, options = {}) {
11 | this.dom = typeof element === 'string' ? document.querySelector(element) : element;
12 | this.format(data);
13 | this.options = options;
14 | this.render();
15 | }
16 | format(data) {
17 | this.data = data;
18 | let start = null;
19 | let end = null;
20 | data.forEach((v) => {
21 | start = minDate(start, v.start);
22 | end = maxDate(end, v.end);
23 | });
24 | this.start = start || new Date();
25 | this.end = end || new Date();
26 | }
27 | setData(data) {
28 | this.format(data);
29 | this.render();
30 | }
31 | setOptions(options) {
32 | this.options = { ...this.options, ...options };
33 | this.render();
34 | }
35 | render() {
36 | const {
37 | data, start, end, options
38 | } = this;
39 | if (this.tree) {
40 | this.dom.removeChild(this.tree);
41 | }
42 | if (options.maxTextWidth === undefined) {
43 | const font = getFont(options.styleOptions || {});
44 | const w = (v) => textWidth(v.text, font, 20);
45 | options.maxTextWidth = max(data.map(w), 0);
46 | }
47 | const props = { ...options, start, end };
48 | this.tree = render();
49 | this.dom.appendChild(this.tree);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/StrGantt.js:
--------------------------------------------------------------------------------
1 | import h from './h';
2 | import Gantt from './gantt';
3 | import render from './render/string';
4 | import { minDate, maxDate } from './utils';
5 |
6 | export default class StrGantt {
7 | constructor(data, options = {}) {
8 | this.format(data);
9 | this.options = options;
10 | }
11 | format(data) {
12 | this.data = data;
13 | let start = null;
14 | let end = null;
15 | data.forEach((v) => {
16 | start = minDate(start, v.start);
17 | end = maxDate(end, v.end);
18 | });
19 | this.start = start || new Date();
20 | this.end = end || new Date();
21 | }
22 | setData(data) {
23 | this.format(data);
24 | }
25 | setOptions(options) {
26 | this.options = { ...this.options, ...options };
27 | }
28 | render() {
29 | const {
30 | data, start, end, options
31 | } = this;
32 | const props = { ...options, start, end };
33 | return render();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/context.js:
--------------------------------------------------------------------------------
1 | export default function createContext(dom) {
2 | const canvas = typeof dom === 'string' ? document.querySelector(dom) : dom;
3 | const ctx = canvas.getContext('2d');
4 | const backingStore = ctx.webkitBackingStorePixelRatio ||
5 | ctx.mozBackingStorePixelRatio ||
6 | ctx.msBackingStorePixelRatio ||
7 | ctx.oBackingStorePixelRatio ||
8 | ctx.backingStorePixelRatio || 1;
9 | const ratio = (window.devicePixelRatio || 1) / backingStore;
10 |
11 | ['width', 'height'].forEach((key) => {
12 | Object.defineProperty(ctx, key, {
13 | get() {
14 | return canvas[key] / ratio;
15 | },
16 | set(v) {
17 | canvas[key] = v * ratio;
18 | canvas.style[key] = `${v}px`;
19 | ctx.scale(ratio, ratio);
20 | },
21 | enumerable: true,
22 | configurable: true
23 | });
24 | });
25 | canvas.addEventListener('click', (e) => {
26 | if (!ctx.onClick) return;
27 | const rect = canvas.getBoundingClientRect();
28 | ctx.onClick({
29 | x: (e.clientX - rect.left) * ratio,
30 | y: (e.clientY - rect.top) * ratio
31 | });
32 | });
33 | return ctx;
34 | }
35 |
--------------------------------------------------------------------------------
/src/gantt/Bar.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 | import { formatDay } from '../utils';
3 |
4 | export default function Bar({
5 | styles, data, unit, height, offsetY, minTime, showDelay, rowHeight, barHeight, maxTextWidth, current, onClick
6 | }) {
7 | const x0 = maxTextWidth;
8 | const y0 = (rowHeight - barHeight) / 2 + offsetY;
9 | const cur = x0 + (current - minTime) / unit;
10 | return (
11 |
12 | {current > minTime ? (
13 |
14 | ) : null}
15 | {data.map((v, i) => {
16 | if (!v.end || !v.start) {
17 | return null;
18 | }
19 | const handler = () => onClick(v);
20 | const x = x0 + (v.start - minTime) / unit;
21 | const y = y0 + i * rowHeight;
22 | const cy = y + barHeight / 2;
23 | if (v.type === 'milestone') {
24 | const size = barHeight / 2;
25 | const points = [
26 | [x, cy - size],
27 | [x + size, cy],
28 | [x, cy + size],
29 | [x - size, cy]
30 | ].map((p) => `${p[0]},${p[1]}`).join(' ');
31 | return (
32 |
33 |
34 |
35 |
36 | );
37 | }
38 | const w1 = (v.end - v.start) / unit;
39 | const w2 = w1 * v.percent;
40 | const bar = v.type === 'group' ? {
41 | back: styles.groupBack,
42 | front: styles.groupFront
43 | } : {
44 | back: styles.taskBack,
45 | front: styles.taskFront
46 | };
47 | if (showDelay) {
48 | if ((x + w2) < cur && v.percent < 0.999999) {
49 | bar.back = styles.warning;
50 | }
51 | if ((x + w1) < cur && v.percent < 0.999999) {
52 | bar.back = styles.danger;
53 | }
54 | }
55 | return (
56 |
57 | {formatDay(v.start)}
58 | {formatDay(v.end)}
59 |
60 | {w2 > 0.000001 ? : null}
61 | {v.type === 'group' ? null : (
62 |
63 |
64 |
65 |
66 | )}
67 |
68 | );
69 | })}
70 |
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/src/gantt/DayHeader.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 | import { getDates } from '../utils';
3 | import YearMonth from './YearMonth';
4 |
5 | export default function DayHeader({
6 | styles, unit, minTime, maxTime, height, offsetY, maxTextWidth
7 | }) {
8 | const dates = getDates(minTime, maxTime);
9 | const ticks = [];
10 | const x0 = maxTextWidth;
11 | const y0 = offsetY / 2;
12 | const RH = height - y0;
13 | const len = dates.length - 1;
14 | for (let i = 0; i < len; i++) {
15 | const cur = new Date(dates[i]);
16 | const day = cur.getDay();
17 | const x = x0 + (dates[i] - minTime) / unit;
18 | const t = (dates[i + 1] - dates[i]) / unit;
19 | ticks.push((
20 |
21 | {day === 0 || day === 6 ? (
22 |
23 | ) : null}
24 |
25 | {cur.getDate()}
26 | {i === len - 1 ? (
27 |
28 | ) : null}
29 |
30 | ));
31 | }
32 | return (
33 |
34 |
43 | {ticks}
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/src/gantt/Grid.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 |
3 | export default function Grid({
4 | styles, data, width, height, offsetY, rowHeight, maxTextWidth
5 | }) {
6 | return (
7 |
8 | {data.map((v, i) => {
9 | const y = (i + 1) * rowHeight + offsetY;
10 | return ;
11 | })}
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/gantt/Labels.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 |
3 | export default function Labels({
4 | styles, data, onClick, rowHeight, offsetY
5 | }) {
6 | return (
7 |
8 | {data.map((v, i) => (
9 | onClick(v)}
16 | >
17 | {v.text}
18 |
19 | ))}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/gantt/Layout.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 |
3 | export default function Layout({
4 | styles, width, height, offsetY, thickWidth, maxTextWidth
5 | }) {
6 | const x0 = thickWidth / 2;
7 | const W = width - thickWidth;
8 | const H = height - thickWidth;
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/gantt/LinkLine.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 | import { p2s } from '../utils';
3 |
4 | export default function LinkLine({
5 | styles, data, unit, offsetY, minTime, rowHeight, barHeight, maxTextWidth
6 | }) {
7 | const x0 = maxTextWidth;
8 | const y0 = (rowHeight / 2) + offsetY;
9 | const map = {};
10 | data.forEach((v, i) => {
11 | map[v.id] = i;
12 | });
13 | return (
14 |
15 | {data.map((s, i) => {
16 | if (!s.end || !s.start || !s.links) {
17 | return null;
18 | }
19 | return s.links.map((l) => {
20 | const j = map[l.target];
21 | const e = data[j];
22 |
23 | if (!e || !e.start || !e.end) return null;
24 |
25 | const gap = 12;
26 | const arrow = 6;
27 | const mgap = e.type === 'milestone' ? barHeight / 2 : 0;
28 | const y1 = y0 + i * rowHeight;
29 | const y2 = y0 + j * rowHeight;
30 |
31 | let vgap = barHeight / 2 + 4;
32 | if (y1 > y2) {
33 | vgap = -vgap;
34 | }
35 |
36 | if (l.type === 'FS') {
37 | const x1 = x0 + (s.end - minTime) / unit;
38 | const x2 = x0 + (e.start - minTime) / unit - mgap;
39 | const p1 = [
40 | [x1, y1],
41 | [x1 + gap, y1]
42 | ];
43 | if (x2 - x1 >= 2 * gap) {
44 | p1.push([x1 + gap, y2]);
45 | } else {
46 | p1.push([x1 + gap, y2 - vgap]);
47 | p1.push([x2 - gap, y2 - vgap]);
48 | p1.push([x2 - gap, y2]);
49 | }
50 | p1.push([x2 - arrow, y2]);
51 | const p2 = [
52 | [x2 - arrow, y2 - arrow],
53 | [x2, y2],
54 | [x2 - arrow, y2 + arrow]
55 | ];
56 | return (
57 |
58 |
59 |
60 |
61 | );
62 | }
63 | if (l.type === 'FF') {
64 | const x1 = x0 + (s.end - minTime) / unit;
65 | const x2 = x0 + (e.end - minTime) / unit + mgap;
66 | const p1 = [
67 | [x1, y1],
68 | [x1 + gap, y1]
69 | ];
70 | if (x2 <= x1) {
71 | p1.push([x1 + gap, y2]);
72 | } else {
73 | p1.push([x1 + gap, y2 - vgap]);
74 | p1.push([x2 + gap, y2 - vgap]);
75 | p1.push([x2 + gap, y2]);
76 | }
77 | p1.push([x2 + arrow, y2]);
78 | const p2 = [
79 | [x2 + arrow, y2 - arrow],
80 | [x2, y2],
81 | [x2 + arrow, y2 + arrow]
82 | ];
83 | return (
84 |
85 |
86 |
87 |
88 | );
89 | }
90 | if (l.type === 'SS') {
91 | const x1 = x0 + (s.start - minTime) / unit;
92 | const x2 = x0 + (e.start - minTime) / unit - mgap;
93 | const p1 = [
94 | [x1, y1],
95 | [x1 - gap, y1]
96 | ];
97 | if (x1 <= x2) {
98 | p1.push([x1 - gap, y2]);
99 | } else {
100 | p1.push([x1 - gap, y2 - vgap]);
101 | p1.push([x2 - gap, y2 - vgap]);
102 | p1.push([x2 - gap, y2]);
103 | }
104 | p1.push([x2 - arrow, y2]);
105 | const p2 = [
106 | [x2 - arrow, y2 - arrow],
107 | [x2, y2],
108 | [x2 - arrow, y2 + arrow]
109 | ];
110 | return (
111 |
112 |
113 |
114 |
115 | );
116 | }
117 | if (l.type === 'SF') {
118 | const x1 = x0 + (s.start - minTime) / unit;
119 | const x2 = x0 + (e.end - minTime) / unit + mgap;
120 | const p1 = [
121 | [x1, y1],
122 | [x1 - gap, y1]
123 | ];
124 | if (x1 - x2 >= 2 * gap) {
125 | p1.push([x1 - gap, y2]);
126 | } else {
127 | p1.push([x1 - gap, y2 - vgap]);
128 | p1.push([x2 + gap, y2 - vgap]);
129 | p1.push([x2 + gap, y2]);
130 | }
131 | p1.push([x2 + arrow, y2]);
132 | const p2 = [
133 | [x2 + arrow, y2 - arrow],
134 | [x2, y2],
135 | [x2 + arrow, y2 + arrow]
136 | ];
137 | return (
138 |
139 |
140 |
141 |
142 | );
143 | }
144 | return null;
145 | });
146 | })}
147 |
148 | );
149 | }
150 |
--------------------------------------------------------------------------------
/src/gantt/MonthHeader.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 | import { getDates } from '../utils';
3 | import Year from './Year';
4 |
5 | export default function MonthHeader({
6 | styles, unit, minTime, maxTime, offsetY, maxTextWidth
7 | }) {
8 | const MONTH = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
9 | const dates = getDates(minTime, maxTime);
10 | const months = dates.filter((v) => (new Date(v)).getDate() === 1);
11 |
12 | months.unshift(minTime);
13 | months.push(maxTime);
14 |
15 | const ticks = [];
16 | const x0 = maxTextWidth;
17 | const y0 = offsetY / 2;
18 | const len = months.length - 1;
19 | for (let i = 0; i < len; i++) {
20 | const cur = new Date(months[i]);
21 | const month = cur.getMonth();
22 | const x = x0 + (months[i] - minTime) / unit;
23 | const t = (months[i + 1] - months[i]) / unit;
24 | ticks.push((
25 |
26 |
27 | {t > 30 ? (
28 | {MONTH[month]}
29 | ) : null}
30 |
31 | ));
32 | }
33 | return (
34 |
35 |
44 | {ticks}
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/src/gantt/WeekHeader.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 | import { getDates, addDays, DAY } from '../utils';
3 | import YearMonth from './YearMonth';
4 |
5 | export default function WeekHeader({
6 | styles, unit, minTime, maxTime, height, offsetY, maxTextWidth
7 | }) {
8 | const dates = getDates(minTime, maxTime);
9 | const weeks = dates.filter((v) => (new Date(v)).getDay() === 0);
10 | weeks.push(maxTime);
11 | const ticks = [];
12 | const x0 = maxTextWidth;
13 | const y0 = offsetY;
14 | const RH = height - y0;
15 | const d = DAY / unit;
16 | const len = weeks.length - 1;
17 | for (let i = 0; i < len; i++) {
18 | const cur = new Date(weeks[i]);
19 | const x = x0 + (weeks[i] - minTime) / unit;
20 | const curDay = cur.getDate();
21 | const prevDay = addDays(cur, -1).getDate();
22 | ticks.push((
23 |
24 |
25 |
26 | {curDay}
27 | {x - x0 > 28 ? (
28 | {prevDay}
29 | ) : null}
30 |
31 | ));
32 | }
33 | return (
34 |
35 |
44 | {ticks}
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/src/gantt/Year.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 |
3 | export default function Year({
4 | styles, months, unit, offsetY, minTime, maxTime, maxTextWidth
5 | }) {
6 | const years = months.filter((v) => (new Date(v)).getMonth() === 0);
7 |
8 | years.unshift(minTime);
9 | years.push(maxTime);
10 |
11 | const ticks = [];
12 | const x0 = maxTextWidth;
13 | const y2 = offsetY / 2;
14 | const len = years.length - 1;
15 | for (let i = 0; i < len; i++) {
16 | const cur = new Date(years[i]);
17 | const x = x0 + (years[i] - minTime) / unit;
18 | const t = (years[i + 1] - years[i]) / unit;
19 | ticks.push((
20 |
21 |
22 | {t > 35 ? (
23 | {cur.getFullYear()}
24 | ) : null}
25 |
26 | ));
27 | }
28 | return {ticks};
29 | }
30 |
--------------------------------------------------------------------------------
/src/gantt/YearMonth.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 | import { formatMonth } from '../utils';
3 |
4 | export default function YearMonth({
5 | styles, dates, unit, offsetY, minTime, maxTime, maxTextWidth
6 | }) {
7 | const months = dates.filter((v) => (new Date(v)).getDate() === 1);
8 |
9 | months.unshift(minTime);
10 | months.push(maxTime);
11 |
12 | const ticks = [];
13 | const x0 = maxTextWidth;
14 | const y2 = offsetY / 2;
15 | const len = months.length - 1;
16 | for (let i = 0; i < len; i++) {
17 | const cur = new Date(months[i]);
18 | const str = formatMonth(cur);
19 | const x = x0 + (months[i] - minTime) / unit;
20 | const t = (months[i + 1] - months[i]) / unit;
21 | ticks.push((
22 |
23 |
24 | {t > 50 ? {str} : null}
25 |
26 | ));
27 | }
28 | return {ticks};
29 | }
30 |
--------------------------------------------------------------------------------
/src/gantt/index.js:
--------------------------------------------------------------------------------
1 | import h from '../h';
2 | import { DAY } from '../utils';
3 | import Layout from './Layout';
4 | import DayHeader from './DayHeader';
5 | import WeekHeader from './WeekHeader';
6 | import MonthHeader from './MonthHeader';
7 | import Grid from './Grid';
8 | import Labels from './Labels';
9 | import LinkLine from './LinkLine';
10 | import Bar from './Bar';
11 | import getStyles from './styles';
12 |
13 | const UNIT = {
14 | day: DAY / 28,
15 | week: 7 * DAY / 56,
16 | month: 30 * DAY / 56
17 | };
18 | function NOOP() {}
19 |
20 | export default function Gantt({
21 | data = [],
22 | onClick = NOOP,
23 | viewMode = 'week',
24 | maxTextWidth = 140,
25 | offsetY = 60,
26 | rowHeight = 40,
27 | barHeight = 16,
28 | thickWidth = 1.4,
29 | styleOptions = {},
30 | showLinks = true,
31 | showDelay = true,
32 | start,
33 | end
34 | }) {
35 | const unit = UNIT[viewMode];
36 | const minTime = start.getTime() - unit * 48;
37 | const maxTime = end.getTime() + unit * 48;
38 |
39 | const width = (maxTime - minTime) / unit + maxTextWidth;
40 | const height = data.length * rowHeight + offsetY;
41 | const box = `0 0 ${width} ${height}`;
42 | const current = Date.now();
43 | const styles = getStyles(styleOptions);
44 |
45 | return (
46 |
134 | );
135 | }
136 |
--------------------------------------------------------------------------------
/src/gantt/styles.js:
--------------------------------------------------------------------------------
1 | const SIZE = '14px';
2 | const TYPE = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
3 |
4 | export function getFont({
5 | fontSize = SIZE,
6 | fontFamily = TYPE
7 | }) {
8 | return `bold ${fontSize} ${fontFamily}`;
9 | }
10 |
11 | export default function getStyles({
12 | bgColor = '#fff',
13 | lineColor = '#eee',
14 | redLineColor = '#f04134',
15 | groupBack = '#3db9d3',
16 | groupFront = '#299cb4',
17 | taskBack = '#65c16f',
18 | taskFront = '#46ad51',
19 | milestone = '#d33daf',
20 | warning = '#faad14',
21 | danger = '#f5222d',
22 | link = '#ffa011',
23 | textColor = '#222',
24 | lightTextColor = '#999',
25 | lineWidth = '1px',
26 | thickLineWidth = '1.4px',
27 | fontSize = SIZE,
28 | smallFontSize = '12px',
29 | fontFamily = TYPE,
30 | whiteSpace = 'pre'
31 | }) {
32 | const line = {
33 | stroke: lineColor,
34 | 'stroke-width': lineWidth
35 | };
36 | const redLine = {
37 | stroke: redLineColor,
38 | 'stroke-width': lineWidth
39 | };
40 | const thickLine = {
41 | stroke: lineColor,
42 | 'stroke-width': thickLineWidth
43 | };
44 | const text = {
45 | fill: textColor,
46 | 'dominant-baseline': 'central',
47 | 'font-size': fontSize,
48 | 'font-family': fontFamily,
49 | 'white-space': whiteSpace
50 | };
51 | const smallText = {
52 | fill: lightTextColor,
53 | 'font-size': smallFontSize
54 | };
55 | return {
56 | week: {
57 | fill: 'rgba(252, 248, 227, .6)'
58 | },
59 | box: {
60 | ...thickLine,
61 | fill: bgColor
62 | },
63 | line,
64 | cline: redLine,
65 | bline: thickLine,
66 | label: text,
67 | groupLabel: {
68 | ...text,
69 | 'font-weight': '600'
70 | },
71 | text1: {
72 | ...text,
73 | ...smallText,
74 | 'text-anchor': 'end'
75 | },
76 | text2: {
77 | ...text,
78 | ...smallText
79 | },
80 | text3: {
81 | ...text,
82 | ...smallText,
83 | 'text-anchor': 'middle'
84 | },
85 | link: {
86 | stroke: link,
87 | 'stroke-width': '1.5px',
88 | fill: 'none'
89 | },
90 | linkArrow: {
91 | fill: link
92 | },
93 | milestone: {
94 | fill: milestone
95 | },
96 | groupBack: {
97 | fill: groupBack
98 | },
99 | groupFront: {
100 | fill: groupFront
101 | },
102 | taskBack: {
103 | fill: taskBack
104 | },
105 | taskFront: {
106 | fill: taskFront
107 | },
108 | warning: {
109 | fill: warning
110 | },
111 | danger: {
112 | fill: danger
113 | },
114 | ctrl: {
115 | display: 'none',
116 | fill: '#f0f0f0',
117 | stroke: '#929292',
118 | 'stroke-width': '1px'
119 | }
120 | };
121 | }
122 |
--------------------------------------------------------------------------------
/src/h.js:
--------------------------------------------------------------------------------
1 | function addChild(c, childNodes) {
2 | if (c === null || c === undefined) return;
3 |
4 | if (typeof c === 'string' || typeof c === 'number') {
5 | childNodes.push(c.toString());
6 | } else if (Array.isArray(c)) {
7 | for (let i = 0; i < c.length; i++) {
8 | addChild(c[i], childNodes);
9 | }
10 | } else {
11 | childNodes.push(c);
12 | }
13 | }
14 |
15 | export default function h(tag, props, ...children) {
16 | const childNodes = [];
17 | addChild(children, childNodes);
18 |
19 | if (typeof tag === 'function') {
20 | return tag({ ...props, children: childNodes });
21 | }
22 |
23 | return {
24 | tag,
25 | props,
26 | children: childNodes
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import SVGGantt from './SVGGantt';
2 | import CanvasGantt from './CanvasGantt';
3 | import StrGantt from './StrGantt';
4 | import * as utils from './utils';
5 |
6 | export default CanvasGantt;
7 | export {
8 | SVGGantt, CanvasGantt, StrGantt, utils
9 | };
10 |
--------------------------------------------------------------------------------
/src/render/canvas.js:
--------------------------------------------------------------------------------
1 | import { s2p } from '../utils';
2 |
3 | export default function render(vnode, ctx, e) {
4 | const { tag, props, children } = vnode;
5 | if (tag === 'svg') {
6 | const { width, height } = props;
7 | ctx.width = width;
8 | ctx.height = height;
9 | }
10 | if (tag === 'line') {
11 | const {
12 | x1, x2, y1, y2, style = {}
13 | } = props;
14 | if (style.stroke) {
15 | ctx.strokeStyle = style.stroke;
16 | ctx.lineWidth = parseFloat(style['stroke-width'] || 1);
17 | }
18 | ctx.beginPath();
19 | ctx.moveTo(x1, y1);
20 | ctx.lineTo(x2, y2);
21 | ctx.stroke();
22 | }
23 | if (tag === 'polyline' || tag === 'polygon') {
24 | const { points, style = {} } = props;
25 | const p = s2p(points);
26 | if (style.stroke) {
27 | ctx.strokeStyle = style.stroke;
28 | ctx.lineWidth = parseFloat(style['stroke-width'] || 1);
29 | }
30 | if (style.fill) {
31 | ctx.fillStyle = style.fill;
32 | }
33 | ctx.beginPath();
34 | ctx.moveTo(p[0][0], p[0][1]);
35 | for (let i = 1; i < p.length; i++) {
36 | ctx.lineTo(p[i][0], p[i][1]);
37 | }
38 | if (tag === 'polyline') {
39 | ctx.stroke();
40 | } else {
41 | ctx.fill();
42 | }
43 | }
44 | if (tag === 'rect') {
45 | const {
46 | x, y, width, height, rx = 0, ry = 0, onClick, style = {}
47 | } = props;
48 |
49 | // From https://github.com/canvg/canvg
50 | ctx.beginPath();
51 | ctx.moveTo(x + rx, y);
52 | ctx.lineTo(x + width - rx, y);
53 | ctx.quadraticCurveTo(x + width, y, x + width, y + ry);
54 | ctx.lineTo(x + width, y + height - ry);
55 | ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);
56 | ctx.lineTo(x + rx, y + height);
57 | ctx.quadraticCurveTo(x, y + height, x, y + height - ry);
58 | ctx.lineTo(x, y + ry);
59 | ctx.quadraticCurveTo(x, y, x + rx, y);
60 | if (e && onClick && ctx.isPointInPath(e.x, e.y)) {
61 | onClick();
62 | }
63 | ctx.closePath();
64 |
65 | if (style.fill) {
66 | ctx.fillStyle = style.fill;
67 | }
68 | ctx.fill();
69 | if (style.stroke) {
70 | ctx.strokeStyle = style.stroke;
71 | ctx.lineWidth = parseFloat(style['stroke-width'] || 1);
72 | ctx.stroke();
73 | }
74 | }
75 | if (tag === 'text') {
76 | const { x, y, style } = props;
77 | if (style) {
78 | ctx.fillStyle = style.fill;
79 | const BL = {
80 | central: 'middle',
81 | middle: 'middle',
82 | hanging: 'hanging',
83 | alphabetic: 'alphabetic',
84 | ideographic: 'ideographic'
85 | };
86 | const AL = {
87 | start: 'start',
88 | middle: 'center',
89 | end: 'end'
90 | };
91 | ctx.textBaseline = BL[style['dominant-baseline']] || 'alphabetic';
92 | ctx.textAlign = AL[style['text-anchor']] || 'start';
93 | ctx.font = `${style['font-weight'] || ''} ${style['font-size']} ${style['font-family']}`;
94 | }
95 | ctx.fillText(children.join(''), x, y);
96 | }
97 |
98 | children.forEach((v) => {
99 | if (typeof v !== 'string') {
100 | render(v, ctx, e);
101 | }
102 | });
103 | }
104 |
--------------------------------------------------------------------------------
/src/render/string.js:
--------------------------------------------------------------------------------
1 | function attrEscape(str) {
2 | return String(str).replace(/&/g, '&')
3 | .replace(//g, '>')
13 | .replace(/\r/g, '
');
14 | }
15 |
16 | export default function render(vnode, ctx) {
17 | const { tag, props, children } = vnode;
18 | const tokens = [];
19 | tokens.push(`<${tag}`);
20 |
21 | Object.keys(props || {}).forEach((k) => {
22 | let v = props[k];
23 | if (k === 'onClick') return;
24 | if (k === 'style' && typeof v === 'object') {
25 | v = Object.keys(v).map((i) => `${i}:${v[i]};`).join('');
26 | }
27 | tokens.push(` ${k}="${attrEscape(v)}"`);
28 | });
29 |
30 | if (!children || !children.length) {
31 | tokens.push(' />');
32 | return tokens.join('');
33 | }
34 |
35 | tokens.push('>');
36 |
37 | children.forEach((v) => {
38 | if (typeof v === 'string') {
39 | tokens.push(escape(v));
40 | } else {
41 | tokens.push(render(v, ctx));
42 | }
43 | });
44 |
45 | tokens.push(`${tag}>`);
46 | return tokens.join('');
47 | }
48 |
--------------------------------------------------------------------------------
/src/render/svg.js:
--------------------------------------------------------------------------------
1 | const NS = 'http://www.w3.org/2000/svg';
2 | const doc = document;
3 |
4 | function applyProperties(node, props) {
5 | Object.keys(props).forEach((k) => {
6 | const v = props[k];
7 | if (k === 'style' && typeof v === 'object') {
8 | Object.keys(v).forEach((sk) => {
9 | // eslint-disable-next-line
10 | node.style[sk] = v[sk];
11 | });
12 | } else if (k === 'onClick') {
13 | if (typeof v === 'function') {
14 | node.addEventListener('click', v);
15 | }
16 | } else {
17 | node.setAttribute(k, v);
18 | }
19 | });
20 | }
21 |
22 | export default function render(vnode, ctx) {
23 | const { tag, props, children } = vnode;
24 | const node = doc.createElementNS(NS, tag);
25 |
26 | if (props) {
27 | applyProperties(node, props);
28 | }
29 |
30 | children.forEach((v) => {
31 | node.appendChild(typeof v === 'string' ? doc.createTextNode(v) : render(v, ctx));
32 | });
33 | return node;
34 | }
35 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | export const DAY = 24 * 3600 * 1000;
2 |
3 | export function addDays(date, days) {
4 | const d = new Date(date.valueOf());
5 | d.setDate(d.getDate() + days);
6 | return d;
7 | }
8 |
9 | export function getDates(begin, end) {
10 | const dates = [];
11 | let s = new Date(begin);
12 | s.setHours(24, 0, 0, 0);
13 | while (s.getTime() <= end) {
14 | dates.push(s.getTime());
15 | s = addDays(s, 1);
16 | }
17 | return dates;
18 | }
19 |
20 | let ctx = null;
21 | export function textWidth(text, font, pad) {
22 | ctx = ctx || document.createElement('canvas').getContext('2d');
23 | ctx.font = font;
24 | return ctx.measureText(text).width + pad;
25 | }
26 |
27 | export function formatMonth(date) {
28 | const y = date.getFullYear();
29 | const m = date.getMonth() + 1;
30 | return `${y}/${m > 9 ? m : `0${m}`}`;
31 | }
32 |
33 | export function formatDay(date) {
34 | const m = date.getMonth() + 1;
35 | const d = date.getDate();
36 | return `${m}/${d}`;
37 | }
38 |
39 | export function minDate(a, b) {
40 | if (a && b) {
41 | return a > b ? b : a;
42 | }
43 | return a || b;
44 | }
45 |
46 | export function maxDate(a, b) {
47 | if (a && b) {
48 | return a < b ? b : a;
49 | }
50 | return a || b;
51 | }
52 |
53 | export function max(list, defaultValue) {
54 | if (list.length) {
55 | return Math.max.apply(null, list);
56 | }
57 | return defaultValue;
58 | }
59 |
60 | export function p2s(arr) {
61 | return arr.map((p) => `${p[0]},${p[1]}`).join(' ');
62 | }
63 |
64 | export function s2p(str) {
65 | return str.split(' ').map((s) => {
66 | const p = s.split(',');
67 | return [parseFloat(p[0]), parseFloat(p[1])];
68 | });
69 | }
70 |
71 | function walkLevel(nodes, level) {
72 | for (let i = 0; i < nodes.length; i++) {
73 | const node = nodes[i];
74 | node.level = `${level}${i + 1}`;
75 | node.text = `${node.level} ${node.name}`;
76 | walkLevel(node.children, `${node.level}.`);
77 | }
78 | }
79 |
80 | function walkDates(nodes) {
81 | let start = null;
82 | let end = null;
83 | let percent = 0;
84 | for (let i = 0; i < nodes.length; i++) {
85 | const node = nodes[i];
86 | if (node.children.length) {
87 | const tmp = walkDates(node.children);
88 | node.start = tmp.start;
89 | node.end = tmp.end;
90 | node.percent = tmp.percent;
91 | if (tmp.start && tmp.end) {
92 | node.duration = (tmp.end - tmp.start) / DAY;
93 | } else {
94 | node.duration = 0;
95 | }
96 | } else {
97 | node.percent = node.percent || 0;
98 | if (node.start) {
99 | node.end = addDays(node.start, node.duration || 0);
100 | }
101 | if (node.type === 'milestone') {
102 | node.end = node.start;
103 | }
104 | }
105 | start = minDate(start, node.start);
106 | end = maxDate(end, node.end);
107 | percent += node.percent;
108 | }
109 | if (nodes.length) {
110 | percent /= nodes.length;
111 | }
112 | return { start, end, percent };
113 | }
114 |
115 | export function formatData(tasks, links, walk) {
116 | const map = {};
117 | const tmp = tasks.map((t, i) => {
118 | map[t.id] = i;
119 | return { ...t, children: [], links: [] };
120 | });
121 | const roots = [];
122 | tmp.forEach((t) => {
123 | const parent = tmp[map[t.parent]];
124 | if (parent) {
125 | parent.children.push(t);
126 | } else {
127 | roots.push(t);
128 | }
129 | });
130 | links.forEach((l) => {
131 | const s = tmp[map[l.source]];
132 | const t = tmp[map[l.target]];
133 | if (s && t) {
134 | s.links.push(l);
135 | }
136 | });
137 |
138 | walkLevel(roots, '');
139 | walkDates(roots);
140 |
141 | if (walk) {
142 | walk(roots);
143 | }
144 |
145 | const list = [];
146 | roots.forEach((r) => {
147 | const stack = [];
148 | stack.push(r);
149 | while (stack.length) {
150 | const node = stack.pop();
151 | const len = node.children.length;
152 | if (len) {
153 | node.type = 'group';
154 | }
155 | list.push(node);
156 | for (let i = len - 1; i >= 0; i--) {
157 | stack.push(node.children[i]);
158 | }
159 | }
160 | });
161 | return list;
162 | }
163 |
164 | export function hasPath(vmap, a, b) {
165 | const stack = [];
166 | stack.push(vmap[a]);
167 | while (stack.length) {
168 | const v = stack.pop();
169 | if (v.id === b) {
170 | return true;
171 | }
172 | for (let i = 0; i < v.links.length; i++) {
173 | stack.push(v.links[i]);
174 | }
175 | }
176 | return false;
177 | }
178 |
179 | export function toposort(links) {
180 | const vmap = {};
181 | links.forEach((l) => {
182 | const init = (id) => ({ id, out: [], in: 0 });
183 | vmap[l.source] = init(l.source);
184 | vmap[l.target] = init(l.target);
185 | });
186 | for (let i = 0; i < links.length; i++) {
187 | const l = links[i];
188 | vmap[l.target].in++;
189 | vmap[l.source].out.push(i);
190 | }
191 | const s = Object.keys(vmap)
192 | .map((k) => vmap[k].id)
193 | .filter((id) => !vmap[id].in);
194 | const sorted = [];
195 | while (s.length) {
196 | const id = s.pop();
197 | sorted.push(id);
198 | for (let i = 0; i < vmap[id].out.length; i++) {
199 | const index = vmap[id].out[i];
200 | const v = vmap[links[index].target];
201 | v.in--;
202 | if (!v.in) {
203 | s.push(v.id);
204 | }
205 | }
206 | }
207 | return sorted;
208 | }
209 |
210 | export function autoSchedule(tasks, links, lockMilestone = false) {
211 | const vmap = {};
212 | links.forEach((l) => {
213 | vmap[l.source] = { id: l.source, links: [] };
214 | vmap[l.target] = { id: l.target, links: [] };
215 | });
216 | const dag = [];
217 | links.forEach((l) => {
218 | const { source, target } = l;
219 | if (!hasPath(vmap, target, source)) {
220 | dag.push(l);
221 | vmap[source].links.push(vmap[target]);
222 | }
223 | });
224 | const sorted = toposort(dag);
225 | const tmap = {};
226 | for (let i = 0; i < tasks.length; i++) {
227 | const task = tasks[i];
228 | if (task.type === 'milestone') {
229 | task.duration = 0;
230 | }
231 | tmap[task.id] = i;
232 | }
233 | const ins = {};
234 | sorted.forEach((id) => {
235 | ins[id] = [];
236 | });
237 | dag.forEach((l) => {
238 | ins[l.target].push(l);
239 | });
240 | sorted.forEach((id) => {
241 | const task = tasks[tmap[id]];
242 | if (!task) return;
243 | const days = task.duration || 0;
244 | if (lockMilestone && task.type === 'milestone') {
245 | return;
246 | }
247 | let start = null;
248 | let end = null;
249 | for (let i = 0; i < ins[id].length; i++) {
250 | const l = ins[id][i];
251 | const v = tasks[tmap[l.source]];
252 | if (v && v.start) {
253 | const s = addDays(v.start, l.lag || 0);
254 | const e = addDays(s, v.duration || 0);
255 | if (l.type === 'SS') {
256 | start = maxDate(start, s);
257 | }
258 | if (l.type === 'FS') {
259 | start = maxDate(start, e);
260 | }
261 | if (l.type === 'SF') {
262 | end = maxDate(end, s);
263 | }
264 | if (l.type === 'FF') {
265 | end = maxDate(end, e);
266 | }
267 | }
268 | }
269 | if (end) {
270 | task.start = addDays(end, -days);
271 | }
272 | if (start) {
273 | task.start = start;
274 | }
275 | });
276 | }
277 |
--------------------------------------------------------------------------------