Forum Discussion

Doug7983's avatar
Doug7983
Helper II
2 years ago
Solved

Help coloring Deneb heatmap

I’m new to Deneb and haven’t been able to figure out how to color this heatmap so that it is colored by row, or category, rather than as a whole:

 

 

 

 

 

 

 

 

 

 

 

I want the rows to be color independently of one another, like this:

This is my first forum post and I don't see a way to post a .pbix. Here is the Deneb specification using Vega Lite:

{
"data": {"name": "dataset"},
"mark": {
"type": "rect",
"stroke": "white",
"tooltip": true
},
"encoding": {
"y": {
"field": "Category",
"type": "nominal",
"title": "",
"axis": {
"domain": false,
"ticks": false,
"labels": true,
"labelAngle": 0,
"labelPadding": 5
}
},
"x": {
"field": "Date",
"type": "ordinal",
"title": "",
"timeUnit": "yearmonth",
"axis": {
"domain": false,
"ticks": false,
"labels": true,
"labelAngle": -90,
"orient": "top"
}
},
"tooltip": [
{
"field": "Category",
"type": "nominal"
},
{
"field": "Date",
"type": "ordinal"
},
{
"field": "Growth (MoYaM)",
"type": "quantitative",
"format": ".1%"
},
{
"field": "Growth (WDC)",
"type": "quantitative",
"format": ".1%"
}
],
"color": {
"aggregate": "max",
"field": "Growth (WDC)",
"type": "quantitative",
"title": "Growth (WDC)",
"scale": {
"scheme": "pbiColorDivergent"
},
"legend": {
"direction": "vertical",
"gradientLength": 120,
"format": ".1%"
}
}
}
}

 

Any help is greatly appreciated. Thanks.

  • Doug7983, I see the issue. I had a rogue closed bracket in the data object that I missed. I deleted it and updated the above spec. Try it now.

      "data": {
        "name": "dataset"
        ] //<-- Should not be there
      }

     

  • Another great catch Doug7983. That's what I get for trying to rush through it 😆

    gist

    {
      "transform": [
        {"calculate": "(month(datum['Date'])+1)", "as": "monthNum"},
        {
          "calculate": "datum['monthNum'] + (datum['monthNum'] < 7 ? 6 : -6)",
          "as": "monthSort"
        },
        {
          "calculate": "'FY ' + toString(year(datum['Date']) + (datum['monthSort'] <= 6 ? +1 : 0))",
          "as": "FY"
        }
      ],
      "facet": {
        "column": {"field": "FY", "title": null, "header": {"labelFontSize": 13}}
      },
      "resolve": {"scale": {"x": "independent"}},
      "spacing": {"column": 5},
      "spec": {
        "width": {"step": 30},
        "mark": {"type": "rect", "stroke": "white", "tooltip": true},
        "encoding": {
          "x": {
            "field": "Date",
            "type": "ordinal",
            "title": null,
            "timeUnit": "month",
            "axis": {
              "domain": false,
              "ticks": false,
              "labels": true,
              "labelOpacity": 0.65,
              "orient": "top"
            },
            "sort": {"field": "monthSort"}
          },
          "y": {
            "field": "Category",
            "type": "nominal",
            "title": "",
            "axis": {
              "domain": false,
              "ticks": false,
              "labels": true,
              "labelOpacity": 0.65,
              "labelAngle": 0,
              "labelPadding": 5
            }
          },
          "tooltip": [
            {"field": "Category", "type": "nominal"},
            {"field": "Date", "type": "temporal"},
            {"field": "Growth (MoYaM)", "type": "quantitative", "format": ".1%"},
            {"field": "Growth (WDC)", "type": "quantitative", "format": ".1%"}
          ],
          "color": {
            "aggregate": "max",
            "field": "Growth (WDC)",
            "type": "quantitative",
            "title": "Growth (WDC)",
            "scale": {"domainMid": 0, "reverse": true, "scheme": "blueorange"},
            "legend": {
              "titleBaseline": "bottom",
              "direction": "vertical",
              "format": ".1%",
              "orient": "right"
            }
          }
        }
      },
      "data": {
        "name": "dataset"
      }
    }

     

