Forum Discussion

prasaddn's avatar
prasaddn
Frequent Visitor
8 months ago
Solved

Python Visual rendering issue on .Net wrapper

I have a power bi dashboard that is hosted in a wrapper portal built on .net or some technology. So that dashboard can be accessed only authorized users.

 

The dashboard is about some surveys. I have qualitative comments (free text) by survey respondents as part of data. Based on the user selections on various slicer filters like gender, event, country, bu, dept, etc, the sub-set of comments (free text) are summarized and a short summary text output is shown.  This summarization and display of text is done using a Python script. All these are working as expected. However, when the dashboard is hosted on wrapper portal, it errors on this visual.  

 

I also found some reference wherein it states Python or R visuals do not render on embedded reports: 

https://learn.microsoft.com/en-us/answers/questions/5626506/issue-with-python-visuals-in-power-bi-embedded-(-n

it suggests to pre-compute the results and load the data for visualization.  Given my scenario, it would be hard to pre-compute the comments summary as the user would select any permutation and combination of filters/slicers. 

 

Any other alternate solutions would be highly appreciated. 

 

Regards,

Prasad  

14 Replies

  • Hii prasaddn 

     

    Python (and R) visuals are not supported in Power BI Embedded when using a .NET wrapper with “Embed for your customers (app owns data)”, so the visual works in Desktop/Service but fails in the embedded portal. This is a platform limitation, not a coding issue. To resolve it, move the Python logic outside Power BI (for example, run the text summarization in Azure Functions, Databricks, or Fabric and store the results) and only display the output using standard Power BI visuals, or use a custom visual that calls a backend API for dynamic summaries.

    • prasaddn's avatar
      prasaddn
      Frequent Visitor

      thank you for the response rohit1991 . Yes, the only alternate I heard is pre-computing. However, it is not feasible with my current scenario.  would be keen to know if you are aware of any such free custom visual that calls a backend API for dynamic summaries and also works in a .NET wrapper environment. 

      • rohit1991's avatar
        rohit1991
        Super User

        Hii prasaddn 

         

        There is no free, out-of-the-box Power BI custom visual that can securely call an external backend API and render dynamic text summaries in an Embed for your customers (.NET / app-owns-data) scenario. Due to sandboxing and security restrictions, custom visuals cannot freely make arbitrary API calls unless they are purpose-built and certified, and most AppSource visuals don’t support this pattern. In practice, the supported approaches are to build your own custom visual that calls a controlled API endpoint, or move the summarization logic outside Power BI (Azure Function/Fabric/Databricks) and load the results into the model for display using standard visuals.

         
  • AshokKunwar's avatar
    AshokKunwar
    Continued Contributor

    Hi prasaddn 

     

    Since pre-computing every permutation of filters isn't feasible for free-text comments, you need to move the Python logic "up" the stack

    Solution 1: Move Logic to the .NET Wrapper (Pro-Code)

    Since you are already using a .NET portal, you can handle the summarization outside of Power BI.

    The Workflow:

    Use the Power BI Client SDK in your .NET app to capture the state of the slicers.

    Use a DAX Query via the REST API (Datasets - Execute Queries) to fetch only the filtered subset of comments.

    Pass those comments to a Python Azure Function (where you can reuse your existing script).

    Display the result in a standard HTML <div> or text box in your portal, positioned next to the report.

    Solution 2: Power Automate + AI Builder (Low-Code)

    If you want to keep the result inside the report area, you can replace the Python visual with a Power Automate visual.

    The Workflow:

    Add the Power Automate for Power BI visual to your report.

    Pass the "Comments" field into the visual data bucket.

    Create a flow that triggers on button click, sends the filtered data to Azure OpenAI or AI Builder, and returns the summary to a "Results" table.

    Benefit: This avoids the sandbox limitations because the "Visual" is just a button, and the actual processing happens in the cloud.

    Solution 3: The "HTML Content" Visual (Fastest Implementation)

    You can use the HTML Content custom visual (available in AppSource) to display dynamic text that standard visuals can't handle.

    Instead of Python, you can use a Calculation Group or a complex DAX measure to concatenate the strings (if the summarization logic is simple).

    If the logic must be Python, you can use Power Query to call a web API during the refresh, though this will not be "real-time" for slicer changes.

     

    If this architectural change helps you bypass the Python visual limitation in your portal, please mark this as an "Accepted Solution" to help others!

    Best regards,

    Vishwanath

    • prasaddn's avatar
      prasaddn
      Frequent Visitor

      Dear AshokKunwar , Firstly thank you for providing 3 different approaches. I need to also call out that I am new to Power BI. 

       

      Solution 1: Move Logic to the .NET Wrapper (Pro-Code)

      I don't have Power BI Client SDK and the .NET portal is also developed by some other team, and we use them to host our dashboard. so literally, I dont have access to edit or modify anything in the portal. I am also not familiar with using Azure functions. 

       

      Solution 2: Power Automate + AI Builder (Low-Code)

      Like I mentioned, do not have exposure or access to Azure or AI functions or token based model. 

      Solution 3: The "HTML Content" Visual (Fastest Implementation). I was thinking we may have some custom visual that could take input as set of text comments, and then return some summarize text. However, I am not sure if this can be done with calcaluation groups as it involves understanding all the comments and creating short summary.   

       

      Please advise if the attached python script here, can be converted in a DAX function or some script that works with HTML Content custom visual and in .NET environment?

       

      apologies as it is getting a long post: 
      ====================

      import matplotlib.pyplot as plt
      import re
      from collections import Counter
      import textwrap

      # ============================================================
      # CONFIG
      # ============================================================
      EVENT_COL = "Event Name"
      TEXT_COL = "Any suggestions for us to improve your experience?"

      MAX_THEMES = 5 # show at most this many themes
      MIN_COUNT_RAW = 4 # min frequency for unmapped raw words (very strict)
      RAW_THEMES_ON = True # set False if you ONLY want curated themes from label_map
      # ============================================================


      # ============================================================
      # STEP 1 — Extract event-name tokens so event names never become themes
      # ============================================================

      def tokenize_event_name(name: str):
      if not isinstance(name, str):
      return []
      name = name.lower()
      name = re.sub(r"[^a-z0-9\s]", " ", name)
      return [t for t in name.split() if len(t) > 2]

      event_tokens = set()
      if EVENT_COL in dataset.columns:
      for ev in dataset[EVENT_COL].dropna().unique():
      event_tokens.update(tokenize_event_name(ev))


      # ============================================================
      # STEP 2 — Robust stopword + junk dictionary
      # ============================================================

      # Base English stopwords (NLTK-style)
      base_stopwords = {
      "i","me","my","myself","we","our","ours","ourselves","you","your","yours","yourself","yourselves",
      "he","him","his","himself","she","her","hers","herself","it","its","itself",
      "they","them","their","theirs","themselves",
      "what","which","who","whom","this","that","these","those",
      "am","is","are","was","were","be","been","being",
      "have","has","had","having","do","does","did","doing",
      "a","an","the","and","but","if","or","because","as","until","while",
      "of","at","by","for","with","about","against","between","into","through","during",
      "before","after","above","below","to","from","up","down","in","out","on","off","over","under",
      "again","further","then","once","here","there","when","where","why","how",
      "all","any","both","each","few","more","most","other","some","such",
      "no","nor","not","only","own","same","so","than","too","very",
      "can","will","just","don","should","now","also","please","include","better"
      }

      # Extra junk/structural words specific to suggestions context
      extra_junk = {
      # Generic praise/fillers – not themes
      "good","great","nice","awesome","amazing","excellent","fantastic","superb","wonderful","best",
      "well","fine","ok","okay","super","memorable","enjoyable","satisfying",

      # Politeness / thanks
      "thank","thanks","thankyou","thanks!","thanks.","thanks!!","thanks!!!","thanks🙏","thankyou🙏",

      # Question echo / filler words
      "suggestions","suggestion","improve","improved","improvement","improvements","experience",
      "feedback","inputs","input","point","points","comment","comments","share","shared","sharing",

      # Event-generic nouns
      "event","events","session","sessions","meeting","meetings","program","programs",
      "show","shows","function","functions","day","days","today","yesterday","tomorrow",
      "evening","morning","afternoon",

      # Generic verbs & vague stuff
      "got","had","made","did","doing","done","make","makes","take","took","taken",
      "see","saw","seen","want","wanted","would","could","should","hope","hoped","hoping",
      "looking","look","felt","feel","feeling","enjoy","enjoyed","liked","like","love","loved",
      "appreciate","appreciated","helped","helpful","managed","arranged","organized","organised",
      "planning","planned","coordinate","coordinated","support","supported","supportive",

      # Short conversational fillers
      "yeah","yes","yep","okie","okay","hmm","hmmm","hahaha","haha","lol",

      # Emojis / junk tokens
      "👍","👏","😊","🙏","😀","😄","😂","😁","🙂","😉","😇","🎉","🎊","💯","❤️","♥","😁😁","😂😂",

      # Known junk/structural words you do not want as themes
      "camp", # often part of event name like "Blood Donation Camp"
      }

      # Combine base stopwords, custom junk, and event tokens
      stopwords = base_stopwords | extra_junk | event_tokens


      # ============================================================
      # STEP 3 — Tokenizer with strong filtering
      # ============================================================

      def clean_tokens(text: str):
      if not isinstance(text, str):
      return []
      text = text.lower()
      text = re.sub(r"[^a-z0-9\s]", " ", text)
      tokens = []
      for t in text.split():
      if not t:
      continue
      t = t.strip()
      # skip pure numbers
      if t.isdigit():
      continue
      # skip very short tokens
      if len(t) <= 2:
      continue
      # skip stopwords and event-name pieces
      if t in stopwords or t in event_tokens:
      continue
      tokens.append(t)
      return tokens


      # ============================================================
      # STEP 4 — Theme dictionary (label_map) tuned for improvements
      # ============================================================

      label_map = {
      # Crowd / queue / flow
      "crowd": "crowd & queues",
      "crowded": "crowd & queues",
      "queue": "crowd & queues",
      "queues": "crowd & queues",
      "line": "crowd & queues",
      "lines": "crowd & queues",
      "entry": "crowd & queues",
      "exit": "crowd & queues",
      "flow": "crowd & queues",

      # Time / schedule / duration
      "timing": "time management",
      "delay": "time management",
      "delays": "time management",
      "late": "time management",
      "earlier": "time management",
      "duration": "time management",
      "schedule": "time management",
      "reschedule": "time management",

      # Venue / seating / facilities
      "venue": "venue & facilities",
      "hall": "venue & facilities",
      "auditorium": "venue & facilities",
      "location": "venue & facilities",
      "space": "seating & space",
      "seating": "seating & space",
      "seat": "seating & space",
      "seats": "seating & space",
      "chairs": "seating & space",
      "chair": "seating & space",
      "fan": "venue & facilities",
      "fans": "venue & facilities",
      "ac": "venue & facilities",
      "air": "venue & facilities",
      "sound": "sound & technical",
      "audio": "sound & technical",
      "mic": "sound & technical",
      "speaker": "sound & technical",
      "speakers": "sound & technical",
      "light": "lighting & ambience",
      "lights": "lighting & ambience",
      "lighting": "lighting & ambience",

      # Food / snacks / water
      "snack": "snacks & refreshments",
      "snacks": "snacks & refreshments",
      "refreshment": "snacks & refreshments",
      "refreshments": "snacks & refreshments",
      "food": "snacks & refreshments",
      "lunch": "snacks & refreshments",
      "dinner": "snacks & refreshments",
      "water": "snacks & refreshments",
      "bottle": "snacks & refreshments",
      "bottles": "snacks & refreshments",

      # Registration / process / communication
      "registration": "registration & process",
      "register": "registration & process",
      "forms": "registration & process",
      "form": "registration & process",
      "process": "registration & process",
      "procedure": "registration & process",
      "instructions": "communication & information",
      "information": "communication & information",
      "info": "communication & information",
      "communication": "communication & information",
      "update": "communication & information",
      "updates": "communication & information",
      "mail": "communication & information",
      "email": "communication & information",
      "whatsapp": "communication & information",
      "notification": "communication & information",
      "notifications": "communication & information",

      # Parking / transport / access
      "parking": "parking & access",
      "park": "parking & access",
      "entrypoint": "parking & access",
      "gate": "parking & access",
      "traffic": "parking & access",

      # Kids & family
      "kids": "kids & family activities",
      "children": "kids & family activities",
      "family": "kids & family activities",
      "families": "kids & family activities",
      "booth": "kids & family activities",
      "photo": "kids & family activities",
      "photos": "kids & family activities",
      "photobooth": "kids & family activities",
      "games": "kids & family activities", # kids game stalls etc.
      "game": "kids & family activities",

      # Frequency / future events
      "frequency": "event frequency & cadence",
      "regular": "event frequency & cadence",
      "regularly": "event frequency & cadence",
      "often": "event frequency & cadence",
      "monthly": "event frequency & cadence",
      "yearly": "event frequency & cadence",
      "quarterly": "event frequency & cadence",

      # Content / activities / engagement
      "content": "content & activities",
      "activities": "content & activities",
      "activity": "content & activities",
      "session": "content & activities",
      "sessions": "content & activities",
      "variety": "content & activities",
      "options": "content & activities",
      "options": "content & activities",

      # Safety / hygiene
      "safety": "safety & hygiene",
      "safe": "safety & hygiene",
      "hygiene": "safety & hygiene",
      "cleanliness": "safety & hygiene",
      "cleaning": "safety & hygiene",
      "washroom": "safety & hygiene",
      "washrooms": "safety & hygiene",
      "toilet": "safety & hygiene",
      "toilets": "safety & hygiene",

      # Staffing / volunteers
      "volunteers": "staffing & volunteers",
      "volunteer": "staffing & volunteers",
      "staff": "staffing & volunteers",
      "helpdesk": "staffing & volunteers",
      }


      # ============================================================
      # STEP 5 — Summariser
      # ============================================================

      def summarize_group(texts):
      tokens = []
      for t in texts:
      tokens.extend(clean_tokens(t))

      if not tokens:
      return ""

      counts = Counter(tokens)

      # 1) Aggregate mapped themes
      theme_counts = Counter()
      for word, c in counts.items():
      if word in label_map:
      theme_counts[label_map[word]] += c

      # 2) Optional: Add very strong unmapped words as raw themes
      if RAW_THEMES_ON:
      for word, c in counts.items():
      if word in label_map:
      continue
      if c < MIN_COUNT_RAW:
      continue
      # avoid obvious verbs/adverbs
      if word.endswith(("ed", "ing", "ly", "s")):
      continue
      if len(word) <= 3:
      continue
      if word in event_tokens:
      continue
      theme_counts[word] += c

      if not theme_counts:
      return ""

      top_themes = [theme for theme, _ in theme_counts.most_common(MAX_THEMES)]
      if not top_themes:
      return ""

      if len(top_themes) == 1:
      return f"Participants most frequently suggested improvements in {top_themes[0]}."
      elif len(top_themes) == 2:
      return (
      f"Participants most frequently suggested improvements in "
      f"{top_themes[0]} and {top_themes[1]}."
      )
      else:
      return (
      "Participants most frequently suggested improvements in "
      + ", ".join(top_themes[:-1])
      + f", and {top_themes[-1]}."
      )


      def wrap_text(text, width=80):
      return "\n".join(textwrap.wrap(text, width))


      # ============================================================
      # APPLY TO CURRENT FILTERED DATA
      # ============================================================

      if TEXT_COL not in dataset.columns:
      summary_text = f"Column '{TEXT_COL}' not found in current visual."
      else:
      texts = dataset[TEXT_COL].dropna()
      summary_text = summarize_group(texts)
      if not summary_text:
      summary_text = "No clear recurring improvement themes found for the current selection."

      wrapped = wrap_text(summary_text)
      # ============================================================
      # DRAW VISUAL — robust against clipping
      # ============================================================
      fig_w, fig_h, dpi = 20, 4, 120
      fontsize = 40

      # Optional: smarter wrapping based on figure + font size
      def dynamic_wrap(text, fig_width_in=fig_w, dpi=dpi, font_size=fontsize, char_width_px=0.6):
      px_width = fig_width_in * dpi
      approx_char_px = font_size * char_width_px
      width_chars = max(30, int(px_width / approx_char_px))
      return "\n".join(textwrap.wrap(text, width=width_chars))

      wrapped = dynamic_wrap(summary_text)

      fig = plt.figure(figsize=(fig_w, fig_h), dpi=dpi)
      # Place text in FIGURE coordinates to avoid axes clipping
      fig.text(0.5, 0.5, wrapped,
      ha="center", va="center",
      fontsize=fontsize, fontweight='bold',
      color='darkblue',
      wrap=True,
      bbox=dict(boxstyle='round,pad=0.5', facecolor='white', alpha=0.95, edgecolor='white'))

      plt.tight_layout(pad=2.0)

      plt.show()

      ====================   

  • Hi prasaddn  

     

    Apologies for misreading your original post and by going through other replies you had made. I believe that I understand your situation now, so I’ve deleted my previous comment.

     

    If I understand correctly, you’re basically looking for an alternative solution for python to use an API as a data source that works like DirectQuery. If that’s the case, please have a look at this article:

     

    https://blog.crossjoin.co.uk/2021/04/25/can-i-build-a-power-bi-directquery-dataset-on-top-of-a-rest-api/

     

    • prasaddn's avatar
      prasaddn
      Frequent Visitor

      Hi kushanNa In my other reply I have posted my Python Script which works as expected. However, it wont support when the dashboard is hosted / embedded on .NET wrapper.  Hence, I am looking for alternate ways to summarize the survey comments, like some custom visual or script that supports in .NET wrapper.   

  • Python (and R) visuals are not supported in embedded scenarios. They render in Desktop (and sometimes in the Service UI), but when you host the report inside a .NET “wrapper” (Power BI Embedded / iframe / app owns data), the Service will not execute that Python script for security/runtime reasons — so the visual fails. That’s expected behavior, not a bug you can fix with settings.

     

    What to do instead :

    1. Precompute + slice 
    If you can reduce the degrees of freedom, precompute summaries at a reasonable grain (e.g., Country × BU × Dept × Event) and let the report filter those results. You don’t need “every permutation”, just the combinations your business actually uses.

     

    2. Move summarization outside Power BI
    Keep the report interactive, but do the summarization in an external service:

    • Send current filter context (selected values) to an API

    • API returns the summary text

    • Display it in Power BI using a custom visual (HTML/React) or a Power BI visual that can call an endpoint
      This is the only way to keep “any combination of slicers” dynamic in embedded.

     

    3. Use a Text Analytics pipeline upstream
    If you’re on Fabric / Azure, you can store embeddings/topics or “key phrases” per comment and summarize based on filtered retrieval (much lighter than re-summarizing raw text every time). The report then aggregates precomputed signals.

     

    4. If you must stay purely in Power BI Embedded
    Then you cannot use Python/R visuals. You’ll need to replace the Python visual with:

    • a standard visual based on precomputed tables, or

    • a certified custom visual that doesn’t require Python execution in the client.