I created a scrollable heatmap and I want to make the x-axis fixed when scrolling the y-axis. I checked some posts here and tried to make the solutions work for example with position fixed, but it did not work out.
Edit: I tried to solve it with this example Fix x-axis position in scrollable Heatmap d3 but now my x-axis description disappeared and the x-axis is placed at the bottom.
var width = 500,
height = 600,
margintop = 50,
marginbottom = 50,
marginright = 10,
marginleft = 50
d3.csv("https://raw.githubusercontent.com/Lea1216/d3/main/heatmap.csv", function(data) {
var svg = d3.select("#my_dataviz")
.append("div")
.classed("chart",true)
.append("svg")
.attr("width",width marginleft marginright)
.attr("height",height margintop marginbottom)
.append("g")
.attr("transform",
"translate(" marginleft "," margintop ")");
var axis = d3.select("#my_dataviz")
.append("svg")
.attr("width", width marginleft marginright)
.attr("height",40)
.append("g")
.attr("transform", "translate(" marginleft ", 0)");
var x_axis = d3.scaleBand()
.range([0, width])
.domain(data.map(function(d) {
return d.group;
}))
.padding(0.01);
axis.call(d3.axisTop(x_axis))
.selectAll("text")
.style("text-anchor", "end")
.style("position","fixed")
.attr("dx",15)
.attr("dy",5)
.attr("transform", "rotate(-65)");
var y_axis = d3.scaleBand()
.range([height, 0])
.domain(data.map(function(d) {
return d.activity;
}))
.padding(0.01);
svg.append("g")
.call(d3.axisLeft(y_axis))
.attr("class", "y_axis")
.selectAll("text")
.on("click", function(d) {
window.open(d.url, "_blank")
});
var myColor = d3.scaleLinear()
.range(["white", "#C37B89"])
.domain([1, 100])
svg.selectAll()
.data(data, function(d) {
return d.group ':' d.activity;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x_axis(d.group)
})
.attr("y", function(d) {
return y_axis(d.activity)
})
.attr("width", x_axis.bandwidth())
.attr("height", y_axis.bandwidth())
.style("fill", function(d) {
return myColor(d.value)
})
.style("stroke-width", 1)
.style("stroke", "none")
})
.rect {
opacity: 0.8;
}
#my_dataviz {
width: 600px;
height: 500px;
overflow-y: scroll;
padding: 50px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="my_dataviz"></div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
There are several possible solutions for this. The first one is to draw two SVG elements on the same level and use position: fixed
on the X-axis element. You can see that the blocks still show up behind the axis, but you could fix that by drawing a rect
the total size of the axis SVG element and giving it fill: white
:
var width = 500,
height = 600,
margintop = 50,
marginbottom = 50,
marginright = 10,
marginleft = 50
d3.csv("https://raw.githubusercontent.com/Lea1216/d3/main/heatmap.csv", function(data) {
// Add the axis *before* adding the SVG, because the order matters in HTML
var axis = d3.select("#my_dataviz")
.append("svg")
.attr("width", width marginleft marginright)
// Add 2 so you have a little bit of room left for the black bar
// i.e. margin top has to be less than total height!
.attr("height", margintop 2)
.style("position", "fixed") // this makes the axis fixed
.append("g")
.attr("transform", "translate(" marginleft ", " margintop ")");
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width marginleft marginright)
.attr("height", height margintop marginbottom)
.append("g")
.attr("transform", "translate(" marginleft ", " margintop ")");
var x_axis = d3.scaleBand()
.range([0, width])
.domain(data.map(function(d) {
return d.group;
}))
.padding(0.01);
axis.call(d3.axisTop(x_axis))
.selectAll("text")
.style("text-anchor", "end")
.style("position", "fixed")
.attr("dx", 15)
.attr("dy", 5)
.attr("transform", "rotate(-65)");
var y_axis = d3.scaleBand()
.range([height, 0])
.domain(data.map(function(d) {
return d.activity;
}))
.padding(0.01);
svg.append("g")
.call(d3.axisLeft(y_axis))
.attr("class", "y_axis")
.selectAll("text")
.on("click", function(d) {
window.open(d.url, "_blank")
});
var myColor = d3.scaleLinear()
.range(["white", "#C37B89"])
.domain([1, 100])
svg.selectAll()
.data(data, function(d) {
return d.group ':' d.activity;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x_axis(d.group)
})
.attr("y", function(d) {
return y_axis(d.activity)
})
.attr("width", x_axis.bandwidth())
.attr("height", y_axis.bandwidth())
.style("fill", function(d) {
return myColor(d.value)
})
.style("stroke-width", 1)
.style("stroke", "none")
})
.rect {
opacity: 0.8;
}
#my_dataviz {
border: solid 1px red;
width: 600px;
height: 300px;
overflow-y: scroll;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="my_dataviz"></div>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
The second option is to put the other SVG in a div
and scroll the div
instead of the SVG. Then, you do need to do more with CSS and you need to move some CSS rules from the #my_dataviz
element to the .chart
element:
var width = 500,
height = 600,
margintop = 50,
marginbottom = 50,
marginright = 10,
marginleft = 50
d3.csv("https://raw.githubusercontent.com/Lea1216/d3/main/heatmap.csv", function(data) {
// Add the axis *before* adding the SVG, because the order matters in HTML
var axis = d3.select("#my_dataviz")
.append("svg")
.attr("width", width marginleft marginright)
// Add 2 so you have a little bit of room left for the black bar
// i.e. margin top has to be less than total height!
.attr("height", margintop 1)
.append("g")
.attr("transform", "translate(" marginleft ", " margintop ")");
var svg = d3.select("#my_dataviz")
.append("div")
// Note the CSS rules for .chart
.attr("class", "chart")
.append("svg")
.attr("width", width marginleft marginright)
// No margin-top required here, because the other element already took care of it
.attr("height", height marginbottom)
.append("g")
// Same, no margin-top
.attr("transform", "translate(" marginleft ", 0)");
var x_axis = d3.scaleBand()
.range([0, width])
.domain(data.map(function(d) {
return d.group;
}))
.padding(0.01);
axis.call(d3.axisTop(x_axis))
.selectAll("text")
.style("text-anchor", "end")
.style("position", "fixed")
.attr("dx", 15)
.attr("dy", 5)
.attr("transform", "rotate(-65)");
var y_axis = d3.scaleBand()
.range([height, 0])
.domain(data.map(function(d) {
return d.activity;
}))
.padding(0.01);
svg.append("g")
.call(d3.axisLeft(y_axis))
.attr("class", "y_axis")
.selectAll("text")
.on("click", function(d) {
window.open(d.url, "_blank")
});
var myColor = d3.scaleLinear()
.range(["white", "#C37B89"])
.domain([1, 100])
svg.selectAll()
.data(data, function(d) {
return d.group ':' d.activity;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x_axis(d.group)
})
.attr("y", function(d) {
return y_axis(d.activity)
})
.attr("width", x_axis.bandwidth())
.attr("height", y_axis.bandwidth())
.style("fill", function(d) {
return myColor(d.value)
})
.style("stroke-width", 1)
.style("stroke", "none")
})
.rect {
opacity: 0.8;
}
#my_dataviz {
width: 600px;
border: solid 1px red;
}
/* To make sure there is no space between the DIV and the SVG */
#my_dataviz > * {
display: block;
}
.chart {
overflow-y: scroll;
max-height: 300px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="my_dataviz"></div>
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>