RemyaJ
RemyaJ

Reputation: 5526

d3 heatmap not showing title unless datset button is clicked

I have a d3 heatmap component(followed the tutorial here) but it does not have the tooltip(the title attr).I think cards.select("title").text((d) => d.value); is not working.When I inspect I cant see the title being appended.In the tutorial itself on initial load the tooltip is not present(loads only when you click on the button datasets).Can someone help me fix this.

ngOnInit() {
            this.margin = { top: 50, right: 0, bottom: 100, left: 30 },
            this.width = 960 - this.margin['left'] - this.margin['right'],
            this.height = 430 - this.margin['top'] - this.margin['bottom'],
            this.gridSize = Math.floor(this.width / 24),
            this.legendElementWidth = this.gridSize*2,
            this.buckets = 9,
            this.colors = this.heatMapConfig["colors"];
            this.days = this.heatMapConfig["days"];
            this.times = this.heatMapConfig["times"];
            this.datasets = ["data.tsv", "data2.tsv"];

          this.svg = d3.select("#chart").append("svg")
              .attr("width", this.width + this.margin['left'] + this.margin['right'])
              .attr("height", this.height + this.margin['top'] + this.margin['bottom'])
              .append("g")
              .attr("transform", "translate(" + this.margin['left'] + "," + this.margin['top'] + ")");

          let dayLabels = this.svg.selectAll(".dayLabel")
              .data(this.days)
              .enter().append("text")
                .text( (d) =>{ return d; })
                .attr("x", 0)
                .attr("y", (d, i) =>{ return i * this.gridSize; })
                .style("text-anchor", "end")
                .attr("transform", "translate(-6," + this.gridSize / 1.5 + ")")
                .attr("class",  (d, i) =>{ return ((i >= 0 && i <= 4) ? "dayLabel mono axis axis-workweek" : "dayLabel mono axis"); });

          let timeLabels = this.svg.selectAll(".timeLabel")
              .data(this.times)
              .enter().append("text")
                .text((d) =>{ return d; })
                .attr("x", (d, i)=> { return i * this.gridSize; })
                .attr("y", 0)
                .style("text-anchor", "middle")
                .attr("transform", "translate(" + this.gridSize / 2 + ", -6)")
                .attr("class", (d, i) =>{ return ((i >= 7 && i <= 16) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"); });

          let datasetpicker = d3.select("#dataset-picker").selectAll(".dataset-button")
            .data(this.datasets);

          datasetpicker.enter()
            .append("input")
            .attr("value", (d)=>{ return "Dataset " + d })
            .attr("type", "button")
            .attr("class", "dataset-button")
            .on("click", (d) =>{
              this.heatmapChart("assets/tsv/data.tsv");// this works
            });
            this.heatmapChart("assets/tsv/data.tsv"); //I tried calling this function here,but doesnt work.
        }

        private heatmapChart(tsvFile) {  

            d3.tsv(tsvFile,
            (d) =>{
              return {
                day: +d.day,
                hour: +d.hour,
                value: +d.value
              };
            },
            (error, data)=> {
              const colorScale = d3.scaleQuantile()
            .domain([0, this.buckets - 1, d3.max(data, (d) => d['value'])])
            .range(this.colors);
         const cards = this.svg.selectAll(".hour")
              .data(data, (d) => d.day+':'+d.hour);


          cards.enter().append("rect")
              .attr("x", (d) => (d.hour - 1) * this.gridSize)
              .attr("y", (d) => (d.day - 1) * this.gridSize)
              .attr("rx", 4)
              .attr("ry", 4)
              .attr("class", "hour bordered")
              .attr("width", this.gridSize)
              .attr("height", this.gridSize)
              .style("stroke", "#E6E6E6")
              .style("stroke-width", "2px")
              .style("fill", this.colors[0])
              .merge(cards)
              .transition()
              .duration(1000)
              .style("fill", (d) => colorScale(d.value));


          cards.select("title").text((d) => d.value);// not working 

          cards.exit().remove();

        const legend = this.svg.selectAll(".legend")
              .data([0].concat(colorScale.quantiles()), (d) => {
                  return d;});

          const legend_g = legend.enter().append("g")
              .attr("class", "legend");

          legend_g.append("rect")
            .attr("x", (d, i) => this.legendElementWidth * i)
            .attr("y", this.height)
            .attr("width", this.legendElementWidth)
            .attr("height", this.gridSize / 2)
            .style("fill", (d, i) => this.colors[i]);

          legend_g.append("text")
            .attr("class", "mono")
            .text((d) => "≥ " + Math.round(d))
            .attr("x", (d, i) => this.legendElementWidth * i)
            .attr("y",this.height + this.gridSize);

          legend.exit().remove();
      };

Upvotes: 2

Views: 235

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

That's not a tutorial, that is just a bl.ocks.

The problem you described seems to be a minor mistake by the author. It can be solved by simply moving this line...

cards.append("title");

... for after the enter selection which append the rectangles.

Here is the updated bl.ocks, now it shows the <title> element the first time the code runs: http://bl.ocks.org/anonymous/f9eac64ec2f962f2cada6456478740f7

EDIT: since you are using D3 v4.x, you cannot anymore rely on the magic of the enter selection, introduced in D3 v2 but removed in v4.

Thus, because you're using a transition selection, give a name to your "enter" selection:

var cardsEnter = cards.enter().append("rect")
    .attr("x", (d) => (d.hour - 1) * gridSize)
    .attr("y", (d) => (d.day - 1) * gridSize)
    .attr("rx", 4)
    .attr("ry", 4)
    .attr("class", "hour bordered")
    .attr("width", gridSize)
    .attr("height", gridSize)
    .style("fill", colors[0]);

cardsEnter.append("title").text((d) => d.value);

cardsEnter.merge(cards)
    .transition()
    .duration(1000)
    .style("fill", (d) => colorScale(d.value));

cards.select("title").text((d) => d.value);

Here is the bl.ocks using v4: http://bl.ocks.org/anonymous/de9ff9b1345a78b665f9e001691ab0ce

Upvotes: 1

Related Questions