2
0
mirror of https://github.com/tenrok/vue-ganttastic.git synced 2026-06-24 04:10:32 +03:00

chore: format

This commit is contained in:
2023-10-11 15:24:41 +03:00
parent edba5c3ae6
commit 6ae2dc4aa3
47 changed files with 11776 additions and 22497 deletions
+3 -3
View File
@@ -1,3 +1,3 @@
> 1% > 1%
last 2 versions last 2 versions
not dead not dead
+9 -9
View File
@@ -1,9 +1,9 @@
root = true root = true
[*] [*]
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
+2 -2
View File
@@ -1,2 +1,2 @@
demo demo
dist dist
+20 -17
View File
@@ -1,17 +1,20 @@
module.exports = { const isProduction = process.env.NODE_ENV === 'production'
root: true,
env: { module.exports = {
node: true root: true,
}, env: {
'extends': [ node: true
'plugin:vue/essential', },
'eslint:recommended' extends: [
], 'plugin:vue/essential',
parserOptions: { 'eslint:recommended',
parser: 'babel-eslint' 'plugin:prettier/recommended'
}, ],
rules: { parserOptions: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', parser: '@babel/eslint-parser'
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' },
} rules: {
} 'no-console': isProduction ? 'warn' : 'off',
'no-debugger': isProduction ? 'warn' : 'off'
}
}
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+31
View File
@@ -0,0 +1,31 @@
name: Deploy to GH Pages
on:
push:
branches:
- main
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@master
with:
persist-credentials: false
- name: Install 🔧
run: npm install
- name: Build 🏗️
run: npm run demo:build
env:
NODE_ENV: production
- name: Deploy to GH Pages 🚀
if: ${{ github.event_name != 'pull_request' }}
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: demo
+17 -27
View File
@@ -1,27 +1,17 @@
.DS_Store ### VisualStudioCode ###
.vscode/*
# local env files !.vscode/settings.json
.env.local !.vscode/tasks.json
.env.*.local !.vscode/launch.json
!.vscode/extensions.json
# Log files
npm-debug.log* ### VisualStudioCode Patch ###
yarn-debug.log* # Ignore all local history of files
yarn-error.log* **/.history
pnpm-debug.log*
# npm
# Editor directories and files node_modules
.idea
.vscode # Output directories
*.suo /demo
*.ntvs* /dist
*.njsproj
*.sln
*.sw?
# npm
node_modules
# output directories
/demo
/dist
+2
View File
@@ -0,0 +1,2 @@
registry=https://registry.npmjs.org/
@tenrok:registry=https://registry.npmjs.org/
+12 -11
View File
@@ -1,11 +1,12 @@
module.exports = { // https://prettier.io/docs/en/options.html
arrowParens: 'avoid',
bracketSpacing: true, module.exports = {
printWidth: 80, arrowParens: 'avoid',
semi: false, bracketSpacing: true,
singleQuote: true, printWidth: 120,
tabWidth: 2, semi: false,
trailingComma: 'none', singleQuote: true,
useTabs: false, tabWidth: 2,
vueIndentScriptAndStyle: true trailingComma: 'none',
} useTabs: false
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["editorconfig.editorconfig", "esbenp.prettier-vscode"]
}
+9
View File
@@ -0,0 +1,9 @@
{
"editor.fontFamily": "'Fira Code', Consolas, 'Courier New', monospace",
"editor.fontLigatures": true,
"editor.fontWeight": "400",
"editor.foldingStrategy": "indentation",
"editor.formatOnSave": true,
"editor.rulers": [120],
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
+49 -43
View File
@@ -1,10 +1,15 @@
# Vue-Ganttastic # Vue-Ganttastic
![screenshot](https://user-images.githubusercontent.com/4740535/143231164-88cd538f-f4ff-4fc6-8cb0-a25f4bab465c.png) [![Version](https://img.shields.io/npm/v/@tenrok/vue-ganttastic.svg)](https://www.npmjs.com/package/@tenrok/vue-ganttastic)
[![License](https://img.shields.io/npm/l/@tenrok/vue-ganttastic.svg)](https://github.com/tenrok/vue-ganttastic/blob/main/LICENSE)
[![Vue.js](https://img.shields.io/badge/vue-2.7.14-brightgreen.svg?logo=vue.js)](https://github.com/vuejs/vue)
[![GitHub last commit](https://img.shields.io/github/last-commit/tenrok/vue-ganttastic.svg)](https://github.com/tenrok/vue-ganttastic)
[![Downloads](https://img.shields.io/npm/dm/@tenrok/vue-ganttastic.svg)](https://npmcharts.com/compare/@tenrok/vue-ganttastic?minimal=true)
[![Demo](https://img.shields.io/badge/demo-demo-blue.svg)](https://tenrok.github.io/vue-ganttastic/)
A simple and easy-to-use Gantt chart component for Vue.js A simple and easy-to-use Gantt chart component for Vue.js
[Demo](http://vue-ganttastic.vercel.app/) ![screenshot](https://user-images.githubusercontent.com/4740535/143231164-88cd538f-f4ff-4fc6-8cb0-a25f4bab465c.png)
## Installation ## Installation
@@ -31,6 +36,7 @@ For more detailed information, such as how to style the bars or additional confi
The following code showcases a simple usage example in a .vue SFC (Single File Component) The following code showcases a simple usage example in a .vue SFC (Single File Component)
<!-- prettier-ignore -->
```html ```html
<template> <template>
... ...
@@ -58,54 +64,54 @@ The following code showcases a simple usage example in a .vue SFC (Single File C
</template> </template>
<script> <script>
import { GGanttChart, GGanttRow } from '@tenrok/vue-ganttastic' import { GGanttChart, GGanttRow } from '@tenrok/vue-ganttastic'
export default { export default {
... ...
components: { components: {
GGanttChart, GGanttChart,
GGanttRow GGanttRow
}, },
data() { data() {
return { return {
myChartStart: "2020-03-01 00:00", myChartStart: "2020-03-01 00:00",
myChartEnd: "2020-03-03 00:00", myChartEnd: "2020-03-03 00:00",
rows: [ rows: [
{ {
label: "My row #1", label: "My row #1",
labelStyle: { labelStyle: {
justifyContent: "end" justifyContent: "end"
},
bars: [
{
myStart: "2020-03-01 12:10",
myEnd: "2020-03-01 16:35"
}
]
}, },
{ bars: [
label: "My row #2", {
bars: [ myStart: "2020-03-01 12:10",
{ myEnd: "2020-03-01 16:35"
myStart: "2020-03-02 01:00", }
myEnd: "2020-03-02 12:00" ]
}, },
{ {
myStart: "2020-03-02 13:00", label: "My row #2",
myEnd: "2020-03-02 22:00" bars: [
} {
] myStart: "2020-03-02 01:00",
} myEnd: "2020-03-02 12:00"
] },
} {
myStart: "2020-03-02 13:00",
myEnd: "2020-03-02 22:00"
}
]
}
]
} }
...
} }
...
}
</script> </script>
<style lang="scss"> <style lang="scss">
+3 -5
View File
@@ -1,5 +1,3 @@
module.exports = { module.exports = {
presets: [ presets: ['@vue/cli-plugin-babel/preset']
'@vue/cli-plugin-babel/preset' }
]
}
File diff suppressed because it is too large Load Diff
+377 -481
View File
@@ -1,481 +1,377 @@
<template> <template>
<div <div class="g-gantt-chart-container" :data-theme="theme" :style="{ width, height }">
class="g-gantt-chart-container" <div class="g-gantt-chart">
:data-theme="theme" <g-gantt-timeaxis
:style="{ width, height }" v-if="!hideTimeaxis"
> :chart-start="chartStart"
<div class="g-gantt-chart"> :chart-end="chartEnd"
<g-gantt-timeaxis :row-label-width="rowLabelWidth"
v-if="!hideTimeaxis" :timemarker-offset="timemarkerOffset"
:chart-start="chartStart" :locale="locale"
:chart-end="chartEnd" :precision="precision"
:row-label-width="rowLabelWidth" :time-format="timeFormat"
:timemarker-offset="timemarkerOffset" :time-count="timeCount"
:locale="locale" :grid-size="gridSize"
:precision="precision" :day-format="dayFormat"
:time-format="timeFormat" :month-format="monthFormat"
:time-count="timeCount" />
:grid-size="gridSize"
:day-format="dayFormat" <div class="g-gantt-rows-container" :style="{ width: `${timeCount * gridSize + rowLabelWidth}px` }">
:month-format="monthFormat" <g-gantt-grid
/> v-if="grid"
:chart-start="chartStart"
<div :chart-end="chartEnd"
class="g-gantt-rows-container" :row-label-width="rowLabelWidth"
:style="{ width: `${timeCount * gridSize + rowLabelWidth}px` }" :highlighted-hours="highlightedHours"
> :highlighted-days="highlightedDays"
<g-gantt-grid :precision="precision"
v-if="grid" :time-count="timeCount"
:chart-start="chartStart" :grid-size="gridSize"
:chart-end="chartEnd" />
:row-label-width="rowLabelWidth" <slot />
:highlighted-hours="highlightedHours" </div>
:highlighted-days="highlightedDays" </div>
:precision="precision" </div>
:time-count="timeCount" </template>
:grid-size="gridSize"
/> <script>
<slot /> import moment from 'moment'
</div> import GGanttTimeaxis from './GGanttTimeaxis.vue'
</div> import GGanttGrid from './GGanttGrid.vue'
</div> import GGanttRow from './GGanttRow.vue'
</template> import GGanttBar from './GGanttBar.vue'
<script> export default {
import moment from 'moment' name: 'GGanttChart',
import GGanttTimeaxis from './GGanttTimeaxis.vue'
import GGanttGrid from './GGanttGrid.vue' components: {
import GGanttRow from './GGanttRow.vue' GGanttTimeaxis,
import GGanttBar from './GGanttBar.vue' GGanttGrid
},
export default {
name: 'GGanttChart', props: {
chartStart: { type: String, required: true },
components: { chartEnd: { type: String, required: true },
GGanttTimeaxis, hideTimeaxis: { type: Boolean, default: false },
GGanttGrid rowLabelWidth: { type: Number, default: 200 },
}, rowHeight: { type: Number, default: 40 },
locale: { type: String, default: 'en' },
props: { theme: { type: String },
chartStart: { type: String, required: true }, grid: { type: Boolean, default: false },
chartEnd: { type: String, required: true }, gridSize: { type: Number, default: 30 },
hideTimeaxis: { type: Boolean, default: false }, highlightedHours: { type: Array, default: () => [] },
rowLabelWidth: { type: Number, default: 200 }, highlightedDays: { type: Array, default: () => [] }, // format YYYY-MM-DD
rowHeight: { type: Number, default: 40 }, width: { type: String, default: '100%' }, // the total width of the entire ganttastic component in %
locale: { type: String, default: 'en' }, height: { type: String, default: '100%' },
theme: { type: String }, pushOnOverlap: { type: Boolean },
grid: { type: Boolean, default: false }, isMagnetic: { type: Boolean },
gridSize: { type: Number, default: 30 }, snapBackOnOverlap: { type: Boolean },
highlightedHours: { type: Array, default: () => [] }, minGapBetweenBars: { type: Number, default: 0 },
highlightedDays: { type: Array, default: () => [] }, // format YYYY-MM-DD defaultBarLength: { type: Number, default: 1 },
width: { type: String, default: '100%' }, // the total width of the entire ganttastic component in % precision: { type: String, default: 'month' }, // 'month', 'day'
height: { type: String, default: '100%' }, barConfigKey: { type: String, default: 'ganttBarConfig' },
pushOnOverlap: { type: Boolean }, barStartKey: { type: String, default: 'start' }, // property name of the bar objects that represents the start datetime
isMagnetic: { type: Boolean }, barEndKey: { type: String, default: 'end' }, // property name of the bar objects that represents the end datetime
snapBackOnOverlap: { type: Boolean }, allowAdd: { type: Boolean, default: true },
minGapBetweenBars: { type: Number, default: 0 }, dayFormat: { type: String, default: 'ddd DD MMMM' },
defaultBarLength: { type: Number, default: 1 }, monthFormat: { type: String, default: 'MMMM YYYY' },
precision: { type: String, default: 'month' }, // 'month', 'day' tooltipFormat: {
barConfigKey: { type: String, default: 'ganttBarConfig' }, type: String,
barStartKey: { type: String, default: 'start' }, // property name of the bar objects that represents the start datetime default: '{start} - {end} duration: {duration}'
barEndKey: { type: String, default: 'end' }, // property name of the bar objects that represents the end datetime }
allowAdd: { type: Boolean, default: true }, },
dayFormat: { type: String, default: 'ddd DD MMMM' },
monthFormat: { type: String, default: 'MMMM YYYY' }, data() {
tooltipFormat: { return {
type: String, timemarkerOffset: 0,
default: '{start} - {end} duration: {duration}' movedBarsInDrag: new Set()
} }
}, },
data() { computed: {
return { timeUnit() {
timemarkerOffset: 0, return this.precision === 'month' ? 'days' : 'hours'
movedBarsInDrag: new Set() },
}
}, timeFormat() {
return this.precision === 'month' ? 'YYYY-MM-DD HH' : 'YYYY-MM-DD HH:mm'
computed: { },
timeUnit() {
return this.precision === 'month' ? 'days' : 'hours' timeCount() {
}, let momentChartStart = moment(this.chartStart)
let momentChartEnd = moment(this.chartEnd)
timeFormat() { return Math.floor(momentChartEnd.diff(momentChartStart, this.timeUnit, true))
return this.precision === 'month' ? 'YYYY-MM-DD HH' : 'YYYY-MM-DD HH:mm' }
}, },
timeCount() { methods: {
let momentChartStart = moment(this.chartStart) getGanttBarChildrenList() {
let momentChartEnd = moment(this.chartEnd) let ganttBarChildren = []
return Math.floor( let ganttRowChildrenList = this.$children.filter(childComp => childComp.$options.name === GGanttRow.name)
momentChartEnd.diff(momentChartStart, this.timeUnit, true) ganttRowChildrenList.forEach(row => {
) let ganttBarChildrenOfRow = row.$children.filter(childComp => childComp.$options.name === GGanttBar.name)
} ganttBarChildren.push(...ganttBarChildrenOfRow)
}, })
return ganttBarChildren
methods: { },
getGanttBarChildrenList() {
let ganttBarChildren = [] getBarsFromBundle(bundleId) {
let ganttRowChildrenList = this.$children.filter( if (bundleId === undefined || bundleId === null) {
childComp => childComp.$options.name === GGanttRow.name return []
) }
ganttRowChildrenList.forEach(row => { return this.getGanttBarChildrenList().filter(ganttBarChild => ganttBarChild.barConfig.bundle === bundleId)
let ganttBarChildrenOfRow = row.$children.filter( },
childComp => childComp.$options.name === GGanttBar.name
) initDragOfBarsFromBundle(gGanttBar, e) {
ganttBarChildren.push(...ganttBarChildrenOfRow) gGanttBar.initDrag(e)
}) this.movedBarsInDrag.add(gGanttBar.bar)
return ganttBarChildren if (gGanttBar.barConfig.bundle !== null && gGanttBar.barConfig.bundle !== undefined) {
}, this.getGanttBarChildrenList().forEach(ganttBarChild => {
if (ganttBarChild.barConfig.bundle === gGanttBar.barConfig.bundle && ganttBarChild !== gGanttBar) {
getBarsFromBundle(bundleId) { ganttBarChild.initDrag(e)
if (bundleId === undefined || bundleId === null) { this.movedBarsInDrag.add(ganttBarChild.bar)
return [] }
} })
return this.getGanttBarChildrenList().filter( }
ganttBarChild => ganttBarChild.barConfig.bundle === bundleId },
)
}, moveBarsFromBundleOfPushedBar(pushedBar, minuteDiff, overlapType) {
this.movedBarsInDrag.add(pushedBar)
initDragOfBarsFromBundle(gGanttBar, e) { let bundleId = pushedBar[this.barConfigKey] ? pushedBar[this.barConfigKey].bundle : null
gGanttBar.initDrag(e) if (bundleId === undefined || bundleId === null) {
this.movedBarsInDrag.add(gGanttBar.bar) return
if ( }
gGanttBar.barConfig.bundle !== null && this.getGanttBarChildrenList().forEach(ganttBarChild => {
gGanttBar.barConfig.bundle !== undefined if (ganttBarChild.barConfig.bundle === bundleId && ganttBarChild.bar !== pushedBar) {
) { ganttBarChild.moveBarByChildPointsAndPush(minuteDiff, overlapType)
this.getGanttBarChildrenList().forEach(ganttBarChild => { this.movedBarsInDrag.add(ganttBarChild.bar)
if ( }
ganttBarChild.barConfig.bundle === gGanttBar.barConfig.bundle && })
ganttBarChild !== gGanttBar },
) {
ganttBarChild.initDrag(e) shouldSnapBackBar(ganttBar) {
this.movedBarsInDrag.add(ganttBarChild.bar) if (this.snapBackOnOverlap && ganttBar.barConfig.pushOnOverlap !== false) {
} let { overlapBar } = ganttBar.getOverlapBarAndType(ganttBar.bar)
}) return !!overlapBar
} }
}, return false
},
moveBarsFromBundleOfPushedBar(pushedBar, minuteDiff, overlapType) {
this.movedBarsInDrag.add(pushedBar) snapBackBundleIfNeeded(ganttBar) {
let bundleId = pushedBar[this.barConfigKey] let barsFromBundle = this.getBarsFromBundle(ganttBar.barConfig.bundle)
? pushedBar[this.barConfigKey].bundle if (this.shouldSnapBackBar(ganttBar) || barsFromBundle.some(gBar => this.shouldSnapBackBar(gBar))) {
: null ganttBar.snapBack()
if (bundleId === undefined || bundleId === null) { barsFromBundle.forEach(gBar => gBar.snapBack())
return return true
} }
this.getGanttBarChildrenList().forEach(ganttBarChild => { return false
if ( },
ganttBarChild.barConfig.bundle === bundleId &&
ganttBarChild.bar !== pushedBar onBarEvent({ event, type, time }, ganttBar) {
) { this.$emit(`${type}-bar`, { event, bar: ganttBar.bar, time })
ganttBarChild.moveBarByChildPointsAndPush(minuteDiff, overlapType) },
this.movedBarsInDrag.add(ganttBarChild.bar)
} onDragendBar(e, ganttBar, action) {
}) let didSnapBack = this.snapBackBundleIfNeeded(ganttBar)
}, let movedBars = didSnapBack ? new Set() : this.movedBarsInDrag
// Magnetic suction
shouldSnapBackBar(ganttBar) { if (movedBars.size && this.isMagnetic) {
if ( let { left, right /*, move*/ } = action
this.snapBackOnOverlap &&
ganttBar.barConfig.pushOnOverlap !== false movedBars.forEach(bar => {
) { if (this.precision === 'month') {
let { overlapBar } = ganttBar.getOverlapBarAndType(ganttBar.bar) if (left && bar == ganttBar.bar) {
return !!overlapBar if (moment(bar[this.barStartKey]).hours() < 12) {
} bar[this.barStartKey] = moment(bar[this.barStartKey]).hours(0).format()
return false } else {
}, bar[this.barStartKey] = moment(bar[this.barStartKey]).hours(24).format()
}
snapBackBundleIfNeeded(ganttBar) { } else if (right && bar == ganttBar.bar) {
let barsFromBundle = this.getBarsFromBundle(ganttBar.barConfig.bundle) if (moment(bar[this.barEndKey]).hours() < 12) {
if ( bar[this.barEndKey] = moment(bar[this.barEndKey]).hours(0).format()
this.shouldSnapBackBar(ganttBar) || } else {
barsFromBundle.some(gBar => this.shouldSnapBackBar(gBar)) bar[this.barEndKey] = moment(bar[this.barEndKey]).hours(24).format()
) { }
ganttBar.snapBack() } else {
barsFromBundle.forEach(gBar => gBar.snapBack()) if (moment(bar[this.barStartKey]).hours() < 12) {
return true bar[this.barStartKey] = moment(bar[this.barStartKey]).hours(0).format()
} bar[this.barEndKey] = moment(bar[this.barEndKey]).hours(0).format()
return false } else {
}, bar[this.barStartKey] = moment(bar[this.barStartKey]).hours(24).format()
bar[this.barEndKey] = moment(bar[this.barEndKey]).hours(24).format()
onBarEvent({ event, type, time }, ganttBar) { }
this.$emit(`${type}-bar`, { event, bar: ganttBar.bar, time }) }
}, } else {
if (left && bar == ganttBar.bar) {
onDragendBar(e, ganttBar, action) { if (moment(bar[this.barStartKey]).minutes() < 30) {
let didSnapBack = this.snapBackBundleIfNeeded(ganttBar) bar[this.barStartKey] = moment(bar[this.barStartKey]).minutes(0).format()
let movedBars = didSnapBack ? new Set() : this.movedBarsInDrag } else {
// Magnetic suction bar[this.barStartKey] = moment(bar[this.barStartKey]).minutes(60).format()
if (movedBars.size && this.isMagnetic) { }
let { left, right /*, move*/ } = action } else if (right && bar == ganttBar.bar) {
if (moment(bar[this.barEndKey]).minutes() < 30) {
movedBars.forEach(bar => { bar[this.barEndKey] = moment(bar[this.barEndKey]).minutes(0).format()
if (this.precision === 'month') { } else {
if (left && bar == ganttBar.bar) { bar[this.barEndKey] = moment(bar[this.barEndKey]).minutes(60).format()
if (moment(bar[this.barStartKey]).hours() < 12) { }
bar[this.barStartKey] = moment(bar[this.barStartKey]) } else {
.hours(0) if (moment(bar[this.barStartKey]).minutes() < 30) {
.format() bar[this.barStartKey] = moment(bar[this.barStartKey]).minutes(0).format()
} else { bar[this.barEndKey] = moment(bar[this.barEndKey]).minutes(0).format()
bar[this.barStartKey] = moment(bar[this.barStartKey]) } else {
.hours(24) bar[this.barStartKey] = moment(bar[this.barStartKey]).minutes(60).format()
.format() bar[this.barEndKey] = moment(bar[this.barEndKey]).minutes(60).format()
} }
} else if (right && bar == ganttBar.bar) { }
if (moment(bar[this.barEndKey]).hours() < 12) { }
bar[this.barEndKey] = moment(bar[this.barEndKey]) })
.hours(0) }
.format() this.movedBarsInDrag = new Set()
} else { this.$emit('dragend-bar', { event: e, bar: ganttBar.bar, movedBars })
bar[this.barEndKey] = moment(bar[this.barEndKey]) },
.hours(24)
.format() // ------------------------------------------------------------------------
} // -------- METHODS FOR SETTING THE DRAG LIMIT OF A BAR ----------------
} else { // ------------------------------------------------------------------------
if (moment(bar[this.barStartKey]).hours() < 12) {
bar[this.barStartKey] = moment(bar[this.barStartKey]) // how far you can drag a bar depends on the position of the closest immobile bar
.hours(0) // note that if a bar from the same row belongs to a bundle
.format() // other rows might need to be taken into consideration, too
bar[this.barEndKey] = moment(bar[this.barEndKey]) setDragLimitsOfGanttBar(bar) {
.hours(0) if (!this.pushOnOverlap || bar.barConfig.pushOnOverlap === false) {
.format() return
} else { }
bar[this.barStartKey] = moment(bar[this.barStartKey]) for (let side of ['left', 'right']) {
.hours(24) let [totalGapDistance, bundleBarsOnPath] = this.countGapDistanceToNextImmobileBar(bar, null, side, false)
.format() for (let i = 0; i < bundleBarsOnPath.length; i++) {
bar[this.barEndKey] = moment(bar[this.barEndKey]) let barFromBundle = bundleBarsOnPath[i].bar
.hours(24) let gapDist = bundleBarsOnPath[i].gapDistance
.format() let otherBarsFromBundle = this.getBarsFromBundle(barFromBundle.barConfig.bundle).filter(
} otherBar => otherBar !== barFromBundle
} )
} else { otherBarsFromBundle.forEach(otherBar => {
if (left && bar == ganttBar.bar) { let [newGapDistance, newBundleBars] = this.countGapDistanceToNextImmobileBar(otherBar, gapDist, side)
if (moment(bar[this.barStartKey]).minutes() < 30) { if (newGapDistance !== null && (newGapDistance < totalGapDistance || !totalGapDistance)) {
bar[this.barStartKey] = moment(bar[this.barStartKey]) totalGapDistance = newGapDistance
.minutes(0) }
.format() newBundleBars.forEach(newBundleBar => {
} else { if (!bundleBarsOnPath.find(barAndGap => barAndGap.bar === newBundleBar.bar)) {
bar[this.barStartKey] = moment(bar[this.barStartKey]) bundleBarsOnPath.push(newBundleBar)
.minutes(60) }
.format() })
} })
} else if (right && bar == ganttBar.bar) { }
if (moment(bar[this.barEndKey]).minutes() < 30) { if (totalGapDistance != null && side === 'left') {
bar[this.barEndKey] = moment(bar[this.barEndKey]) bar.dragLimitLeft = bar.$refs['g-bar'].offsetLeft - totalGapDistance
.minutes(0) } else if (totalGapDistance != null && side === 'right') {
.format() bar.dragLimitRight = bar.$refs['g-bar'].offsetLeft + bar.$refs['g-bar'].offsetWidth + totalGapDistance
} else { }
bar[this.barEndKey] = moment(bar[this.barEndKey]) }
.minutes(60) // all bars from the bundle of the clicked bar need to have the same drag limit:
.format() let barsFromBundleOfClickedBar = this.getBarsFromBundle(bar.barConfig.bundle)
} barsFromBundleOfClickedBar.forEach(barFromBundle => {
} else { barFromBundle.dragLimitLeft = bar.dragLimitLeft
if (moment(bar[this.barStartKey]).minutes() < 30) { barFromBundle.dragLimitRight = bar.dragLimitRight
bar[this.barStartKey] = moment(bar[this.barStartKey]) })
.minutes(0) },
.format()
bar[this.barEndKey] = moment(bar[this.barEndKey]) // returns the gap distance to the next immobile bar
.minutes(0) // in the row where the given bar (parameter) is (added to gapDistanceSoFar)
.format() // and a list of all bars on that path that belong to a bundle
} else { countGapDistanceToNextImmobileBar(bar, gapDistanceSoFar, side = 'left', ignoreShadows = true) {
bar[this.barStartKey] = moment(bar[this.barStartKey]) let bundleBarsAndGapDist = bar.barConfig.bundle ? [{ bar, gapDistance: gapDistanceSoFar }] : []
.minutes(60) let currentBar = bar
.format() let nextBar = this.getNextGanttBar(currentBar, side)
bar[this.barEndKey] = moment(bar[this.barEndKey]) // left side:
.minutes(60) if (side === 'left') {
.format() while (nextBar) {
} let nextBarOffsetRight = nextBar.$refs['g-bar'].offsetLeft + nextBar.$refs['g-bar'].offsetWidth
} gapDistanceSoFar += currentBar.$refs['g-bar'].offsetLeft - nextBarOffsetRight
} if (nextBar.barConfig.immobile || (nextBar.barConfig.isShadow && !ignoreShadows)) {
}) return [gapDistanceSoFar, bundleBarsAndGapDist]
} } else if (nextBar.barConfig.bundle) {
this.movedBarsInDrag = new Set() bundleBarsAndGapDist.push({
this.$emit('dragend-bar', { event: e, bar: ganttBar.bar, movedBars }) bar: nextBar,
}, gapDistance: gapDistanceSoFar
})
// ------------------------------------------------------------------------ }
// -------- METHODS FOR SETTING THE DRAG LIMIT OF A BAR ---------------- currentBar = nextBar
// ------------------------------------------------------------------------ nextBar = this.getNextGanttBar(nextBar, 'left')
}
// 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 if (side === 'right') {
// other rows might need to be taken into consideration, too while (nextBar) {
setDragLimitsOfGanttBar(bar) { let currentBarOffsetRight = currentBar.$refs['g-bar'].offsetLeft + currentBar.$refs['g-bar'].offsetWidth
if (!this.pushOnOverlap || bar.barConfig.pushOnOverlap === false) { gapDistanceSoFar += nextBar.$refs['g-bar'].offsetLeft - currentBarOffsetRight
return if (nextBar.barConfig.immobile || (nextBar.barConfig.isShadow && !ignoreShadows)) {
} return [gapDistanceSoFar, bundleBarsAndGapDist]
for (let side of ['left', 'right']) { } else if (nextBar.barConfig.bundle) {
let [totalGapDistance, bundleBarsOnPath] = bundleBarsAndGapDist.push({
this.countGapDistanceToNextImmobileBar(bar, null, side, false) bar: nextBar,
for (let i = 0; i < bundleBarsOnPath.length; i++) { gapDistance: gapDistanceSoFar
let barFromBundle = bundleBarsOnPath[i].bar })
let gapDist = bundleBarsOnPath[i].gapDistance }
let otherBarsFromBundle = this.getBarsFromBundle( currentBar = nextBar
barFromBundle.barConfig.bundle nextBar = this.getNextGanttBar(nextBar, 'right')
).filter(otherBar => otherBar !== barFromBundle) }
otherBarsFromBundle.forEach(otherBar => { }
let [newGapDistance, newBundleBars] = return [null, bundleBarsAndGapDist]
this.countGapDistanceToNextImmobileBar(otherBar, gapDist, side) },
if (
newGapDistance !== null && getNextGanttBar(bar, side = 'left') {
(newGapDistance < totalGapDistance || !totalGapDistance) let allBarsLeftOrRight = []
) { if (side === 'left') {
totalGapDistance = newGapDistance allBarsLeftOrRight = bar.$parent.$children.filter(gBar => {
} return (
newBundleBars.forEach(newBundleBar => { gBar.$options.name === GGanttBar.name &&
if ( gBar.$parent === bar.$parent &&
!bundleBarsOnPath.find( gBar.$refs['g-bar'] &&
barAndGap => barAndGap.bar === newBundleBar.bar gBar.$refs['g-bar'].offsetLeft < bar.$refs['g-bar'].offsetLeft &&
) gBar.barConfig.pushOnOverlap !== false
) { )
bundleBarsOnPath.push(newBundleBar) })
} } else {
}) allBarsLeftOrRight = bar.$parent.$children.filter(gBar => {
}) return (
} gBar.$options.name === GGanttBar.name &&
if (totalGapDistance != null && side === 'left') { gBar.$parent === bar.$parent &&
bar.dragLimitLeft = bar.$refs['g-bar'].offsetLeft - totalGapDistance gBar.$refs['g-bar'] &&
} else if (totalGapDistance != null && side === 'right') { gBar.$refs['g-bar'].offsetLeft > bar.$refs['g-bar'].offsetLeft &&
bar.dragLimitRight = gBar.barConfig.pushOnOverlap !== false
bar.$refs['g-bar'].offsetLeft + )
bar.$refs['g-bar'].offsetWidth + })
totalGapDistance }
} if (allBarsLeftOrRight.length > 0) {
} return allBarsLeftOrRight.reduce((bar1, bar2) => {
// all bars from the bundle of the clicked bar need to have the same drag limit: let bar1Dist = Math.abs(bar1.$refs['g-bar'].offsetLeft - bar.$refs['g-bar'].offsetLeft)
let barsFromBundleOfClickedBar = this.getBarsFromBundle( let bar2Dist = Math.abs(bar2.$refs['g-bar'].offsetLeft - bar.$refs['g-bar'].offsetLeft)
bar.barConfig.bundle return bar1Dist < bar2Dist ? bar1 : bar2
) }, allBarsLeftOrRight[0])
barsFromBundleOfClickedBar.forEach(barFromBundle => { } else {
barFromBundle.dragLimitLeft = bar.dragLimitLeft return null
barFromBundle.dragLimitRight = bar.dragLimitRight }
}) }
},
// ------------------------------------------------------------------------
// returns the gap distance to the next immobile bar // ------------------------------------------------------------------------
// 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 },
countGapDistanceToNextImmobileBar(
bar, // all child components of GGanttChart may have access to
gapDistanceSoFar, // the following values by using Vue's "inject" option:
side = 'left', provide() {
ignoreShadows = true return {
) { getTimeCount: () => this.timeCount,
let bundleBarsAndGapDist = bar.barConfig.bundle getChartProps: () => this.$props,
? [{ bar, gapDistance: gapDistanceSoFar }] initDragOfBarsFromBundle: (bundleId, e) => this.initDragOfBarsFromBundle(bundleId, e),
: [] moveBarsFromBundleOfPushedBar: (bar, minuteDiff, overlapType) =>
let currentBar = bar this.moveBarsFromBundleOfPushedBar(bar, minuteDiff, overlapType),
let nextBar = this.getNextGanttBar(currentBar, side) setDragLimitsOfGanttBar: ganttBar => this.setDragLimitsOfGanttBar(ganttBar),
// left side: onBarEvent: (e, ganttBar) => this.onBarEvent(e, ganttBar),
if (side === 'left') { onDragendBar: (e, ganttBar, action) => this.onDragendBar(e, ganttBar, action),
while (nextBar) { getTimeUnit: () => this.timeUnit,
let nextBarOffsetRight = getTimeFormat: () => this.timeFormat
nextBar.$refs['g-bar'].offsetLeft + }
nextBar.$refs['g-bar'].offsetWidth }
gapDistanceSoFar += }
currentBar.$refs['g-bar'].offsetLeft - nextBarOffsetRight </script>
if (
nextBar.barConfig.immobile ||
(nextBar.barConfig.isShadow && !ignoreShadows)
) {
return [gapDistanceSoFar, bundleBarsAndGapDist]
} else if (nextBar.barConfig.bundle) {
bundleBarsAndGapDist.push({
bar: nextBar,
gapDistance: gapDistanceSoFar
})
}
currentBar = nextBar
nextBar = this.getNextGanttBar(nextBar, 'left')
}
}
if (side === 'right') {
while (nextBar) {
let currentBarOffsetRight =
currentBar.$refs['g-bar'].offsetLeft +
currentBar.$refs['g-bar'].offsetWidth
gapDistanceSoFar +=
nextBar.$refs['g-bar'].offsetLeft - currentBarOffsetRight
if (
nextBar.barConfig.immobile ||
(nextBar.barConfig.isShadow && !ignoreShadows)
) {
return [gapDistanceSoFar, bundleBarsAndGapDist]
} else if (nextBar.barConfig.bundle) {
bundleBarsAndGapDist.push({
bar: nextBar,
gapDistance: gapDistanceSoFar
})
}
currentBar = nextBar
nextBar = this.getNextGanttBar(nextBar, 'right')
}
}
return [null, bundleBarsAndGapDist]
},
getNextGanttBar(bar, side = 'left') {
let allBarsLeftOrRight = []
if (side === 'left') {
allBarsLeftOrRight = bar.$parent.$children.filter(gBar => {
return (
gBar.$options.name === GGanttBar.name &&
gBar.$parent === bar.$parent &&
gBar.$refs['g-bar'] &&
gBar.$refs['g-bar'].offsetLeft < bar.$refs['g-bar'].offsetLeft &&
gBar.barConfig.pushOnOverlap !== false
)
})
} else {
allBarsLeftOrRight = bar.$parent.$children.filter(gBar => {
return (
gBar.$options.name === GGanttBar.name &&
gBar.$parent === bar.$parent &&
gBar.$refs['g-bar'] &&
gBar.$refs['g-bar'].offsetLeft > bar.$refs['g-bar'].offsetLeft &&
gBar.barConfig.pushOnOverlap !== false
)
})
}
if (allBarsLeftOrRight.length > 0) {
return allBarsLeftOrRight.reduce((bar1, bar2) => {
let bar1Dist = Math.abs(
bar1.$refs['g-bar'].offsetLeft - bar.$refs['g-bar'].offsetLeft
)
let bar2Dist = Math.abs(
bar2.$refs['g-bar'].offsetLeft - bar.$refs['g-bar'].offsetLeft
)
return bar1Dist < bar2Dist ? bar1 : bar2
}, allBarsLeftOrRight[0])
} else {
return null
}
}
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
},
// all child components of GGanttChart may have access to
// the following values by using Vue's "inject" option:
provide() {
return {
getTimeCount: () => this.timeCount,
getChartProps: () => this.$props,
initDragOfBarsFromBundle: (bundleId, e) =>
this.initDragOfBarsFromBundle(bundleId, e),
moveBarsFromBundleOfPushedBar: (bar, minuteDiff, overlapType) =>
this.moveBarsFromBundleOfPushedBar(bar, minuteDiff, overlapType),
setDragLimitsOfGanttBar: ganttBar =>
this.setDragLimitsOfGanttBar(ganttBar),
onBarEvent: (e, ganttBar) => this.onBarEvent(e, ganttBar),
onDragendBar: (e, ganttBar, action) =>
this.onDragendBar(e, ganttBar, action),
getTimeUnit: () => this.timeUnit,
getTimeFormat: () => this.timeFormat
}
}
}
</script>
+69 -78
View File
@@ -1,78 +1,69 @@
<template> <template>
<div <div class="g-gantt-grid" :style="{ left: `${rowLabelWidth}px`, width: `${timeCount * gridSize}px` }">
class="g-gantt-grid" <div
:style="{ left: `${rowLabelWidth}px`, width: `${timeCount * gridSize}px` }" v-for="(childPoint, index) in allChildPoints"
> :key="index"
<div :class="[
v-for="(childPoint, index) in allChildPoints" 'g-gantt-grid__line',
:key="index" { 'g-gantt-grid-line-last': index === allChildPoints.length - 1 },
:class="[ { 'g-gantt-line-highlighted': isHighlighted(childPoint) }
'g-gantt-grid__line', ]"
{ 'g-gantt-grid-line-last': index === allChildPoints.length - 1 }, :style="{ width: `${gridSize}px` }"
{ 'g-gantt-line-highlighted': isHighlighted(childPoint) } />
]" </div>
:style="{ width: `${gridSize}px` }" </template>
/>
</div> <script>
</template> import moment from 'moment'
<script> export default {
import moment from 'moment' name: 'GGanttGrid',
export default { props: {
name: 'GGanttGrid', chartStart: { type: String },
chartEnd: { type: String },
props: { rowLabelWidth: { type: Number },
chartStart: { type: String }, highlightedHours: { type: Array, default: () => [] },
chartEnd: { type: String }, highlightedDays: { type: Array, default: () => [] },
rowLabelWidth: { type: Number }, precision: { type: String },
highlightedHours: { type: Array, default: () => [] }, timeCount: { type: Number },
highlightedDays: { type: Array, default: () => [] }, gridSize: { type: Number }
precision: { type: String }, },
timeCount: { type: Number },
gridSize: { type: Number } computed: {
}, allChildPoints() {
const start = moment(this.chartStart)
computed: { const end = moment(this.chartEnd)
allChildPoints() { const res = []
const start = moment(this.chartStart) while (start.isBefore(end)) {
const end = moment(this.chartEnd) switch (this.precision) {
const res = [] case 'day':
while (start.isBefore(end)) { res.push(start.format('YYYY-MM-DD H'))
switch (this.precision) { start.add(1, 'hour')
case 'day': break
res.push(start.format('YYYY-MM-DD H')) case 'month':
start.add(1, 'hour') res.push(start.format('YYYY-MM-DD'))
break start.add(1, 'day').hour(23)
case 'month': break
res.push(start.format('YYYY-MM-DD')) }
start.add(1, 'day').hour(23) }
break return res
} }
} },
return res
} methods: {
}, isHighlighted(point) {
switch (this.precision) {
methods: { case 'day':
isHighlighted(point) { if (this.highlightedDays.includes(moment(point, 'YYYY-MM-DD').format('YYYY-MM-DD'))) {
switch (this.precision) { return true
case 'day': } else {
if ( return this.highlightedHours.includes(moment(point, 'YYYY-MM-DD H').get('hour'))
this.highlightedDays.includes( }
moment(point, 'YYYY-MM-DD').format('YYYY-MM-DD') case 'month':
) return this.highlightedDays.includes(point)
) { }
return true }
} else { }
return this.highlightedHours.includes( }
moment(point, 'YYYY-MM-DD H').get('hour') </script>
)
}
case 'month':
return this.highlightedDays.includes(point)
}
}
}
}
</script>
+189 -203
View File
@@ -1,203 +1,189 @@
<template> <template>
<div <div ref="g-row" class="g-gantt-row" :style="{ height: `${chartProps.rowHeight}px` }" v-on="$listeners">
ref="g-row" <div class="g-gantt-row__label" :style="rowLabelStyle">
class="g-gantt-row" <span :title="label">
:style="{ height: `${chartProps.rowHeight}px` }" <slot name="label">{{ label }}</slot>
v-on="$listeners" </span>
> </div>
<div class="g-gantt-row__label" :style="rowLabelStyle"> <div
<span :title="label"> class="g-gantt-row__bars-container"
<slot name="label">{{ label }}</slot> ref="barContainer"
</span> :style="rowStyle"
</div> @dragover="onDragover($event)"
<div @dragleave="onDragleave($event)"
class="g-gantt-row__bars-container" @drop="onDrop($event)"
ref="barContainer" @dblclick.self="onDoubleClick($event)"
:style="rowStyle" @mouseover="onMouseover()"
@dragover="onDragover($event)" @mouseleave="onMouseleave()"
@dragleave="onDragleave($event)" >
@drop="onDrop($event)" <g-gantt-bar
@dblclick.self="onDoubleClick($event)" v-for="(bar, index) in localBars"
@mouseover="onMouseover()" :key="`ganttastic_bar_${index}`"
@mouseleave="onMouseleave()" ref="ganttBar"
> :bar="bar"
<g-gantt-bar :bar-container="barContainer"
v-for="(bar, index) in localBars" :all-bars-in-row="localBars"
:key="`ganttastic_bar_${index}`" >
ref="ganttBar" <template #bar-label="{ bar }">
:bar="bar" <slot name="bar-label" :bar="bar" />
:bar-container="barContainer" </template>
:all-bars-in-row="localBars" </g-gantt-bar>
> </div>
<template #bar-label="{ bar }"> </div>
<slot name="bar-label" :bar="bar" /> </template>
</template>
</g-gantt-bar> <script>
</div> import GGanttBar from './GGanttBar.vue'
</div> import moment from 'moment'
</template>
export default {
<script> name: 'GGanttRow',
import GGanttBar from './GGanttBar.vue'
import moment from 'moment' components: {
GGanttBar
export default { },
name: 'GGanttRow',
props: {
components: { label: { type: String, default: 'Row' },
GGanttBar labelStyle: { type: Object },
}, rowStyle: { type: Object },
bars: { type: Array, default: () => [] },
props: { highlightOnHover: { type: Boolean }
label: { type: String, default: 'Row' }, },
labelStyle: { type: Object },
rowStyle: { type: Object }, inject: ['getChartProps', 'getTimeCount', 'getTimeUnit', 'getTimeFormat'],
bars: { type: Array, default: () => [] },
highlightOnHover: { type: Boolean } data() {
}, return {
barContainer: {},
inject: ['getChartProps', 'getTimeCount', 'getTimeUnit', 'getTimeFormat'], localBars: this.bars
}
data() { },
return {
barContainer: {}, computed: {
localBars: this.bars defaultBarLength() {
} return this.chartProps.defaultBarLength
}, },
computed: { chartProps() {
defaultBarLength() { return this.getChartProps()
return this.chartProps.defaultBarLength },
},
chartStart() {
chartProps() { return this.chartProps.chartStart
return this.getChartProps() },
},
barConfigKey() {
chartStart() { return this.chartProps.barConfigKey
return this.chartProps.chartStart },
},
timeUnit() {
barConfigKey() { return this.getTimeUnit()
return this.chartProps.barConfigKey },
},
timeFormat() {
timeUnit() { return this.getTimeFormat()
return this.getTimeUnit() },
},
timeCount() {
timeFormat() { return this.getTimeCount()
return this.getTimeFormat() },
},
rowLabelStyle() {
timeCount() { return {
return this.getTimeCount() width: `${this.chartProps.rowLabelWidth}px`,
}, ...this.labelStyle
}
rowLabelStyle() { }
return { },
width: `${this.chartProps.rowLabelWidth}px`,
...this.labelStyle watch: {
} 'chartProps.chartStart'() {
} this.barContainer = this.$refs.barContainer.getBoundingClientRect()
}, },
watch: { 'chartProps.chartEnd'() {
'chartProps.chartStart'() { this.barContainer = this.$refs.barContainer.getBoundingClientRect()
this.barContainer = this.$refs.barContainer.getBoundingClientRect() },
},
'chartProps.rowLabelWidth'() {
'chartProps.chartEnd'() { this.barContainer = this.$refs.barContainer.getBoundingClientRect()
this.barContainer = this.$refs.barContainer.getBoundingClientRect() },
},
'chartProps.gridSize'() {
'chartProps.rowLabelWidth'() { this.barContainer = this.$refs.barContainer.getBoundingClientRect()
this.barContainer = this.$refs.barContainer.getBoundingClientRect() },
},
bars(value) {
'chartProps.gridSize'() { this.localBars = value
this.barContainer = this.$refs.barContainer.getBoundingClientRect() }
}, },
bars(value) { mounted() {
this.localBars = value this.barContainer = this.$refs.barContainer.getBoundingClientRect()
} window.addEventListener('resize', this.onWindowResize)
}, },
mounted() { destroyed() {
this.barContainer = this.$refs.barContainer.getBoundingClientRect() window.removeEventListener('resize', this.onWindowResize)
window.addEventListener('resize', this.onWindowResize) },
},
methods: {
destroyed() { onDragover(e) {
window.removeEventListener('resize', this.onWindowResize) e.preventDefault() // enables dropping content on row
}, if (this.highlightOnHover) {
this.$refs['g-row'].classList.add('g-gantt-row-highlighted')
methods: { }
onDragover(e) { },
e.preventDefault() // enables dropping content on row
if (this.highlightOnHover) { onDragleave() {
this.$refs['g-row'].classList.add('g-gantt-row-highlighted') this.$refs['g-row'].classList.remove('g-gantt-row-highlighted')
} },
},
onDrop(e) {
onDragleave() { const barContainer = this.$refs.barContainer.getBoundingClientRect()
this.$refs['g-row'].classList.remove('g-gantt-row-highlighted') const xPos = e.clientX - barContainer.left
}, const timeDiffFromStart = (xPos / barContainer.width) * this.timeCount
const time = moment(this.chartStart).add(timeDiffFromStart, this.timeUnit)
onDrop(e) { const bar = this.localBars.find(bar =>
const barContainer = this.$refs.barContainer.getBoundingClientRect() time.isBetween(bar[this.chartProps.barStartKey], bar[this.chartProps.barEndKey])
const xPos = e.clientX - barContainer.left )
const timeDiffFromStart = (xPos / barContainer.width) * this.timeCount this.$emit('drop', { event: e, bar, time: time.format(this.timeFormat) })
const time = moment(this.chartStart).add(timeDiffFromStart, this.timeUnit) },
const bar = this.localBars.find(bar =>
time.isBetween( onDoubleClick(e) {
bar[this.chartProps.barStartKey], if (!this.chartProps.allowAdd) return
bar[this.chartProps.barEndKey] let barContainer = this.$refs.barContainer.getBoundingClientRect()
) let xPos = e.clientX - barContainer.left
) let timeDiffFromStart = Math.floor((xPos / barContainer.width) * this.timeCount)
this.$emit('drop', { event: e, bar, time: time.format(this.timeFormat) }) let time = moment(this.chartStart).add(timeDiffFromStart, this.timeUnit)
}, let bar = {}
bar[this.chartProps.barStartKey] = time.format()
onDoubleClick(e) { bar[this.chartProps.barEndKey] = time.add(this.defaultBarLength, this.timeUnit).format()
if (!this.chartProps.allowAdd) return
let barContainer = this.$refs.barContainer.getBoundingClientRect() bar[this.barConfigKey] = { handles: true }
let xPos = e.clientX - barContainer.left this.localBars.push(bar)
let timeDiffFromStart = Math.floor( this.localBars.sort((first, second) =>
(xPos / barContainer.width) * this.timeCount moment(first[this.chartProps.barStartKey]).diff(second[this.chartProps.barStartKey])
) )
let time = moment(this.chartStart).add(timeDiffFromStart, this.timeUnit) },
let bar = {}
bar[this.chartProps.barStartKey] = time.format() onMouseover() {
bar[this.chartProps.barEndKey] = time if (this.highlightOnHover) {
.add(this.defaultBarLength, this.timeUnit) this.$refs['g-row'].classList.add('g-gantt-row-highlighted')
.format() }
},
bar[this.barConfigKey] = { handles: true }
this.localBars.push(bar) onMouseleave() {
this.localBars.sort((first, second) => this.$refs['g-row'].classList.remove('g-gantt-row-highlighted')
moment(first[this.chartProps.barStartKey]).diff( },
second[this.chartProps.barStartKey]
) onWindowResize() {
) // re-initialize the barContainer DOMRect variable, which will trigger re-rendering in the gantt bars
}, if (this.$refs.barContainer) {
this.barContainer = this.$refs.barContainer.getBoundingClientRect()
onMouseover() { }
if (this.highlightOnHover) { }
this.$refs['g-row'].classList.add('g-gantt-row-highlighted') }
} }
}, </script>
onMouseleave() {
this.$refs['g-row'].classList.remove('g-gantt-row-highlighted')
},
onWindowResize() {
// re-initialize the barContainer DOMRect variable, which will trigger re-rendering in the gantt bars
if (this.$refs.barContainer) {
this.barContainer = this.$refs.barContainer.getBoundingClientRect()
}
}
}
}
</script>
+192 -224
View File
@@ -1,224 +1,192 @@
<template> <template>
<div <div ref="g-timeaxis" class="g-gantt-timeaxis" :style="{ width: `${timeCount * gridSize + rowLabelWidth}px` }">
ref="g-timeaxis" <div
class="g-gantt-timeaxis" class="g-gantt-timeaxis__empty-space"
:style="{ width: `${timeCount * gridSize + rowLabelWidth}px` }" :style="{
> minWidth: `${rowLabelWidth}px`
<div }"
class="g-gantt-timeaxis__empty-space" />
:style="{ <div class="g-gantt-timeaxis__days">
minWidth: `${rowLabelWidth}px` <div v-for="point in axisPoints" :key="point.text" class="g-gantt-timeaxis__day">
}" <div v-html="pointFormatted(point) || '&nbsp;'"></div>
/> <div class="g-gantt-timeaxis__hours">
<div class="g-gantt-timeaxis__days"> <div
<div v-for="childPoint in point.childPoints"
v-for="point in axisPoints" :key="childPoint.fullDatetime"
:key="point.text" class="g-gantt-timeaxis__hour"
class="g-gantt-timeaxis__day" :style="{ width: `${gridSize}px` }"
> >
<div v-html="pointFormatted(point) || '&nbsp;'"></div> <span :style="{ fontSize: hourFontSize }">{{ childPoint.text }}</span>
<div class="g-gantt-timeaxis__hours"> <div class="g-gantt-timeaxis__hour-pin" />
<div </div>
v-for="childPoint in point.childPoints" </div>
:key="childPoint.fullDatetime" </div>
class="g-gantt-timeaxis__hour" </div>
:style="{ width: `${gridSize}px` }" <div ref="g-timeaxis-marker" class="g-gantt-timeaxis__marker" />
> </div>
<span :style="{ fontSize: hourFontSize }">{{ </template>
childPoint.text
}}</span> <script>
<div class="g-gantt-timeaxis__hour-pin" /> import moment from 'moment'
</div>
</div> export default {
</div> name: 'GGanttTimeaxis',
</div>
<div ref="g-timeaxis-marker" class="g-gantt-timeaxis__marker" /> props: {
</div> chartStart: { type: String },
</template> chartEnd: { type: String },
rowLabelWidth: { type: Number },
<script> timemarkerOffset: { type: Number, default: 0 },
import moment from 'moment' locale: { type: String },
precision: { type: String },
export default { timeFormat: { type: String },
name: 'GGanttTimeaxis', timeCount: { type: Number },
gridSize: { type: Number },
props: { dayFormat: { type: String },
chartStart: { type: String }, monthFormat: { type: String }
chartEnd: { type: String }, },
rowLabelWidth: { type: Number },
timemarkerOffset: { type: Number, default: 0 }, data() {
locale: { type: String }, return {
precision: { type: String }, axisPoints: [],
timeFormat: { type: String }, timemarker: null,
timeCount: { type: Number }, hourFontSize: '11px'
gridSize: { type: Number }, }
dayFormat: { type: String }, },
monthFormat: { type: String }
}, watch: {
chartStart() {
data() { this.initAxis()
return { },
axisPoints: [], chartEnd() {
timemarker: null, this.initAxis()
hourFontSize: '11px' },
} gridSize() {
}, this.onWindowResize()
}
watch: { },
chartStart() {
this.initAxis() mounted() {
}, this.timemarker = this.$refs['g-timeaxis-marker']
chartEnd() { this.initAxis()
this.initAxis() this.onWindowResize()
}, window.addEventListener('resize', this.onWindowResize)
gridSize() { window.addEventListener('mousemove', this.moveTimemarker)
this.onWindowResize() window.addEventListener('dragover', this.moveTimemarker)
} },
},
destroyed() {
mounted() { window.removeEventListener('resize', this.onWindowResize)
this.timemarker = this.$refs['g-timeaxis-marker'] window.removeEventListener('mousemove', this.moveTimemarker)
this.initAxis() window.removeEventListener('dragover', this.moveTimemarker)
this.onWindowResize() },
window.addEventListener('resize', this.onWindowResize)
window.addEventListener('mousemove', this.moveTimemarker) methods: {
window.addEventListener('dragover', this.moveTimemarker) initAxis() {
}, this.precision === 'day' ? this.initAxisDaysAndHours() : this.initAxisMonthsAndDays()
},
destroyed() {
window.removeEventListener('resize', this.onWindowResize) initAxisMonthsAndDays() {
window.removeEventListener('mousemove', this.moveTimemarker) let start = moment(this.chartStart)
window.removeEventListener('dragover', this.moveTimemarker) let end = moment(this.chartEnd)
}, this.axisPoints = []
while (start.isBefore(end)) {
methods: { let dayCountOfMonth = start.isSame(end, 'month') ? end.date() - 1 : start.daysInMonth() - start.date() + 1
initAxis() { let widthPercentage = (dayCountOfMonth / this.timeCount) * 100
this.precision === 'day' let endDay = start.isSame(end, 'month') ? end.date() - 1 : start.daysInMonth()
? this.initAxisDaysAndHours() this.axisPoints.push(this.getAxisMonthObject(start, widthPercentage, endDay))
: this.initAxisMonthsAndDays() start.add(1, 'month').date(1).hour(0)
}, }
},
initAxisMonthsAndDays() {
let start = moment(this.chartStart) initAxisDaysAndHours() {
let end = moment(this.chartEnd) let start = moment(this.chartStart)
this.axisPoints = [] let end = moment(this.chartEnd)
while (start.isBefore(end)) { this.axisPoints = []
let dayCountOfMonth = start.isSame(end, 'month') while (start.isBefore(end)) {
? end.date() - 1 let hourCountOfDay = start.isSame(end, 'day') ? end.hour() : 24 - start.hour()
: start.daysInMonth() - start.date() + 1 let widthPercentage = (hourCountOfDay / this.timeCount) * 100
let widthPercentage = (dayCountOfMonth / this.timeCount) * 100 let endHour = start.isSame(end, 'day') ? end.hour() - 1 : 23 // -1 because the last hour is not included e.g if chartEnd=04:00 the last interval we display is between 03 and 04
let endDay = start.isSame(end, 'month') this.axisPoints.push(this.getAxisDayObject(start, widthPercentage, endHour))
? end.date() - 1 start.add(1, 'day').hour(0)
: start.daysInMonth() }
this.axisPoints.push( },
this.getAxisMonthObject(start, widthPercentage, endDay)
) getAxisMonthObject(datetime, widthPercentage, endDay) {
start.add(1, 'month').date(1).hour(0) let datetimeMoment = moment(datetime)
} let axisMonthObject = {
}, widthPercentage: widthPercentage,
value: moment(datetime, 'YYYY-MM'),
initAxisDaysAndHours() { childPoints: []
let start = moment(this.chartStart) }
let end = moment(this.chartEnd) let startDay = datetimeMoment.date()
this.axisPoints = [] for (let i = 0; i <= endDay - startDay; i++) {
while (start.isBefore(end)) { let day = {
let hourCountOfDay = start.isSame(end, 'day') text: datetimeMoment.format('D'),
? end.hour() fullDatetime: datetimeMoment.format(this.timeFormat)
: 24 - start.hour() }
let widthPercentage = (hourCountOfDay / this.timeCount) * 100 axisMonthObject.childPoints.push(day)
let endHour = start.isSame(end, 'day') ? end.hour() - 1 : 23 // -1 because the last hour is not included e.g if chartEnd=04:00 the last interval we display is between 03 and 04 datetimeMoment.add(1, 'day')
this.axisPoints.push( }
this.getAxisDayObject(start, widthPercentage, endHour) return axisMonthObject
) },
start.add(1, 'day').hour(0)
} getAxisDayObject(datetime, widthPercentage, endHour) {
}, let datetimeMoment = moment(datetime)
let axisDayObject = {
getAxisMonthObject(datetime, widthPercentage, endDay) { widthPercentage: widthPercentage,
let datetimeMoment = moment(datetime) value: moment(datetime, 'YYYY-MM-DD'), // ISO 8601
let axisMonthObject = { childPoints: []
widthPercentage: widthPercentage, }
value: moment(datetime, 'YYYY-MM'), let startHour = datetimeMoment.hour()
childPoints: [] for (let i = 0; i <= endHour - startHour; i++) {
} let hour = {
let startDay = datetimeMoment.date() text: datetimeMoment.format('HH'),
for (let i = 0; i <= endDay - startDay; i++) { fullDatetime: datetimeMoment.format(this.timeFormat)
let day = { }
text: datetimeMoment.format('D'), axisDayObject.childPoints.push(hour)
fullDatetime: datetimeMoment.format(this.timeFormat) datetimeMoment.add(1, 'hour')
} }
axisMonthObject.childPoints.push(day) return axisDayObject
datetimeMoment.add(1, 'day') },
}
return axisMonthObject moveTimemarker(event) {
}, const chart = this.timemarker.closest('.g-gantt-chart')
if (!chart) return
getAxisDayObject(datetime, widthPercentage, endHour) { let pos = chart.scrollLeft + event.clientX - this.timemarkerOffset - this.horizontalAxisContainer.left
let datetimeMoment = moment(datetime) if (pos > this.horizontalAxisContainer.width) pos = this.horizontalAxisContainer.width
let axisDayObject = { this.timemarker.style.left = `${pos}px`
widthPercentage: widthPercentage, },
value: moment(datetime, 'YYYY-MM-DD'), // ISO 8601
childPoints: [] onWindowResize() {
} if (!this.$refs['g-timeaxis']) return
let startHour = datetimeMoment.hour() this.horizontalAxisContainer = this.$refs['g-timeaxis'].getBoundingClientRect()
for (let i = 0; i <= endHour - startHour; i++) { this.hourFontSize = Math.min(9.5, 0.75 * (this.horizontalAxisContainer.width / this.timeCount)) + 'px'
let hour = { },
text: datetimeMoment.format('HH'),
fullDatetime: datetimeMoment.format(this.timeFormat) pointFormatted(point) {
} switch (this.precision) {
axisDayObject.childPoints.push(hour) case 'day':
datetimeMoment.add(1, 'hour') return this.dayFormatted(point)
} case 'month':
return axisDayObject return this.monthFormatted(point)
}, }
},
moveTimemarker(event) {
const chart = this.timemarker.closest('.g-gantt-chart') monthFormatted(point) {
if (!chart) return // do not display month text if the month is smaller than x%
let pos = return point.widthPercentage >= (3 / 32) * 100
chart.scrollLeft + ? moment(point.value).locale(this.locale).format(this.monthFormat)
event.clientX - : ''
this.timemarkerOffset - },
this.horizontalAxisContainer.left
if (pos > this.horizontalAxisContainer.width) dayFormatted(point) {
pos = this.horizontalAxisContainer.width // do not display day text if the day is smaller than 12%
this.timemarker.style.left = `${pos}px` // return point.widthPercentage >= 12
}, // ? moment(point.value).locale(this.locale).format(this.dayFormat)
// : ''
onWindowResize() { return moment(point.value).locale(this.locale).format(this.dayFormat)
if (!this.$refs['g-timeaxis']) return }
this.horizontalAxisContainer = }
this.$refs['g-timeaxis'].getBoundingClientRect() }
this.hourFontSize = </script>
Math.min(
9.5,
0.75 * (this.horizontalAxisContainer.width / this.timeCount)
) + 'px'
},
pointFormatted(point) {
switch (this.precision) {
case 'day':
return this.dayFormatted(point)
case 'month':
return this.monthFormatted(point)
}
},
monthFormatted(point) {
// do not display month text if the month is smaller than x%
return point.widthPercentage >= (3 / 32) * 100
? moment(point.value).locale(this.locale).format(this.monthFormat)
: ''
},
dayFormatted(point) {
// do not display day text if the day is smaller than 12%
// return point.widthPercentage >= 12
// ? moment(point.value).locale(this.locale).format(this.dayFormat)
// : ''
return moment(point.value).locale(this.locale).format(this.dayFormat)
}
}
}
</script>
+19 -19
View File
@@ -1,19 +1,19 @@
import GGanttChart from './components/GGanttChart.vue' import GGanttChart from './components/GGanttChart.vue'
import GGanttRow from './components/GGanttRow.vue' import GGanttRow from './components/GGanttRow.vue'
import './scss/index.scss' import './scss/index.scss'
const VueGanttastic = { GGanttChart, GGanttRow } const VueGanttastic = { GGanttChart, GGanttRow }
const install = Vue => { const install = Vue => {
Object.keys(VueGanttastic).forEach(name => { Object.keys(VueGanttastic).forEach(name => {
Vue.component(name, VueGanttastic[name]) Vue.component(name, VueGanttastic[name])
}) })
} }
if (typeof window !== 'undefined' && window.Vue) { if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue) install(window.Vue)
} }
export default VueGanttastic export default VueGanttastic
export { GGanttChart, GGanttRow } export { GGanttChart, GGanttRow }
+13 -13
View File
@@ -1,13 +1,13 @@
$primary: #eee !default; $primary: #eee !default;
$secondary: #e0e0e0 !default; $secondary: #e0e0e0 !default;
$ternary: #f5f5f5 !default; $ternary: #f5f5f5 !default;
$hover-highlight: rgba(204, 216, 219, 0.5) !default; $hover-highlight: rgba(204, 216, 219, 0.5) !default;
$text: #404040 !default; $text: #404040 !default;
$background: #fff !default; $background: #fff !default;
$highlighted: #dcefff !default; $highlighted: #dcefff !default;
$border-color: #eaeaea !default; $border-color: #eaeaea !default;
$timeaxis-background: #fff !default; $timeaxis-background: #fff !default;
$marker-background: #000 !default; $marker-background: #000 !default;
$row-label-border-color: $border-color !default; $row-label-border-color: $border-color !default;
$tooltip-background: #000; $tooltip-background: #000;
$tooltip-color: #fff; $tooltip-color: #fff;
+36 -36
View File
@@ -1,36 +1,36 @@
@use "sass:meta"; @use 'sass:meta';
// default // default
@import './variables'; @import './variables';
@import './ganttastic'; @import './ganttastic';
[data-theme] { [data-theme] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
$themes: 'creamy', 'crimson', 'dark', 'flare', 'fuchsia', 'grove', $themes: 'creamy', 'crimson', 'dark', 'flare', 'fuchsia', 'grove',
'material-blue', 'sky', 'slumber', 'vue'; 'material-blue', 'sky', 'slumber', 'vue';
@each $theme in $themes { @each $theme in $themes {
[data-theme='#{$theme}'] { [data-theme='#{$theme}'] {
@include meta.load-css('./themes/#{$theme}'); @include meta.load-css('./themes/#{$theme}');
} }
} }
.fade-enter-active { .fade-enter-active {
animation: fade-in 0.3s; animation: fade-in 0.3s;
} }
.fade-leave-active { .fade-leave-active {
animation: fade-in 0.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;
} }
} }
+7 -7
View File
@@ -1,7 +1,7 @@
$primary: #ffe8d9; $primary: #ffe8d9;
$secondary: #fcdcc5; $secondary: #fcdcc5;
$ternary: #fff6f0; $ternary: #fff6f0;
$hover-highlight: rgba(230, 221, 202, 0.5); $hover-highlight: rgba(230, 221, 202, 0.5);
$text: #542d05; $text: #542d05;
$background: #fff; $background: #fff;
$row-label-border-color: #f7efe9; $row-label-border-color: #f7efe9;
+3 -3
View File
@@ -1,3 +1,3 @@
@import '../../variables'; @import '../../variables';
@import './variables'; @import './variables';
@import '../../ganttastic'; @import '../../ganttastic';
+7 -7
View File
@@ -1,7 +1,7 @@
$primary: #a82039; $primary: #a82039;
$secondary: #c41238; $secondary: #c41238;
$ternary: #db4f56; $ternary: #db4f56;
$hover-highlight: rgba(196, 141, 141, 0.5); $hover-highlight: rgba(196, 141, 141, 0.5);
$text: white; $text: white;
$background: white; $background: white;
$row-label-border-color: #e6535a; $row-label-border-color: #e6535a;
+3 -3
View File
@@ -1,3 +1,3 @@
@import '../../variables'; @import '../../variables';
@import './variables'; @import './variables';
@import '../../ganttastic'; @import '../../ganttastic';
+10 -10
View File
@@ -1,10 +1,10 @@
$primary: #404040; $primary: #404040;
$secondary: #303030; $secondary: #303030;
$ternary: #353535; $ternary: #353535;
$hover-highlight: rgba(159, 160, 161, 0.5); $hover-highlight: rgba(159, 160, 161, 0.5);
$text: #adb5bd; $text: #adb5bd;
$background: #525252; $background: #525252;
$highlighted: #7e7e7e; $highlighted: #7e7e7e;
$border-color: #444444; $border-color: #444444;
$marker-background: #666666; $marker-background: #666666;
$row-label-border-color: $border-color; $row-label-border-color: $border-color;
+3 -3
View File
@@ -1,3 +1,3 @@
@import '../../variables'; @import '../../variables';
@import './variables'; @import './variables';
@import '../../ganttastic'; @import '../../ganttastic';
+7 -7
View File
@@ -1,7 +1,7 @@
$primary: #e08a38; $primary: #e08a38;
$secondary: #e67912; $secondary: #e67912;
$ternary: #5e5145; $ternary: #5e5145;
$hover-highlight: rgba(196, 141, 141, 0.5); $hover-highlight: rgba(196, 141, 141, 0.5);
$text: #fff; $text: #fff;
$background: #fff; $background: #fff;
$row-label-border-color: #6b5c4e; $row-label-border-color: #6b5c4e;
+3 -3
View File
@@ -1,3 +1,3 @@
@import '../../variables'; @import '../../variables';
@import './variables'; @import './variables';
@import '../../ganttastic'; @import '../../ganttastic';
+9 -9
View File
@@ -1,9 +1,9 @@
$primary: #de1d5a; $primary: #de1d5a;
$secondary: #b50b41; $secondary: #b50b41;
$ternary: #ff7da6; $ternary: #ff7da6;
$hover-highlight: rgba(196, 141, 141, 0.5); $hover-highlight: rgba(196, 141, 141, 0.5);
$text: #fff; $text: #fff;
$background: #fff; $background: #fff;
$row-label-border-color: #f2779e; $row-label-border-color: #f2779e;
$tooltip-background: $primary; $tooltip-background: $primary;
$tooltip-color: #fff; $tooltip-color: #fff;
+3 -3
View File
@@ -1,3 +1,3 @@
@import '../../variables'; @import '../../variables';
@import './variables'; @import './variables';
@import '../../ganttastic'; @import '../../ganttastic';
+7 -7
View File
@@ -1,7 +1,7 @@
$primary: #3d9960; $primary: #3d9960;
$secondary: #288542; $secondary: #288542;
$ternary: #72b585; $ternary: #72b585;
$hover-highlight: rgba(160, 219, 171, 0.5); $hover-highlight: rgba(160, 219, 171, 0.5);
$text: white; $text: white;
$background: white; $background: white;
$row-label-border-color: #73bd88; $row-label-border-color: #73bd88;
+3 -3
View File
@@ -1,3 +1,3 @@
@import '../../variables'; @import '../../variables';
@import './variables'; @import './variables';
@import '../../ganttastic'; @import '../../ganttastic';
@@ -1,7 +1,7 @@
$primary: #0d47a1; $primary: #0d47a1;
$secondary: #1565c0; $secondary: #1565c0;
$ternary: #42a5f5; $ternary: #42a5f5;
$hover-highlight: rgba(110, 165, 196, 0.5); $hover-highlight: rgba(110, 165, 196, 0.5);
$text: white; $text: white;
$background: white; $background: white;
$row-label-border-color: #45abff; $row-label-border-color: #45abff;
+3 -3
View File
@@ -1,3 +1,3 @@
@import '../../variables'; @import '../../variables';
@import './variables'; @import './variables';
@import '../../ganttastic'; @import '../../ganttastic';
+7 -7
View File
@@ -1,7 +1,7 @@
$primary: #b5e3ff; $primary: #b5e3ff;
$secondary: #a1d6f7; $secondary: #a1d6f7;
$ternary: #d6f7ff; $ternary: #d6f7ff;
$hover-highlight: rgba(193, 202, 214, 0.5); $hover-highlight: rgba(193, 202, 214, 0.5);
$text: #022c47; $text: #022c47;
$background: white; $background: white;
$row-label-border-color: #cbf4fe; $row-label-border-color: #cbf4fe;
+3 -3
View File
@@ -1,3 +1,3 @@
@import '../../variables'; @import '../../variables';
@import './variables'; @import './variables';
@import '../../ganttastic'; @import '../../ganttastic';
+10 -10
View File
@@ -1,10 +1,10 @@
$primary: #2c2e36; $primary: #2c2e36;
$secondary: #2f3447; $secondary: #2f3447;
$ternary: #35394d; $ternary: #35394d;
$hover-highlight: rgba(179, 162, 127, 0.5); $hover-highlight: rgba(179, 162, 127, 0.5);
$text: #ffe0b3; $text: #ffe0b3;
$background: #38383b; $background: #38383b;
$highlighted: #7e7e7e; $highlighted: #7e7e7e;
$border-color: #444; $border-color: #444;
$marker-background: #666; $marker-background: #666;
$row-label-border-color: $border-color; $row-label-border-color: $border-color;
+3 -3
View File
@@ -1,3 +1,3 @@
@import '../../variables'; @import '../../variables';
@import './variables'; @import './variables';
@import '../../ganttastic'; @import '../../ganttastic';
+7 -7
View File
@@ -1,7 +1,7 @@
$primary: #258a5d; $primary: #258a5d;
$secondary: #41b883; $secondary: #41b883;
$ternary: #35495e; $ternary: #35495e;
$hover-highlight: rgba(160, 219, 171, 0.5); $hover-highlight: rgba(160, 219, 171, 0.5);
$text: #fff; $text: #fff;
$background: #fff; $background: #fff;
$row-label-border-color: #384d63; $row-label-border-color: #384d63;
+3 -3
View File
@@ -1,3 +1,3 @@
@import '../../variables'; @import '../../variables';
@import './variables'; @import './variables';
@import '../../ganttastic'; @import '../../ganttastic';
+9603 -20106
View File
File diff suppressed because it is too large Load Diff
+19 -16
View File
@@ -15,6 +15,10 @@
"bugs": { "bugs": {
"url": "https://github.com/tenrok/vue-ganttastic/issues" "url": "https://github.com/tenrok/vue-ganttastic/issues"
}, },
"repository": {
"type": "git",
"url": "git+https://github.com/tenrok/vue-ganttastic.git"
},
"license": "MIT", "license": "MIT",
"author": { "author": {
"name": "Marko Zunic", "name": "Marko Zunic",
@@ -31,32 +35,31 @@
"url": "https://github.com/solodyagin" "url": "https://github.com/solodyagin"
} }
], ],
"main": "dist/vue-ganttastic.common.js",
"files": [ "files": [
"dist/vue-ganttastic.*" "dist/vue-ganttastic.*"
], ],
"main": "dist/vue-ganttastic.common.js",
"repository": {
"type": "git",
"url": "git+https://github.com/tenrok/vue-ganttastic.git"
},
"scripts": { "scripts": {
"serve": "vue-cli-service serve --open", "build": "vue-cli-service build --target lib --name vue-ganttastic lib/index.js",
"demo:build": "vue-cli-service build", "demo:build": "vue-cli-service build",
"lib:build": "vue-cli-service build --target lib --name vue-ganttastic lib/index.js", "lint": "vue-cli-service lint --fix",
"lint": "vue-cli-service lint" "serve": "vue-cli-service serve --open"
}, },
"dependencies": { "dependencies": {
"vue": "^2.7.14" "vue": "^2.7.14"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.5.19", "@babel/eslint-parser": "^7.22.15",
"@vue/cli-plugin-eslint": "~4.5.19", "@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-service": "~4.5.19", "@vue/cli-plugin-eslint": "~5.0.8",
"babel-eslint": "^10.1.0", "@vue/cli-service": "~5.0.8",
"eslint": "^6.7.2", "eslint": "^8.51.0",
"eslint-plugin-vue": "^9.16.1", "eslint-config-prettier": "^8.6.0",
"sass": "^1.69.1", "eslint-plugin-prettier": "^4.2.1",
"sass-loader": "^10.4.1", "eslint-plugin-vue": "^9.17.0",
"prettier": "^2.8.8",
"sass": "^1.69.2",
"sass-loader": "^13.3.2",
"vue-template-compiler": "^2.7.14" "vue-template-compiler": "^2.7.14"
}, },
"peerDependencies": { "peerDependencies": {
+374 -382
View File
@@ -1,382 +1,374 @@
<template> <template>
<div> <div>
<select v-model="theme"> <select v-model="theme">
<option v-for="option in themes" :key="option" :value="option"> <option v-for="option in themes" :key="option" :value="option">
{{ option }} {{ option }}
</option> </option>
</select> </select>
<h2>Chart #1</h2> <h2>Chart #1</h2>
<g-gantt-chart <g-gantt-chart
:chart-start="chart1.chartStart" :chart-start="chart1.chartStart"
:chart-end="chart1.chartEnd" :chart-end="chart1.chartEnd"
:grid="chart1.grid" :grid="chart1.grid"
:grid-size="chart1.gridSize" :grid-size="chart1.gridSize"
day-format="dddd, DD. MMMM" day-format="dddd, DD. MMMM"
:hide-timeaxis="chart1.hideTimeaxis" :hide-timeaxis="chart1.hideTimeaxis"
:push-on-overlap="chart1.pushOnOverlap" :push-on-overlap="chart1.pushOnOverlap"
snap-back-on-overlap snap-back-on-overlap
:precision="chart1.precision" :precision="chart1.precision"
:is-magnetic="chart1.isMagnetic" :is-magnetic="chart1.isMagnetic"
:highlighted-hours="chart1.highlightedHours" :highlighted-hours="chart1.highlightedHours"
:highlighted-days="chart1.highlightedDays" :highlighted-days="chart1.highlightedDays"
:row-label-width="chart1.rowLabelWidth" :row-label-width="chart1.rowLabelWidth"
:row-height="chart1.rowHeight" :row-height="chart1.rowHeight"
:theme="theme" :theme="theme"
bar-config-key="config" bar-config-key="config"
bar-start-key="myStart" bar-start-key="myStart"
bar-end-key="myEnd" bar-end-key="myEnd"
@dragend-bar="onDragend($event)" @dragend-bar="onDragend($event)"
> >
<g-gantt-row <g-gantt-row
v-for="row in chart1.rows" v-for="row in chart1.rows"
:key="row.label" :key="row.label"
:label="row.label" :label="row.label"
:label-style="row.labelStyle" :label-style="row.labelStyle"
:row-style="row.style" :row-style="row.style"
:bars="row.bars" :bars="row.bars"
:highlight-on-hover="chart1.highlightOnHover" :highlight-on-hover="chart1.highlightOnHover"
> >
<template #bar-label="{ bar }"> <template #bar-label="{ bar }">
<span>{{ bar.label }}</span> <span>{{ bar.label }}</span>
</template> </template>
</g-gantt-row> </g-gantt-row>
</g-gantt-chart> </g-gantt-chart>
<h2>Chart #2</h2> <h2>Chart #2</h2>
<g-gantt-chart <g-gantt-chart
:chart-start="chart2.chartStart" :chart-start="chart2.chartStart"
:chart-end="chart2.chartEnd" :chart-end="chart2.chartEnd"
:grid="chart2.grid" :grid="chart2.grid"
:grid-size="chart2.gridSize" :grid-size="chart2.gridSize"
:hide-timeaxis="chart2.hideTimeaxis" :hide-timeaxis="chart2.hideTimeaxis"
:push-on-overlap="chart2.pushOnOverlap" :push-on-overlap="chart2.pushOnOverlap"
snap-back-on-overlap snap-back-on-overlap
:precision="chart2.precision" :precision="chart2.precision"
:is-magnetic="chart2.isMagnetic" :is-magnetic="chart2.isMagnetic"
:highlighted-days="chart2.highlightedDays" :highlighted-days="chart2.highlightedDays"
:row-label-width="chart2.rowLabelWidth" :row-label-width="chart2.rowLabelWidth"
:row-height="chart2.rowHeight" :row-height="chart2.rowHeight"
:theme="theme" :theme="theme"
:width="chart2.width" :width="chart2.width"
:height="chart2.height" :height="chart2.height"
:allow-add="chart2.allowAdd" :allow-add="chart2.allowAdd"
> >
<g-gantt-row <g-gantt-row
v-for="row in chart2.rows" v-for="row in chart2.rows"
:key="row.label" :key="row.label"
:label="row.label" :label="row.label"
:row-style="row.style" :row-style="row.style"
:bars="row.bars" :bars="row.bars"
:highlight-on-hover="chart2.highlightOnHover" :highlight-on-hover="chart2.highlightOnHover"
> >
<template #bar-label="{ bar }"> <template #bar-label="{ bar }">
<span>{{ bar.label }}</span> <span>{{ bar.label }}</span>
</template> </template>
</g-gantt-row> </g-gantt-row>
</g-gantt-chart> </g-gantt-chart>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
data: () => ({ data: () => ({
theme: 'default', theme: 'default',
themes: [ themes: [
'creamy', 'creamy',
'crimson', 'crimson',
'dark', 'dark',
'default', 'default',
'flare', 'flare',
'fuchsia', 'fuchsia',
'grove', 'grove',
'material-blue', 'material-blue',
'sky', 'sky',
'slumber', 'slumber',
'vue' 'vue'
], ],
chart1: { chart1: {
chartStart: '2020-03-02 00:00', chartStart: '2020-03-02 00:00',
chartEnd: '2020-03-10 10:00', chartEnd: '2020-03-10 10:00',
precision: 'day', precision: 'day',
pushOnOverlap: true, pushOnOverlap: true,
isMagnetic: true, isMagnetic: true,
grid: true, grid: true,
gridSize: 30, gridSize: 30,
rowHeight: 40, rowHeight: 40,
rowLabelWidth: 200, rowLabelWidth: 200,
hideTimeaxis: false, hideTimeaxis: false,
highlightOnHover: true, highlightOnHover: true,
highlightedDays: ['2020-03-08'], highlightedDays: ['2020-03-08'],
highlightedHours: [10, 12], highlightedHours: [10, 12],
rows: [ rows: [
{ {
label: 'Row #1', label: 'Row #1',
bars: [ bars: [
{ {
myStart: '2020-03-03 18:00', myStart: '2020-03-03 18:00',
myEnd: '2020-03-03 23:00', myEnd: '2020-03-03 23:00',
label: 'Immobile', label: 'Immobile',
tooltip: 'Bar tooltip', tooltip: 'Bar tooltip',
config: { config: {
color: 'white', color: 'white',
backgroundColor: '#404040', backgroundColor: '#404040',
opacity: 0.5, opacity: 0.5,
immobile: true immobile: true
} }
}, },
{ {
myStart: '2020-03-03 04:00', myStart: '2020-03-03 04:00',
myEnd: '2020-03-03 15:00', myEnd: '2020-03-03 15:00',
label: 'Bar', label: 'Bar',
tooltip: 'Bar tooltip', tooltip: 'Bar tooltip',
config: { config: {
color: 'white', color: 'white',
backgroundColor: '#2e74a3', backgroundColor: '#2e74a3',
bundle: 'blueBundle' bundle: 'blueBundle'
} }
} }
] ]
}, },
{ {
label: 'Row #2', label: 'Row #2',
labelStyle: { labelStyle: {
justifyContent: 'end' justifyContent: 'end'
}, },
style: { style: {
background: '#ffb0b07f' background: '#ffb0b07f'
}, },
bars: [ bars: [
{ {
myStart: '2020-03-02 09:00', myStart: '2020-03-02 09:00',
myEnd: '2020-03-02 18:00', myEnd: '2020-03-02 18:00',
label: 'Bar', label: 'Bar',
tooltip: 'Bar tooltip', tooltip: 'Bar tooltip',
config: { config: {
color: 'white', color: 'white',
backgroundColor: '#de3b26', backgroundColor: '#de3b26',
bundle: 'redBundle' bundle: 'redBundle'
} }
}, },
{ {
myStart: '2020-03-03 04:00', myStart: '2020-03-03 04:00',
myEnd: '2020-03-03 15:00', myEnd: '2020-03-03 15:00',
label: 'We belong together ^', label: 'We belong together ^',
tooltip: 'Bar tooltip', tooltip: 'Bar tooltip',
config: { config: {
color: 'white', color: 'white',
backgroundColor: '#2e74a3', backgroundColor: '#2e74a3',
bundle: 'blueBundle' bundle: 'blueBundle'
} }
}, },
{ {
myStart: '2020-03-03 18:00', myStart: '2020-03-03 18:00',
myEnd: '2020-03-03 22:00', myEnd: '2020-03-03 22:00',
label: 'Bar', label: 'Bar',
tooltip: 'Bar tooltip', tooltip: 'Bar tooltip',
config: { color: 'white', backgroundColor: '#aa34a3' } config: { color: 'white', backgroundColor: '#aa34a3' }
} }
] ]
}, },
{ {
label: 'Row #3', label: 'Row #3',
bars: [ bars: [
{ {
myStart: '2020-03-02 09:00', myStart: '2020-03-02 09:00',
myEnd: '2020-03-02 18:00', myEnd: '2020-03-02 18:00',
label: 'We belong together ^', label: 'We belong together ^',
tooltip: 'Bar tooltip', tooltip: 'Bar tooltip',
config: { config: {
color: 'white', color: 'white',
backgroundColor: '#de3b26', backgroundColor: '#de3b26',
bundle: 'redBundle' bundle: 'redBundle'
} }
}, },
{ {
myStart: '2020-03-02 22:30', myStart: '2020-03-02 22:30',
myEnd: '2020-03-03 05:00', myEnd: '2020-03-03 05:00',
label: 'With handles!', label: 'With handles!',
tooltip: 'Bar tooltip', tooltip: 'Bar tooltip',
config: { config: {
color: 'white', color: 'white',
backgroundColor: '#a23def', backgroundColor: '#a23def',
handles: true handles: true
} }
}, },
{ {
myStart: '2020-03-02 01:00', myStart: '2020-03-02 01:00',
myEnd: '2020-03-02 07:00', myEnd: '2020-03-02 07:00',
label: 'Bar', label: 'Bar',
tooltip: 'Bar tooltip', tooltip: 'Bar tooltip',
config: { config: {
color: 'white', color: 'white',
backgroundColor: '#5effad', backgroundColor: '#5effad',
pushOnOverlap: false, pushOnOverlap: false,
zIndex: 2 zIndex: 2
} }
}, },
{ {
myStart: '2020-03-03 14:00', myStart: '2020-03-03 14:00',
myEnd: '2020-03-03 20:00', myEnd: '2020-03-03 20:00',
label: 'Woooow!', label: 'Woooow!',
tooltip: 'Bar tooltip', tooltip: 'Bar tooltip',
config: { config: {
color: 'white', color: 'white',
background: background: 'repeating-linear-gradient(45deg, #de7359, #de7359 10px, #ffc803 10px, #ffc803 20px)'
'repeating-linear-gradient(45deg, #de7359, #de7359 10px, #ffc803 10px, #ffc803 20px)' }
} }
} ]
] },
}, {
{ label: 'Row #4',
label: 'Row #4', bars: [
bars: [ {
{ myStart: '2020-03-03 06:30',
myStart: '2020-03-03 06:30', myEnd: '2020-03-03 20:00',
myEnd: '2020-03-03 20:00', label: 'Bar',
label: 'Bar', tooltip: 'Bar tooltip',
tooltip: 'Bar tooltip', config: {
config: { color: 'white',
color: 'white', backgroundColor: '#d18aaf',
backgroundColor: '#d18aaf', handles: true
handles: true }
} },
}, {
{ myStart: '2020-03-02 00:30',
myStart: '2020-03-02 00:30', myEnd: '2020-03-03 01:00',
myEnd: '2020-03-03 01:00', label: 'Rectangular',
label: 'Rectangular', tooltip: 'Bar tooltip',
tooltip: 'Bar tooltip', config: {
config: { color: 'white',
color: 'white', backgroundColor: '#f2840f',
backgroundColor: '#f2840f', borderRadius: 0
borderRadius: 0 }
} }
} ]
] }
} ]
] },
}, chart2: {
chart2: { chartStart: '2020-03-01 00:00',
chartStart: '2020-03-01 00:00', chartEnd: '2020-04-01 00:00',
chartEnd: '2020-04-01 00:00', precision: 'month',
precision: 'month', pushOnOverlap: false,
pushOnOverlap: false, isMagnetic: true,
isMagnetic: true, grid: true,
grid: true, gridSize: 50,
gridSize: 50, rowHeight: 40,
rowHeight: 40, rowLabelWidth: 300,
rowLabelWidth: 300, hideTimeaxis: false,
hideTimeaxis: false, highlightOnHover: true,
highlightOnHover: true, highlightedDays: ['2020-03-01', '2020-03-08', '2020-03-15', '2020-03-22', '2020-03-29'],
highlightedDays: [ width: '90vw',
'2020-03-01', height: '250px',
'2020-03-08', allowAdd: false,
'2020-03-15', rows: [
'2020-03-22', {
'2020-03-29' label: 'Row #1',
], bars: [
width: '90vw', {
height: '250px', start: '2020-03-05 00:00',
allowAdd: false, end: '2020-03-10 23:59',
rows: [ label: 'Bar',
{ tooltip: 'Bar tooltip',
label: 'Row #1', ganttBarConfig: {
bars: [ color: 'white',
{ backgroundColor: '#2e74a3',
start: '2020-03-05 00:00', bundle: 'blueBundle'
end: '2020-03-10 23:59', }
label: 'Bar', }
tooltip: 'Bar tooltip', ]
ganttBarConfig: { },
color: 'white', {
backgroundColor: '#2e74a3', label: 'Row #2',
bundle: 'blueBundle' bars: [
} {
} start: '2020-03-02 00:00',
] end: '2020-03-09 23:59',
}, label: 'We belong together ^',
{ tooltip: 'Bar tooltip',
label: 'Row #2', ganttBarConfig: {
bars: [ color: 'white',
{ backgroundColor: '#2e74a3',
start: '2020-03-02 00:00', bundle: 'blueBundle'
end: '2020-03-09 23:59', }
label: 'We belong together ^', },
tooltip: 'Bar tooltip', {
ganttBarConfig: { start: '2020-03-24 00:00',
color: 'white', end: '2020-03-26 23:00',
backgroundColor: '#2e74a3', label: 'Bar',
bundle: 'blueBundle' tooltip: 'Bar tooltip',
} ganttBarConfig: {
}, color: 'white',
{ backgroundColor: '#de3b26'
start: '2020-03-24 00:00', }
end: '2020-03-26 23:00', }
label: 'Bar', ]
tooltip: 'Bar tooltip', },
ganttBarConfig: { {
color: 'white', label: 'Row #3',
backgroundColor: '#de3b26' bars: [
} {
} start: '2020-03-15 00:00',
] end: '2020-03-18 23:59',
}, label: 'Bar',
{ tooltip: 'Bar tooltip',
label: 'Row #3', ganttBarConfig: {
bars: [ color: 'white',
{ backgroundColor: '#408e2f'
start: '2020-03-15 00:00', }
end: '2020-03-18 23:59', }
label: 'Bar', ],
tooltip: 'Bar tooltip', style: {
ganttBarConfig: { background: 'linear-gradient(-45deg, rgba(0, 0, 0, 0) 48%, rgba(0, 0, 0, 0.2) 50%, rgba(0, 0, 0, 0) 52%)',
color: 'white', backgroundSize: '1em 1em'
backgroundColor: '#408e2f' }
} },
} {
], label:
style: { 'Lorem ipsum dolor sit amet. Vel odit debitis qui aliquam sequi et reprehenderit Quis. Et ipsam enim aut culpa quia sed maiores veniam in consequuntur accusantium.',
background: bars: []
'linear-gradient(-45deg, rgba(0, 0, 0, 0) 48%, rgba(0, 0, 0, 0.2) 50%, rgba(0, 0, 0, 0) 52%)', },
backgroundSize: '1em 1em' {
} label: 'Row #5',
}, bars: []
{ },
label: {
'Lorem ipsum dolor sit amet. Vel odit debitis qui aliquam sequi et reprehenderit Quis. Et ipsam enim aut culpa quia sed maiores veniam in consequuntur accusantium.', label: 'Row #6',
bars: [] bars: []
}, },
{ {
label: 'Row #5', label: 'Row #7',
bars: [] bars: []
}, },
{ {
label: 'Row #6', label: 'Row #8',
bars: [] bars: []
}, }
{ ]
label: 'Row #7', }
bars: [] }),
},
{ methods: {
label: 'Row #8', onDragend(e) {
bars: [] const { event, bar, movedBars } = e
} // eslint-disable-next-line
] console.log('onDragend', { event: event.type, bar, movedBars })
} }
}), }
}
methods: { </script>
onDragend(e) {
const { event, bar, movedBars } = e <style lang="scss" src="../lib/scss/index.scss"></style>
// eslint-disable-next-line
console.log('onDragend', { event: event.type, bar, movedBars })
}
}
}
</script>
<style lang="scss" src="../lib/scss/index.scss"></style>
+12 -12
View File
@@ -1,12 +1,12 @@
import Vue from 'vue' import Vue from 'vue'
import App from './App.vue' import App from './App.vue'
import { GGanttChart, GGanttRow } from '../lib' import { GGanttChart, GGanttRow } from '../lib'
Vue.component('GGanttChart', GGanttChart) Vue.component('GGanttChart', GGanttChart)
Vue.component('GGanttRow', GGanttRow) Vue.component('GGanttRow', GGanttRow)
Vue.config.productionTip = false Vue.config.productionTip = false
new Vue({ new Vue({
render: h => h(App) render: h => h(App)
}).$mount('#app') }).$mount('#app')
+8 -8
View File
@@ -1,8 +1,8 @@
// vetur.config.js // vetur.config.js
/** @type {import('vls').VeturConfig} */ /** @type {import('vls').VeturConfig} */
module.exports = { module.exports = {
settings: { settings: {
'vetur.ignoreProjectWarning': true, 'vetur.ignoreProjectWarning': true
}, },
projects: ['./'], projects: ['./']
} }
+29 -24
View File
@@ -1,24 +1,29 @@
const isBuildLib = const isProduction = process.env.NODE_ENV === 'production'
(process.env.npm_lifecycle_script || '').indexOf('--target lib') > 0 const isBuildLib =
(process.env.npm_lifecycle_script || '').indexOf('--target lib') > 0
module.exports = {
publicPath: '', module.exports = {
outputDir: isBuildLib ? 'dist' : 'demo', publicPath: isProduction ? '/vue-ganttastic/' : '',
css: {
loaderOptions: { outputDir: isBuildLib ? 'dist' : 'demo',
sass: {
implementation: require('sass') css: {
} loaderOptions: {
}, sass: {
extract: true implementation: require('sass')
}, }
productionSourceMap: false, },
chainWebpack: config => { extract: true
if (process.env.VUE_CLI_BUILD_TARGET === 'lib') { },
config.externals({
...config.get('externals'), productionSourceMap: false,
moment: 'moment'
}) chainWebpack: config => {
} if (isBuildLib) {
} config.externals({
} ...config.get('externals'),
moment: 'moment'
})
}
}
}