//Requirement: - D3v7 // - jquery // data input: var data = [{"count":0,"date":"2023-11-20","day":0,"hour":0} // based on gist nbremer/62cf60e116ae821c06602793d265eaf6 // container_id = #heatmapweekhour const create_heatmap_week_hour = (container_id, data, options) => { var days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], times = d3.range(24); var margin = { top: 80, right: 50, bottom: 20, left: 50 }; var width = Math.max(Math.min(window.innerWidth, 1000), 500) - margin.left - margin.right - 20, gridSize = Math.floor(width / times.length), height = gridSize * (days.length + 2); var heatmap_font_size = width * 62.5 / 900; //SVG container var svg = d3.select(container_id) .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // create a tooltip const tooltip = d3.select("body") .append("div") .attr("class", "tooltip_heatmap") .style("opacity", 0) .style("position", "absolute") .style("pointer-events", "none") .style("background-color", "white") .style("border", "solid") .style("border-width", "2px") .style("border-radius", "5px") .style("padding", "5px") // Three function that change the tooltip when user hover / move / leave a cell const mouseover = function(event, d) { d3.select(event.target) .style("stroke", "black") .style("stroke-opacity", 1) let d3_pageX = event.pageX; let d3_pageY = event.pageY; tooltip.html(d.date + " " + d.hour + "-" + (d.hour + 1) + "h: " + d.count + " messages") .style("left", (d3_pageX) + "px") .style("top", (d3_pageY - 28) + "px"); tooltip.transition() .duration(200) .style("opacity", 1); } const mouseleave = function(event, d) { d3.select(event.target) .style("stroke", "white") //.style("stroke-opacity", 0.8) tooltip.transition() .duration(200) .style("opacity", 0) } /////////////////////////////////////////////////////////////////////////// //////////////////////////// Draw Heatmap ///////////////////////////////// /////////////////////////////////////////////////////////////////////////// var colorScale = d3.scaleLinear() .domain([0, d3.max(data, function (d) { return d.count; }) / 2, d3.max(data, function (d) { return d.count; })]) .range(["#FFFFF6", "#3E9583", "#1F2D86"]) //.interpolate(d3.interpolateHcl); var dayLabels = svg.selectAll(".dayLabel") .data(days) .enter().append("text") .text(function (d) { return d; }) .attr("x", 0) .attr("y", function (d, i) { return i * gridSize; }) .style("text-anchor", "end") .attr("transform", "translate(-36, -11)") .attr("class", function (d, i) { return ((i >= 0 && i <= 4) ? "dayLabel mono axis axis-workweek" : "dayLabel mono axis"); }) .style("font-size", heatmap_font_size + "%"); var timeLabels = svg.selectAll(".timeLabel") .data(times) .enter().append("text") .text(function (d) { return d; }) .attr("x", function (d, i) { return i * gridSize; }) .attr("y", 0) .style("text-anchor", "middle") .attr("transform", "translate(-" + gridSize / 2 + ", -36)") .attr("class", function (d, i) { return ((i >= 8 && i <= 17) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"); }) .style("font-size", heatmap_font_size + "%"); var heatMap = svg.selectAll(".hour") .data(data) .enter().append("rect") .attr("x", function (d) { return (d.hour - 1) * gridSize; }) .attr("y", function (d) { return (d.day - 1) * gridSize; }) .attr("class", "hour bordered") .attr("width", gridSize) .attr("height", gridSize) .style("stroke", "white") .style("stroke-opacity", 0.6) .style("fill", function (d) { return colorScale(d.count); }) .on("mouseover", mouseover) .on("mouseleave", mouseleave); //Append title to the top svg.append("text") .attr("class", "title") .attr("x", width / 2) .attr("y", -60) .style("text-anchor", "middle") .text("Chat Messages"); /////////////////////////////////////////////////////////////////////////// //////////////// Create the gradient for the legend /////////////////////// /////////////////////////////////////////////////////////////////////////// //Extra scale since the color scale is interpolated var countScale = d3.scaleLinear() .domain([0, d3.max(data, function (d) { return d.count; })]) .range([0, width]) //Calculate the variables for the temp gradient var numStops = 10; countRange = countScale.domain(); countRange[2] = countRange[1] - countRange[0]; countPoint = []; for (var i = 0; i < numStops; i++) { countPoint.push(i * countRange[2] / (numStops - 1) + countRange[0]); } //Create the gradient svg.append("defs") .append("linearGradient") .attr("id", "legend-heatmap") .attr("x1", "0%").attr("y1", "0%") .attr("x2", "100%").attr("y2", "0%") .selectAll("stop") .data(d3.range(numStops)) .enter().append("stop") .attr("offset", function (d, i) { return countScale(countPoint[i]) / width; }) .attr("stop-color", function (d, i) { return colorScale(countPoint[i]); }); /////////////////////////////////////////////////////////////////////////// ////////////////////////// Draw the legend //////////////////////////////// /////////////////////////////////////////////////////////////////////////// var legendWidth = Math.min(width * 0.8, 400); //Color Legend container var legendsvg = svg.append("g") .attr("class", "legendWrapper") .attr("transform", "translate(" + (width / 2) + "," + (gridSize * days.length) + ")"); // 319 //Draw the Rectangle legendsvg.append("rect") .attr("class", "legendRect") .attr("x", -legendWidth / 2) .attr("y", 0) //.attr("rx", hexRadius*1.25/2) .attr("width", legendWidth) .attr("height", 10) .style("fill", "url(#legend-heatmap)"); //Append title legendsvg.append("text") .attr("class", "legendTitle") .attr("x", 0) .attr("y", -10) .style("text-anchor", "middle") .text("Number of Messages"); //Set scale for x-axis var xScale = d3.scaleLinear() .range([-legendWidth / 2, legendWidth / 2]) .domain([0, d3.max(data, function (d) { return d.count; })]); //Define x-axis var xAxis = d3.axisBottom(xScale) //.orient("bottom") .ticks(5); //.tickFormat(formatPercent) //Set up X axis legendsvg.append("g") .attr("class", "axis") .attr("transform", "translate(0," + (10) + ")") .call(xAxis); // return svg ??? // return svg }