Skip to content

Commit

Permalink
v 0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
Marco Zocca committed Jan 8, 2024
1 parent ce8f889 commit 32da210
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 18 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 0.3

* the HTMX swap mechanism works once more as expected: this extension now receives an *object* from the server, which
is unpacked into Plotly restyle data and HTML markup.

# 0.2

* use [`plotly_utils.py`](https://cdn.jsdelivr.net/gh/ocramz/[email protected]/plotly_utils.py) to convert between
Plotly objects and restyle-friendly JSON.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Possible applications include: simple dashboards, data apps, and similar.
Load the script from CDN into the head of your HTML file:

```html
<script src="https://cdn.jsdelivr.net/gh/ocramz/[email protected]/htmx-plotly.js" integrity="sha256-0lbEDYe4H+Z2f/YMKEgbTnyvT2Wa837+a+D7XaPcKIo=" crossorigin="anonymous"></script>```

```

and of course also HTMX and Plotly:
Expand All @@ -26,15 +26,17 @@ and of course also HTMX and Plotly:
Add these attributes to a page element:
* `hx-ext="htmx-plotly"` means this element uses the extension
* `hx-post="/get-data"` the HTTP endpoint that returns the new plot data
* `hx-swap="none"` don't mutate the DOM with the result (we need to call the Plotly API instead)
* `plot-id="my-plot"` ID of the DOM element that hosts the Plotly chart.

Example: here we make an `<a>` text link trigger the update of the plot within element `my-plot`:

```html
<a href="#" hx-ext="htmx-plotly" hx-post="/get-data" hx-swap="none" plot-id="my-plot"><h1>UPDATE</h1></a>
<a href="#" hx-ext="htmx-plotly" hx-post="/get-data" plot-id="my-plot"><h1>UPDATE</h1></a>
```

NB: As of `v0.3` the HTMX swap mechanism works once more as expected: this extension now receives an *object* from the server, which
is unpacked into Plotly restyle data and HTML markup.

### Setup (frontend)

Plotly charts need an empty div element as well as a script tag for initialization:
Expand Down
14 changes: 11 additions & 3 deletions htmx-plotly.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
// adapted from https://unpkg.com/[email protected]/dist/ext/client-side-templates.js

function htmlUnescape(input) {
// // HTML-in-JSON should be escaped on the wire, and decoded safely here with DOMParser to avoid XSS
var doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent
}

htmx.defineExtension('htmx-plotly', {
transformResponse : (text, xhr, elt) => {

const verbose = true
const pedantic = false

var plotEl = htmx.closest(elt, "[plot-id]"); // closest including div element
const payload = JSON.parse(text)
const dataNew = payload['restyle_data']
n = dataNew.length
const markup = htmlUnescape(payload['markup']) // to be passed back to HTMX for swapping
if (plotEl) {
const dataNew = JSON.parse(text)
n = dataNew.length
const plotId = plotEl.getAttribute('plot-id'); // lookup value of ? in < .. plot-id="?">
var plotDiv = htmx.find("#" + plotId); // div element pointed at
if (plotDiv) {
Expand All @@ -31,7 +39,7 @@ htmx.defineExtension('htmx-plotly', {
console.log('No plot-id attribute defined')
}

return ''
return markup

}
}
Expand Down
5 changes: 3 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@

<!-- <a href="#" hx-ext="htmx-plotly" hx-post="/get-data" hx-swap="none" plot-id="my-plot"><h1>UPDATE</h1></a> -->

<form action="#" hx-ext="htmx-plotly" hx-post="/get-data" hx-swap="none" plot-id="my-plot">
<form action="#" hx-ext="htmx-plotly" hx-post="/get-data" hx-swap="innerHTML" hx-target="#xxx" plot-id="my-plot">
<select name="veggies">
<option value="potatoes">potatoes</option>
<option value="carrots">carrots</option>
<option value="broccoli">broccoli</option>
</select>
<input type="submit" value="Submit"/>
</form>

<div id="xxx"></div>

<div id="my-plot"></div>


Expand Down
10 changes: 8 additions & 2 deletions plotly_utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import json
from markupsafe import Markup, escape

def plotlyToRestyle(x):
def plotlyToRestyle(x, htmlStr):
"""
:param x: a Plotly object with a .to_json() implem, e.g. a Figure
:param htmlStr: HTML string that will be passed to HTMX for swapping
:return: data that can be passed to restyle in Plotly.js
"""
z = x.to_json() # .to_json() is Plotly implem
def duplicate(w):
w['x'] = [w['x']] # the .restyle() nested array bs
w['y'] = [w['y']]
return w
return [duplicate(w) for w in json.loads(z)['data']]
obj = {
'restyle_data': [duplicate(w) for w in json.loads(z)['data']],
'markup': str(escape(htmlStr))
}
return obj
12 changes: 4 additions & 8 deletions server.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

from flask import Flask, request, send_file, make_response
import json
from markupsafe import Markup, escape
import jsonpickle
# import json
# from markupsafe import Markup, escape
# import jsonpickle
from plotly_utils import plotlyToRestyle

from plotly_compound_scatter_test import irisScatter1
Expand All @@ -19,11 +19,7 @@ def getData():
# return f'Hello, {escape(name)}!'
x = irisScatter1()

# z = x.to_json() # .to_json() is Plotly implem
# w = json.loads(z)['data'][0] # parse back into dict and extract data
# w['x'] = [w['x']] # the .restyle() nested array bs
# w['y'] = [w['y']]
w = plotlyToRestyle(x)
w = plotlyToRestyle(x, '<b>It wOrKs</b>')
print(f'.restyle data: {w}')
return make_response(w)

Expand Down

0 comments on commit 32da210

Please sign in to comment.