Forum Discussion

cogsie's avatar
cogsie
Helper I
6 years ago
Solved

Overlapping Tick Marks on X-Axis

I am creating a custom visual and hvaing difficulty with the tick mark values on the x-azis.  If I shrink the visual size the labels start overlapping and looking jumbled (see picture below).  Is there anyway to prevent this?  I think it has something to do with the hideCollidedLabels method in powerbi-visuals-utils-chartutils but I can't figure out how to make it work.  Any suggestions or examples of how to fix this would be much appreciated.

 

 

  • In case anyone runs into the same problem, this is how I solved the issue.

     

    I put the creation of the X axis inside a do/while loop.  The loop begins with creating the axis then loops through each text element and gets the width of the text.  The width is added to an array from which the largest width is determined and multiplied by the total number of ticks to get an estimate of how much space all the labels would need.  This is then compared to the plot width and if it is greater (i.e. the labels require more room than is available in the plot area and are overlapping) the whole loop is rerun (and the axis recreated) with one fewer ticks until there is no overlapping.  Generally it only needs to run 5 or fewer times, but I put in an iteration counter and added it to the while condition to avoid an infinite loop.

     

    I am pasting the code below in case it is helpful.  It needs to be cleaned up considerably (I have a lot of console logs to help with debugging), but here it is for what it's worth.

     

    If any one has a more efficient way I would love to hear it.

     

    do {
    
      var xAxis = d3.axisBottom(xScale)
        .tickFormat((d) => valueFormatter.format(d))
        .ticks(tickMarks);
    
        this.xAxisContainer
          .attr("class", "xAxis")
          .attr("transform", "translate(" + plotArea.x + "," + (plotArea.height + plotArea.y) + ")")
          .call(xAxis)
          .style("font-size", viewModel.XAxisFontSize)
          .style("font-family", viewModel.FontFamily)
    
    
          console.log("******************")
          console.log("iteration" + m)
          console.log("View Port Width:" + options.viewport.width)
          var gCounter = 0
          var g = 0
          var gArray = []
          var labelWidth = 0
          var gMax = 0
          d3.select(".xAxis").selectAll("text")
            .each(function (d,i) {
              console.log("starting")
              let textProperties: TextProperties = {
                text: valueFormatter.format(d),
                fontFamily: viewModel.FontFamily,
                fontSize: "" + viewModel.XAxisFontSize
              }
                            
              let h = textMeasurementService.measureSvgTextRect(textProperties).width
              gCounter++
              g = h + g
              gArray.push(h)
              gMax = d3.max(gArray)
              labelWidth = ((gCounter - .5) * gMax)
                            
              console.log("----------------------")
              console.log("Font Family: " + textProperties.fontFamily)
              console.log("Font Size: " + textProperties.fontSize)
              console.log("width of " + textProperties.text + ":" + h)
              console.log("Counter:" + gCounter)
              console.log("g:" + g)
              console.log("gArray: " + gArray)
              console.log("gMax: " + gMax)
              console.log("Label Width:" + labelWidth)
              console.log("Plot Width: " + plotArea.width)
             
              return(labelWidth)
            });
    
                
                    
            --tickMarks  
            console.log("TickMarks:" + tickMarks)
            ++m
            console.log("Plot Width: " + plotArea.width)
            console.log("Label Width:" + labelWidth)
            console.log("Test: ", plotArea.width < labelWidth)
                    
          } while (m<100 && plotArea.width < labelWidth)

     

