posit-dev / py-shiny

Shiny for Python
https://shiny.posit.co/py/
MIT License
1.32k stars 82 forks source link

Using D3.js with Shiny in Python #932

Open tomicapretto opened 11 months ago

tomicapretto commented 11 months ago

This is more a question than an issue (sorry if this is the wrong place!). I've been using Shiny in R a lot. In the past I used the package {r2d3} to include D3.js visualizations in my Shiny apps.

Thanks in advance!

jcheng5 commented 11 months ago

Hi! I don't know of a similar library in the Python world. The easiest way to connect d3.js and Shiny right now is probably https://anywidget.dev/, which can be used with Shiny via the shinywidgets package.

(Unfortunately, the below example doesn't work with shinylive.io, or I would've linked you to a live demo; something within anywidgets and/or shinywidgets is asking where on disk the Shiny app lives, and shinylive.io doesn't have an answer.)

import anywidget
import traitlets
from shinywidgets import output_widget, render_widget

from shiny import App, render, ui

class D3PlotWidget(anywidget.AnyWidget):
    _esm = """
    import * as d3 from "https://esm.sh/d3@7";

    export function render({ model, el }) {
      // Declare the chart dimensions and margins.
      const width = 640;
      const height = 400;
      const marginTop = 20;
      const marginRight = 20;
      const marginBottom = 30;
      const marginLeft = 40;

      // Declare the x (horizontal position) scale.
      const x = d3.scaleUtc()
          .domain([new Date(model.get("startDate")), new Date(model.get("endDate"))])
          .range([marginLeft, width - marginRight]);

      // Declare the y (vertical position) scale.
      const y = d3.scaleLinear()
          .domain([0, 100])
          .range([height - marginBottom, marginTop]);

      // Create the SVG container.
      const svg = d3.create("svg")
          .attr("width", width)
          .attr("height", height);

      // Add the x-axis.
      svg.append("g")
          .attr("transform", `translate(0,${height - marginBottom})`)
          .call(d3.axisBottom(x));

      // Add the y-axis.
      svg.append("g")
          .attr("transform", `translate(${marginLeft},0)`)
          .call(d3.axisLeft(y));

      svg.classed("d3-plot-widget", true)

      // Return the SVG element.
      el.appendChild(svg.node());
    }
    """
    _css = """
    .d3-plot-widget { border: 1px solid #ea580c; }
    """
    startDate = traitlets.Unicode("2023-01-01").tag(sync=True)
    endDate = traitlets.Unicode("2024-01-01").tag(sync=True)

app_ui = ui.page_fluid(
    ui.input_date_range("dates", "Date range", start="2023-01-01", end="2024-01-01"),
    output_widget("widget"),
)

def server(input, output, session):
    @render_widget
    def widget():
        d3plot = D3PlotWidget()
        d3plot.startDate = input.dates()[0].strftime("%Y-%m-%d")
        d3plot.endDate = input.dates()[1].strftime("%Y-%m-%d")
        return d3plot

app = App(app_ui, server)
tomicapretto commented 11 months ago

@jcheng5 thank you very much! I think this is already what I needed. I'll try to use it for the app I'm developing (I was working on it using R Shiny and r2d2) and if you want I can report back. In the meantime, feel free to close the issue or leave it open, as you consider. Thanks again!

crazycapivara commented 9 months ago

@tomicapretto Maybe shinyobservable with docs here is also useful for your usecase.

tomicapretto commented 9 months ago

Thank you for the reference @crazycapivara! For my use case, I was trying to do some 3d visualization and discovered d3 is not good for that. I ended up using {rgl} and {shiny} in R. I you're interested I can point you to the app once it's public :). The app is about sampling from 2d distributions using hamiltonian monte carlo.