24 Replies

  • Hey Doug7983. Welcome to the forums! That's awesome that you're diving into Deneb. Thank you for providing your spec and screenshots. Here's how to provide a .pbix, which would be very helpful. Alternatively, if you're able to provide a sanitized dataset that matches your schema, that will also work. 

    Happy to try to help once I have either the dataset or .pbix. 

    • Doug7983's avatar
      Doug7983
      Helper II

      Thanks for the reply, giammariam. Security policies prevent me from uploading a .pbix as described in your linked instructions, and character limitations prevent pasting the full file here. I'm attempting to paste in a partial data set. 

      DateCategoryGrowth (WDC)Growth (MoYaM)
      11/1/21Category 0123.2%29.7%
      12/1/21Category 0111.9%11.9%
      1/1/22Category 0112.9%18.9%
      2/1/22Category 019.1%9.1%
      3/1/22Category 0120.8%20.8%
      4/1/22Category 0129.3%23.5%
      5/1/22Category 0135.6%42.7%
      6/1/22Category 0127.6%27.6%
      7/1/22Category 0142.4%13.9%
      8/1/22Category 0127.7%33.5%
      9/1/22Category 0117.7%23.6%
      10/1/22Category 0118.7%18.7%
      11/1/22Category 019.0%9.0%
      12/1/22Category 0114.4%14.4%
      1/1/23Category 0110.1%10.1%
      2/1/23Category 018.5%8.5%
      3/1/23Category 0113.0%13.0%
      4/1/23Category 016.0%0.9%
      5/1/23Category 013.0%13.3%
      6/1/23Category 016.9%6.9%
      7/1/23Category 0111.8%11.8%
      8/1/23Category 0113.8%13.8%
      9/1/23Category 0117.6%12.0%
      10/1/23Category 0114.5%-61.8%
      11/1/21Category 0315.2%21.3%
      12/1/21Category 0310.6%10.6%
      1/1/22Category 0314.2%20.2%
      2/1/22Category 0312.4%12.4%
      3/1/22Category 0312.2%12.2%
      4/1/22Category 0313.5%8.3%
      5/1/22Category 0315.5%21.5%
      6/1/22Category 036.5%6.5%
      7/1/22Category 0317.9%-5.7%
      8/1/22Category 037.7%12.6%
      9/1/22Category 03-1.7%3.2%
      10/1/22Category 038.0%8.0%
      11/1/22Category 0312.3%12.3%
      12/1/22Category 036.7%6.7%
      1/1/23Category 034.0%4.0%
      2/1/23Category 038.4%8.4%
      3/1/23Category 0318.8%18.8%
      4/1/23Category 0314.0%8.6%
      5/1/23Category 035.2%15.7%
      6/1/23Category 0312.7%12.7%
      7/1/23Category 038.7%8.7%
      8/1/23Category 0310.5%10.5%
      9/1/23Category 035.8%0.8%
      10/1/23Category 031.7%-66.1%
      11/1/21Category 1024.0%30.5%
      12/1/21Category 1015.8%15.8%
      1/1/22Category 1011.8%17.7%
      2/1/22Category 1011.3%11.3%
      3/1/22Category 1018.3%18.3%
      4/1/22Category 1021.6%16.1%
      5/1/22Category 1014.6%20.6%
      6/1/22Category 1012.5%12.5%
      7/1/22Category 1025.4%0.3%
      8/1/22Category 108.7%13.6%
      9/1/22Category 109.0%14.4%
      10/1/22Category 1012.6%12.6%
      11/1/22Category 102.6%2.6%
      12/1/22Category 100.5%0.5%
      1/1/23Category 103.9%3.9%
      2/1/23Category 100.1%0.1%
      3/1/23Category 10-8.4%-8.4%
      4/1/23Category 10-7.7%-12.1%
      5/1/23Category 10-5.0%4.5%
      6/1/23Category 10-6.0%-6.0%
      7/1/23Category 10-3.0%-3.0%
      8/1/23Category 10-0.1%-0.1%
      9/1/23Category 10-1.6%-6.3%
      10/1/23Category 10-7.2%-69.1%
  • Thanks for the dataset, Doug7983. Let me know if something like this scratches the itch.
    Here is a gist that contains the spec. I've also included it below.

     

     

    {
      "resolve": {"scale": {"color": "independent"}},
      "facet": {
        "row": {
          "field": "Category",
          "header": {"labelAngle": 0, "labelAlign": "left"},
          "title": null
        }
      },
      "spacing": -5,
      "spec": {
        "mark": {"type": "rect", "stroke": "white", "tooltip": true},
        "encoding": {
          "x": {
            "field": "Date",
            "type": "ordinal",
            "title": null,
            "timeUnit": "yearmonth",
            "axis": {
              "domain": false,
              "ticks": false,
              "labels": true,
              "labelAngle": -90,
              "orient": "top"
            }
          },
          "tooltip": [
            {"field": "Category", "type": "nominal"},
            {"field": "Date", "type": "ordinal"},
            {"field": "Growth (MoYaM)", "type": "quantitative", "format": ".1%"},
            {"field": "Growth (WDC)", "type": "quantitative", "format": ".1%"}
          ],
          "color": {
            "aggregate": "max",
            "field": "Growth (WDC)",
            "type": "quantitative",
            "title": "Growth (WDC)",
            "legend": {
              "direction": "horizontal",
              "title": null,
              "gradientLength": 120,
              "format": ".1%",
              "labelOffset": {"expr": "-child_height*0.6"},
              "labelColor": {
                "expr": "luminance(scale('child_color', max(0, parseFloat(datum.label)/100))) < 0.5 ? '#fff' : '#000'"
              }
            }
          }
        }
      },
      "data": {
        "name": "dataset"
       }
      }
    }

     

    • Doug7983's avatar
      Doug7983
      Helper II

      giammariam 

       

      This is helpful, thank you. For context, I'm a long-time Tableau user and relatively new to both Power BI and Deneb. I have found it challenging to accurately describe what I'm trying to accomplish, so please bear with me.

       

      Example (and screenshot below):

       

      My data has a range of -12.4% to 64.8%:

      • The deepest blue will always correspond to the highest value in the data set, regardless of row.

      • The deepest orange will always correspond to the lowest value in the data set, regardless of row.

      The spectrum of color is applied such that a particular shade of blue, for example, will represent the same value, no matter where it appears. Same for shades of orange and everything in between. I’ve added labels here to see if maybe that helps:

       

       

      Aside from all that, a question:  I see in your spec that you are declaring the range of values. My data changes every day, so that wouldn't be practical. Is there a way to avoid that and have the range be dynamic?

      Thanks,

      Doug

  • Hey Doug7983, I'm only providing the values in the values array in the gist for example purposes. You can see that in the embedded spec (not the gist) I left that array out. Deneb assumes that you have named your data "dataset", and will receive the values dynamically from Power BI.

    In the example that I provided, each row is independent from one another color-wise and thus has its own legend. That's why the lightest color for one row has a different minimum input domain (lowest value) than that of another row. If you were to go to the gist and comment out the following line:

     

     

      "resolve": {"scale": {"color": "independent"}},

     

     

    then the legend would no longer be independent for each row.

    (color domains and legends are independent per row)

    vs.

    (color domain and legend are shared across entire dataset)


    Forgive me, but I'm having trouble understanding. I'm confused about what you're trying to achieve because this:

     

    My data has a range of -12.4% to 64.8%:
    
    The deepest blue will always correspond to the highest value in the data set, regardless of row.
    
    The deepest orange will always correspond to the lowest value in the data set, regardless of row.
    
    The spectrum of color is applied such that a particular shade of blue, for example, will represent the same value, no matter where it appears. Same for shades of orange and everything in between. 

     


    seems to conflict with:

     

    I’m new to Deneb and haven’t been able to figure out how to color this heatmap so that it is colored by row, or category, rather than as a whole:
    
    <.png>
    
    I want the rows to be color independently

     

     

     

     

     
    For example, if the deepest blue will always correspond to the highest value in the data set regardless of row, the heatmap would not be colored independently.

    • Doug7983's avatar
      Doug7983
      Helper II

      Hey giammariam 

      You're absolutely right. Those are conflicting statements. I think maybe the missing piece is describing how the orange-blue diverging color palette is applied? 

       

      When I don't attempt to intervene in how Deneb applies pbiColorDivergent, Category 6 dominates the visual. 

       

      But that is far different from the original Tableau chart:

       

      i think the difference may lie in how the colors are being applied. Here is a screenshot from Tableau:

      I’ve highlighted the dynamically created Start, End, and Center points. By default, the center of the spectrum is zero and the end colors are at equal distances from zero. Tableau assigns a lighter orange to -0.124 because the darkest orange would correspond to -0.648 – exactly opposite the maximum positive number. This tells the viewer that the lowest value, in orange, is not as far away from zero as the highest value.
       
      I hope I haven't confused you further. I'm confused enough for both of us. Thanks for the help.
       
      Doug

       

       

      • giammariam's avatar
        giammariam
        Solution Sage

        Ah, this is very helpful! The midpoint of the input range is set to 0 in your Tableau screenshot. The scale in vega-lite is just taking into account the min/max values, so the midpoint here would be (max-min)/2. This would explain the color discrepancy. Let me play with it and see if I can get the scale to account for the 0 center threshold.

  • Hey Doug7983, let me know if this is any closer. Note - you can eliminate the spacing between the FYs by setting the column spacing to -5.

    Here's the gist.

    Here's the spec:

     

    {
      "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
      "transform": [{"calculate": "'FY ' + year(datum['Date'])", "as": "FY"}],
      "facet": {
        "column": {"field": "FY", "title": null, "header": {"labelFontSize": 13}}
      },
      "resolve": {"scale": {"x": "independent"}},
      "spacing": {"column": 5},
      "spec": {
        "width": {"step": 30},
        "mark": {"type": "rect", "stroke": "white", "tooltip": true},
        "encoding": {
          "x": {
            "field": "Date",
            "type": "ordinal",
            "title": null,
            "timeUnit": "month",
            "axis": {
              "domain": false,
              "ticks": false,
              "labels": true,
              "labelOpacity": 0.65,
              "orient": "top"
            }
          },
          "y": {
            "field": "Category",
            "type": "nominal",
            "title": "",
            "axis": {
              "domain": false,
              "ticks": false,
              "labels": true,
              "labelOpacity": 0.65,
              "labelAngle": 0,
              "labelPadding": 5
            }
          },
          "tooltip": [
            {"field": "Category", "type": "nominal"},
            {"field": "Date", "type": "ordinal"},
            {"field": "Growth (MoYaM)", "type": "quantitative", "format": ".1%"},
            {"field": "Growth (WDC)", "type": "quantitative", "format": ".1%"}
          ],
          "color": {
            "aggregate": "max",
            "field": "Growth (WDC)",
            "type": "quantitative",
            "title": "Growth (WDC)",
            "scale": {"domainMid": 0, "reverse": true, "scheme": "blueorange"},
            "legend": {
              "titleBaseline": "bottom",
              "direction": "vertical",
              "format": ".1%",
              "orient": "right"
            }
          }
        }
      },
      "data": {
        "name": "dataset"
      }
    }

     

     

    • Doug7983's avatar
      Doug7983
      Helper II

      Hey giammariam. The screenshot looks extremely promising, but the spec is throwing this error message:

       

      Invalid specification {}. Make sure the specification includes at least one of the following properties: "mark", "layer", "facet", "hconcat", "vconcat", "concat", or "repeat".

      I'm not far enough down the learning path to troubleshoot it, but I wonder whether it may have something to do with this piece about "FY":

       

        "transform": [{"calculate": "'FY ' + year(datum['Date'])", "as": "FY"}],
        "facet": {
          "column": {"field": "FY", "title": null, "header": {"labelFontSize": 13}}
       
      Thanks for your help. This looks like it could well work if we can get past that error.

       

       

      • giammariam's avatar
        giammariam
        Solution Sage

        Doug7983, I see the issue. I had a rogue closed bracket in the data object that I missed. I deleted it and updated the above spec. Try it now.

          "data": {
            "name": "dataset"
            ] //<-- Should not be there
          }

         

  • Doug7983, dang, that's no good. What does the date tooltip say for cells in this column? I can see if I can recreate it on my end

  • Doug7983, no problem! Sorry it took so long for me to comprehend what you were after, but glad we got it in the end. Feel free to tag me in any future posts if you have any Deneb related questions. Always happy to get some vega/vega-lite practice in.