Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Find everything you need to get certified on Fabric—skills challenges, live sessions, exam prep, role guidance, and more. Get started

Reply
vfx661
Advocate I
Advocate I

advanced cross filtering with deneb and vega - brushed area disappearing when doing cross filter

My report has a deneb vega line chart and a table.  I want to filter the table by brushing the chart and then clicking on the brushed area.  The idea is to only apply cross filtering when required.


PBIX file

The filter works partially:

 

(1) brushing on the area works as expected:

vfx661_1-1724397055444.png

(2) Then clicking on the brushed area filters the table:

vfx661_2-1724397102488.png

However the brushed area disappears.  I'd like to keep the brushed area after the click.

I've tried different debounce, throttle and consume values for the event - not sure what is causing the brushed area to disappear.

 

 

{
  "data": [
    {
      "name": "dataset"
    }
  ],
  "width": 200,
  "height": 200,
  "signals": [
    {
      "name": "colour",
      "value": "blue"
    },
    // brush
    {
      "name": "brush",
      "value": 0,
      "on": [
        {
          "events": "@main:pointerdown",
          "update": "[x(), x()]"
        },
        {
          "events": "[@main:pointerdown, window:pointerup] > window:pointermove!",
          "update": "[brush[0], clamp(round(x()-brush[0])+brush[0], 0, width)]"
        },
        {
          "events": {
            "signal": "delta"
          },
          "update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
        }
      ]
    },
    // anchor
    {
      "name": "anchor",
      "value": null,
      "on": [
        {
          "events": "@brush:pointerdown",
          "update": "slice(brush)"
        }
      ]
    },
    // xdown
    {
      "name": "xdown",
      "value": 0,
      "on": [
        {
          "events": "@brush:pointerdown",
          "update": "x()"
        }
      ]
    },
    // delta
    {
      "name": "delta",
      "value": 0,
      "on": [
        {
          "events": "[@brush:pointerdown, window:pointerup] > window:pointermove!",
          "update": "x() - xdown"
        }
      ]
    },
    // main_brush
    {
      "name": "main_brush",
      // "push": "outer",
      "on": [
        {
          "events": {
            "signal": "brush"
          },
          "update": "span(brush) ? invert('x_scale', brush) : null"
        }
      ]
    },
    // cross filter start ------------------------------------------
    {
      "name": "pbiCrossFilterSelection",
      "value": [],
      "on": [
        // when mark is brushed apply cross filter
        {
          "events": {
            "markname": "brush",
            "type": "click",
            "consume": true,
            "debounce": 100,
            "throttle": 100
          },
          "update": "pbiCrossFilterApply(event, \"datum['x'] >= main_brush[0] & datum['x'] <= main_brush[1]\" )"
        }
      ]
    }
    // cross filter end ------------------------------------------
  ],
  "scales": [
    {
      "name": "x_scale",
      "range": "width",
      "padding": 10,
      "domain": {
        "data": "dataset",
        "field": "x"
      }
    },
    {
      "name": "y_scale",
      "range": "height",
      "padding": 10,
      "domain": {
        "data": "dataset",
        "field": "y"
      }
    }
  ],
  "axes": [
    {
      "orient": "bottom",
      "scale": "x_scale"
    },
    {
      "orient": "left",
      "scale": "y_scale"
    }
  ],
  "marks": [
    {
      "name": "main",
      "type": "group",
      "encode": {
        "enter": {
          "x": {
            "value": 0
          },
          "y": {
            "value": 0
          },
          "height": {
            "value": 200
          },
          "width": {
            "value": 200
          },
          "fill": {
            "value": "transparent"
          }
        }
      },
      "marks": [
        // main line chart
        {
          "name": "main_line",
          "type": "line",
          "from": {
            "data": "dataset"
          },
          "encode": {
            "enter": {
              "x": {
                "scale": "x_scale",
                "field": "x"
              },
              "y": {
                "scale": "y_scale",
                "field": "y"
              }
            },
            "update": {
              "stroke": {
                "signal": "colour"
              }
            }
          }
        },
        // brush area mark
        {
          "type": "rect",
          "name": "brush",
          "encode": {
            "enter": {
              "y": {
                "value": 0
              },
              "height": {
                "value": 200
              },
              "fill": {
                "value": "#333"
              },
              "fillOpacity": {
                "value": 0.2
              }
            },
            "update": {
              "x": {
                "signal": "brush[0]"
              },
              "x2": {
                "signal": "brush[1]"
              }
            }
          }
        }
      ]
    }
  ]
}

 

 

 

 

 





 

 

