Calculating and positioning absolute DIV’s relative to other elements

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

time schedule

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

later events

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>
}