I’m displaying a time schedule for a day with events occuring during this day.
Events have starting time and ending time.
It looks like this
I made event boxes absolute div’s relative to schedule container. I then TRY to calculate height of box (dependant on event length) and offsetY (transform: translateY()) from top of container in order to place event starting and ending times correctly relative to timeline.
In the first picture there are 2 events, first on starts 9:30 ends 10:30. Second one starts 10:30 ends 13:30
When you look at them they basicly seem correct, but there is a issue with calculating offsetY. Look at this second picture
In 2nd picture there are also 2 events. First starts 15:30 ends 16:30 , second 17:30 to 18:30 but now you can see there is a drift in positioning. Starting times dont match and error gets bigger down the line. Bigger error for 17:30 start than for 15:30.
This is how I calcualte offsetY :
First in Schedule component I set ref’s for Schedule container and one for timeline container which contains span with time text and hr element for line. I get container heights (scrollHeight for schedule container) and widths in this component and send them to MeetingBox component where I calculate this offset
export const Schedule = ({meetings}: ScheduleProps):JSX.Element => {
const scheduleStart = 7
const scheduleEnd = 22
const scheduleLength = scheduleEnd - scheduleStart
const containerRef = useRef<HTMLDivElement>(null)
const timelineRef = useRef<HTMLDivElement>(null)
const [refData, setRefData] = useState<RefElementData>({scheduleContainerHeight: 0, timelineContainerHeigth: 0, timelineContainerWidth: 0})
useEffect(() => {
if(timelineRef.current && containerRef.current){
setRefData({
scheduleContainerHeight: containerRef.current.scrollHeight,
timelineContainerHeigth: timelineRef.current.clientHeight,
timelineContainerWidth: timelineRef.current.clientWidth})
}
}, [timelineRef.current, containerRef.current])
const createScheduleTimes = ():string[] => {
const scheduleTimes:string[] = []
for(let i = scheduleStart; i < scheduleEnd+1; i++){
scheduleTimes.push(`${i}:00`)
scheduleTimes.push(`${i}:30`)
}
return scheduleTimes
}
return <div className="sidebar-schedule-container" ref={containerRef}>
{createScheduleTimes().map((time, index) => {
return <div key={index} className="sidebar-timeline-container" ref={timelineRef}>
<span className="sidebar-timeline-time">{time}</span><hr className="sidebar-time-line"/>
</div>
})}
{meetings.map((meeting, index) => {
return <MeetingBox key={index} refElementData={refData} scheduleStart={scheduleStart} scheduleLength={scheduleLength} meeting={meeting}/>
})}
<Spotter refElementData={refData} scheduleStart={scheduleStart} scheduleLength={scheduleLength}/>
</div>
}
Here is MeetingBox which calculates offsetY.
In a nutshell what I try :
I take scrollHeight of Schedule container div and I map this height to schedule length.
lets say container scrollHeight is 1500 px and schedule starts at 7 and ends 22 like in my example. Then schedule length is 15, this means scrollHeight at 0px equals to 7:00 oclock (0 hours).
9:30 equals to 2.5 (normalized value) units from schedule start (9:30 – 7:00). I then calculate how much that is in percentage by normalized value / schedule length. For example 2.5 / 15.
When I have percentage this is final offsetY calculation :
offsetY: containerHeigth * offsetPercentage - (normalized * modification / 2)
Explanation is that I first tried simply
offsetY: containerHeigth * offsetPercentage
where there were obvious errors, I then modified to remove half of timeline container height because I center align hr element in that.
offsetY: containerHeigth * offsetPercentage - modification / 2
THere were still errors quite obviously and especially with bottom 2 events. I then put in there normalized value because that differs for each of the events and I ened up with
offsetY: containerHeigth * offsetPercentage - (normalized * modification / 2)
This gets me to this point where I am now. It seems quite close, errors on bottom events are much reduced, top events look alright. But I cant seem to figure out what am I missing to make them fit perfect ?
const MeetingBox = ({refElementData, scheduleStart, scheduleLength, meeting}: MeetingBoxProps):JSX.Element => {
const [boxOffsets, setBoxOffsets] = useState<BoxOffsets>({offsetY: 0, boxHeigth: 0, boxWidth: 0})
useEffect(() => {
setBoxOffsets(calculateBoxOffsets(refElementData.timelineContainerHeigth, refElementData.scheduleContainerHeight))
}, [refElementData])
const calculateBoxOffsets = (modification: number, containerHeigth: number):BoxOffsets => {
// calculate Y offset
const startDate = new Date(meeting.StartTime)
const startTime = startDate.getHours() + startDate.getMinutes() / 60
const normalized = startTime - scheduleStart
const offsetPercentage = normalized / scheduleLength
console.log(normalized)
// calculate heigth
const endDate = new Date(meeting.EndTime)
const meetingTime = (endDate.getHours() + endDate.getMinutes() / 60)
const normalizedHeigth = meetingTime - startTime
const heightPercentage = normalizedHeigth / scheduleLength
// calculate X offset
return {offsetY: containerHeigth * offsetPercentage - (normalized * modification / 2), boxHeigth: containerHeigth * heightPercentage - normalizedHeigth * modification / 2, boxWidth: refElementData.timelineContainerWidth}
}
return <div className="sidebar-meetingbox-container" style={{transform: `translateY(${boxOffsets.offsetY}px) translateX(47px)`, height:`${boxOffsets.boxHeigth}px`}}>
<div className="sidebar-meetinbox-edge"></div>
</div>
}