const parseTimeslot = timeslot => { const [start, end] = timeslot.split('-').map(ts => ts.trim()); const [startHour, startMin] = start.split(':').map(s => parseInt(s)); const [endHour, endMin] = end.split(':').map(s => parseInt(s)); return [startHour * 60 + startMin, endHour * 60 + endMin]; }; const formatTimeslot = minute => { const hourStart = (Math.floor(minute / 60) + '').padStart(2, '0'); const minuteStart = ((minute % 60) + '').padStart(2, '0'); return `${hourStart}:${minuteStart}`; }; const load = function () { const mins = Array.from(new Array(48), (x, i) => i * 30); const capacityData = window._data; const allData = []; capacityData.forEach((capacity, i) => { mins.forEach((min, j) => { const quotainfo = capacity.quotainfo.filter(quota => { const [start, end] = parseTimeslot(quota.timeslot); return start <= min && end > min; }); allData.push({ x: j, y: i, quotainfo, min }); }); }); const svg = d3.select('#chart') .append('svg') .attr('width', 1400).attr('height', 800) .append('g') .attr('transform', 'translate(30, 50)'); const offset = 15; svg.selectAll('.hours') .data(mins).enter().append('text') .attr('x', (d, i) => 75 + i * 25) .attr('y', offset - 5) .attr('transform', function() { return `rotate(-75, ${d3.select(this).attr('x')}, ${d3.select(this).attr('y')})`; }) .text(formatTimeslot); svg.selectAll('.store-name') .data(capacityData).enter().append('text') .attr('x', () => 5) .attr('y', (d, i) => 30 + i * 25) .text(d => d.store); const tooltip = d3.select("body").append("div") .attr("class", "tooltip") .style("opacity", 0); // console.log(allData); svg.selectAll('.cell') .data(allData).enter().append('rect') .attr('x', d => 70 + d.x * 25) .attr('y', d => offset + d.y * 25) .attr('width', d => { const { quotainfo } = d; if (quotainfo.length > 1) { throw new Error('found more than one matching slot', d); } if (quotainfo.length === 0) { return 20; } const [start, end] = parseTimeslot(quotainfo[0].timeslot); return end === d.min + 30 ? 20 : 25; }) .attr('height', 20) .attr('class', d => { const { quotainfo } = d; if (quotainfo.length > 1) { throw new Error('found more than one matching slot', d); } if (quotainfo.length === 0) { return; } const fullness = quotainfo[0].filled / quotainfo[0].capacity; return fullness < 0.80 ? 'capacity-normal' : (fullness < 0.96 ? 'capacity-danger' : 'capacity-full'); }) .on('mouseover', function(d) { const { quotainfo } = d; if (quotainfo.length) { tooltip.transition().duration(200).style('opacity', .9); tooltip.html(`${quotainfo[0].timeslot}
Kapasite:${quotainfo[0].capacity}
Dolu:${quotainfo[0].filled}`) .style('left', `${d3.event.pageX}px`) .style('top', `${d3.event.pageY - 28}px`); } d3.select(this).classed('selected-timeslot', true); }) .on('mouseout', function() { tooltip.transition().duration(300).style('opacity', 0); d3.select(this).classed('selected-timeslot', false); }); } load();