Skip to content

Commit

Permalink
[ADD] Custom label columns
Browse files Browse the repository at this point in the history
  • Loading branch information
Xeyos88 committed Dec 22, 2024
1 parent a300a39 commit 0131ec2
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 45 deletions.
36 changes: 31 additions & 5 deletions docs/.vitepress/theme/components/MultiColumnDemo.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
<script setup lang="ts">
import { ref } from 'vue'
import { GGanttChart, GGanttRow } from 'hy-vue-gantt'
import { GGanttChart, GGanttRow, type ChartRow, type LabelColumnConfig } from 'hy-vue-gantt'
const chartStart = ref('2024-01-01')
const chartEnd = ref('2024-03-31')
const precision = ref('day')
const barStart = ref('start')
const barEnd = ref('end')
const multiColumnLabel = ref([
'Label',
'StartDate',
'Duration'
const getN = (row: ChartRow) => {
return row.bars.length
}
const sortN = (a: ChartRow, b: ChartRow) => {
const aId = a.bars.length ?? 0
const bId = b.bars.length ?? 0
return aId < bId ? -1 : aId > bId ? 1 : 0
}
const multiColumnLabel = ref<LabelColumnConfig[]>([
{
field: 'Id',
sortable: false,
},
{
field: 'Label',
},
{
field: 'StartDate',
},
{
field: 'Duration',
},
{
field: 'Bars N°',
valueGetter: getN,
sortFn: sortN,
},
])
const rows = ref([
Expand Down
17 changes: 14 additions & 3 deletions docs/api/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ HyVue Gantt exposes several TypeScript types and interfaces for use in your appl
// Time Units
type TimeUnit = 'hour' | 'day' | 'week' | 'month';

// Sort Direction
type SortDirection = 'asc' | 'desc' | 'none';

// Connection Types
type ConnectionType = 'bezier' | 'straight' | 'squared';
type ConnectionPattern = 'solid' | 'dot' | 'dash' | 'dashdot';
Expand Down Expand Up @@ -57,4 +54,18 @@ interface BarConnection {
animated?: boolean;
animationSpeed?: ConnectionSpeed;
}
```
## Label Data Types

```typescript
export type LabelColumnField = "Id" | "Label" | "StartDate" | "EndDate" | "Duration"

export type SortFunction = (a: ChartRow, b: ChartRow) => number

interface LabelColumnConfig {
field: LabelColumnField | string
sortable?: boolean
valueGetter?: (row: ChartRow) => string | number
sortFn?: SortFunction
}
```
2 changes: 1 addition & 1 deletion docs/components/g-gantt-chart.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Here's a minimal example of using the GGanttChart component:
| labelColumnTitle | `string` | `''` | Title for the label column |
| labelColumnWidth | `string` | `'150px'` | Width of the label column |
| sortable | `boolean` | `true` | Enable row sorting functionality |
| multiColumnLabel | `LabelColumnField[]` | `[]` | Array of columns to display in the header |
| multiColumnLabel | `LabelColumnConfig[]` | `[]` | Array of columns to display in the header |
| commands | `boolean` | `true` | Show chart control commands |
| enableMinutes | `boolean` | `false` | Enable minutes precision for hour view |
| enableConnections | `boolean` | `true` | Enable connections between bars |
Expand Down
3 changes: 2 additions & 1 deletion src/components/GGanttChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ const rowManager = useRows(
slots,
{
barStart: toRef(props, "barStart"),
barEnd: toRef(props, "barEnd")
barEnd: toRef(props, "barEnd"),
multiColumnLabel: toRef(props, "multiColumnLabel")
},
props.initialRows ? toRef(props, "initialRows") : undefined
)
Expand Down
64 changes: 39 additions & 25 deletions src/components/GGanttLabelColumn.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import provideConfig from "../provider/provideConfig"
import provideBooleanConfig from "../provider/provideBooleanConfig"
import { ref, computed, inject, reactive, onMounted } from "vue"
import type { CSSProperties } from "vue"
import type { LabelColumnField, ChartRow, GanttBarObject } from "../types"
import type { LabelColumnField, ChartRow, GanttBarObject, LabelColumnConfig } from "../types"
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"
import { faArrowDownAZ, faArrowDownZA, faSort } from "@fortawesome/free-solid-svg-icons"
import useDayjsHelper from "../composables/useDayjsHelper"
Expand Down Expand Up @@ -39,15 +39,15 @@ const draggedColumn = ref<string | null>(null)
const initializeColumnWidths = () => {
columns.value.forEach((column) => {
if (!columnWidths.has(column)) {
columnWidths.set(column, labelColumnWidth.value)
if (!columnWidths.has(column.field)) {
columnWidths.set(column.field, labelColumnWidth.value)
}
})
}
const columns = computed<LabelColumnField[]>(() => {
const columns = computed<LabelColumnConfig[]>(() => {
if (!multiColumnLabel.value?.length || !labelColumnTitle.value) {
return ["Label"]
return [{ field: "Label", sortable: true }]
}
return multiColumnLabel.value
})
Expand Down Expand Up @@ -112,8 +112,12 @@ const calculateDuration = (startDate: string, endDate: string) => {
}
}
const getRowValue = (row: ChartRow, column: LabelColumnField, index: number) => {
switch (column) {
const getRowValue = (row: ChartRow, column: LabelColumnConfig, index: number) => {
if (column.valueGetter) {
return column.valueGetter(row)
}
switch (column.field) {
case "Id":
return row.id ?? index + 1
case "Label":
Expand Down Expand Up @@ -154,6 +158,7 @@ const getRowValue = (row: ChartRow, column: LabelColumnField, index: number) =>
const labelContainer = ref<HTMLElement | null>(null)
const labelContainerStyle = computed<CSSProperties>(() => {
console.log(maxRows.value, rows.value.length)
if (maxRows.value === 0) return {}
const minRows = Math.min(maxRows.value, rows.value.length)
Expand All @@ -163,15 +168,22 @@ const labelContainerStyle = computed<CSSProperties>(() => {
}
})
const getSortIcon = (column: LabelColumnField) => {
if (column !== sortState.value.column || sortState.value.direction === "none") {
const getSortIcon = (field: string) => {
if (field !== sortState.value.column || sortState.value.direction === "none") {
return faSort
}
return sortState.value.direction === "asc" ? faArrowDownAZ : faArrowDownZA
}
const isValidColumn = (column: LabelColumnField) => {
return ["Id", "Label", "StartDate", "EndDate", "Duration"].includes(column)
const isValidColumn = (field: string): field is LabelColumnField => {
return ["Id", "Label", "StartDate", "EndDate", "Duration"].includes(field)
}
const isSortable = (column: LabelColumnConfig) => {
if (column.sortable === false) return false
return (
(sortable || (!sortable && column.sortable)) && (isValidColumn(column.field) || column.sortFn)
)
}
const emit = defineEmits<{
Expand Down Expand Up @@ -211,23 +223,26 @@ defineExpose({
<div class="g-label-column-header" :style="{ background: colors.primary }">
<template v-for="column in columns" :key="column">
<div
v-if="isValidColumn(column)"
v-if="isValidColumn(column.field) || column.valueGetter"
class="g-label-column-header-cell"
:class="{ sortable: sortable }"
:class="{ sortable: isSortable(column) }"
role="columnheader"
:style="getColumnStyle(column)"
:style="getColumnStyle(column.field)"
>
<div class="header-content" @click="sortable ? toggleSort(column) : undefined">
<span class="text-ellipsis">{{ column }}</span>
<span v-if="sortable" class="sort-icon">
<FontAwesomeIcon :icon="getSortIcon(column)" />
<div
class="header-content"
@click="isSortable(column) ? toggleSort(column.field) : undefined"
>
<span class="text-ellipsis">{{ column.field }}</span>
<span v-if="isSortable(column)" class="sort-icon">
<FontAwesomeIcon :icon="getSortIcon(column.field)" />
</span>
</div>
<div
v-if="labelResizable"
class="column-resizer"
@mousedown="(e) => handleDragStart(e, column)"
:class="{ 'is-dragging': isDragging && draggedColumn === column }"
@mousedown="(e) => handleDragStart(e, column.field)"
:class="{ 'is-dragging': isDragging && draggedColumn === column.field }"
></div>
</div>
</template>
Expand All @@ -249,14 +264,14 @@ defineExpose({
}"
>
<div class="g-label-column-row-inner">
<template v-for="column in columns" :key="column">
<template v-if="isValidColumn(column)">
<template v-for="column in columns" :key="column.field">
<template v-if="isValidColumn(column.field) || column.valueGetter">
<slot
:name="`label-column-${column.toLowerCase()}`"
:name="`label-column-${column.field.toLowerCase()}`"
:row="row"
:value="getRowValue(row, column, index)"
>
<div class="g-label-column-cell" :style="getColumnStyle(column)">
<div class="g-label-column-cell" :style="getColumnStyle(column.field)">
<div class="cell-content">
<span class="text-ellipsis">
{{ getRowValue(row, column, index) }}
Expand Down Expand Up @@ -353,7 +368,6 @@ defineExpose({
.g-label-column-rows {
width: 100%;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
Expand Down
26 changes: 21 additions & 5 deletions src/composables/useRows.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { ref, computed, type Ref, type Slots } from "vue"
import type { ChartRow, LabelColumnField, SortState } from "../types"
import type { ChartRow, LabelColumnConfig, LabelColumnField, SortState } from "../types"
import dayjs from "dayjs"

export interface UseRowsReturn {
rows: Ref<ChartRow[]>
sortState: Ref<SortState>
toggleSort: (column: LabelColumnField) => void
toggleSort: (column: string) => void
getChartRows: () => ChartRow[]
onSortChange: (callback: () => void) => () => void
}

export interface UseRowsProps {
barStart: Ref<string>
barEnd: Ref<string>
multiColumnLabel: Ref<LabelColumnConfig[]>
}

export function useRows(
slots: Slots,
{ barStart, barEnd }: UseRowsProps,
{ barStart, barEnd, multiColumnLabel }: UseRowsProps,
initialRows?: Ref<ChartRow[]>,
initialSortColumn: LabelColumnField = "Label"
): UseRowsReturn {
Expand Down Expand Up @@ -60,7 +61,13 @@ export function useRows(
return dayjs()
}

const compareValues = (a: ChartRow, b: ChartRow, column: LabelColumnField): number => {
const compareValues = (a: ChartRow, b: ChartRow, column: LabelColumnField | string): number => {
const columnConfig = multiColumnLabel.value?.find((conf) => conf.field === column)

if (columnConfig?.sortFn && !isStandardField(column)) {
return columnConfig.sortFn(a, b)
}

switch (column) {
case "Id":
const aId = a.id ?? 0
Expand Down Expand Up @@ -94,10 +101,19 @@ export function useRows(
return aDuration - bDuration
}
default:
if (columnConfig?.valueGetter) {
const aValue = columnConfig.valueGetter(a)
const bValue = columnConfig.valueGetter(b)
return String(aValue).localeCompare(String(bValue))
}
return 0
}
}

const isStandardField = (field: string): field is LabelColumnField => {
return ["Id", "Label", "StartDate", "EndDate", "Duration"].includes(field)
}

const rows = computed(() => {
let sourceRows: ChartRow[]
if (initialRows?.value?.length) {
Expand All @@ -116,7 +132,7 @@ export function useRows(
return sourceRows
})

const toggleSort = (column: LabelColumnField) => {
const toggleSort = (column: string) => {
if (sortState.value.column !== column) {
sortState.value = {
column,
Expand Down
23 changes: 21 additions & 2 deletions src/hy-vue-gantt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ import type { ColorScheme } from "./color-schemes"

import GGanttChart from "./components/GGanttChart.vue"
import GGanttRow from "./components/GGanttRow.vue"
import type { GanttBarObject, GanttBarConfig } from "./types"
import type {
GanttBarObject,
GanttBarConfig,
BarConnection,
ChartRow,
LabelColumnConfig,
ConnectionType,
ConnectionPattern,
ConnectionSpeed
} from "./types"

export function extendDayjs() {
dayjs.extend(isSameOrBefore)
Expand All @@ -24,7 +33,17 @@ export function extendDayjs() {
dayjs.extend(advancedFormat)
}

export type { ColorScheme, GanttBarObject, GanttBarConfig }
export type {
ColorScheme,
GanttBarObject,
GanttBarConfig,
BarConnection,
ChartRow,
LabelColumnConfig,
ConnectionType,
ConnectionPattern,
ConnectionSpeed
}
export { GGanttChart, GGanttRow }

export const hyvuegantt: Plugin = {
Expand Down
11 changes: 10 additions & 1 deletion src/types/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,17 @@ export type ConnectionSpeed = "slow" | "normal" | "fast"
export type SortDirection = "asc" | "desc" | "none"
export type LabelColumnField = "Id" | "Label" | "StartDate" | "EndDate" | "Duration"

export type SortFunction = (a: ChartRow, b: ChartRow) => number

export interface LabelColumnConfig {
field: LabelColumnField | string
sortable?: boolean
valueGetter?: (row: ChartRow) => string | number
sortFn?: SortFunction
}

export interface SortState {
column: LabelColumnField
column: string
direction: SortDirection
}

Expand Down
4 changes: 2 additions & 2 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
ConnectionPattern,
ConnectionSpeed,
ConnectionType,
LabelColumnField,
LabelColumnConfig,
TimeUnit
} from "./chart"
import type { ColorScheme } from "./style"
Expand All @@ -31,7 +31,7 @@ export interface GGanttChartProps {
font?: string
labelColumnTitle?: string
labelColumnWidth?: number
multiColumnLabel?: LabelColumnField[]
multiColumnLabel?: LabelColumnConfig[]
commands?: boolean
enableMinutes?: boolean
enableConnections?: boolean
Expand Down

0 comments on commit 0131ec2

Please sign in to comment.