1 ACCEPTED SOLUTION
dm-p
Super User
Super User

Hi @vfx661,

 

When you apply cross-filtering, Deneb will currently re-initialize the Vega view. This is partially due to how Power BI updates the canvas to keep the selection state in sync across other visuals. I will be working on this for future versions of Deneb so that the Vega view retains a single lifecycle in line with that of the visual container and patches in updates to data from Power BI rather than re-building. Still, it is a significant change (and will likely materialize as Deneb 2.0, which I don't have an ETA for yet).

 

For now, though, this means signal values are not preserved between visual update events, so the conventional ways you handle this for a Vega view outside of Power BI will not work as expected. Therefore, you must try to construct the selected area from your dataset's __selected__ values after it updates. You may be able to do this using pluck and extent.

 

I don't have the available capacity to fix your specification up completely, but the following may get you closer to where you need to be:

 

1. Add a dataset that is filtered to selected values, e.g.:

 

{
  "data": [
    ...
    {
      "name": "cross_filter_on",
      "source": "dataset",
      "transform": [
        {
          "type": "filter",
          "expr": "datum.__selected__ == 'on'"
        }
      ]
    }
  ]
  ...
}

 

 

2. Add a signal to get the extents from this, e.g.:

 

{
  ...
  "signals": [
    ...
    {
      "name": "cross_filter_extents",
      "update": "extent(pluck(data('cross_filter_on'), 'x'))"
    },
    ...
  ],
  ...
}

 

 

3. Modify your brush signal to use the scaled values from this, e.g.:

 

{
  ...
  "signals": [
    ...
    {
      "brush": "cross_filter_extents",
      "init": "[scale('x_scale', cross_filter_extents[0]), scale('x_scale', cross_filter_extents[1])]",
      ... // rest of your signal event logic
    },
    ...
  ],
  ...
}

 

 

Hopefully this may help you on your way. Good luck!

 

Daniel





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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


On how to ask a technical question, if you really want an answer (courtesy of SQLBI)




View solution in original post

7 REPLIES 7
vfridkin
New Member

Hopefully this link works to the pbix - I couldn't find a way to share from work so am using another account:
crossfiltering.pbix

dm-p
Super User
Super User

Hi @vfx661,

 

When you apply cross-filtering, Deneb will currently re-initialize the Vega view. This is partially due to how Power BI updates the canvas to keep the selection state in sync across other visuals. I will be working on this for future versions of Deneb so that the Vega view retains a single lifecycle in line with that of the visual container and patches in updates to data from Power BI rather than re-building. Still, it is a significant change (and will likely materialize as Deneb 2.0, which I don't have an ETA for yet).

 

For now, though, this means signal values are not preserved between visual update events, so the conventional ways you handle this for a Vega view outside of Power BI will not work as expected. Therefore, you must try to construct the selected area from your dataset's __selected__ values after it updates. You may be able to do this using pluck and extent.

 

I don't have the available capacity to fix your specification up completely, but the following may get you closer to where you need to be:

 

1. Add a dataset that is filtered to selected values, e.g.:

 

{
  "data": [
    ...
    {
      "name": "cross_filter_on",
      "source": "dataset",
      "transform": [
        {
          "type": "filter",
          "expr": "datum.__selected__ == 'on'"
        }
      ]
    }
  ]
  ...
}

 

 

2. Add a signal to get the extents from this, e.g.:

 

{
  ...
  "signals": [
    ...
    {
      "name": "cross_filter_extents",
      "update": "extent(pluck(data('cross_filter_on'), 'x'))"
    },
    ...
  ],
  ...
}

 

 

3. Modify your brush signal to use the scaled values from this, e.g.:

 

{
  ...
  "signals": [
    ...
    {
      "brush": "cross_filter_extents",
      "init": "[scale('x_scale', cross_filter_extents[0]), scale('x_scale', cross_filter_extents[1])]",
      ... // rest of your signal event logic
    },
    ...
  ],
  ...
}

 

 

Hopefully this may help you on your way. Good luck!

 

Daniel





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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


On how to ask a technical question, if you really want an answer (courtesy of SQLBI)




Thank you - I really appreciate learning about the persistance of _selected_ and your suggested steps.  I've gone ahead and implemented and there are two lingering issues - the second one (2) I don't understand.

1) After brushing, if the brushed area is clicked, then it recreates the brushed area according to your idea above:

a) do the brushing