7 Replies

  • In case anyone runs into the same problem, this is how I solved the issue.

     

    I put the creation of the X axis inside a do/while loop.  The loop begins with creating the axis then loops through each text element and gets the width of the text.  The width is added to an array from which the largest width is determined and multiplied by the total number of ticks to get an estimate of how much space all the labels would need.  This is then compared to the plot width and if it is greater (i.e. the labels require more room than is available in the plot area and are overlapping) the whole loop is rerun (and the axis recreated) with one fewer ticks until there is no overlapping.  Generally it only needs to run 5 or fewer times, but I put in an iteration counter and added it to the while condition to avoid an infinite loop.

     

    I am pasting the code below in case it is helpful.  It needs to be cleaned up considerably (I have a lot of console logs to help with debugging), but here it is for what it's worth.

     

    If any one has a more efficient way I would love to hear it.

     

    do {
    
      var xAxis = d3.axisBottom(xScale)
        .tickFormat((d) => valueFormatter.format(d))
        .ticks(tickMarks);
    
        this.xAxisContainer
          .attr("class", "xAxis")
          .attr("transform", "translate(" + plotArea.x + "," + (plotArea.height + plotArea.y) + ")")
          .call(xAxis)
          .style("font-size", viewModel.XAxisFontSize)
          .style("font-family", viewModel.FontFamily)
    
    
          console.log("******************")
          console.log("iteration" + m)
          console.log("View Port Width:" + options.viewport.width)
          var gCounter = 0
          var g = 0
          var gArray = []
          var labelWidth = 0
          var gMax = 0
          d3.select(".xAxis").selectAll("text")
            .each(function (d,i) {
              console.log("starting")
              let textProperties: TextProperties = {
                text: valueFormatter.format(d),
                fontFamily: viewModel.FontFamily,
                fontSize: "" + viewModel.XAxisFontSize
              }
                            
              let h = textMeasurementService.measureSvgTextRect(textProperties).width
              gCounter++
              g = h + g
              gArray.push(h)
              gMax = d3.max(gArray)
              labelWidth = ((gCounter - .5) * gMax)
                            
              console.log("----------------------")
              console.log("Font Family: " + textProperties.fontFamily)
              console.log("Font Size: " + textProperties.fontSize)
              console.log("width of " + textProperties.text + ":" + h)
              console.log("Counter:" + gCounter)
              console.log("g:" + g)
              console.log("gArray: " + gArray)
              console.log("gMax: " + gMax)
              console.log("Label Width:" + labelWidth)
              console.log("Plot Width: " + plotArea.width)
             
              return(labelWidth)
            });
    
                
                    
            --tickMarks  
            console.log("TickMarks:" + tickMarks)
            ++m
            console.log("Plot Width: " + plotArea.width)
            console.log("Label Width:" + labelWidth)
            console.log("Test: ", plotArea.width < labelWidth)
                    
          } while (m<100 && plotArea.width < labelWidth)

     

  • dm-p's avatar
    dm-p
    Super User

    Hi cogsie,

    Looks like I missed this one orginally, and you've solved it yourself, but I'll share my method for managing this in case you wish to have a look.

    If my axis is linear, it's essentially a combination of using getRecommendedNumberOfTicksForXAxis or getRecommendedNumberOfTicksForYAxis in powerbi-visuals-utils-chartutils.

    If I have a fixed width for each tick, I then use getTailoredTextOrDefault in powerbi-visuals-utils-formattingutils to make the text display an ellipsis (...) if it gets too small.

    Here's an example of how if I first change the height, my linear ticks will reduce to make room, and then if I reduce the width, the category labels will truncate and then eventually disappear if there's no room to display them:

    The truncation of the category text will also work if the font size is adjusted too large to fit the width.

    If you want to have a look through my code, you can see how I'm managing in this function here (for ticks) and this function (for labels).

    Regards,

    Daniel

    • cogsie's avatar
      cogsie
      Helper I

      Thanks for the feedback.  That was very helpful.  I tried using getRecommendedNumberOfTicksForXAxis but I couldn't figure out how to get it to work so I stuck with the method I used.  Not as elegant as what you suggested, but it seems to get the job done.

       

      However, your suggestion for using getTailoredTextOrDefault  on categorical labels worked really well.  I was able to use it on my Y-axis so the categories don't overlap.  Thanks for sharing that one - I never would have figured that out.

       

      I really appreciate the help.

       

      Now I just need to figure out how to add data labels and a legend.

      • dm-p's avatar
        dm-p
        Super User

        You're welcome! Your visual is looking great BTW 🙂

        If you want to use the MS-supported tooling for legend and data labels, you can find the doc in the same library (powerbi-visuals-utils-chartuilts):

        I personally haven't used the data labels tooling, but the legend works just fine. I've messed with it too much in the visual I used above for it to be a reliable example for you, but you can see how I'm using it in this visual - specifically here (adding to DOM), here (rendering) and I've created a function here to hide the legend if the container gets too small. Hopefully these may save you some time when having a go yourself.

        Good luck!

        Daniel