//Datasource
const data1= [
{id: 1,group:"A",variable:"v1",value: 98},{id: 2,group:"A",variable:"v2",value:95},
{id: 3,group:"A",variable:"v3",value: 22},{id: 4,group:"A",variable:"v4",value:14},
{id: 11,group:"B",variable:"v1",value: 37},{id: 12,group:"B",variable:"v2",value:50},
{id: 13,group:"B",variable:"v3",value: 81},{id: 14,group:"B",variable:"v4",value:79},
{id: 21,group:"C",variable:"v1",value: 96},{id: 22,group:"C",variable:"v2",value:13},
{id: 23,group:"C",variable:"v3",value: 98},{id: 24,group:"C",variable:"v4",value:10},
{id: 31,group:"D",variable:"v1",value: 75},{id: 32,group:"D",variable:"v2",value:18},
{id: 33,group:"D",variable:"v3",value: 92},{id: 34,group:"D",variable:"v4",value:43},
{id: 41,group:"E",variable:"v1",value: 44},{id: 42,group:"E",variable:"v2",value:29},
{id: 43,group:"E",variable:"v3",value: 58},{id: 44,group:"E",variable:"v4",value:55},
{id: 51,group:"F",variable:"v1",value: 35},{id: 52,group:"F",variable:"v2",value:80},
{id: 53,group:"F",variable:"v3",value: 8},{id: 54,group:"F",variable:"v4",value:46}
];
var i_region_static_id = "heatmap"
var i_height = 450;
var margin = {top: 0, right: 50, bottom: 25, left: 70};
var width_client = document.body.clientWidth;
var height_client = document.body.clientHeight;
const innerWidth = width_client - margin.left - margin.right;
const innerHeight = i_height - margin.top - margin.bottom;
var colorMidValue = 20;
const labelGroup = "X-Label"
const labelVars = "Y-Label"
const color_start = "#f9f9f9"
const colorEnd = "#6e69b3"
const colorMid = "#bd326c"
// Funktion zum Aufrunden der Y-Axis auf 5
function updateColorAxis(a,b){
if(b%5 !== 0){
b = b+(5-(b%5));
}
return [a,b];
}
// Funktion zum Berechnen der Anzahl der Ticks
function setColorAxisTicks(a,b){
if(b%5 === 0){
return b/5;
}
return b/10;
}
// Returns Black/White depending on node color
function calculateContrast(values){
var rgb = values.match(/d+/g);
// Wenn rgb leer, dann das erste sonst das zweite
const brightness = (rgb != undefined) ? Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) /1000) : 0;
return brightness < 150 ? 'white' : 'black'
}
//-----------------------------------------------------------------------------------
// Anfügen von svg Elementen zu #heatmap
const svg = d3.select('#svg_heatmap')
// Attribute svg
.attr("height", innerHeight)
.attr("width", width_client + margin.left + margin.right)
const g_area = d3.select("#svg_" + i_region_static_id)
// Anfügen einer Gruppe an das SVG Element
.append("g")
.attr("id", i_region_static_id + "_area")
.attr("transform", `translate(${margin.left},${margin.top})`);
const g_nodes = g_area
.append("g")
.attr("id", i_region_static_id + "_nodes")
//-----------------------------------------------------------------------------------
// X-Skalierung
const x_scale = d3.scaleBand()
.range([0, innerWidth])
.padding(0.01);
const xAxisCall = d3.axisBottom(x_scale)
// X-Achse erstellen
const xAxis = g_area
.append("g")
.attr("id", i_region_static_id + "_x_axis")
// Einfügen xAxis Values
xAxis.append("g")
.attr("id", i_region_static_id + "_x_axis_value")
.attr("class", "x axis")
.attr("transform", `translate(0, ${innerHeight})`)
// Einfügen xAxis Label
xAxis.append("text")
.attr("id", i_region_static_id + "_x_axis_label")
.attr("class", "x label")
.attr("x", innerWidth / 2)
.attr("y", innerHeight + 50)
.attr("text-anchor", "middle")
.text(labelGroup);
//-----------------------------------------------------------------------------------
// Y-Skalierung
const y_scale = d3.scaleBand()
.range([innerHeight, 0])
.padding(0.03);
const yAxisCall = d3.axisLeft(y_scale)
// Y-Achse erstellen
const yAxis = g_area
.append("g")
.attr("id", i_region_static_id + "_y_axis")
// Einfügen yAxis Value
yAxis.append("g")
.attr("id", i_region_static_id + "_y_axis_value")
.attr("class", "y axis")
// Einfügen yAxis Label
yAxis.append("text")
.attr("id", i_region_static_id + "_y_axis_label")
.attr("class", "y label")
.attr("y", - 45) // Y ist hierbei die horizontale ausrichtung
.attr("x", - innerHeight / 2) // X ist hierbei die vertikale ausrichtung
.attr("transform", "rotate(-90)")
.attr("text-anchor", "middle")
.text(labelVars)
//-----------------------------------------------------------------------------------
// Erstelle yColorscale
const yColorscale = d3.scaleLinear()
.range([innerHeight - 70, 0]);
// ColorScale Legend
const g_colorScale = g_area
.append("g")
.attr("id", i_region_static_id + "_colorLegend")
// Einfügen der Color Achse
g_colorScale.append("g")
.attr("id" , i_region_static_id + "_colorscale_axis")
.attr("transform", `translate(${innerWidth + 27},${35})`)
// Einfügen des Farbbalkens
g_colorScale.append("rect")
.attr("id", i_region_static_id + "_colorScale_legend")
.attr("x", innerWidth + 15)
.attr("y", 35)
.attr("rx", "4px")
.attr("width", 12)
.attr("height", innerHeight - 70)
.attr("stroke", "black")
.attr("stroke-width", "0.5px")
.attr("text-anchor", "middle")
.style("fill", "url(#"+ i_region_static_id + "_grad)");
const g_def = g_colorScale.append("defs")
.append("linearGradient")
.attr("id", i_region_static_id + "_grad")
.attr("x1", "0%")
.attr("x2", "0%")
.attr("y1", "0%")
.attr("y2", "100%");
//-----------------------------------------------------------------------------------
// Funktion für das Create, Update or Delete der D3 nodes
function update_heatmap(data){
const myId = data.map(function(d){return d.id});
const myGroups = data.map(function(d){return d.group});
const myVars = data.map(function(d){return d.variable});
const value = data.map(function(d){return d.value});
const extent = d3.extent(data, function(d){;return d.value});
var colorArrayValue = updateColorAxis(extent[0], extent[1]);
colorArrayValue.push(colorMidValue);
colorArrayValue.sort(function(a, b){return a - b});
const myColor = d3.scaleLinear()
.range([color_start, colorMid, colorEnd])
.domain(colorArrayValue)
const GradColors = [myColor(updateColorAxis(extent[0], extent[1])[1]), myColor(colorMidValue), myColor(updateColorAxis(extent[0], extent[1])[0])];
//-----------------------------------------------------------------------------------
// Dynamisches Update der X-Achse
x_scale.domain(myGroups)
const xAxisGroup = xAxis.select("#" + i_region_static_id + "_x_axis_value")
.call(xAxisCall);
//-----------------------------------------------------------------------------------
// Dynamisches Update der Y-Achse
y_scale.domain(myVars)
const yAxisGroup = yAxis.select("#" + i_region_static_id + "_y_axis_value")
.call(yAxisCall);
//-----------------------------------------------------------------------------------
// ColorScale Legend
yColorscale.domain(updateColorAxis(extent[0], extent[1]))
const yAxisColorCall = d3.axisRight(yColorscale)
.ticks(setColorAxisTicks(updateColorAxis(extent[0], extent[1])[0], updateColorAxis(extent[0], extent[1])[1]))
const yAxisColorGroup = g_colorScale.select("#" + i_region_static_id + "_colorscale_axis")
.call(yAxisColorCall);
g_def.selectAll(i_region_static_id + "stop")
.data(GradColors)
.enter()
.append("stop")
.style("stop-color", function(d){return d;})
.attr("offset", function(d,i){
return 100 * (i / (GradColors.length - 1)) + "%";
})
//-----------------------------------------------------------------------------------
// Create, Delete and Update
const rect_nodes = g_nodes.selectAll("." + i_region_static_id + "_node_grp")
.data(data)
const rect_node_grp = rect_nodes.enter()
.append("g")
/*each() für Create Sektion*/
/*Mit der Each() wird alles in der gruppe geupdated*/
.each(function(d, i) {
//Append Elemente an g
d3.select(this).append('rect')
.style("fill", function(d) {return myColor(d.value)})
d3.select(this).append('text')
})
.merge(rect_nodes)
.attr("class", i_region_static_id + "_node_grp")
//jedes Rect eine ID zu ordnen
.attr("id", function (d){return i_region_static_id + "_node_grp_" + d.id})
.attr("group", function(d) {return d.group})
.attr("variable", function(d) {return d.variable})
.attr("value", function(d) {return d.value})
/*each() für Update Sektion*/
.each(function(d, i) {
// Update meine Elemente in g tag
d3.select(this).select('rect')
.attr("class", i_region_static_id + "_node")
.transition()
.delay(50)
.attr("x", function(d) {return x_scale(d.group)})
.attr("y", function(d) {return y_scale(d.variable)})
.attr("width", x_scale.bandwidth())
.attr("height", y_scale.bandwidth())
.style("fill", function(d) {return myColor(d.value)})
d3.select(this).select('text').attr("class", i_region_static_id + "_label")
.transition()
.delay(50)
.attr("x", function(d) {return x_scale(d.group)})
.attr("y", function(d) { return y_scale(d.variable)})
.attr("dx", x_scale.bandwidth() / 2)
.attr("dy", y_scale.bandwidth() / 2)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.attr("fill", function(d){return calculateContrast(myColor(d.value))})
.style("font-size", "14px")
.style("font-family", "sans-serif")
.text(function(d) {return (d.value)})
})
rect_nodes.exit().remove()
}
function resize() {
var innerWidth = parseInt(d3.select("#svg_" + i_region_static_id).style("width")) - margin.left - margin.right,
innerHeight
if(parseInt(d3.select("#svg_" + i_region_static_id).style("height")) < i_height){
innerHeight = parseInt(d3.select("#svg_heatmap").style("height")) + margin.top + margin.bottom;
}else{
innerHeight = i_height - margin.top - margin.bottom;
}
// SVG Dimension
svg.attr("height", innerHeight)
.attr("width", innerWidth)
// Aktualisieren der Range vom scale mit neuer breite und Höhe
x_scale.range([0, innerWidth], 0.1);
y_scale.range([innerHeight, 0]);
// Position der X-Achse und X-Label
xAxis
.call(xAxisCall)
.select("#" + i_region_static_id + "_x_axis_label")
.attr("x", innerWidth / 2)
.attr("y", innerHeight + 50);
// Position der Y-Achse und X-Label
yAxis
.call(yAxisCall)
.attr("transform", "translate(0," + 0 + ")")
.select("#" + i_region_static_id + "_y_axis_label")
.attr("y", - 45)
.attr("x", - innerHeight / 2)
// Position ColorSkale Farb-Balken
g_colorScale
.select("#" + i_region_static_id + "_colorScale_legend")
.attr("x", innerWidth + 15)
.attr("y", 35)
.attr("height", innerHeight - 70)
// Position ColorSkale Achse
g_colorScale
.select("#" + i_region_static_id + "_colorscale_axis")
.attr("transform", `translate(${innerWidth + 27},${35})`)
// D3 Kästchen dimension wird neu kalkuliert
g_nodes.selectAll("." + i_region_static_id + "_node")
.attr("x", function(d) {return x_scale(d.group)})
.attr("y", function(d) {return y_scale(d.variable)})
.attr("width", x_scale.bandwidth())
.attr("height", y_scale.bandwidth())
// D3 Kästchen Label wir neu Positioniert
g_nodes.selectAll("." + i_region_static_id + "_label")
.attr("x", function(d) {return x_scale(d.group)})
.attr("y", function(d) { return y_scale(d.variable)})
.attr("dx", x_scale.bandwidth() / 2)
.attr("dy", y_scale.bandwidth() / 2)
};
//-----------------------------------------------------------------------------------
d3.select(window).on('resize', resize);
update_heatmap(data1)
<style>
.label{
fill: rgba(0, 0, 0, 0.65);
font-size: 18px;
font-weight: bold;
font-family: sans-serif;
}
.axis{
color: rgba(0, 0, 0, 0.65);
font-size: 16px;
font-family: arial;
}
#svg_heatmap {
width: 100%;
height: 100%;
position: absolute;
}
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title> 1.Grundgerüst </title>
</head>
<body>
<svg id="svg_heatmap"></svg>
<script src="https://d3js.org/d3.v7.js" charset="utf-8"></script>
<script src="https://unpkg.com/d3fc" charset="utf-8"> </script>
</body>
</html>