vfx661_0-1724646381787.png

b) click on the brushed area:

vfx661_1-1724646426421.png

I believe the area changes because it is recreated from the x values which are less precise than the original brushed area.  I'm guessing this can probably be fixed by 'quantising' the x-scale?

2) However if I click again on this new area, the brush disappears as before - I was expecting nothing to change.

vfx661_2-1724646657462.png

*Updated*
It is working now - seems the brush disappearing has to do with the quantisation of the x-scale which is updated below. 

 

{
  "data": [
    {
      "name": "dataset",
      "transform": [
        {
          "type": "extent",
          "field": "x",
          "signal": "extent_x"
        }
      ]
    },
// get dataset on refresh 
    {
      "name": "selected",
      "source": "dataset",
      "transform": [
        {
          "type": "filter",
          "expr": "datum.__selected__ == 'on'"
        },
        {
          "type": "extent",
          "field": "x",
          "signal": "extent_sel_x"
        }
      ]
    }     
  ],
  "width": 500,
  "height": 500,
  "signals": [
    {
      "name": "colour",
      "value": "blue"
    },
    {
      "name": "c", 
      "update": "(481)/(1+extent_x[1]-extent_x[0])"
    },
    {
      "name": "brush_init",
      "update": "[(extent_sel_x[0]-1)*c, extent_sel_x[1]*c]"
    },
    // brush
    {
      "name": "brush",
      "init": "brush_init",
      "on": [
        {
          "events": "@main:pointerdown",
          "update": "[x(), x()]"
        },
        {
          "events": "[@main:pointerdown, window:pointerup] > window:pointermove!",
          "update": "[round(brush[0]/c)*c, clamp(round(x()/c)*c, 0, width)]"
        },
        {
          "events": {
            "signal": "delta"
          },
          "update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
        }
      ]
    },
    // anchor
    {
      "name": "anchor",
      "value": null,
      "on": [
        {
          "events": "@brush:pointerdown",
          "update": "slice(brush)"
        }
      ]
    },
    // xdown
    {
      "name": "xdown",
      "value": 0,
      "on": [
        {
          "events": "@brush:pointerdown",
          "update": "x()"
        }
      ]
    },
    // delta
    {
      "name": "delta",
      "value": 0,
      "on": [
        {
          "events": "[@brush:pointerdown, window:pointerup] > window:pointermove!",
          "update": "round((x() - xdown)/c)*c"
        }
      ]
    },
    // main_brush
    {
      "name": "main_brush",
      // "push": "outer",
      "on": [
        {
          "events": {
            "signal": "brush"
          },
          "update": "span(brush) ? invert('x_scale', brush) : null"
        }
      ]
    },
    // cross filter start ------------------------------------------
    {
      "name": "pbiCrossFilterSelection",
      "value": [],
      "on": [
        // when mark is brushed apply cross filter
        {
          "events": {
            "markname": "brush",
            "type": "click"
          },
          "update": "pbiCrossFilterApply(event, \"datum['x'] >= main_brush[0] & datum['x'] <= main_brush[1]\" )"
        }
      ]
    }
    // cross filter end ------------------------------------------
  ],
  "scales": [
    {
      "name": "x_scale",
      "range": "width",
      "zero": false,
      "padding": {"signal": "c/2"},
      "domain": {
        "data": "dataset",
        "field": "x"
      }
    },
    {
      "name": "y_scale",
      "range": "height",
      "padding": 10,
      "domain": {
        "data": "dataset",
        "field": "y"
      }
    }
  ],
  "axes": [
    {
      "orient": "bottom",
      "scale": "x_scale"
    },
    {
      "orient": "left",
      "scale": "y_scale"
    }
  ],
  "marks": [
    {
      "name": "main",
      "type": "group",
      "encode": {
        "enter": {
          "x": {
            "value": 0
          },
          "y": {
            "value": 0
          },
          "height": {
            "signal": "height"
          },
          "width": {
            "signal": "width"
          },
          "fill": {
            "value": "transparent"
          }
        }
      },
      "marks": [
        // main line chart
        {
          "name": "main_line",
          "type": "line",
          "from": {
            "data": "dataset"
          },
          "encode": {
            "enter": {
              "x": {
                "scale": "x_scale",
                "field": "x"
              },
              "y": {
                "scale": "y_scale",
                "field": "y"
              }
            },
            "update": {
              "stroke": {
                "signal": "colour"
              }
            }
          }
        },
        // brush area mark
        {
          "type": "rect",
          "name": "brush",
          "encode": {
            "enter": {
              "y": {
                "value": 0
              },
              "height": {
                "signal": "height"
              },
              "fill": {
                "value": "#333"
              },
              "fillOpacity": {
                "value": 0.2
              }
            },
            "update": {
              "x": {
                "signal": "brush[0]"
              },
              "x2": {
                "signal": "brush[1]"
              }
            }
          }
        }
      ]
    }
  ]
}

 

 

 

 

