Currently I'm developing CRM system, where I need to implement gant chart on the front-end!
I'm Using Gant Chart from AmChart Library to show data on the front-end. Now, my project manager demands me, to put time of the gant chart on the top of the canvas.
Any ideas how to make it?
Here is the link for the demo of the gant chart: https://www.amcharts.com/demos/gantt-chart/
My implementation:
const Graphic : FC<GraphicProps> = ({shift}) => {
const [isCreateModalVisible, setIsCreateModalVisible] : [boolean, Dispatch<SetStateAction<boolean>>] =
useState<boolean>(false);
const [isChangeModalVisible, setIsChangeModalVisible] : [boolean, Dispatch<SetStateAction<boolean>>] =
useState<boolean>(false);
const [marginTop, setMarginTop] : [number, Dispatch<SetStateAction<number>>] =
useState<number>(0);
const [categories, setCategories] : [CategoryInterface[] | undefined, Dispatch<SetStateAction<CategoryInterface[] | undefined>>] =
useState<CategoryInterface[] | undefined>(undefined);
const [mappedData, setMappedData] : [EventTrainDataInterface[] | undefined, Dispatch<SetStateAction<EventTrainDataInterface[] | undefined>>] =
useState<EventTrainDataInterface[] | undefined>(undefined);
const [selectedEventRecordTrain, setSelectedEventRecordTrain] : [IEventRecordDtoResponse | undefined, Dispatch<SetStateAction<IEventRecordDtoResponse | undefined>>] =
useState<IEventRecordDtoResponse | undefined>(undefined);
const handleScroll = () => {
const position = window.scrollY;
setMarginTop(position);
};
const equipmentByShiftToCategory = (equipmentByShift : IEquipmentShiftDtoResponse) : CategoryInterface => {
if (equipmentByShift.secondEquipment) {
return {
category: equipmentByShift.equipment.title + equipmentByShift.secondEquipment.title
}
}
return {
category: equipmentByShift.equipment.title
}
}
const eventRecordToChartData = (eventRecordTrain: IEventRecordDtoResponse, colors: am5.ColorSet ) : EventTrainDataInterface => {
console.log(eventRecordTrain.startedDate)
return {
category: eventRecordTrain.equipmentShift.equipment.title,
fromDate: DateFormatter.dateToFormattedTypeDateTime(eventRecordTrain.startedDate),
toDate: DateFormatter.dateToFormattedTypeDateTime(eventRecordTrain.finishedDate),
columnSettings: {
fill : am5.Color.brighten(colors!.getIndex(2), 0)
},
eventRecord: eventRecordTrain
}
}
useEffect(() => {
window.addEventListener('scroll', handleScroll, { passive: true });
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => {
if(categories === undefined) {
EquipmentShiftService.getAllByShiftId(shift?.id)
.then(({data}) => setCategories(data.map(equipmentsByShift => equipmentByShiftToCategory(equipmentsByShift))));
}
if (mappedData === undefined ) {
EventRecordService.getAllByShiftId(shift?.id)
.then(({data}) => setMappedData(data.map(eventRecordTrain => eventRecordToChartData(eventRecordTrain, colors!))));
}
let root = am5.Root.new("chartdiv");
root.dateFormatter.setAll({
dateFormat: "yyyy-MM-dd",
dateFields: ["valueX", "openValueX"]
});
root.setThemes([
am5themes_Animated.new(root)
]);
let chart = root.container.children.push(am5xy.XYChart.new(root, {
panX: false,
panY: false,
wheelX: "panX",
wheelY: "zoomX",
layout: root.verticalLayout
}));
let legend = chart.children.push(am5.Legend.new(root, {
centerX: am5.p50,
x: am5.p50
}))
let colors = chart.get("colors");
let yAxis = chart.yAxes.push(
am5xy.CategoryAxis.new(root, {
categoryField: "category",
renderer: am5xy.AxisRendererY.new(root, { inversed: true }),
tooltip: am5.Tooltip.new(root, {
themeTags: ["axis"],
animationDuration: 200
})
})
);
if (categories) {
yAxis.data.setAll(categories);
}
let xAxis = chart.xAxes.push(
am5xy.DateAxis.new(root, {
baseInterval: { timeUnit: "minute", count: 1 },
renderer: am5xy.AxisRendererX.new(root, {})
})
);
let series = chart.series.push(am5xy.ColumnSeries.new(root, {
xAxis: xAxis,
yAxis: yAxis,
openValueXField: "fromDate",
valueXField: "toDate",
categoryYField: "category",
sequencedInterpolation: true
}));
let cellSize = yAxis.data.length * 2;
series.events.on("datavalidated", function(ev) {
let series = ev.target;
let chart = series.chart;
let xAxis = chart!.xAxes.getIndex(0);
// Calculate how we need to adjust chart height
let chartHeight = series.data.length * cellSize + xAxis!.height() + chart!.get("paddingTop", 0) + chart!.get("paddingBottom", 0);
// Set it on chart's container
chart!.root.dom.style.height = chartHeight + "px";
});
series.columns.template.setAll({
templateField: "columnSettings",
strokeOpacity: 0,
tooltipText: "{category}: {openValueX.formatDate('yyyy-MM-dd HH:mm')} - {valueX.formatDate('yyyy-MM-dd HH:mm')}"
});
series.data.processor = am5.DataProcessor.new(root, {
dateFields: ["fromDate", "toDate"],
dateFormat: "yyyy-MM-dd HH:mm"
});
if (mappedData) {
series.data.setAll(mappedData)
}
chart.set("scrollbarX", am5.Scrollbar.new(root, {
orientation: "horizontal"
}));
function myFunction(event: any) {
setIsChangeModalVisible(true);
setSelectedEventRecordTrain(event.target.dataItem.dataContext.eventRecord);
}
series.columns.template.events.on("click", myFunction, this);
series.columns.template.events.on("pointerover", () => {
xAxis!.chart!.events.disable();
})
series.columns.template.events.on("pointerout", () => {
xAxis!.chart!.events.enable();
})
xAxis!.chart!.events.on("click", () => {
setIsCreateModalVisible(true);
}, this);
series.appear();
chart.appear(1000, 100);
return () => {
root.dispose();
};
}, [mappedData, shift,categories])
return(
<div className={"flex flex-col "}>
<div className={"flex pl-5 mt-4"}>
<button className={"btn btn-sm btn-accent"} onClick={() => {
setIsCreateModalVisible(true);
}}>Добавить</button>
</div>
<div id="chartdiv" className={"flex w-full min-h-screen"} >
</div>
{
isCreateModalVisible ? (
<GraphicCreateModal setIsCreateModalVisible={setIsCreateModalVisible} marginTop={marginTop}/>
) : (
<></>
)
}
{
isChangeModalVisible ? (
<GraphicChangePanel setIsChangeModalVisible={setIsChangeModalVisible} marginTop={marginTop} eventRecordTrain={selectedEventRecordTrain} />
) : (<></>)
}
</div>
);
}