Skip to content

Instantly share code, notes, and snippets.

@nyilmaz
Created January 29, 2019 11:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nyilmaz/484deb8b3534fbbe38a4a9c2d233f262 to your computer and use it in GitHub Desktop.
Save nyilmaz/484deb8b3534fbbe38a4a9c2d233f262 to your computer and use it in GitHub Desktop.
window._data = [
{
"store": "Atasehir",
"quotainfo" :[
{
"timeslot": "09:00 - 10:00",
"capacity": 10,
"filled": 10
},
{
"timeslot": "10:00 - 11:00",
"capacity": 10,
"filled": 9
},
{
"timeslot": "11:00 - 12:00",
"capacity": 10,
"filled": 8
},
{
"timeslot": "12:00 - 13:00",
"capacity": 10,
"filled": 6
},
{
"timeslot": "13:00 - 14:00",
"capacity": 10,
"filled": 4
},
{
"timeslot": "14:00 - 15:00",
"capacity": 10,
"filled": 0
}
]
},
{
"store": "Novada",
"quotainfo" :[
{
"timeslot": "09:00 - 10:00",
"capacity": 10,
"filled": 8
},
{
"timeslot": "10:00 - 11:00",
"capacity": 10,
"filled": 7
},
{
"timeslot": "11:00 - 12:00",
"capacity": 10,
"filled": 10
},
{
"timeslot": "12:00 - 13:00",
"capacity": 10,
"filled": 2
},
{
"timeslot": "13:00 - 14:00",
"capacity": 10,
"filled": 8
},
{
"timeslot": "14:00 - 15:00",
"capacity": 10,
"filled": 9
}
]
}
];
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<style>
.cell {
fill: black;
box-sizing: border-box;
border: 1px solid red;
}
rect {
fill: cadetblue
}
rect.selected-timeslot {
fill: orange;
box-sizing: content-box;
border: 2px solid red;
}
.capacity-normal {
fill: green;
}
.capacity-danger {
fill: orange;
}
.capacity-full {
fill: red;
}
div.tooltip {
position: absolute;
text-align: left;
width: 100px;
height: 42px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<div id="chart"></div>
<script src="./padStart.polyfill.js"></script>
<script src="./data.js"></script>
<script src="./script.js"></script>
</body>
</html>
if (!String.prototype.padStart) {
String.prototype.padStart = function padStart(targetLength, padString) {
targetLength = targetLength >> 0; //truncate if number, or convert non-number to 0;
padString = String(typeof padString !== 'undefined' ? padString : ' ');
if (this.length >= targetLength) {
return String(this);
} else {
targetLength = targetLength - this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
}
return padString.slice(0, targetLength) + String(this);
}
};
}
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}<br/>Kapasite:${quotainfo[0].capacity}<br/>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();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment