Forum Discussion

y5famfnatudu's avatar
y5famfnatudu
Icon for Resolver I rankResolver I
10 months ago

Extending Deneb/Vega-Lite Toggle Switch from 2-State to 3-State (Working Day / Non-Working Day / All

Hi everyone,

In my dataset, I have a flag column called “Is Working Day”, which contains values of either 0 or 1.

I’ve created a toggle switch in Deneb using Vega-Lite, allowing users to switch between viewing only working days (1) and showing all records (no filter applied). This is achieved with the following condition in the calculate property:

{
  "calculate": "datum['IsWorkingDay'] == 1 ? 'Working Day' : 'All'",
  "as": "Option_Name"
}

 

Now, I’d like to extend this functionality to support a 3-state toggle switch with the following behavior:

  1. First click: Show records where datum['IsWorkingDay'] == 1 (Working Days)

  2. Second click: Show records where datum['IsWorkingDay'] == 0 (Non-Working Days)

  3. Third click: Show All records (no filter)

Does anyone have suggestions or examples of how this 3-click toggle logic could be implemented in Vega-Lite within Deneb?

Thanks in advance for your help!

3 Replies

  • y5famfnatudu Hi! I suggest you this workaround that I had implemented a time:

     

    Setup the cycles between 3 filter modes with this code:

    {
    "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
    "data": {"name": "dataset"},

    "params": [
    {
    "name": "viewMode",
    "value": 2,
    "select": {"type": "point", "on": "click"},
    "bind": {
    "input": "none"
    },
    "update": "viewMode === 2 ? 0 : viewMode + 1"
    }
    ],

    "transform": [
    {
    "filter": "viewMode == 2 ? true : datum.IsWorkingDay == viewMode"
    }
    ],

    "mark": "bar",
    "encoding": {
    "x": {"field": "Category", "type": "nominal"},
    "y": {"aggregate": "count"}
    }
    }

     

    It requires a click target in your chart.

     

    Otherwise you can use a Dropdown and a bound input param:

    {
    "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
    "data": {"name": "dataset"},
    "params": [
    {
    "name": "dayFilter",
    "value": "All",
    "bind": {
    "input": "select",
    "options": ["Working", "Non-working", "All"],
    "labels": ["Working Days", "Non-working Days", "All Days"]
    }
    }
    ],
    "transform": [
    {
    "filter": "dayFilter == 'All' ? true : (dayFilter == 'Working' ? datum.IsWorkingDay == 1 : datum.IsWorkingDay == 0)"
    }
    ],
    "mark": "bar",
    "encoding": {
    "x": {"field": "Category", "type": "nominal"},
    "y": {"aggregate": "count"}
    }
    }

     

    BBF


    💡 Did I answer your question? Mark my post as a solution!

    👍 Kudos are appreciated

    🔥 Proud to be a Super User!

    • y5famfnatudu's avatar
      y5famfnatudu
      Icon for Resolver I rankResolver I

      Dear BeaBF,

      Thanks for the suggestions! I appreciate the help.

       

      Unfortunately, those approaches would pull me too far from the visual style I'm aiming for. I need the toggle to match a specific design aesthetic, so I'll need to find a solution that stays closer to that direction.

      Thanks again for taking the time to respond, so much appreciated!

       

      Best regards,

      Simon

  • Hi y5famfnatudu,

    I hope you are doing well ☺️❤️

    To implement a 3 state toggle switch in Deneb/Vega-Lite you need to use a parameter (signal) with a cyclic update expression. Here is a couple of Approaches:

     

    First Approach: Complete Deneb/Vega-Lite Specification

    {
      "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
      "params": [
        {
          "name": "filterToggle",
          "value": 0,
          "bind": {
            "input": "radio",
            "options": [0, 1, 2],
            "labels": ["Working Days (1)", "Non-Working Days (0)", "All Records"]
          }
        }
      ],
      "data": {"name": "dataset"},
      "transform": [
        {
          "filter": {
            "condition": {
              "param": "filterToggle",
              "value": 2,
              "empty": false
            },
            "value": {
              "field": "IsWorkingDay",
              "equal": {
                "signal": "filterToggle == 0 ? 1 : 0"
              }
            }
          }
        },
        {
          "calculate": "datum.IsWorkingDay == 1 ? 'Working Day' : 'Non-Working Day'",
          "as": "Day_Type"
        }
      ],
      "mark": "bar",  // or your preferred mark type
      "encoding": {
        // Your encoding here
        "x": {"field": "SomeField", "type": "nominal"},
        "y": {"aggregate": "count", "type": "quantitative"},
        "color": {"field": "Day_Type", "type": "nominal"}
      }
    }

     

    Alternative ApproachClickCycling Button 

    • If you want a single button that cycles through states on each click:

    {
      "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
      "data": {"name": "dataset"},
      "signals": [
        {
          "name": "toggleState",
          "value": 0,
          "on": [
            {
              "events": {"type": "click", "markname": "toggleButton"},
              "update": "(toggleState + 1) % 3"
            }
          ]
        }
      ],
      "marks": [
        {
          "type": "rect",
          "name": "toggleButton",
          "encode": {
            "enter": {
              "x": {"value": 0},
              "y": {"value": 0},
              "width": {"value": 120},
              "height": {"value": 30},
              "fill": {"value": "lightgray"},
              "cornerRadius": {"value": 15}
            },
            "update": {
              "fill": {
                "signal": "toggleState == 0 ? '#4CAF50' : toggleState == 1 ? '#F44336' : '#2196F3'"
              }
            }
          }
        },
        {
          "type": "text",
          "name": "toggleLabel",
          "encode": {
            "enter": {
              "x": {"value": 60},
              "y": {"value": 18},
              "align": {"value": "center"},
              "baseline": {"value": "middle"},
              "fontSize": {"value": 12},
              "fill": {"value": "white"}
            },
            "update": {
              "text": {
                "signal": "toggleState == 0 ? 'Working Days' : toggleState == 1 ? 'Non-Working Days' : 'All Days'"
              }
            }
          }
        }
      ],
      "transform": [
        {
          "filter": {
            "condition": {"signal": "toggleState == 2"},
            "value": {
              "field": "IsWorkingDay",
              "equal": {"signal": "toggleState == 0 ? 1 : 0"}
            }
          }
        }
      ],
      "layer": [
        // Your visualization layers here
        {
          "mark": "bar",
          "encoding": {
            "x": {"field": "SomeField", "type": "nominal"},
            "y": {"aggregate": "count", "type": "quantitative"},
            "color": {
              "field": "IsWorkingDay",
              "type": "nominal",
              "scale": {
                "domain": [0, 1],
                "range": ["#F44336", "#4CAF50"]
              },
              "legend": {
                "title": "Day Type",
                "labelExpr": "datum.value == 1 ? 'Working Day' : 'Non-Working Day'"
              }
            }
          }
        }
      ]
    }

     

    Bonus Approach: Simple Radio Button Version

    • If you prefer a simpler radio button
    {
      "params": [
        {
          "name": "dayFilter",
          "value": 2,
          "bind": {
            "input": "select",
            "options": [0, 1, 2],
            "labels": ["Working Days Only", "Non-Working Days Only", "All Days"],
            "name": "Filter: "
          }
        }
      ],
      "transform": [
        {
          "filter": {
            "condition": {"param": "dayFilter", "value": 2},
            "value": {"field": "IsWorkingDay", "equal": {"param": "dayFilter"}}
          }
        }
      ]
    }

     

    Choose the Approach that best fits your visualization needs. The first approach with a cycling button is more interactive while the radio/select approaches are more explicit for users

     

    if this post helps, then I would appreciate a thumbs up and mark it as the solution to help the other members find it more quickly.