//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: 5,group:"A",variable:"v5",value: 59},{id: 6,group:"A",variable:"v6",value:52},
{id: 7,group:"A",variable:"v7",value: 88},{id: 8,group:"A",variable:"v8",value:20},
{id: 9,group:"A",variable:"v9",value: 99},{id: 10,group:"A",variable:"v10",value:66},
{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: 15,group:"B",variable:"v5",value: 84},{id: 16,group:"B",variable:"v6",value:91},
{id: 17,group:"B",variable:"v7",value: 82},{id: 18,group:"B",variable:"v8",value:89},
{id: 19,group:"B",variable:"v9",value: 6},{id: 20,group:"B",variable:"v10",value:67},
{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: 25,group:"C",variable:"v5",value: 86},{id: 26,group:"C",variable:"v6",value:23},
{id: 27,group:"C",variable:"v7",value: 74},{id: 28,group:"C",variable:"v8",value:47},
{id: 29,group:"C",variable:"v9",value: 73},{id: 30,group:"C",variable:"v10",value:40},
{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: 35,group:"D",variable:"v5",value: 16},{id: 36,group:"D",variable:"v6",value:27},
{id: 37,group:"D",variable:"v7",value: 76},{id: 38,group:"D",variable:"v8",value:24},
{id: 39,group:"D",variable:"v9",value: 1},{id: 40,group:"D",variable:"v10",value:87},
{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: 45,group:"E",variable:"v5",value: 65},{id: 46,group:"E",variable:"v6",value:56},
{id: 47,group:"E",variable:"v7",value: 9},{id: 48,group:"E",variable:"v8",value:78},
{id: 49,group:"E",variable:"v9",value: 49},{id: 50,group:"E",variable:"v10",value:36},
{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},
{id: 55,group:"F",variable:"v5",value: 48},{id: 56,group:"F",variable:"v6",value:102}
];
var selectedID = undefined;
var selectedArray = []
var i_region_static_id = "heatmap"
var parentDiv = document.getElementById(i_region_static_id)
var width_client = parentDiv.clientWidth;
var i_height = 450;
var selectedVar;
var selectedGroup;
var colorMidValue = 20;
var showLegend = true;
var margin
if (showLegend){
margin = {top: 0, right: 35, bottom: 25, left: 70};
}else{
margin = {top: 0, right: 10, bottom: 25, left: 70};
}
const width = width_client - margin.left - margin.right;
const height = i_height - margin.top - margin.bottom;
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;
}
//-----------------------------------------------------------------------------------
// Anfügen von svg Elementen zu #heatmap
const svg = d3.select('#' + i_region_static_id)
// Anfügen von svg an #heatmap
.append("svg")
.attr("id", "svg_" + i_region_static_id)
.attr("viewBox", `0 0 ${width_client + margin.right} ${parseInt(i_height) + margin.top + margin.bottom}`)
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, width])
.padding(0.01);
// X-Achse erstellen
const xAxis = g_area
.append("g")
.attr("id", i_region_static_id + "_x_axis")
xAxis.append("g")
.attr("id", i_region_static_id + "_x_axis_value")
.attr("transform", `translate(0, ${height})`)
.style("color", "rgba(0, 0, 0, 0.65)")
.style("font-size", "16px")
.style("font-family", "arial")
// Einfügen xAxis Label
xAxis.append("text")
.attr("id", i_region_static_id + "_x_axis_label")
.attr("x", width / 2)
.attr("y", height + 50)
.style("fill", "rgba(0, 0, 0, 0.65)")
.style("font-size", "16px")
.style("font-weight", "bold")
.style("font-family", "sans-serif")
.attr("text-anchor", "middle")
.text(labelGroup);
//-----------------------------------------------------------------------------------
// Y-Skalierung
const y_scale = d3.scaleBand()
.range([height, 0])
.padding(0.03);
// Y-Achse erstellen
const yAxis = g_area
.append("g")
.attr("id", i_region_static_id + "_y_axis")
yAxis.append("g")
.attr("id", i_region_static_id + "_y_axis_value")
.style("color", "rgba(0, 0, 0, 0.65)")
.style("font-size", "16px")
.style("font-family", "arial")
// Einfügen yAxis Label
yAxis.append("text")
.attr("id", i_region_static_id + "_y_axis_label")
.attr("y", - 45) //Y ist hierbei die horizontale ausrichtung
.attr("x", - height / 2) //X ist hierbei die vertikale ausrichtung
.attr("transform", "rotate(-90)")
.style("fill", "rgba(0, 0, 0, 0.65)")
.style("font-size", "18px")
.style("font-weight", "bold")
.style("font-family", "sans-serif")
.attr("text-anchor", "middle")
.text(labelVars)
//-----------------------------------------------------------------------------------
// ColorScale Legend
const g_colorScale = g_area
.append("g")
.attr("id", i_region_static_id + "_colorLegend")
// Erstelle yColorscale
var yColorscale = d3.scaleLinear()
.range([height - 70, 0]);
g_colorScale.append("g")
.attr("id" , i_region_static_id + "_colorscale_axis")
.attr("transform", `translate(${width + 30},${35})`)
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%");
g_colorScale.append("rect")
.attr("id", i_region_static_id + "_colorScale_legend")
.attr("x", width + 15)
.attr("y", 35)
.attr("rx", "4px")
.attr("width", 15)
.attr("height", height - 70)
.attr("stroke", "black")
.attr("stroke-width", "0.5px")
.attr("text-anchor", "middle")
.style("fill", "url(#"+ i_region_static_id + "_grad)");
//-----------------------------------------------------------------------------------
//Erstellen eines Tooltip
const tooltip = d3.select("#" + i_region_static_id)
.append("div")
.attr("id", i_region_static_id + "tooltip")
.style("position", "absolute")
.style("visibility", "hidden")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "10px")
.style("padding", "5px");
const dsad = d3.select("#" + i_region_static_id)
.append("div")
.attr("id", i_region_static_id + "tooltip")
.style("position", "absolute")
.style("visibility", "hidden")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "10px")
.style("padding", "5px");
const mouseover = function(event,d) {
tooltip
.style("visibility", "visible")
d3.select(this).select("." + i_region_static_id + "_node")
.style("stroke", "black")
.style("stroke-width", "2px")
};
const mousemove = function(event,d) {
tooltip
//.text("Wert: " + d.value)
.html("Wert: " + d.value + "<br>Group: " + d.group + "<br>Series: " + d.variable)
.style("left", (event.offsetX + 20) + "px")
.style("top", (event.offsetY + 95) + "px")
};
const mouseleave = function(d) {
tooltip.style("visibility", "hidden")
d3.select(this).select("." + i_region_static_id + "_node")
.style("stroke", "none")
};
const click = function(d, i) {
var clickedID = i.id
if (event.ctrlKey) {
if (!selectedArray.includes(clickedID)){
if (selectedArray.length === 0){
svg.selectAll("." + i_region_static_id + "_node_grp").style("opacity", 0.2)
}
selectedID = clickedID; //Single Click
selectedArray.push(clickedID); // Multiple Click
d3.select(this).style("opacity", 1)
}else{
svg.selectAll("." + i_region_static_id + "_node_grp").style("opacity", 1)
selectedArray = [];
}
}else{
selectedArray = [];
if (clickedID != selectedID){
selectedID = clickedID;
selectedArray.push(clickedID);
svg.selectAll("." + i_region_static_id + "_node_grp").style("opacity", 0.2)
d3.select(this).style("opacity", 1)
}else{
svg.selectAll("." + i_region_static_id + "_node_grp").style("opacity", 1)
selectedID = undefined;
}
};
};
//-----------------------------------------------------------------------------------
// 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});
function longestStringReduce(arr) {
return arr.reduce((a, b) => a.length < b.length ? b : a, "");
}
console.log(longestStringReduce(myGroups))
//-----------------------------------------------------------------------------------
// Dynamisches Update der X-Achse
const xAxisGroup = d3.select("#" + i_region_static_id + "_x_axis_value")
x_scale.domain(myGroups)
const xAxisCall = d3.axisBottom(x_scale)
xAxisGroup.call(xAxisCall);
//-----------------------------------------------------------------------------------
// Dynamisches Update der Y-Achse
const yAxisGroup = d3.select("#" + i_region_static_id + "_y_axis_value")
y_scale.domain(myVars)
const yAxisCall = d3.axisLeft(y_scale)
yAxisGroup.call(yAxisCall);
const myColor = d3.scaleLinear()
.range([color_start, colorMid, colorEnd])
.domain(colorArrayValue)
/*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'
}
//-----------------------------------------------------------------------------------
// ColorScale Legend
const GradColors = [myColor(updateColorAxis(extent[0], extent[1])[1]), myColor(colorMidValue), myColor(updateColorAxis(extent[0], extent[1])[0])];//NEU
// Fügt scales zur Achse hinzu
const yAxisColorGroup = d3.select("#" + i_region_static_id + "_colorscale_axis")
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]))
yAxisColorGroup.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})
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
.on("click", click)
.style("cursor", "pointer")
/*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()
}
//-----------------------------------------------------------------------------------
update_heatmap(data1)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title> 1.Grundgerüst </title>
</head>
<body>
<div id="heatmap"></div>
<script src="https://d3js.org/d3.v7.js" charset="utf-8"></script>
</body>
</html>