javascriptechartspyscript

How to get element from PyScript to JavaScript


I would like to display a radar chart with JavaScript by using the element values from PyScript. However, I could not display the chart after trying my code.

I have tried:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8" />
    <title>
    Read CSV with Pandas using PyScript
    </title>
    <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
    <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    <script defer src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
    <script src="echarts.js"></script>
    </head>
    <body>
    <p id="csv"></p>
        <py-config type="json">
            {
              "packages": ["pandas"]
            }
        </py-config>
        <py-script>
            import pandas as pd
            import numpy as np
            from pyodide.http import open_url
            #from js import createObject
            #from pyodide.ffi import create_proxy
            #createObject(create_proxy(globals()), "pyodideGlobals")
            url_content = open_url("https://raw.githubusercontent.com/linnlim/Laptop/main/Laptop.csv")
            df = pd.read_csv(url_content)
            
            L1_VRAM = df['VRAM'].loc[df.index[0]]
            L2_VRAM = df['VRAM'].loc[df.index[1]]
            L3_VRAM = df['VRAM'].loc[df.index[2]]
            
            L1_RAM = df['RAM'].loc[df.index[0]]
            L2_RAM = df['RAM'].loc[df.index[1]]
            L3_RAM = df['RAM'].loc[df.index[2]]
            
            L1_ROM = df['ROM'].loc[df.index[0]]
            L2_ROM = df['ROM'].loc[df.index[1]]
            L3_ROM = df['ROM'].loc[df.index[2]]
            
            L1_CPU = df['CPU'].loc[df.index[0]]
            L2_CPU = df['CPU'].loc[df.index[1]]
            L3_CPU = df['CPU'].loc[df.index[2]]
            
            L1_Price = df['Price'].loc[df.index[0]]
            L2_Price = df['Price'].loc[df.index[1]]
            L3_Price = df['Price'].loc[df.index[2]]
            
            
            def L1_VRAM():
                return df['VRAM'].loc[df.index[0]]
            def L2_VRAM():
                return df['VRAM'].loc[df.index[1]]
            def L3_VRAM():
                return df['VRAM'].loc[df.index[2]]
                
            def L1_RAM():
                return df['RAM'].loc[df.index[0]]
            def L2_RAM():
                return df['RAM'].loc[df.index[1]]
            def L3_RAM():
                return df['RAM'].loc[df.index[2]]
                
            def L1_ROM():
                return df['ROM'].loc[df.index[0]]
            def L2_ROM():
                return df['ROM'].loc[df.index[1]]
            def L3_ROM():
                return df['ROM'].loc[df.index[2]]
                
            def L1_CPU():
                return df['CPU'].loc[df.index[0]]
            def L2_CPU():
                return df['CPU'].loc[df.index[1]]
            def L3_CPU():
                return df['CPU'].loc[df.index[2]]
                
            def L1_Price():
                return df['Price'].loc[df.index[0]]
            def L2_Price():
                return df['Price'].loc[df.index[1]]
            def L3_Price():
                return df['Price'].loc[df.index[2]]
                
            csv = Element('csv')
            csv.write(L3_CPU())
        </py-script>
        <div id="main" style="width: 600px;height:400px;"></div>
        <script type="text/javascript">
        var chartDom = document.getElementById('main');
        var myChart = echarts.init(chartDom);
        var option;
        console.log(`In Python right now, x = ${pyscript.interpreter.globals.get('L1_CPU')})
        option = {
          title: {
            text: 'Basic Radar Chart'
          },
          legend: {
            data: ['Laptop 1', 'Laptop 2', 'Laptop 3']
          },
          radar: {
            // shape: 'circle',
            indicator: [
              { name: 'VRAM', max: 10 },
              { name: 'RAM', max: 10 },
              { name: 'ROM', max: 10 },
              { name: 'CPU', max: 10 },
              { name: 'Price', max: 10 }
            ]
          },
          series: [
            {
              name: 'Laptop Specification',
              type: 'radar',
              data: [
                {
                  value: [${pyscript.interpreter.globals.get('L1_VRAM')}, ${pyscript.interpreter.globals.get('L1_RAM')}, ${pyscript.interpreter.globals.get('L1_ROM')}, ${pyscript.interpreter.globals.get('L1_CPU')}, ${pyscript.interpreter.globals.get('L1_Price')}],
                  name: 'Laptop 1'
                },
                {
                  value: [${pyscript.interpreter.globals.get('L2_VRAM')}, ${pyscript.interpreter.globals.get('L2_RAM')}, ${pyscript.interpreter.globals.get('L2_ROM')}, ${pyscript.interpreter.globals.get('L2_CPU')}, ${pyscript.interpreter.globals.get('L2_Price')}],
                  name: 'Laptop 2'
                },
                {
                  value: [${pyscript.interpreter.globals.get('L3_VRAM')}, ${pyscript.interpreter.globals.get('L3_RAM')}, ${pyscript.interpreter.globals.get('L3_ROM')}, ${pyscript.interpreter.globals.get('L3_CPU')}, ${pyscript.interpreter.globals.get('L3_Price')}],
                  name: 'Laptop 3'
                }
              ]
            }
          ]
        };
        

        option && myChart.setOption(option);
        </script>
    </body>
</html>

I am expecting to display a radar chart with 3 polygons of the laptop specifications (VRAM, RAM, ROM, CPU, Price). I think the problem is with the ${pyscript.interpreter.globals.get()} function.

Thanks in advance.


Solution

  • There are some minor issues with the code you posted:

    The main issue is when the javascript gets executed. The way you run it, the javascript code was executed way before the pyscript had completed, so it had no data to represent. Clearly, the code that builds the chart has to be included inside a function (I called it makeChart), and that function has to be called when pyscript is ready.

    The approach taken by most examples (examples that start a javascript function that uses pyscript defined variables), is to call the function as an event handler - that is display a button initially let the user press it and then display the chart.

    To avoid that, it seems sufficient to just allow the code sequence that called the function complete, and then, when the event loop takes over, call the function that needs to access pyscript global variables.

    For that I defined a javascript function startScript

    function startScript(){
        setTimeout(makeChart, 0); // let the sequence that called startScript complete
    }
    
    <py-script>
        # .............
        
        # at the end of the pyscript
        js.startScript()
    </py-script>
    

    Here's a full snippet with these and other details:

    function startScript() {
      setTimeout(makeChart, 0); // let the sequence that called startScript complete
    }
    
    function makeChart() {
      var chartDom = document.getElementById('main');
      var myChart = echarts.init(chartDom);
      var option;
      console.log(`In Python right now, x = ${pyscript.interpreter.globals.get('L1_CPU')()}`)
      option = {
        title: {
          text: 'Basic Radar Chart'
        },
        legend: {
          data: ['Laptop 1', 'Laptop 2', 'Laptop 3']
        },
        radar: {
          // shape: 'circle',
          indicator: [{
              name: 'VRAM',
              max: 10
            },
            {
              name: 'RAM',
              max: 10
            },
            {
              name: 'ROM',
              max: 10
            },
            {
              name: 'CPU',
              max: 10
            },
            {
              name: 'Price',
              max: 10
            }
          ]
        },
        series: [{
          name: 'Laptop Specification',
          type: 'radar',
          data: [{
              value: [
                Number(pyscript.interpreter.globals.get('L1_VRAM')()),
                Number(pyscript.interpreter.globals.get('L1_RAM')()),
                Number(pyscript.interpreter.globals.get('L1_ROM')()),
                Number(pyscript.interpreter.globals.get('L1_CPU')()),
                Number(pyscript.interpreter.globals.get('L1_Price')())
              ],
              name: 'Laptop 1'
            },
            {
              value: [
                Number(pyscript.interpreter.globals.get('L2_VRAM')()),
                Number(pyscript.interpreter.globals.get('L2_RAM')()),
                Number(pyscript.interpreter.globals.get('L2_ROM')()),
                Number(pyscript.interpreter.globals.get('L2_CPU')()),
                Number(pyscript.interpreter.globals.get('L2_Price')())
              ],
              name: 'Laptop 2'
            },
            {
              value: [
                Number(pyscript.interpreter.globals.get('L3_VRAM')()),
                Number(pyscript.interpreter.globals.get('L3_RAM')()),
                Number(pyscript.interpreter.globals.get('L3_ROM')()),
                Number(pyscript.interpreter.globals.get('L3_CPU')()),
                Number(pyscript.interpreter.globals.get('L3_Price')())
              ],
              name: 'Laptop 3'
            }
          ]
        }]
      };
    
      option && myChart.setOption(option);
    }
    <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    <script defer src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
    <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
    
    <p id="csv"></p>
    
    <py-config type="json">
        {
        "packages": ["pandas"]
        }
    </py-config>
    <py-script>
        import pandas as pd
        import numpy as np
        from pyodide.http import open_url
        #from js import createObject
        #from pyodide.ffi import create_proxy
        #createObject(create_proxy(globals()), "pyodideGlobals")
        url_content = open_url("https://raw.githubusercontent.com/linnlim/Laptop/main/Laptop.csv")
        df = pd.read_csv(url_content)
    
        L1_VRAM = df['VRAM'].loc[df.index[0]]
        L2_VRAM = df['VRAM'].loc[df.index[1]]
        L3_VRAM = df['VRAM'].loc[df.index[2]]
    
        L1_RAM = df['RAM'].loc[df.index[0]]
        L2_RAM = df['RAM'].loc[df.index[1]]
        L3_RAM = df['RAM'].loc[df.index[2]]
    
        L1_ROM = df['ROM'].loc[df.index[0]]
        L2_ROM = df['ROM'].loc[df.index[1]]
        L3_ROM = df['ROM'].loc[df.index[2]]
    
        L1_CPU = df['CPU'].loc[df.index[0]]
        L2_CPU = df['CPU'].loc[df.index[1]]
        L3_CPU = df['CPU'].loc[df.index[2]]
    
        L1_Price = df['Price'].loc[df.index[0]]
        L2_Price = df['Price'].loc[df.index[1]]
        L3_Price = df['Price'].loc[df.index[2]]
    
    
        def L1_VRAM():
            return df['VRAM'].loc[df.index[0]]
        def L2_VRAM():
            return df['VRAM'].loc[df.index[1]]
        def L3_VRAM():
            return df['VRAM'].loc[df.index[2]]
    
        def L1_RAM():
            return df['RAM'].loc[df.index[0]]
        def L2_RAM():
            return df['RAM'].loc[df.index[1]]
        def L3_RAM():
            return df['RAM'].loc[df.index[2]]
    
        def L1_ROM():
            return df['ROM'].loc[df.index[0]]
        def L2_ROM():
            return df['ROM'].loc[df.index[1]]
        def L3_ROM():
            return df['ROM'].loc[df.index[2]]
    
        def L1_CPU():
            return df['CPU'].loc[df.index[0]]
        def L2_CPU():
            return df['CPU'].loc[df.index[1]]
        def L3_CPU():
            return df['CPU'].loc[df.index[2]]
    
        def L1_Price():
            return df['Price'].loc[df.index[0]]
        def L2_Price():
            return df['Price'].loc[df.index[1]]
        def L3_Price():
            return df['Price'].loc[df.index[2]]
    
        csv = Element('csv')
        csv.write(L3_CPU())
    
        js.startScript()
    </py-script>
    <div id="main" style="width: 600px;height:400px;"></div>

    I refrained from optimizing the code in order to keep it as much as possible similar to the original post. While the snippet shows the javascript on top, the original sequence that had the javascript at the end of the file works.