@@ -123,6 +123,275 @@ const rows = ref([
123123</style>
124124```
125125
126+ ## Planned vs Actual Timeline Tracking
127+
128+ The planned bars feature allows you to visualize both planned and actual timelines for project tracking and variance analysis:
129+
130+ ``` vue
131+ <template>
132+ <g-gantt-chart
133+ v-bind="chartConfig"
134+ :show-planned-bars="true"
135+ >
136+ <g-gantt-row
137+ v-for="row in projectRows"
138+ :key="row.label"
139+ :label="row.label"
140+ :bars="row.bars"
141+ />
142+ </g-gantt-chart>
143+ </template>
144+
145+ <script setup lang="ts">
146+ import { ref } from 'vue'
147+
148+ const chartConfig = ref({
149+ chartStart: '2024-01-01',
150+ chartEnd: '2024-03-31',
151+ precision: 'day',
152+ barStart: 'start',
153+ barEnd: 'end'
154+ })
155+
156+ const projectRows = ref([
157+ {
158+ label: 'Planning Phase',
159+ bars: [{
160+ start: '2024-01-03', // Actual started 2 days late
161+ end: '2024-01-18', // Finished 3 days late
162+ start_planned: '2024-01-01', // Originally planned start
163+ end_planned: '2024-01-15', // Originally planned end
164+ ganttBarConfig: {
165+ id: 'planning',
166+ label: 'Requirements & Design',
167+ style: {
168+ backgroundColor: '#4caf50',
169+ color: 'white'
170+ },
171+ plannedStyle: {
172+ backgroundColor: '#e8f5e8',
173+ border: '2px dashed #4caf50',
174+ opacity: 0.6
175+ }
176+ }
177+ }]
178+ },
179+ {
180+ label: 'Development',
181+ bars: [{
182+ start: '2024-01-20', // Started on time
183+ end: '2024-02-25', // Running 5 days over
184+ start_planned: '2024-01-20', // Planned start
185+ end_planned: '2024-02-20', // Planned end
186+ ganttBarConfig: {
187+ id: 'development',
188+ label: 'Core Implementation',
189+ style: {
190+ backgroundColor: '#2196f3',
191+ color: 'white'
192+ },
193+ plannedStyle: {
194+ backgroundColor: '#e3f2fd',
195+ border: '2px dashed #2196f3',
196+ opacity: 0.6
197+ }
198+ }
199+ }]
200+ },
201+ {
202+ label: 'Testing',
203+ bars: [{
204+ start: '2024-03-01', // Delayed start due to dev overrun
205+ end: '2024-03-20', // Compressed timeline
206+ start_planned: '2024-02-25', // Originally planned earlier
207+ end_planned: '2024-03-15', // Originally shorter duration
208+ ganttBarConfig: {
209+ id: 'testing',
210+ label: 'QA & Bug Fixes',
211+ style: {
212+ backgroundColor: '#ff9800',
213+ color: 'white'
214+ },
215+ plannedStyle: {
216+ backgroundColor: '#fff3e0',
217+ border: '2px dashed #ff9800',
218+ opacity: 0.6
219+ }
220+ }
221+ }]
222+ }
223+ ])
224+ </script>
225+ ```
226+
227+ ### Project Variance Analysis Example
228+
229+ ``` vue
230+ <template>
231+ <div class="project-dashboard">
232+ <h3>Project Dashboard with Variance Tracking</h3>
233+
234+ <!-- Summary Stats -->
235+ <div class="variance-summary">
236+ <div class="stat-card">
237+ <strong>Total Delay:</strong> {{ calculateTotalDelay() }} days
238+ </div>
239+ <div class="stat-card">
240+ <strong>Tasks Over Budget:</strong> {{ tasksOverBudget() }}
241+ </div>
242+ </div>
243+
244+ <g-gantt-chart
245+ v-bind="chartConfig"
246+ :show-planned-bars="true"
247+ @click-bar="handleBarClick"
248+ >
249+ <g-gantt-row
250+ v-for="row in varianceRows"
251+ :key="row.label"
252+ :label="row.label"
253+ :bars="row.bars"
254+ >
255+ <template #bar-tooltip="{ bar, barStart, barEnd }">
256+ <div class="variance-tooltip">
257+ <h4>{{ bar.ganttBarConfig.label }}</h4>
258+ <div><strong>Actual:</strong> {{ barStart }} → {{ barEnd }}</div>
259+ <div v-if="bar.start_planned">
260+ <strong>Planned:</strong> {{ bar.start_planned }} → {{ bar.end_planned }}
261+ </div>
262+ <div class="variance-info">
263+ <strong>Variance:</strong>
264+ <span :class="getVarianceClass(bar)">
265+ {{ calculateVariance(bar) }}
266+ </span>
267+ </div>
268+ </div>
269+ </template>
270+ </g-gantt-row>
271+ </g-gantt-chart>
272+ </div>
273+ </template>
274+
275+ <script setup lang="ts">
276+ import dayjs from 'dayjs'
277+
278+ const varianceRows = ref([
279+ {
280+ label: 'Frontend Development',
281+ bars: [{
282+ start: '2024-01-10',
283+ end: '2024-02-15',
284+ start_planned: '2024-01-05',
285+ end_planned: '2024-02-05',
286+ ganttBarConfig: {
287+ id: 'frontend',
288+ label: 'React Components',
289+ plannedStyle: {
290+ backgroundColor: '#ffebee',
291+ border: '2px dashed #f44336',
292+ opacity: 0.7
293+ }
294+ }
295+ }]
296+ },
297+ {
298+ label: 'Backend API',
299+ bars: [{
300+ start: '2024-01-08',
301+ end: '2024-01-28',
302+ start_planned: '2024-01-10',
303+ end_planned: '2024-02-10',
304+ ganttBarConfig: {
305+ id: 'backend',
306+ label: 'REST API Development',
307+ plannedStyle: {
308+ backgroundColor: '#e8f5e8',
309+ border: '2px dashed #4caf50',
310+ opacity: 0.7
311+ }
312+ }
313+ }]
314+ }
315+ ])
316+
317+ function calculateVariance(bar) {
318+ if (!bar.start_planned || !bar.end_planned) return 'N/A'
319+
320+ const actualDuration = dayjs(bar.end).diff(dayjs(bar.start), 'days')
321+ const plannedDuration = dayjs(bar.end_planned).diff(dayjs(bar.start_planned), 'days')
322+ const variance = actualDuration - plannedDuration
323+
324+ return variance > 0 ? `+${variance} days` : `${variance} days`
325+ }
326+
327+ function getVarianceClass(bar) {
328+ if (!bar.start_planned) return ''
329+ const variance = dayjs(bar.end).diff(dayjs(bar.end_planned), 'days')
330+ return variance > 0 ? 'over-budget' : variance < 0 ? 'under-budget' : 'on-time'
331+ }
332+
333+ function calculateTotalDelay() {
334+ return varianceRows.value.reduce((total, row) => {
335+ return total + row.bars.reduce((rowTotal, bar) => {
336+ const delay = bar.start_planned
337+ ? Math.max(0, dayjs(bar.end).diff(dayjs(bar.end_planned), 'days'))
338+ : 0
339+ return rowTotal + delay
340+ }, 0)
341+ }, 0)
342+ }
343+
344+ function tasksOverBudget() {
345+ return varianceRows.value.reduce((count, row) => {
346+ return count + row.bars.filter(bar =>
347+ bar.start_planned && dayjs(bar.end).isAfter(dayjs(bar.end_planned))
348+ ).length
349+ }, 0)
350+ }
351+
352+ function handleBarClick({ bar }) {
353+ console.log('Bar clicked:', bar.ganttBarConfig.label)
354+ // Handle bar interaction
355+ }
356+ </script>
357+
358+ <style scoped>
359+ .project-dashboard {
360+ padding: 20px;
361+ }
362+
363+ .variance-summary {
364+ display: flex;
365+ gap: 20px;
366+ margin-bottom: 20px;
367+ }
368+
369+ .stat-card {
370+ padding: 12px;
371+ background: #f5f5f5;
372+ border-radius: 6px;
373+ border-left: 4px solid #2196f3;
374+ }
375+
376+ .variance-tooltip {
377+ padding: 10px;
378+ background: white;
379+ border-radius: 4px;
380+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
381+ }
382+
383+ .variance-info {
384+ margin-top: 8px;
385+ padding-top: 8px;
386+ border-top: 1px solid #eee;
387+ }
388+
389+ .over-budget { color: #f44336; font-weight: bold; }
390+ .under-budget { color: #4caf50; font-weight: bold; }
391+ .on-time { color: #2196f3; font-weight: bold; }
392+ </style>
393+ ```
394+
126395## Multi Label Columns
127396
128397``` vue
0 commit comments