packages feed

ihaskell-hvega-0.1.0.1: src/IHaskell/Display/Hvega.hs

{-# LANGUAGE OverloadedStrings #-}

module IHaskell.Display.Hvega where

import qualified Data.Text.Lazy as LT

import Data.Aeson.Text (encodeToLazyText)
import Data.Monoid ((<>))

import Graphics.Vega.VegaLite (VegaLite, fromVL)

import IHaskell.Display (IHaskellDisplay(..), Display(..), javascript)


-- ^ Use <https://vega.github.io/vega-lite/usage/embed.html Vega-Embed>
--   to include the Vega-Lite visualization in an IHaskell notebook.
--
--   There is currently no way to pass
--   <https://github.com/vega/vega-embed#options options>
--   to the @vegaEmbed@ call.
--
--   Note that local file access is __not__ guaranteed to work - e.g.
--   @dataFromUrl@ where the file name refers to a local file -
--   since the JavaScript @fs@ module may not be loaded.
--
instance IHaskellDisplay VegaLite where
  display vl = 
  
    let -- Note: need to look in the package.json files for these packages
        -- to find the "full name" (the contents of the jsdelivr key),
        -- since requirejs does seem to like appending .js to everything.
        --
        jsname n v = "'https://cdn.jsdelivr.net/npm/"
                     <> n
                     <> "@" <> v
                     <> "/build/"
                     <> n <> ".min'"

        -- Should the config be set on require or requirejs?
        --
        -- These versions are known to work; later versions
        -- do not work but I have not tried to work out the
        -- latest version that does work.
        --
        config = "requirejs({paths:{vg:" <> jsname "vega" "3.2.1"
                 <> ",vl:" <> jsname "vega-lite" "2.3.0"
                 <> ",vge:" <> jsname "vega-embed" "3.5.2"
                 <> "},shim:{vge:{deps:['vg.global','vl.global']}"
                 <> ",vl:{deps:['vg']}"
                 <> "}});"
                 <> "define('vg.global',['vg'],function(g){window.vega = g;});"
                 <> "define('vl.global',['vl'],function(g){window.vl = g;});"

        -- rely on the element variable being set up; it appears to be an array
        -- so use the first element to add the div to.
        makeDiv = "var ndiv = document.createElement('div');"
                   <> "ndiv.innerHTML = "
                   <> "'Awesome Vega-Lite visualization to appear here';"
                   <> "element[0].appendChild(ndiv);"
                
        js = LT.unpack (encodeToLazyText (fromVL vl))

        -- Use the div element we have just created for the plot.
        -- More options could be passed to vegaEmbed.
        --
        plot = "require(['vge'],function(vegaEmbed){"
               <> "vegaEmbed(ndiv," <> js <> ").then("
               <> "function (result) { console.log(result); }).catch("
               <> "function (error) { ndiv.innerHTML = "
               <> "'There was an error: ' + error; });"
               <> "});"
                      
        ds = [javascript (config <> makeDiv <> plot)]
             
    in pure (Display ds)