Forum Discussion

13055's avatar
13055
New Member
2 years ago

Deneb / Vega - Variable Width Cummulative Bar Chart

In my dataset, I have capacity and total cost for a list of assets. I need to plot all the assets from lowest cost to highest cost (y-axis) on a cummulative capacity basis (x-axis). To do this, I need to create a variable width bar chart where the bar width represents the asset capacity. I also need a tooltip that shows asset details when I hover over the bar chart. I've tried using Deneb and R Scripts but ran into different issues with both. Any guidance would be much appreciated. 

 

I used R scripts to create the variable width bar chart that I need, but now I can't get the tool tip to work using plotly and ggplotly. 

 

# The following code to create a dataframe and remove duplicated rows is always executed and acts as a preamble for your script:

# dataset <- data.frame(AssetID, Capacity, TotalCost, Company, CumulativeCapacity)
# dataset <- unique(dataset)

# Paste or type your script code here:
library(ggplot2)

df <- data.frame(TotalCost= dataset$TotalCost,width=dataset$Capacity,**bleep**=dataset$CumulativeCapacity)


df$w <- cumsum(df$width) #cumulative sums
df$wm <- df$w - df$width
df$CAPS <- with(df, wm + (w - wm)/2)


p  <- ggplot(df, aes(ymin = 0))
p1 <- p + geom_rect(aes(xmin = wm, xmax = w, ymax = TotalCost, fill = TotalCost))

p2 <- p1 + geom_text(aes(x = CAPS, y = TotalCost, label=NA),size=20,angle = 45)

p2

 

I also used Deneb to create a sorted bar chart with tooltip, but I'm lost on how to make it variable width. I have a sorted CumulativeCapacity column in my dataset, but I think I would need to calculate this column using the Capacity column to implement the variable width feature. 

 

{
"data": {"name": "dataset"},
"mark": {"type": "bar",
"tooltip":true
},

"encoding" : {
"x":{
"field":"CumulativeCapacity",
"type" :"quantitative"
},
"y":{
"field":"TotalCost",
"type" :"quantitative",
"sort": "-y"
}
}
}

 

 

 

 

 

 

 

 

9 Replies

  • Anonymous's avatar
    Anonymous
    Not applicable

    I have been working on this problem myself and this that I have found a reasonable solution to creating a variable width bar/column chart where the area of the bar/column is meaningful. The Vega-Lite JSON with a dummy dataset is below.

     

    {
      "data": {
        "values": [
          {"vWidth": 10, "vHeight": 1, "vType": "A"},
          {"vWidth": 20, "vHeight": 2, "vType": "B"},
          {"vWidth": 1, "vHeight": 3, "vType": "C"},
          {"vWidth": 5, "vHeight": 4, "vType": "D"}
        ]
      },
      "transform": [
        {
          "calculate": "datum.vWidth*datum.vHeight", "as": "cArea"
        },
        {
          "window": [{"op": "sum", "field": "vWidth", "as": "cum_total"}],
          "sort": [{"field": "cArea", "order": "descending"}],
          "frame": [null, 0]
        },
        {
          "window": [{"op": "first_value", "field": "cum_total", "as": "shift"}],
          "sort": [{"field": "cArea", "order": "descending"}],
          "frame": [-1, 0]
        },
        {
          "calculate": "datum.cum_total === datum.shift ? 0 : datum.shift", "as": "leftedge"
        }
      ],
      "mark": {"type": "rect"},
      "encoding": {
        "y": {
          "field": "vHeight",
          "type": "quantitative"
        },
        "x": {
          "field": "cum_total",
          "type": "quantitative"
        },
        "x2": {
          "field": "leftedge"
        },
        "color": {
          "field": "vType"
        }
      }
    }

     

    The data is presented as a width and height value with a label.

     

    I made use of 4 transforms.

    1. Calculate the area (width x height) so I could use if for sorting. I don't think you need this, but could be helpful as a reference.
    2. Create a cumulative sum (with required sorting) to determine the right edge of each bar ("cum_total").
    3. Create a window to look at the immediately preceeding value (same sorting) to determine the left edge of each bar ("shift"). This is essentially a rightward shift of the data. However, the first value will be filled in with a duplicate of its own value.
    4. Since the first value has a zero-width bar, the left edge needs to be moved to the zero of the chart. The last calculation just replaces that left edge with a 0 based on the shifted value and cumulative sum being the same. My method will cause problems if any of your width values are supposed to be zero.

    The resultant chart from the above is below.

     

     

    Hope that helps! I'm just getting into Vega-Lite myself and this is a chart type that I've been trying to create for a while.

     

    • lbendlin's avatar
      lbendlin
      Icon for Super User rankSuper User

      Anonymous very nice! Just in time for a capacity metrics report visual I am working on. Thank you!

       

       

      • Anonymous's avatar
        Anonymous
        Not applicable

        Thank you for the kudos. Glad it could help you out! After refining a bit more, I found that I could come closer to the OP's code and simplify the process by finding the left edge by subtracting the width from the cumulative total. I have also added data labels and tooltips, but that is beyond what might be needed.

        {
          "data": {
            "values": [
              {"vWidth": 10, "vHeight": 1, "vType": "A"},
              {"vWidth": 20, "vHeight": 2, "vType": "B"},
              {"vWidth": 1, "vHeight": 3, "vType": "C"},
              {"vWidth": 5, "vHeight": 4, "vType": "D"}
            ]
          },
          "transform": [
            {
              "calculate": "datum.vWidth*datum.vHeight", "as": "cArea"
            },
            {
              "window": [{"op": "sum", "field": "vWidth", "as": "cum_total"}],
              "sort": [{"field": "cArea", "order": "descending"}],
              "frame": [null, 0]
            },
            {
              "calculate": "datum.cum_total - datum.vWidth", "as": "leftedge"
            }
          ],
          "encoding": {
            "y": {
              "field": "vHeight",
              "type": "quantitative"
            },
            "x": {
              "field": "cum_total",
              "type": "quantitative"
            },
            "x2": {
              "field": "leftedge"
            },
            "color": {
              "field": "vType"
            }
          },
            "layer": [
            {
              "mark": {
                "type": "rect",
                "tooltip": true
              }
            },
            {
              "mark": {
                "type": "text",
                "align": "right",
                "baseline": "bottom",
                "dx": -5,
                "dy": -5
              },
              "encoding": {
                "text": {
                  "field": "cArea"
                }
              }
            }
          ]
        }