2
0
mirror of https://github.com/tenrok/vue-ganttastic.git synced 2026-06-25 09:20:34 +03:00

refactor: remove hardcode format and unit of time

This commit is contained in:
yicone
2021-02-02 15:54:56 +08:00
parent 0b79d21097
commit ac56ec12d7
4 changed files with 686 additions and 514 deletions
+264 -180
View File
@@ -5,23 +5,20 @@
ref="g-gantt-bar" ref="g-gantt-bar"
:style="barStyle" :style="barStyle"
@mouseenter.stop="onMouseenter($event)" @mouseenter.stop="onMouseenter($event)"
@mouseleave.stop ="onMouseleave($event)" @mouseleave.stop="onMouseleave($event)"
@mousedown.stop="onMousedown($event)" @mousedown.stop="onMousedown($event)"
@click.stop="onClick($event)" @click.stop="onClick($event)"
@dblclick="onDblclick($event)" @dblclick="onDblclick($event)"
@contextmenu="onContextmenu($event)" @contextmenu="onContextmenu($event)"
> >
<div class="g-gantt-bar-label"> <div class="g-gantt-bar-label">
<slot <slot name="bar-label" :bar="bar">
name="bar-label" {{ barConfig.label || '' }}
:bar="bar"
>
{{barConfig.label || ""}}
</slot> </slot>
</div> </div>
<template v-if="barConfig.handles"> <template v-if="barConfig.handles">
<div class="g-gantt-bar-handle-left"/> <div class="g-gantt-bar-handle-left" />
<div class="g-gantt-bar-handle-right"/> <div class="g-gantt-bar-handle-right" />
</template> </template>
</div> </div>
@@ -33,14 +30,16 @@
> >
<div <div
class="color-indicator" class="color-indicator"
:style="{background: this.barStyle.background || this.barStyle.backgroundColor}" :style="{
background:
this.barStyle.background || this.barStyle.backgroundColor,
}"
/> />
{{bar[barStart] | TimeFilter}} {{ bar[barStart] }}
- -
{{bar[barEnd] | TimeFilter}} {{ bar[barEnd] }}
</div> </div>
</transition> </transition>
</div> </div>
</template> </template>
@@ -48,28 +47,30 @@
import moment from 'moment' import moment from 'moment'
export default { export default {
name: "GGanttBar", name: 'GGanttBar',
props:{ props: {
bar: {type: Object}, bar: { type: Object },
barStart: {type: String}, // property name of the bar objects that represents the start datetime barStart: { type: String }, // property name of the bar objects that represents the start datetime
barEnd: {type: String}, // property name of the bar objects that represents the end datetime, barEnd: { type: String }, // property name of the bar objects that represents the end datetime,
barContainer: [Object, DOMRect], barContainer: [Object, DOMRect],
allBarsInRow: {type: Array} allBarsInRow: { type: Array },
}, },
inject: [ inject: [
"getHourCount", 'getTimeCount',
"ganttChartProps", 'ganttChartProps',
"initDragOfBarsFromBundle", 'initDragOfBarsFromBundle',
"moveBarsFromBundleOfPushedBar", 'moveBarsFromBundleOfPushedBar',
"setDragLimitsOfGanttBar", 'setDragLimitsOfGanttBar',
"onBarEvent", 'onBarEvent',
"onDragendBar", 'onDragendBar',
"getMinGapBetweenBars", 'getMinGapBetweenBars',
'getTimeUnit',
'getTimeFormat',
], ],
data(){ data() {
return { return {
showTooltip: false, showTooltip: false,
tooltipTimeout: null, tooltipTimeout: null,
@@ -83,42 +84,50 @@ export default {
// possible values: drag, dragByHandleLeft, dragByHandleRight, // possible values: drag, dragByHandleLeft, dragByHandleRight,
barStartBeforeDrag: null, barStartBeforeDrag: null,
barEndBeforeDrag: null, barEndBeforeDrag: null,
timeUnit: this.getTimeUnit(),
timeChildKey: this.getTimeUnit() === 'days' ? 'hours' : 'minutes',
timeFormat: this.getTimeFormat(),
} }
}, },
computed:{ computed: {
// use these computed moment objects to work with the bar's start/end dates: // use these computed moment objects to work with the bar's start/end dates:
// instead of directly mutating them: // instead of directly mutating them:
barStartMoment:{ barStartMoment: {
get(){ get() {
return moment(this.bar[this.barStart]) return moment(this.bar[this.barStart])
}, },
set(value){ set(value) {
this.bar[this.barStart] = moment(value).format("YYYY-MM-DD HH:mm:ss") this.bar[this.barStart] = moment(value).format(this.timeFormat)
} },
}, },
barEndMoment: { barEndMoment: {
get(){ get() {
return moment(this.bar[this.barEnd]) return moment(this.bar[this.barEnd])
}, },
set(value){ set(value) {
this.bar[this.barEnd] = moment(value).format("YYYY-MM-DD HH:mm:ss") this.bar[this.barEnd] = moment(value).format(this.timeFormat)
} },
}, },
barConfig(){ barConfig() {
if(this.bar.ganttBarConfig) { if (this.bar.ganttBarConfig) {
return { return {
...this.bar.ganttBarConfig, ...this.bar.ganttBarConfig,
background: this.bar.ganttBarConfig.isShadow ? "grey" : this.bar.ganttBarConfig.background || this.bar.ganttBarConfig.backgroundColor, background: this.bar.ganttBarConfig.isShadow
opacity: this.bar.ganttBarConfig.isShadow ? "0.3" : this.bar.ganttBarConfig.opacity ? 'grey'
: this.bar.ganttBarConfig.background ||
this.bar.ganttBarConfig.backgroundColor,
opacity: this.bar.ganttBarConfig.isShadow
? '0.3'
: this.bar.ganttBarConfig.opacity,
} }
} }
return {} return {}
}, },
barStyle(){ barStyle() {
let xStart = this.mapTimeToPosition(this.barStartMoment) let xStart = this.mapTimeToPosition(this.barStartMoment)
let xEnd = this.mapTimeToPosition(this.barEndMoment) let xEnd = this.mapTimeToPosition(this.barEndMoment)
return { return {
@@ -126,77 +135,87 @@ export default {
left: `${xStart}px`, left: `${xStart}px`,
width: `${xEnd - xStart}px`, width: `${xEnd - xStart}px`,
height: `${this.ganttChartProps.rowHeight - 6}px`, height: `${this.ganttChartProps.rowHeight - 6}px`,
zIndex: this.barConfig.zIndex || (this.isDragging ? 2 : 1) zIndex: this.barConfig.zIndex || (this.isDragging ? 2 : 1),
} }
}, },
tooltipStyle(){ tooltipStyle() {
return{ return {
left: this.barStyle.left, left: this.barStyle.left,
top:`${this.ganttChartProps.rowHeight}px`, top: `${this.ganttChartProps.rowHeight}px`,
} }
}, },
chartStartMoment(){ chartStartMoment() {
return moment(this.ganttChartProps.chartStart) return moment(this.ganttChartProps.chartStart)
}, },
chartEndMoment(){ chartEndMoment() {
return moment(this.ganttChartProps.chartEnd) return moment(this.ganttChartProps.chartEnd)
} },
}, },
methods:{ methods: {
onMouseenter(e) {
onMouseenter(e){ if (this.tooltipTimeout) {
if(this.tooltipTimeout){
clearTimeout(this.tooltipTimeout) clearTimeout(this.tooltipTimeout)
} }
this.tooltipTimeout = setTimeout(() => this.showTooltip = true, 800) this.tooltipTimeout = setTimeout(() => (this.showTooltip = true), 800)
this.onBarEvent({event: e, type: e.type}, this) this.onBarEvent({ event: e, type: e.type }, this)
}, },
onMouseleave(e){ onMouseleave(e) {
clearTimeout(this.tooltipTimeout) clearTimeout(this.tooltipTimeout)
this.showTooltip = false this.showTooltip = false
this.onBarEvent({event: e, type: e.type}, this) this.onBarEvent({ event: e, type: e.type }, this)
}, },
onContextmenu(e){ onContextmenu(e) {
const time = this.mapPositionToTime(e.clientX - this.barContainer.left).format("YYYY-MM-DD HH:mm:ss") const time = this.mapPositionToTime(
this.onBarEvent({event: e, type: e.type, time}, this) e.clientX - this.barContainer.left
).format(this.timeFormat)
this.onBarEvent({ event: e, type: e.type, time }, this)
}, },
onClick(e){ onClick(e) {
const time = this.mapPositionToTime(e.clientX - this.barContainer.left).format("YYYY-MM-DD HH:mm:ss") const time = this.mapPositionToTime(
this.onBarEvent({event: e, type: e.type, time}, this) e.clientX - this.barContainer.left
).format(this.timeFormat)
this.onBarEvent({ event: e, type: e.type, time }, this)
}, },
onDblclick(e) { onDblclick(e) {
const time = this.mapPositionToTime(e.clientX - this.barContainer.left).format("YYYY-MM-DD HH:mm:ss") const time = this.mapPositionToTime(
this.onBarEvent({event: e, type: e.type, time}, this) e.clientX - this.barContainer.left
).format(this.timeFormat)
this.onBarEvent({ event: e, type: e.type, time }, this)
}, },
onMousedown(e){ onMousedown(e) {
e.preventDefault() e.preventDefault()
if(e.button === 2){ if (e.button === 2) {
return return
} }
if(!this.barConfig.immobile && !this.barConfig.isShadow) { if (!this.barConfig.immobile && !this.barConfig.isShadow) {
this.setDragLimitsOfGanttBar(this) this.setDragLimitsOfGanttBar(this)
// initialize the dragging on next mousemove event: // initialize the dragging on next mousemove event:
window.addEventListener("mousemove", this.onFirstMousemove, {once: true}) window.addEventListener('mousemove', this.onFirstMousemove, {
once: true,
})
// if next mousemove happens after mouse up (if user just presses mouse button down, then up, without moving): // if next mousemove happens after mouse up (if user just presses mouse button down, then up, without moving):
window.addEventListener("mouseup", window.addEventListener(
() => window.removeEventListener("mousemove", this.onFirstMousemove), 'mouseup',
{once: true} () => window.removeEventListener('mousemove', this.onFirstMousemove),
{ once: true }
) )
} }
const time = this.mapPositionToTime(e.clientX - this.barContainer.left).format("YYYY-MM-DD HH:mm:ss") const time = this.mapPositionToTime(
this.onBarEvent({event: e, type: e.type, time}, this) e.clientX - this.barContainer.left
).format(this.timeFormat)
this.onBarEvent({ event: e, type: e.type, time }, this)
}, },
onFirstMousemove(e){ onFirstMousemove(e) {
this.isMainBarOfDrag = true this.isMainBarOfDrag = true
// this method is injected here by GGanttChart.vue, and calls initDrag() // this method is injected here by GGanttChart.vue, and calls initDrag()
// for all GGanttBars that belong to the same bundle as this bar: // for all GGanttBars that belong to the same bundle as this bar:
@@ -206,165 +225,230 @@ export default {
/* --------------------------------------------------------- */ /* --------------------------------------------------------- */
/* ------------- METHODS FOR DRAGGING THE BAR -------------- */ /* ------------- METHODS FOR DRAGGING THE BAR -------------- */
/* --------------------------------------------------------- */ /* --------------------------------------------------------- */
initDrag(e){ // "e" must be the mousedown event initDrag(e) {
// "e" must be the mousedown event
this.isDragging = true this.isDragging = true
this.barStartBeforeDrag = this.bar[this.barStart] this.barStartBeforeDrag = this.bar[this.barStart]
this.barEndBeforeDrag = this.bar[this.barEnd] this.barEndBeforeDrag = this.bar[this.barEnd]
let barX = this.$refs["g-gantt-bar"].getBoundingClientRect().left let barX = this.$refs['g-gantt-bar'].getBoundingClientRect().left
this.cursorOffsetX = e.clientX - barX this.cursorOffsetX = e.clientX - barX
let mousedownType = e.target.className let mousedownType = e.target.className
switch(mousedownType){ switch (mousedownType) {
case "g-gantt-bar-handle-left": case 'g-gantt-bar-handle-left':
document.body.style.cursor = "w-resize" document.body.style.cursor = 'w-resize'
this.mousemoveCallback = this.dragByHandleLeft this.mousemoveCallback = this.dragByHandleLeft
break break
case "g-gantt-bar-handle-right": case 'g-gantt-bar-handle-right':
document.body.style.cursor = "w-resize" document.body.style.cursor = 'w-resize'
this.mousemoveCallback = this.dragByHandleRight this.mousemoveCallback = this.dragByHandleRight
break break
default: this.mousemoveCallback = this.drag default:
this.mousemoveCallback = this.drag
} }
window.addEventListener("mousemove", this.mousemoveCallback) window.addEventListener('mousemove', this.mousemoveCallback)
window.addEventListener("mouseup", this.endDrag) window.addEventListener('mouseup', this.endDrag)
}, },
drag(e){ drag(e) {
let barWidth = this.$refs["g-gantt-bar"].getBoundingClientRect().width let barWidth = this.$refs['g-gantt-bar'].getBoundingClientRect().width
let newXStart = (e.clientX-this.barContainer.left) - this.cursorOffsetX let newXStart = e.clientX - this.barContainer.left - this.cursorOffsetX
let newXEnd = newXStart + barWidth let newXEnd = newXStart + barWidth
if(this.isPosOutOfDragRange(newXStart, newXEnd)){ if (this.isPosOutOfDragRange(newXStart, newXEnd)) {
return return
} }
this.barStartMoment = this.mapPositionToTime(newXStart) this.barStartMoment = this.mapPositionToTime(newXStart)
this.barEndMoment = this.mapPositionToTime(newXEnd) this.barEndMoment = this.mapPositionToTime(newXEnd)
this.manageOverlapping() this.manageOverlapping()
this.onBarEvent({event: e, type: "drag"}, this) this.onBarEvent({ event: e, type: 'drag' }, this)
}, },
dragByHandleLeft(e){ dragByHandleLeft(e) {
let newXStart = e.clientX - this.barContainer.left let newXStart = e.clientX - this.barContainer.left
let newStartMoment = this.mapPositionToTime(newXStart) let newStartMoment = this.mapPositionToTime(newXStart)
if(newStartMoment.isSameOrAfter(this.barEndMoment) || this.isPosOutOfDragRange(newXStart, null)){ if (
newStartMoment.isSameOrAfter(this.barEndMoment) ||
this.isPosOutOfDragRange(newXStart, null)
) {
return return
} }
this.barStartMoment = newStartMoment this.barStartMoment = newStartMoment
this.manageOverlapping() this.manageOverlapping()
}, },
dragByHandleRight(e){ dragByHandleRight(e) {
let newXEnd = e.clientX - this.barContainer.left let newXEnd = e.clientX - this.barContainer.left
let newEndMoment = this.mapPositionToTime(newXEnd) let newEndMoment = this.mapPositionToTime(newXEnd)
if(newEndMoment.isSameOrBefore(this.barStartMoment) || this.isPosOutOfDragRange(null, newXEnd)){ if (
newEndMoment.isSameOrBefore(this.barStartMoment) ||
this.isPosOutOfDragRange(null, newXEnd)
) {
return return
} }
this.barEndMoment = newEndMoment this.barEndMoment = newEndMoment
this.manageOverlapping() this.manageOverlapping()
}, },
isPosOutOfDragRange(xStart, xEnd){ isPosOutOfDragRange(xStart, xEnd) {
// 不能推动旁边的bar时,拖拽就不停止 // 不能推动旁边的bar时,拖拽就不停止
if(!this.ganttChartProps.pushOnOverlap) { if (!this.ganttChartProps.pushOnOverlap) {
return false return false
} }
if(xStart && xStart < 0) { if (xStart && xStart < 0) {
return true return true
} }
if(xStart && this.dragLimitLeft !== null && xStart < this.dragLimitLeft + this.getMinGapBetweenBars()){ if (
xStart &&
this.dragLimitLeft !== null &&
xStart < this.dragLimitLeft + this.getMinGapBetweenBars()
) {
return true return true
} }
if(xEnd && this.dragLimitRight !== null && xEnd > this.dragLimitRight - this.getMinGapBetweenBars()){ if (
xEnd &&
this.dragLimitRight !== null &&
xEnd > this.dragLimitRight - this.getMinGapBetweenBars()
) {
return true return true
} }
return false return false
}, },
endDrag(e){ endDrag(e) {
this.isDragging = false this.isDragging = false
this.dragLimitLeft = null this.dragLimitLeft = null
this.dragLimitRight = null this.dragLimitRight = null
document.body.style.cursor = "auto" document.body.style.cursor = 'auto'
window.removeEventListener("mousemove", this.mousemoveCallback) window.removeEventListener('mousemove', this.mousemoveCallback)
window.removeEventListener("mouseup", this.endDrag) window.removeEventListener('mouseup', this.endDrag)
if(this.isMainBarOfDrag){ if (this.isMainBarOfDrag) {
this.onDragendBar(e, this) this.onDragendBar(e, this)
this.isMainBarOfDrag = false this.isMainBarOfDrag = false
} }
}, },
snapBack(){ snapBack() {
this.barStartMoment = this.barStartBeforeDrag this.barStartMoment = this.barStartBeforeDrag
this.barEndMoment = this.barEndBeforeDrag this.barEndMoment = this.barEndBeforeDrag
}, },
manageOverlapping(){ manageOverlapping() {
if(!this.ganttChartProps.pushOnOverlap || this.barConfig.pushOnOverlap === false){ if (
!this.ganttChartProps.pushOnOverlap ||
this.barConfig.pushOnOverlap === false
) {
return return
} }
let currentBar = this.bar let currentBar = this.bar
let {overlapBar, overlapType} = this.getOverlapBarAndType(currentBar) let { overlapBar, overlapType } = this.getOverlapBarAndType(currentBar)
while(overlapBar){ while (overlapBar) {
let minuteDiff let minuteDiff
let currentStartMoment = moment(currentBar[this.barStart]) let currentStartMoment = moment(currentBar[this.barStart])
let currentEndMoment = moment(currentBar[this.barEnd]) let currentEndMoment = moment(currentBar[this.barEnd])
let overlapStartMoment = moment(overlapBar[this.barStart]) let overlapStartMoment = moment(overlapBar[this.barStart])
let overlapEndMoment = moment(overlapBar[this.barEnd]) let overlapEndMoment = moment(overlapBar[this.barEnd])
switch(overlapType){ switch (overlapType) {
case "left": case 'left':
minuteDiff = overlapEndMoment.diff(currentStartMoment, "minutes", true) + this.getMinGapBetweenBars() minuteDiff =
overlapBar[this.barEnd] = currentStartMoment.subtract(this.getMinGapBetweenBars(), "minutes", true).format("YYYY-MM-DD HH:mm:ss") overlapEndMoment.diff(
overlapBar[this.barStart] = overlapStartMoment.subtract(minuteDiff, "minutes", true).format("YYYY-MM-DD HH:mm:ss") currentStartMoment,
this.timeChildKey,
true
) + this.getMinGapBetweenBars()
overlapBar[this.barEnd] = currentStartMoment
.subtract(this.getMinGapBetweenBars(), this.timeChildKey, true)
.format(this.timeFormat)
overlapBar[this.barStart] = overlapStartMoment
.subtract(minuteDiff, this.timeChildKey, true)
.format(this.timeFormat)
break break
case "right": case 'right':
minuteDiff = currentEndMoment.diff(overlapStartMoment, "minutes", true) + this.getMinGapBetweenBars() minuteDiff =
overlapBar[this.barStart] = currentEndMoment.add(this.getMinGapBetweenBars(), "minutes", true).format("YYYY-MM-DD HH:mm:ss") currentEndMoment.diff(
overlapBar[this.barEnd] = overlapEndMoment.add(minuteDiff, "minutes", true).format("YYYY-MM-DD HH:mm:ss") overlapStartMoment,
this.timeChildKey,
true
) + this.getMinGapBetweenBars()
overlapBar[this.barStart] = currentEndMoment
.add(this.getMinGapBetweenBars(), this.timeChildKey, true)
.format(this.timeFormat)
overlapBar[this.barEnd] = overlapEndMoment
.add(minuteDiff, this.timeChildKey, true)
.format(this.timeFormat)
break break
default: default:
// eslint-disable-next-line // eslint-disable-next-line
console.warn("One bar is inside of the other one! This should never occur while push-on-overlap is active!") console.warn(
'One bar is inside of the other one! This should never occur while push-on-overlap is active!'
)
return return
} }
this.moveBarsFromBundleOfPushedBar(overlapBar, minuteDiff, overlapType) this.moveBarsFromBundleOfPushedBar(overlapBar, minuteDiff, overlapType)
currentBar = overlapBar; currentBar = overlapBar
({overlapBar, overlapType} = this.getOverlapBarAndType(overlapBar)) ;({ overlapBar, overlapType } = this.getOverlapBarAndType(overlapBar))
} }
}, },
getOverlapBarAndType(bar){ getOverlapBarAndType(bar) {
let barStartMoment = moment(bar[this.barStart]) let barStartMoment = moment(bar[this.barStart])
let barEndMoment = moment(bar[this.barEnd]) let barEndMoment = moment(bar[this.barEnd])
let overlapLeft, overlapRight, overlapInBetween let overlapLeft, overlapRight, overlapInBetween
let overlapBar = this.allBarsInRow.find(otherBar => { let overlapBar = this.allBarsInRow.find((otherBar) => {
if(otherBar === bar || otherBar.ganttBarConfig.pushOnOverlap === false){ if (
otherBar === bar ||
otherBar.ganttBarConfig.pushOnOverlap === false
) {
return false return false
} }
let otherBarStart = moment(otherBar[this.barStart]) let otherBarStart = moment(otherBar[this.barStart])
let otherBarEnd = moment(otherBar[this.barEnd]) let otherBarEnd = moment(otherBar[this.barEnd])
overlapLeft = barStartMoment.isBetween(otherBarStart, otherBarEnd) overlapLeft = barStartMoment.isBetween(otherBarStart, otherBarEnd)
overlapRight = barEndMoment.isBetween(otherBarStart, otherBarEnd) overlapRight = barEndMoment.isBetween(otherBarStart, otherBarEnd)
overlapInBetween = otherBarStart.isBetween(barStartMoment, barEndMoment) overlapInBetween =
|| otherBarEnd.isBetween(barStartMoment, barEndMoment) otherBarStart.isBetween(barStartMoment, barEndMoment) ||
otherBarEnd.isBetween(barStartMoment, barEndMoment)
return overlapLeft || overlapRight || overlapInBetween return overlapLeft || overlapRight || overlapInBetween
}) })
let overlapType = overlapLeft ? "left" : (overlapRight ? "right" : (overlapInBetween ? "between" : null)) let overlapType = overlapLeft
return {overlapBar, overlapType} ? 'left'
: overlapRight
? 'right'
: overlapInBetween
? 'between'
: null
return { overlapBar, overlapType }
}, },
// this is used in GGanttChart, when a bar from a bundle is pushed // this is used in GGanttChart, when a bar from a bundle is pushed
// so that bars from its bundle also get pushed // so that bars from its bundle also get pushed
moveBarByMinutesAndPush(minuteCount, direction){ moveBarByMinutesAndPush(minuteCount, direction) {
switch(direction){ switch (direction) {
case "left": case 'left':
this.barStartMoment = moment(this.barStartMoment).subtract(minuteCount, "minutes", true) this.barStartMoment = moment(this.barStartMoment).subtract(
this.barEndMoment = moment(this.barEndMoment).subtract(minuteCount, "minutes", true) minuteCount,
this.timeChildKey,
true
)
this.barEndMoment = moment(this.barEndMoment).subtract(
minuteCount,
this.timeChildKey,
true
)
break break
case "right": case 'right':
this.barStartMoment = moment(this.barStartMoment).add(minuteCount, "minutes", true) this.barStartMoment = moment(this.barStartMoment).add(
this.barEndMoment = moment(this.barEndMoment).add(minuteCount, "minutes", true) minuteCount,
this.timeChildKey,
true
)
this.barEndMoment = moment(this.barEndMoment).add(
minuteCount,
this.timeChildKey,
true
)
break break
default: default:
// eslint-disable-next-line // eslint-disable-next-line
console.warn("wrong direction in moveBarByMinutesAndPush") console.warn('wrong direction in moveBarByMinutesAndPush')
return return
} }
this.manageOverlapping() this.manageOverlapping()
@@ -373,28 +457,26 @@ export default {
/* --------------------------------------------------------- */ /* --------------------------------------------------------- */
/* ------- MAPPING POSITION TO TIME (AND VICE VERSA) ------- */ /* ------- MAPPING POSITION TO TIME (AND VICE VERSA) ------- */
/* --------------------------------------------------------- */ /* --------------------------------------------------------- */
mapTimeToPosition(time){ mapTimeToPosition(time) {
let hourDiffFromStart = moment(time).diff(this.chartStartMoment, "hour", true) let timeDiffFromStart = moment(time).diff(
return (hourDiffFromStart / this.getHourCount()) * this.barContainer.width this.chartStartMoment,
this.timeUnit,
true
)
return (timeDiffFromStart / this.getTimeCount()) * this.barContainer.width
}, },
mapPositionToTime(xPos){ mapPositionToTime(xPos) {
let hourDiffFromStart = (xPos/this.barContainer.width)*this.getHourCount() let timeDiffFromStart =
return this.chartStartMoment.clone().add(hourDiffFromStart, "hours") (xPos / this.barContainer.width) * this.getTimeCount()
return this.chartStartMoment.clone().add(timeDiffFromStart, this.timeUnit)
}, },
}, },
filters:{
TimeFilter(value){
return moment(value).format("HH:mm")
}
}
} }
</script> </script>
<style scoped> <style scoped>
.g-gantt-bar { .g-gantt-bar {
position: absolute; position: absolute;
top: 2px; top: 2px;
left: 30px; left: 30px;
@@ -407,9 +489,9 @@ export default {
border-radius: 15px; border-radius: 15px;
background: #79869c; background: #79869c;
overflow: hidden; overflow: hidden;
} }
.g-gantt-bar-label { .g-gantt-bar-label {
width: 100%; width: 100%;
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
@@ -417,15 +499,16 @@ export default {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.g-gantt-bar-label > * { .g-gantt-bar-label > * {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.g-gantt-bar > .g-gantt-bar-handle-left, .g-gantt-bar > .g-gantt-bar-handle-right { .g-gantt-bar > .g-gantt-bar-handle-left,
.g-gantt-bar > .g-gantt-bar-handle-right {
position: absolute; position: absolute;
width: 10px; width: 10px;
height: 100%; height: 100%;
@@ -433,34 +516,34 @@ export default {
opacity: 0.7; opacity: 0.7;
border-radius: 40px; border-radius: 40px;
cursor: w-resize; cursor: w-resize;
} }
.g-gantt-bar-handle-left { .g-gantt-bar-handle-left {
left: 0; left: 0;
} }
.g-gantt-bar-handle-right { .g-gantt-bar-handle-right {
right: 0; right: 0;
} }
.g-gantt-bar-label img { .g-gantt-bar-label img {
pointer-events: none; pointer-events: none;
} }
.g-gantt-tooltip{ .g-gantt-tooltip {
position: absolute; position: absolute;
background: black; background: black;
color: white; color: white;
z-index: 3; z-index: 3;
font-size: 0.70em; font-size: 0.7em;
padding: 3px; padding: 3px;
border-radius: 3px; border-radius: 3px;
transition: opacity 0.2s; transition: opacity 0.2s;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.g-gantt-tooltip:before { .g-gantt-tooltip:before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
@@ -472,28 +555,29 @@ export default {
border-top: 0; border-top: 0;
margin-left: -5px; margin-left: -5px;
margin-top: -5px; margin-top: -5px;
} }
.g-gantt-tooltip > .color-indicator { .g-gantt-tooltip > .color-indicator {
width: 8px; width: 8px;
height: 8px; height: 8px;
border-radius: 100%; border-radius: 100%;
margin-right: 4px; margin-right: 4px;
} }
.fade-enter-active { .fade-enter-active {
animation: fade-in .3s; animation: fade-in 0.3s;
} }
.fade-leave-active { .fade-leave-active {
animation: fade-in .3s reverse; animation: fade-in 0.3s reverse;
} }
@keyframes fade-in { @keyframes fade-in {
from { from {
opacity: 0; opacity: 0;
} to { }
to {
opacity: 1; opacity: 1;
} }
} }
</style> </style>
+207 -128
View File
@@ -1,7 +1,7 @@
<template> <template>
<div <div
id="g-gantt-chart" id="g-gantt-chart"
:style="{width: width, background: themeColors.background}" :style="{ width: width, background: themeColors.background }"
> >
<g-gantt-timeaxis <g-gantt-timeaxis
v-if="!hideTimeaxis" v-if="!hideTimeaxis"
@@ -22,9 +22,9 @@
/> />
<div id="g-gantt-rows-container"> <div id="g-gantt-rows-container">
<slot/> <!-- the g-gantt-row components go here --> <slot />
<!-- the g-gantt-row components go here -->
</div> </div>
</div> </div>
</template> </template>
@@ -37,82 +37,93 @@ import GGanttRow from './GGanttRow.vue'
import GGanttBar from './GGanttBar.vue' import GGanttBar from './GGanttBar.vue'
export default { export default {
name: 'GGanttChart',
name: "GGanttChart", components: {
components:{
GGanttTimeaxis, GGanttTimeaxis,
GGanttGrid GGanttGrid,
}, },
props:{ props: {
chartStart: {type: String, default: moment().startOf("day").format("YYYY-MM-DD HH:mm:ss")}, chartStart: { type: String },
chartEnd: {type: String, default: moment().startOf("day").add(12,"hours").format("YYYY-MM-DD HH:mm:ss")}, chartEnd: { type: String },
hideTimeaxis: Boolean, hideTimeaxis: Boolean,
rowLabelWidth: {type: String, default: "10%"}, rowLabelWidth: { type: String, default: '10%' },
rowHeight: {type: Number, default: 40}, rowHeight: { type: Number, default: 40 },
locale: {type: String, default: "en"}, locale: { type: String, default: 'en' },
theme: {type: String}, theme: { type: String },
grid: {type: Boolean}, grid: { type: Boolean },
highlightedHours: {type: Array, default: () => []}, highlightedHours: { type: Array, default: () => [] },
width: {type: String, default: "100%"}, // the total width of the entire ganttastic component in % width: { type: String, default: '100%' }, // the total width of the entire ganttastic component in %
pushOnOverlap: {type: Boolean}, pushOnOverlap: { type: Boolean },
snapBackOnOverlap: {type: Boolean}, snapBackOnOverlap: { type: Boolean },
minGapBetweenBars: { minGapBetweenBars: {
type: Number, type: Number,
default: 0 default: 0,
}, },
defaultBarLength: { type: Number, default: 1 }, defaultBarLength: { type: Number, default: 1 },
// ["month_days", "day_hours"] // ["month_days", "day_hours"]
timeaxisMode: {type: String, default: "month_days"} timeaxisMode: { type: String, default: 'month_days' },
}, },
data(){ data() {
return{ return {
timemarkerOffset: 0, timemarkerOffset: 0,
movedBarsInDrag: new Set() movedBarsInDrag: new Set(),
timeUnit: this.timeaxisMode === 'month_days' ? 'days' : 'hours',
timeFormat:
this.timeaxisMode === 'month_days' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm',
} }
}, },
computed:{ computed: {
timeCount() {
hourCount(){
let momentChartStart = moment(this.chartStart) let momentChartStart = moment(this.chartStart)
let momentChartEnd = moment(this.chartEnd) let momentChartEnd = moment(this.chartEnd)
return Math.floor(momentChartEnd.diff(momentChartStart, "hour", true)) return Math.floor(momentChartEnd.diff(momentChartStart, this.timeUnit, true))
}, },
themeColors(){ themeColors() {
return GanttasticThemeColors[this.theme] || GanttasticThemeColors.default return GanttasticThemeColors[this.theme] || GanttasticThemeColors.default
}, },
}, },
methods: { methods: {
getGanttBarChildrenList() {
getGanttBarChildrenList(){
let ganttBarChildren = [] let ganttBarChildren = []
let ganttRowChildrenList = this.$children.filter(childComp => childComp.$options.name === GGanttRow.name) let ganttRowChildrenList = this.$children.filter(
ganttRowChildrenList.forEach(row => { (childComp) => childComp.$options.name === GGanttRow.name
let ganttBarChildrenOfRow = row.$children.filter(childComp => childComp.$options.name === GGanttBar.name) )
ganttRowChildrenList.forEach((row) => {
let ganttBarChildrenOfRow = row.$children.filter(
(childComp) => childComp.$options.name === GGanttBar.name
)
ganttBarChildren.push(...ganttBarChildrenOfRow) ganttBarChildren.push(...ganttBarChildrenOfRow)
}) })
return ganttBarChildren return ganttBarChildren
}, },
getBarsFromBundle(bundleId){ getBarsFromBundle(bundleId) {
if(bundleId === undefined || bundleId === null){ if (bundleId === undefined || bundleId === null) {
return [] return []
} }
return this.getGanttBarChildrenList().filter(ganttBarChild => ganttBarChild.barConfig.bundle === bundleId) return this.getGanttBarChildrenList().filter(
(ganttBarChild) => ganttBarChild.barConfig.bundle === bundleId
)
}, },
initDragOfBarsFromBundle(gGanttBar, e){ initDragOfBarsFromBundle(gGanttBar, e) {
gGanttBar.initDrag(e) gGanttBar.initDrag(e)
this.movedBarsInDrag.add(gGanttBar.bar) this.movedBarsInDrag.add(gGanttBar.bar)
if(gGanttBar.barConfig.bundle !== null && gGanttBar.barConfig.bundle !== undefined){ if (
this.getGanttBarChildrenList().forEach(ganttBarChild => { gGanttBar.barConfig.bundle !== null &&
if(ganttBarChild.barConfig.bundle === gGanttBar.barConfig.bundle && ganttBarChild !== gGanttBar){ gGanttBar.barConfig.bundle !== undefined
) {
this.getGanttBarChildrenList().forEach((ganttBarChild) => {
if (
ganttBarChild.barConfig.bundle === gGanttBar.barConfig.bundle &&
ganttBarChild !== gGanttBar
) {
ganttBarChild.initDrag(e) ganttBarChild.initDrag(e)
this.movedBarsInDrag.add(ganttBarChild.bar) this.movedBarsInDrag.add(ganttBarChild.bar)
} }
@@ -120,47 +131,56 @@ export default {
} }
}, },
moveBarsFromBundleOfPushedBar(pushedBar, minuteDiff, overlapType){ moveBarsFromBundleOfPushedBar(pushedBar, minuteDiff, overlapType) {
this.movedBarsInDrag.add(pushedBar) this.movedBarsInDrag.add(pushedBar)
let bundleId = pushedBar.ganttBarConfig.bundle let bundleId = pushedBar.ganttBarConfig.bundle
if(bundleId === undefined || bundleId === null){ if (bundleId === undefined || bundleId === null) {
return return
} }
this.getGanttBarChildrenList().forEach(ganttBarChild => { this.getGanttBarChildrenList().forEach((ganttBarChild) => {
if(ganttBarChild.barConfig.bundle === bundleId && ganttBarChild.bar !== pushedBar){ if (
ganttBarChild.barConfig.bundle === bundleId &&
ganttBarChild.bar !== pushedBar
) {
ganttBarChild.moveBarByMinutesAndPush(minuteDiff, overlapType) ganttBarChild.moveBarByMinutesAndPush(minuteDiff, overlapType)
this.movedBarsInDrag.add(ganttBarChild.bar) this.movedBarsInDrag.add(ganttBarChild.bar)
} }
}) })
}, },
shouldSnapBackBar(ganttBar){ shouldSnapBackBar(ganttBar) {
if(this.snapBackOnOverlap && ganttBar.barConfig.pushOnOverlap !== false){ if (
let {overlapBar} = ganttBar.getOverlapBarAndType(ganttBar.bar) this.snapBackOnOverlap &&
ganttBar.barConfig.pushOnOverlap !== false
) {
let { overlapBar } = ganttBar.getOverlapBarAndType(ganttBar.bar)
return !!overlapBar return !!overlapBar
} }
return false return false
}, },
snapBackBundleIfNeeded(ganttBar){ snapBackBundleIfNeeded(ganttBar) {
let barsFromBundle = this.getBarsFromBundle(ganttBar.barConfig.bundle) let barsFromBundle = this.getBarsFromBundle(ganttBar.barConfig.bundle)
if(this.shouldSnapBackBar(ganttBar) || barsFromBundle.some(gBar => this.shouldSnapBackBar(gBar))){ if (
this.shouldSnapBackBar(ganttBar) ||
barsFromBundle.some((gBar) => this.shouldSnapBackBar(gBar))
) {
ganttBar.snapBack() ganttBar.snapBack()
barsFromBundle.forEach(gBar => gBar.snapBack()) barsFromBundle.forEach((gBar) => gBar.snapBack())
return true return true
} }
return false return false
}, },
onBarEvent({event, type, time}, ganttBar){ onBarEvent({ event, type, time }, ganttBar) {
this.$emit(`${type}-bar`, {event, bar: ganttBar.bar, time}) this.$emit(`${type}-bar`, { event, bar: ganttBar.bar, time })
}, },
onDragendBar(e, ganttBar){ onDragendBar(e, ganttBar) {
let didSnapBack = this.snapBackBundleIfNeeded(ganttBar) let didSnapBack = this.snapBackBundleIfNeeded(ganttBar)
let movedBars = didSnapBack ? new Set() : this.movedBarsInDrag let movedBars = didSnapBack ? new Set() : this.movedBarsInDrag
this.movedBarsInDrag = new Set() this.movedBarsInDrag = new Set()
this.$emit("dragend-bar", {event: e, bar: ganttBar.bar, movedBars}) this.$emit('dragend-bar', { event: e, bar: ganttBar.bar, movedBars })
}, },
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@@ -170,37 +190,58 @@ export default {
// how far you can drag a bar depends on the position of the closest immobile bar // how far you can drag a bar depends on the position of the closest immobile bar
// note that if a bar from the same row belongs to a bundle // note that if a bar from the same row belongs to a bundle
// other rows might need to be taken into consideration, too // other rows might need to be taken into consideration, too
setDragLimitsOfGanttBar(bar){ setDragLimitsOfGanttBar(bar) {
if(!this.pushOnOverlap || bar.barConfig.pushOnOverlap === false){ if (!this.pushOnOverlap || bar.barConfig.pushOnOverlap === false) {
return return
} }
for(let side of ["left", "right"]){ for (let side of ['left', 'right']) {
let [totalGapDistance, bundleBarsOnPath] = this.countGapDistanceToNextImmobileBar(bar, null, side, false) let [
for(let i=0; i< bundleBarsOnPath.length; i++){ totalGapDistance,
bundleBarsOnPath,
] = this.countGapDistanceToNextImmobileBar(bar, null, side, false)
for (let i = 0; i < bundleBarsOnPath.length; i++) {
let barFromBundle = bundleBarsOnPath[i].bar let barFromBundle = bundleBarsOnPath[i].bar
let gapDist = bundleBarsOnPath[i].gapDistance let gapDist = bundleBarsOnPath[i].gapDistance
let otherBarsFromBundle = this.getBarsFromBundle(barFromBundle.barConfig.bundle).filter(otherBar => otherBar !== barFromBundle) let otherBarsFromBundle = this.getBarsFromBundle(
otherBarsFromBundle.forEach(otherBar => { barFromBundle.barConfig.bundle
let [newGapDistance, newBundleBars] = this.countGapDistanceToNextImmobileBar(otherBar, gapDist, side) ).filter((otherBar) => otherBar !== barFromBundle)
if(newGapDistance !== null && (newGapDistance < totalGapDistance || !totalGapDistance)){ otherBarsFromBundle.forEach((otherBar) => {
let [
newGapDistance,
newBundleBars,
] = this.countGapDistanceToNextImmobileBar(otherBar, gapDist, side)
if (
newGapDistance !== null &&
(newGapDistance < totalGapDistance || !totalGapDistance)
) {
totalGapDistance = newGapDistance totalGapDistance = newGapDistance
} }
newBundleBars.forEach(newBundleBar => { newBundleBars.forEach((newBundleBar) => {
if(!bundleBarsOnPath.find(barAndGap => barAndGap.bar === newBundleBar.bar)){ if (
!bundleBarsOnPath.find(
(barAndGap) => barAndGap.bar === newBundleBar.bar
)
) {
bundleBarsOnPath.push(newBundleBar) bundleBarsOnPath.push(newBundleBar)
} }
}) })
}) })
} }
if(totalGapDistance != null && side === "left"){ if (totalGapDistance != null && side === 'left') {
bar.dragLimitLeft = bar.$refs['g-gantt-bar'].offsetLeft - totalGapDistance bar.dragLimitLeft =
} else if(totalGapDistance != null && side === "right"){ bar.$refs['g-gantt-bar'].offsetLeft - totalGapDistance
bar.dragLimitRight = bar.$refs['g-gantt-bar'].offsetLeft+ bar.$refs['g-gantt-bar'].offsetWidth + totalGapDistance } else if (totalGapDistance != null && side === 'right') {
bar.dragLimitRight =
bar.$refs['g-gantt-bar'].offsetLeft +
bar.$refs['g-gantt-bar'].offsetWidth +
totalGapDistance
} }
} }
// all bars from the bundle of the clicked bar need to have the same drag limit: // all bars from the bundle of the clicked bar need to have the same drag limit:
let barsFromBundleOfClickedBar = this.getBarsFromBundle(bar.barConfig.bundle) let barsFromBundleOfClickedBar = this.getBarsFromBundle(
barsFromBundleOfClickedBar.forEach(barFromBundle => { bar.barConfig.bundle
)
barsFromBundleOfClickedBar.forEach((barFromBundle) => {
barFromBundle.dragLimitLeft = bar.dragLimitLeft barFromBundle.dragLimitLeft = bar.dragLimitLeft
barFromBundle.dragLimitRight = bar.dragLimitRight barFromBundle.dragLimitRight = bar.dragLimitRight
}) })
@@ -209,105 +250,143 @@ export default {
// returns the gap distance to the next immobile bar // returns the gap distance to the next immobile bar
// in the row where the given bar (parameter) is (added to gapDistanceSoFar) // in the row where the given bar (parameter) is (added to gapDistanceSoFar)
// and a list of all bars on that path that belong to a bundle // and a list of all bars on that path that belong to a bundle
countGapDistanceToNextImmobileBar(bar, gapDistanceSoFar, side="left", ignoreShadows = true){ countGapDistanceToNextImmobileBar(
let bundleBarsAndGapDist = bar.barConfig.bundle ? [{bar, gapDistance: gapDistanceSoFar}] : [] bar,
gapDistanceSoFar,
side = 'left',
ignoreShadows = true
) {
let bundleBarsAndGapDist = bar.barConfig.bundle
? [{ bar, gapDistance: gapDistanceSoFar }]
: []
let currentBar = bar let currentBar = bar
let nextBar = this.getNextGanttBar(currentBar, side) let nextBar = this.getNextGanttBar(currentBar, side)
// left side: // left side:
if(side === "left"){ if (side === 'left') {
while(nextBar){ while (nextBar) {
let nextBarOffsetRight = nextBar.$refs['g-gantt-bar'].offsetLeft + nextBar.$refs['g-gantt-bar'].offsetWidth let nextBarOffsetRight =
gapDistanceSoFar += currentBar.$refs['g-gantt-bar'].offsetLeft - nextBarOffsetRight nextBar.$refs['g-gantt-bar'].offsetLeft +
if(nextBar.barConfig.immobile || (nextBar.barConfig.isShadow && !ignoreShadows)){ nextBar.$refs['g-gantt-bar'].offsetWidth
gapDistanceSoFar +=
currentBar.$refs['g-gantt-bar'].offsetLeft - nextBarOffsetRight
if (
nextBar.barConfig.immobile ||
(nextBar.barConfig.isShadow && !ignoreShadows)
) {
return [gapDistanceSoFar, bundleBarsAndGapDist] return [gapDistanceSoFar, bundleBarsAndGapDist]
} else if(nextBar.barConfig.bundle){ } else if (nextBar.barConfig.bundle) {
bundleBarsAndGapDist.push({bar: nextBar, gapDistance: gapDistanceSoFar}) bundleBarsAndGapDist.push({
bar: nextBar,
gapDistance: gapDistanceSoFar,
})
} }
currentBar = nextBar currentBar = nextBar
nextBar = this.getNextGanttBar(nextBar, "left") nextBar = this.getNextGanttBar(nextBar, 'left')
} }
} }
if(side === "right"){ if (side === 'right') {
while(nextBar){ while (nextBar) {
let currentBarOffsetRight = currentBar.$refs['g-gantt-bar'].offsetLeft + currentBar.$refs['g-gantt-bar'].offsetWidth let currentBarOffsetRight =
gapDistanceSoFar += nextBar.$refs['g-gantt-bar'].offsetLeft - currentBarOffsetRight currentBar.$refs['g-gantt-bar'].offsetLeft +
if(nextBar.barConfig.immobile || (nextBar.barConfig.isShadow && !ignoreShadows)){ currentBar.$refs['g-gantt-bar'].offsetWidth
gapDistanceSoFar +=
nextBar.$refs['g-gantt-bar'].offsetLeft - currentBarOffsetRight
if (
nextBar.barConfig.immobile ||
(nextBar.barConfig.isShadow && !ignoreShadows)
) {
return [gapDistanceSoFar, bundleBarsAndGapDist] return [gapDistanceSoFar, bundleBarsAndGapDist]
} else if(nextBar.barConfig.bundle){ } else if (nextBar.barConfig.bundle) {
bundleBarsAndGapDist.push({bar: nextBar, gapDistance: gapDistanceSoFar}) bundleBarsAndGapDist.push({
bar: nextBar,
gapDistance: gapDistanceSoFar,
})
} }
currentBar = nextBar currentBar = nextBar
nextBar = this.getNextGanttBar(nextBar, "right") nextBar = this.getNextGanttBar(nextBar, 'right')
} }
} }
return [null, bundleBarsAndGapDist] return [null, bundleBarsAndGapDist]
}, },
getNextGanttBar(bar, side="left"){ getNextGanttBar(bar, side = 'left') {
let allBarsLeftOrRight = [] let allBarsLeftOrRight = []
if(side === "left"){ if (side === 'left') {
allBarsLeftOrRight = bar.$parent.$children.filter(gBar => { allBarsLeftOrRight = bar.$parent.$children.filter((gBar) => {
return gBar.$options.name === GGanttBar.name return (
&& gBar.$parent === bar.$parent gBar.$options.name === GGanttBar.name &&
&& gBar.$refs['g-gantt-bar'] gBar.$parent === bar.$parent &&
&& gBar.$refs['g-gantt-bar'].offsetLeft < bar.$refs['g-gantt-bar'].offsetLeft gBar.$refs['g-gantt-bar'] &&
&& gBar.barConfig.pushOnOverlap !== false gBar.$refs['g-gantt-bar'].offsetLeft <
bar.$refs['g-gantt-bar'].offsetLeft &&
gBar.barConfig.pushOnOverlap !== false
)
}) })
} else { } else {
allBarsLeftOrRight = bar.$parent.$children.filter(gBar => { allBarsLeftOrRight = bar.$parent.$children.filter((gBar) => {
return gBar.$options.name === GGanttBar.name return (
&& gBar.$parent === bar.$parent gBar.$options.name === GGanttBar.name &&
&& gBar.$refs['g-gantt-bar'] gBar.$parent === bar.$parent &&
&& gBar.$refs['g-gantt-bar'].offsetLeft > bar.$refs['g-gantt-bar'].offsetLeft gBar.$refs['g-gantt-bar'] &&
&& gBar.barConfig.pushOnOverlap !== false gBar.$refs['g-gantt-bar'].offsetLeft >
bar.$refs['g-gantt-bar'].offsetLeft &&
gBar.barConfig.pushOnOverlap !== false
)
}) })
} }
if(allBarsLeftOrRight.length > 0){ if (allBarsLeftOrRight.length > 0) {
return allBarsLeftOrRight.reduce( return allBarsLeftOrRight.reduce((bar1, bar2) => {
(bar1, bar2) => { let bar1Dist = Math.abs(
let bar1Dist = Math.abs(bar1.$refs['g-gantt-bar'].offsetLeft - bar.$refs['g-gantt-bar'].offsetLeft) bar1.$refs['g-gantt-bar'].offsetLeft -
let bar2Dist = Math.abs(bar2.$refs['g-gantt-bar'].offsetLeft - bar.$refs['g-gantt-bar'].offsetLeft) bar.$refs['g-gantt-bar'].offsetLeft
return bar1Dist < bar2Dist ? bar1 : bar2
},
allBarsLeftOrRight[0]
) )
let bar2Dist = Math.abs(
bar2.$refs['g-gantt-bar'].offsetLeft -
bar.$refs['g-gantt-bar'].offsetLeft
)
return bar1Dist < bar2Dist ? bar1 : bar2
}, allBarsLeftOrRight[0])
} else { } else {
return null return null
} }
} },
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
}, },
// all child components of GGanttChart may have access to // all child components of GGanttChart may have access to
// the following values by using Vue's "inject" option: // the following values by using Vue's "inject" option:
provide(){ provide() {
return { return {
getChartStart: () => this.chartStart, getChartStart: () => this.chartStart,
getChartEnd: () => this.chartEnd, getChartEnd: () => this.chartEnd,
getHourCount: () => this.hourCount, getTimeCount: () => this.timeCount,
ganttChartProps: this.$props, ganttChartProps: this.$props,
getThemeColors: () => this.themeColors, getThemeColors: () => this.themeColors,
initDragOfBarsFromBundle: (bundleId, e) => this.initDragOfBarsFromBundle(bundleId, e), initDragOfBarsFromBundle: (bundleId, e) =>
moveBarsFromBundleOfPushedBar: (bar, minuteDiff, overlapType) => this.moveBarsFromBundleOfPushedBar(bar, minuteDiff, overlapType), this.initDragOfBarsFromBundle(bundleId, e),
setDragLimitsOfGanttBar : (ganttBar) => this.setDragLimitsOfGanttBar(ganttBar), moveBarsFromBundleOfPushedBar: (bar, minuteDiff, overlapType) =>
this.moveBarsFromBundleOfPushedBar(bar, minuteDiff, overlapType),
setDragLimitsOfGanttBar: (ganttBar) =>
this.setDragLimitsOfGanttBar(ganttBar),
onBarEvent: (e, ganttBar) => this.onBarEvent(e, ganttBar), onBarEvent: (e, ganttBar) => this.onBarEvent(e, ganttBar),
onDragendBar: (e, ganttBar) => this.onDragendBar(e, ganttBar), onDragendBar: (e, ganttBar) => this.onDragendBar(e, ganttBar),
shouldSnapBackOnOverlap: () => this.snapBackOnOverlap, shouldSnapBackOnOverlap: () => this.snapBackOnOverlap,
snapBackBundle: (ganttBar) => this.snapBackBundle(ganttBar), snapBackBundle: (ganttBar) => this.snapBackBundle(ganttBar),
getMinGapBetweenBars: () => this.minGapBetweenBars, getMinGapBetweenBars: () => this.minGapBetweenBars,
getDefaultBarLength: () => this.defaultBarLength, getDefaultBarLength: () => this.defaultBarLength,
getTimeaxisMode:() => this.timeaxisMode getTimeaxisMode: () => this.timeaxisMode,
} getTimeUnit: () => this.timeUnit,
getTimeFormat: () => this.timeFormat
} }
},
} }
</script> </script>
<style scoped> <style scoped>
#g-gantt-chart{ #g-gantt-chart {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -319,13 +398,13 @@ export default {
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
padding-bottom: 23px; padding-bottom: 23px;
} }
#g-gantt-chart >>> * { #g-gantt-chart >>> * {
font-family: Roboto, Verdana; font-family: Roboto, Verdana;
} }
#g-gantt-rows-container{ #g-gantt-rows-container {
position: relative; position: relative;
} }
</style> </style>
+86 -77
View File
@@ -2,15 +2,12 @@
<div <div
class="g-gantt-row" class="g-gantt-row"
ref="g-gantt-row" ref="g-gantt-row"
:style="{height: `${$parent.rowHeight}px`}" :style="{ height: `${$parent.rowHeight}px` }"
v-on="$listeners" v-on="$listeners"
> >
<div <div class="g-gantt-row-label" :style="rowLabelStyle">
class="g-gantt-row-label"
:style="rowLabelStyle"
>
<slot name="label"> <slot name="label">
{{label}} {{ label }}
</slot> </slot>
</div> </div>
<div <div
@@ -34,11 +31,8 @@
:bar-container="barContainer" :bar-container="barContainer"
:all-bars-in-row="bars" :all-bars-in-row="bars"
> >
<template #bar-label="{bar}"> <template #bar-label="{ bar }">
<slot <slot name="bar-label" :bar="bar" />
name="bar-label"
:bar="bar"
/>
</template> </template>
</g-gantt-bar> </g-gantt-bar>
</div> </div>
@@ -50,147 +44,162 @@ import GGanttBar from './GGanttBar.vue'
import moment from 'moment' import moment from 'moment'
export default { export default {
name: 'GGanttRow',
name: "GGanttRow", components: {
GGanttBar,
components:{
GGanttBar
}, },
props:{ props: {
label: {type: String, default: "Row"}, label: { type: String, default: 'Row' },
bars: {type: Array, default: () => []}, bars: { type: Array, default: () => [] },
barStart: {type: String, required: true}, // property name of the bar objects that represents the start datetime barStart: { type: String, required: true }, // property name of the bar objects that represents the start datetime
barEnd: {type: String, required: true}, // property name of the bar objects that represents the end datetime, barEnd: { type: String, required: true }, // property name of the bar objects that represents the end datetime,
highlightOnHover: Boolean, highlightOnHover: Boolean,
}, },
inject: [ inject: [
"ganttChartProps", 'ganttChartProps',
"getThemeColors", 'getThemeColors',
"getHourCount", 'getTimeCount',
"getChartStart", 'getChartStart',
"getChartEnd", 'getChartEnd',
"getDefaultBarLength" 'getDefaultBarLength',
'getTimeUnit',
'getTimeFormat',
], ],
data(){ data() {
return { return {
barContainer: {} barContainer: {},
timeUnit: this.getTimeUnit(),
timeFormat: this.getTimeFormat(),
} }
}, },
computed:{ computed: {
rowLabelStyle() {
rowLabelStyle(){
return { return {
width: this.ganttChartProps.rowLabelWidth, width: this.ganttChartProps.rowLabelWidth,
height: this.ganttChartProps.rowHeight, height: this.ganttChartProps.rowHeight,
background: this.$parent.themeColors.ternary, background: this.$parent.themeColors.ternary,
color: this.$parent.themeColors.text color: this.$parent.themeColors.text,
} }
}, },
barsContainerStyle(){ barsContainerStyle() {
return{ return {
width: `${100 - this.ganttChartProps.rowLabelWidth.replace('%','')}%`, width: `${100 - this.ganttChartProps.rowLabelWidth.replace('%', '')}%`,
} }
}, },
}, },
mounted(){ mounted() {
this.barContainer = this.$refs.barContainer.getBoundingClientRect() this.barContainer = this.$refs.barContainer.getBoundingClientRect()
window.addEventListener("resize", this.onWindowResize) window.addEventListener('resize', this.onWindowResize)
}, },
methods:{ methods: {
onDragover(e) { onDragover(e) {
e.preventDefault() // enables dropping content on row e.preventDefault() // enables dropping content on row
if(this.highlightOnHover){ if (this.highlightOnHover) {
console.log({backgroundColor: this.$refs["g-gantt-row"].style.backgroundColor }) console.log({
this.$refs["g-gantt-row"].style.backgroundColor = this.getThemeColors().hoverHighlight backgroundColor: this.$refs['g-gantt-row'].style.backgroundColor,
console.log({backgroundColor: this.$refs["g-gantt-row"].style.backgroundColor }) })
this.$refs[
'g-gantt-row'
].style.backgroundColor = this.getThemeColors().hoverHighlight
console.log({
backgroundColor: this.$refs['g-gantt-row'].style.backgroundColor,
})
} }
}, },
onDragleave() { onDragleave() {
this.$refs["g-gantt-row"].style.backgroundColor = null this.$refs['g-gantt-row'].style.backgroundColor = null
}, },
onDrop(e) {
onDrop(e){
let barContainer = this.$refs.barContainer.getBoundingClientRect() let barContainer = this.$refs.barContainer.getBoundingClientRect()
let xPos = e.clientX - barContainer.left let xPos = e.clientX - barContainer.left
let hourDiffFromStart = (xPos/barContainer.width) * this.getHourCount() let timeDiffFromStart = (xPos / barContainer.width) * this.getTimeCount()
let time = moment(this.getChartStart()).add(hourDiffFromStart, "hours") let time = moment(this.getChartStart()).add(
let bar = this.bars.find(bar => time.isBetween(bar[this.barStart], bar[this.barEnd])) timeDiffFromStart,
this.$emit("drop", {event: e, bar, time: time.format("YYYY-MM-DD HH:mm:ss")}) this.timeUnit
)
let bar = this.bars.find((bar) =>
time.isBetween(bar[this.barStart], bar[this.barEnd])
)
this.$emit('drop', { event: e, bar, time: time.format(this.timeFormat) })
}, },
onDoubleClick(e){ onDoubleClick(e) {
let barContainer = this.$refs.barContainer.getBoundingClientRect() let barContainer = this.$refs.barContainer.getBoundingClientRect()
let xPos = e.clientX - barContainer.left let xPos = e.clientX - barContainer.left
let hourDiffFromStart = (xPos/barContainer.width) * this.getHourCount() let timeDiffFromStart = (xPos / barContainer.width) * this.getTimeCount()
let time = moment(this.getChartStart()).add(hourDiffFromStart, "hours") let time = moment(this.getChartStart()).add(
let bar = {}; timeDiffFromStart,
bar[this.barStart] = time.format("YYYY-MM-DD HH:mm:ss") this.timeUnit
bar[this.barEnd] = time.add(this.getDefaultBarLength(), "hours").format("YYYY-MM-DD HH:mm:ss") )
bar.ganttBarConfig = {handles: true} let bar = {}
bar[this.barStart] = time
bar[this.barEnd] = time.add(this.getDefaultBarLength(), this.timeUnit)
bar.ganttBarConfig = { handles: true }
this.bars.push(bar) this.bars.push(bar)
}, },
onMouseover(){ onMouseover() {
if(this.highlightOnHover){ if (this.highlightOnHover) {
this.$refs["g-gantt-row"].style.backgroundColor = this.getThemeColors().hoverHighlight this.$refs[
'g-gantt-row'
].style.backgroundColor = this.getThemeColors().hoverHighlight
} }
}, },
onMouseleave(){ onMouseleave() {
this.$refs["g-gantt-row"].style.backgroundColor = null this.$refs['g-gantt-row'].style.backgroundColor = null
}, },
onWindowResize(){ onWindowResize() {
// re-initialize the barContainer DOMRect variable, which will trigger re-rendering in the gantt bars // re-initialize the barContainer DOMRect variable, which will trigger re-rendering in the gantt bars
this.barContainer = this.$refs.barContainer.getBoundingClientRect() this.barContainer = this.$refs.barContainer.getBoundingClientRect()
} },
}, },
watch:{ watch: {
'ganttChartProps.rowLabelWidth' : function(){ 'ganttChartProps.rowLabelWidth': function () {
this.barContainer = this.$refs.barContainer.getBoundingClientRect() this.barContainer = this.$refs.barContainer.getBoundingClientRect()
} },
} },
} }
</script> </script>
<style scoped> <style scoped>
.g-gantt-row{ .g-gantt-row {
display: flex; display: flex;
width: 100%; width: 100%;
height: 40px; height: 40px;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
.g-gantt-row > .g-gantt-row-label{ .g-gantt-row > .g-gantt-row-label {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 20%; width: 20%;
background: #E8E8E8; background: #e8e8e8;
color: #424242; color: #424242;
font-size: 0.9em; font-size: 0.9em;
z-index: 3; z-index: 3;
overflow: hidden; overflow: hidden;
font-weight: bold; font-weight: bold;
} }
.g-gantt-row > .g-gantt-row-bars-container{ .g-gantt-row > .g-gantt-row-bars-container {
position: relative; position: relative;
border-top: 1px solid #eaeaea; border-top: 1px solid #eaeaea;
width: 70%; width: 70%;
border-bottom: 1px solid #eaeaea; border-bottom: 1px solid #eaeaea;
} }
</style> </style>
+8 -8
View File
@@ -55,7 +55,7 @@ import moment from 'moment'
export default { export default {
name: 'GGanttTimeaxis', name: 'GGanttTimeaxis',
inject: ['getTimeaxisMode'], inject: ['getTimeaxisMode', 'getTimeUnit', 'getTimeFormat'],
props: { props: {
chartStart: String, chartStart: String,
@@ -74,6 +74,8 @@ export default {
hourFontSize: '11px', hourFontSize: '11px',
dayFormat: 'MM-DD', // ISO 8601 dayFormat: 'MM-DD', // ISO 8601
mode: this.getTimeaxisMode(), mode: this.getTimeaxisMode(),
timeUnit: this.getTimeUnit(),
timeFormat: this.getTimeFormat()
} }
}, },
@@ -100,8 +102,7 @@ export default {
let end = moment(this.chartEnd) let end = moment(this.chartEnd)
this.childPointCount = Math.floor(end.diff(start, 'day', true)) this.childPointCount = Math.floor(end.diff(start, 'day', true))
while (start.isBefore(end)) { while (start.isBefore(end)) {
let dayCountOfMonth = let dayCountOfMonth = start.isSame(end, 'month')
start.isSame(end, 'day')
? end.date() ? end.date()
: start.daysInMonth() - start.date() + 1 : start.daysInMonth() - start.date() + 1
let widthPercentage = (dayCountOfMonth / this.childPointCount) * 100 let widthPercentage = (dayCountOfMonth / this.childPointCount) * 100
@@ -119,8 +120,7 @@ export default {
let end = moment(this.chartEnd) let end = moment(this.chartEnd)
this.childPointCount = Math.floor(end.diff(start, 'hour', true)) this.childPointCount = Math.floor(end.diff(start, 'hour', true))
while (start.isBefore(end)) { while (start.isBefore(end)) {
let hourCountOfDay = let hourCountOfDay = start.isSame(end, 'day')
start.isSame(end, 'day')
? end.hour() ? end.hour()
: 24 - start.hour() : 24 - start.hour()
@@ -144,7 +144,7 @@ export default {
for (let i = 0; i <= endDay - startDay; i++) { for (let i = 0; i <= endDay - startDay; i++) {
let day = { let day = {
text: datetimeMoment.format('D'), text: datetimeMoment.format('D'),
fullDatetime: datetimeMoment.format('YYYY-MM-DD'), // ISO 8601 fullDatetime: datetimeMoment.format(this.timeFormat),
} }
axisMonthObject.ganttDays.push(day) axisMonthObject.ganttDays.push(day)
datetimeMoment.add(1, 'day') datetimeMoment.add(1, 'day')
@@ -163,7 +163,7 @@ export default {
for (let i = 0; i <= endHour - startHour; i++) { for (let i = 0; i <= endHour - startHour; i++) {
let hour = { let hour = {
text: datetimeMoment.format('HH'), text: datetimeMoment.format('HH'),
fullDatetime: datetimeMoment.format('YYYY-MM-DD HH:mm'), // ISO 8601 fullDatetime: datetimeMoment.format(this.timeFormat),
} }
axisDayObject.ganttHours.push(hour) axisDayObject.ganttHours.push(hour)
datetimeMoment.add(1, 'hour') datetimeMoment.add(1, 'hour')
@@ -190,7 +190,7 @@ export default {
) + 'px' ) + 'px'
}, },
pointFormatted(point){ pointFormatted(point) {
if (this.mode === 'month_days') { if (this.mode === 'month_days') {
return this.monthFormatted(point) return this.monthFormatted(point)
} else if (this.mode === 'day_hours') { } else if (this.mode === 'day_hours') {