Your questions are getting a little more involved than I have the capacity for at present, so tagging @giammariam to see if he can offer any insight on this one





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

Proud to be a Super User!


My course: Introduction to Developing Power BI Visuals


On how to ask a technical question, if you really want an answer (courtesy of SQLBI)




I've got a brushed time series that cross filters a table when clicking on the brushed area mark.  This has a problem that when the brushed area is dragged left or right, it activates the click event and filters the table.  I want to be able to drag the area and not activate the filtering.

 

For example, I've brushed x=3 and then clicked the area to filter the table on the right. 

vfx661_0-1724677119317.png

Now I drag the brushed area to x=6:

vfx661_1-1724677192437.png

This immediately filters the table for x=6, however the desired behaviour is to stay filtered at x=3 and only update the filter when clicking the brushed area.  It seems the act of dragging includes a mouse down and mouse up which trigger the click.

 

One possible solution is to have a mark set up as a button (e.g. a text mark) and have it trigger the cross filter.  The issue with this approach is that it seems the clicked mark needs to include the filtered data.  The brushed area has this data.  Is there a way to associate the brushed area dataset with a symbol mark that is being used as a button?  

For example, I've added a text mark in the top left corner with text value 'activate filter' to be used as the filter trigger:

vfx661_2-1724678090866.png

I want to be able to click on the text mark to activate the filter.  How can I associate the data from the brushed area with the event?

 

Here's the code for the last screenshot.  

*Updated
If I move the group mark down below the button, it partly works when clicking on the button to send the cross filter.  To my dismay, when dragging the brushed area left or right after filtering with the button, the brushed area disappears.  The idea is to maintain the brushed area so it can be dragged around and used as a cross filter when clicking on the button.

I feel each time it is two steps forward, one step back 😅

Click on the button now works:

vfx661_2-1724706035377.png

But then dragging the brushed area with the mouse and releasing clears the brushed area unexpectedly.

 

 

 

 

