Getting d3.js to work within pyRevit

Hi all! I’m attempting to do some data viz with d3.js + pyRevit, but running into a couple hiccups. I got the idea by stumbling on the example here, which I realize is a few years old. Originally the reference in the html file (line 20):
<script src="https://unpkg.com/d3"></script>, wasn’t working for me. I then downloaded the d3 js file and pointed to it like this:
<script type="text/javascript" src="d3.v4.js"></script> .
This worked, though for some reason d3.v6.js wouldn’t work, but I can probably live with that for now.

My main issue now is that I want to break out the d3 portion into a separate javascript file(s) rather than have everything within the html file as is done in the example in the link above. However, once I create a separate .js file and reference it in the html, it fails to recognize ‘d3’ the same way as if you keep it all within the html.
image

I tried importing the d3.v4.js file as a module into the js file directly like so:
import * as d3 from './d3.v4.js';
However this throws a Syntax error for some reason.

Would be curious to hear if anyone has successfully done this / knows what I’m doing wrong. Perhaps there’s a reason the example was not broken out into separate .js files as would normally be done?

Hope this generally makes sense. Thanks all in advance!

Realizing I should probably use the Routes framework for what I’m trying to do, so I’ll repose my question.

Once I’ve setup the route, how would I render an html file from it in the browser? For example, if using Flask it might look something like this:

from flask import render_template

@app.route("/")
def index():
    return render_template("index.html")

I don’t see a similar function in Routes, but maybe I’m missing something.

1 Like

The error occurs because your javascript code that is using d3 is being executed before the download for d3 is finished. So you’d want to make sure d3 object is available. AFAIK import is a node.js javascript feature and not supported in browsers yet

1 Like

Get the output object in pyRevit

from pyrevit import script

output = script.get_output()

the output object will give you access to the output window features and web renderer

https://pyrevit.readthedocs.io/en/latest/pyrevit/output.html

see these methods

output.open_url()
output.open_page()
output.print_html()

Thanks for the reply @eirannejad, and sorry for my delayed reply (I was on vacation for a bit, and am just getting back to wrestling with this). Deferring the loading of my javascript file helped solve the first issue as you had pointed out with d3 being referenced before it had been loaded.

Now I’m running into a CORS issue since my js file is attempting to load a json file (exact error in the browser: URL scheme must be "http" or "https" for CORS request) . As I understand it, this is a security issue when running the html from a local directory, and the pyRevit output window by default is loading from the local temp folder. So I suppose my question is: Is there a way to render the output object from a server (perhaps via pyRevit routes) instead of the local temp folder so that I can load local files in the javascript file?

Ultimately, I’m attempting to display some Revit data in the output window with d3 (Yes, I’m aware of some of the Charts functionality available in pyRevit, but was looking to do something a bit more customized.). My current thinking was to dump a json file, load it via d3, and display it in the browser, but maybe there’s a cleaner way. Also, this is something I’m looking to deploy, so ideally it wouldn’t rely on the user having Python 3 installed, which I know would be another route to take.

Thanks in advance for any suggestions. I’ll happily share my results if I ever get this working :slight_smile:

Just an update - I did figure out a serverless - and rather hacky - way to do this, and just wanted to share for anyone interested or if people want to suggest better ways (surely there are). Steps are outlined in more detail below but the short summary of the workflow is that from the python script you extract whatever data from your model you are looking to visualize as a JSON string, then search and replace some placeholder text in your js file (this is to avoid having to load external files later and thus the CORS issue). Then you call d3 and the updates js file from your html file as you normally would.

In javascript file:
Create a temporary placeholder for your data

var json_str = 'placeholder';
var data = JSON.parse(json_str);

var root = d3.hierarchy(data);

In Python:
Collect your data from Revit, dump it as json string, search and replace js file, load output window.

json_str = json.dumps(your_data)
js_path = <PATH TO YOUR JS FILE>
with open(js_path,'r') as f:
   txt = f.read()

txt = txt.replace('placeholder', json_str)
new_path = <PATH TO NEW FILE> #This is optional for if you don't want to override the original js file

with open(new_path,'w') as f:
   f.write(txt)

output = script.get_output()
output.open_page(<YOUR HTML FILE PATH>)

In HTML:
Load d3 and any other libraries in the header, and load your javascript in the body. I found that I needed to pass absolute file paths for my local css and js files since the output window doesn’t open from the directory of where your tool lives. I was also using an older version of d3, but you can probably use a newer one if you make sure to update the browser compatibility, etc.

<!-- In header -->
<link rel="stylesheet" type="text/css" href="<ABSOLUTE FILE PATH TO CSS>" >
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.1.0/d3.min.js"></script>

<!-- In body-->
<script type="text/javascript" src="<ABSOLUTE FILE PATH TO JS FILE>"></script>
1 Like