{
  "data": [
    {
      "name": "dataset",
      "transform": [
        {
          "type": "extent",
          "field": "x",
          "signal": "extent_x"
        }
      ]
    },
    // get dataset on refresh 
    {
      "name": "selected",
      "source": "dataset",
      "transform": [
        {
          "type": "filter",
          "expr": "datum.__selected__ == 'on'"
        },
        {
          "type": "extent",
          "field": "x",
          "signal": "extent_sel_x"
        }
      ]
    }
  ],
  "width": 500,
  "height": 500,
  "signals": [
    {
      "name": "colour",
      "value": "blue"
    },
    {
      "name": "c",
      "update": "(481)/(1+extent_x[1]-extent_x[0])"
    },
    {
      "name": "brush_init",
      "update": "[(extent_sel_x[0]-1)*c, extent_sel_x[1]*c]"
    },
    // brush
    {
      "name": "brush",
      "init": "brush_init",
      "on": [
        {
          "events": "@main:pointerdown",
          "update": "[x(), x()]"
        },
        {
          "events": "[@main:pointerdown, window:pointerup] > window:pointermove!",
          "update": "[round(brush[0]/c)*c, clamp(round(x()/c)*c, 0, width)]"
        },
        {
          "events": {
            "signal": "delta"
          },
          "update": "clampRange([anchor[0] + delta, anchor[1] + delta], 0, width)"
        }
      ]
    },
    // anchor
    {
      "name": "anchor",
      "value": null,
      "on": [
        {
          "events": "@brush:pointerdown",
          "update": "slice(brush)"
        }
      ]
    },
    // xdown
    {
      "name": "xdown",
      "value": 0,
      "on": [
        {
          "events": "@brush:pointerdown",
          "update": "x()"
        }
      ]
    },
    // delta
    {
      "name": "delta",
      "value": 0,
      "on": [
        {
          "events": "[@brush:pointerdown, window:pointerup] > window:pointermove!",
          "update": "round((x() - xdown)/c)*c"
        }
      ]
    },
    // main_brush
    {
      "name": "main_brush",
      // "push": "outer",
      "on": [
        {
          "events": {
            "signal": "brush"
          },
          "update": "span(brush) ? invert('x_scale', brush) : null"
        }
      ]
    },
    // cross filter start ------------------------------------------
    {
      "name": "pbiCrossFilterSelection",
      "value": [],
      "on": [
        // when mark is brushed apply cross filter
        {
          "events": {
            "markname": "activate_filter",
            "type": "click"
          },
          "update": "pbiCrossFilterApply(event, \"datum['x'] >= main_brush[0] & datum['x'] <= main_brush[1]\" )"
        }
      ]
    }
    // cross filter end ------------------------------------------
  ],
  "scales": [
    {
      "name": "x_scale",
      "range": "width",
      "zero": false,
      "padding": {
        "signal": "c/2"
      },
      "domain": {
        "data": "dataset",
        "field": "x"
      }
    },
    {
      "name": "y_scale",
      "range": "height",
      "padding": 10,
      "domain": {
        "data": "dataset",
        "field": "y"
      }
    }
  ],
  "marks": [
    {
      "name": "activate_filter",
      "type": "text",
      "encode": {
        "enter": {
          "xc": {
            "value": 10
          },
          "yc": {
            "value": 10
          },
          "text": {
            "value": "activate filter"
          }
        }
      }
    },
    {
      "name": "main",
      "type": "group",
      "encode": {
        "enter": {
          "x": {
            "value": 0
          },
          "y": {
            "value": 20
          },
          "height": {
            "signal": "height"
          },
          "width": {
            "signal": "width"
          },
          "fill": {
            "value": "transparent"
          }
        }
      },
      "axes": [
        {
          "orient": "bottom",
          "scale": "x_scale"
        },
        {
          "orient": "left",
          "scale": "y_scale"
        }
      ],
      "marks": [
        // main line chart
        {
          "name": "main_line",
          "type": "line",
          "from": {
            "data": "dataset"
          },
          "encode": {
            "enter": {
              "x": {
                "scale": "x_scale",
                "field": "x"
              },
              "y": {
                "scale": "y_scale",
                "field": "y"
              }
            },
            "update": {
              "stroke": {
                "signal": "colour"
              }
            }
          }
        },
        // brush area mark
        {
          "type": "rect",
          "name": "brush",
          "encode": {
            "enter": {
              "y": {
                "value": 0
              },
              "height": {
                "signal": "height"
              },
              "fill": {
                "value": "#333"
              },
              "fillOpacity": {
                "value": 0.2
              }
            },
            "update": {
              "x": {
                "signal": "brush[0]"
              },
              "x2": {
                "signal": "brush[1]"
              }
            }
          }
        }
      ]
    }
  ]
}

 

 

 

 

 

 

I've found that if the brush area is moved after cross filtering and the mouseup action is performed outside the chart area, then the brushed area remains.  Not sure how the mouseup action is causing the brushed area to vanish.

PS: Is there some way to upload a PBIX without having to create a share?  I can't create a shared file from work.


Is there some way to upload a PBIX without having to create a share?

you need to be a super user to be able to upload some (small) files.

Helpful resources

Announcements
Sept PBI Carousel

Power BI Monthly Update - September 2024

Check out the September 2024 Power BI update to learn about new features.

September Hackathon Carousel

Microsoft Fabric & AI Learning Hackathon

Learn from experts, get hands-on experience, and win awesome prizes.

Sept NL Carousel

Fabric Community Update - September 2024

Find out what's new and trending in the Fabric Community.

Top Kudoed Authors