Как мне взаимодействовать с этим кодом Javascript из Fable F #? - PullRequest
0 голосов
/ 23 июня 2019

Я хочу создать привязку библиотеки Plotly.js к Fable. Я смотрю на этот код JS

import React from 'react';
import Plot from 'react-plotly.js';
class App extends React.Component {
  render() {
    return (
      <Plot
        data={[
          {
            x: [1, 2, 3],
            y: [2, 6, 3],
            type: 'scatter',
            mode: 'lines+points',
            marker: {color: 'red'},
          },
          {type: 'bar', x: [1, 2, 3], y: [2, 5, 3]},
        ]}
        layout={ {width: 320, height: 240, title: 'A Fancy Plot'} }
      />
    );
  }
}

и моя (ошибочная) попытка создания простой тестовой привязки выглядит следующим образом

open Fable.Core
open Fable.Core.JsInterop
open Browser.Types
open Fable.React

// module Props =
type Chart =
    |X of int list
    |Y of int List
    |Type of string

type IProp =
    | Data of obj list

let inline plot (props: IProp) : ReactElement =
    ofImport "Plot" "react-plotly.js" props []

let myTrace = createObj [
    "x" ==> [1,2,3]
    "y" ==> [2,6,3]
    "type" ==> "scatter"
    "mode" ==> "lines"
]

let myData = Data [myTrace]

let testPlot = plot myData

Но, очевидно, это не работает. Как мне заставить его работать? Кроме того, что означает {[...]}? Я новичок в Javascript, и, насколько я знаю, {...} обозначает объект, который должен содержать пары имя-значение, а [...] обозначает массив. Так что {[...]}, кажется, обозначает объект с одним безымянным членом, который является массивом, но, насколько я знаю, нет объектов с безымянными членами.

1 Ответ

2 голосов
/ 25 июня 2019

Мне удалось воспроизвести приведенный вами пример. Пожалуйста, обратите внимание, что я не Plotly и что я пошел эмпирическим путем, и поэтому вещи, вероятно, могут быть улучшены:)

Я создал код, как, вероятно, сделал бы, если бы мне пришлось использовать его в моем производственном приложении. Так что кода немного больше, чем в вашем вопросе, потому что я не использую createObj.

Если вам не нравится типизированный DSL, вы всегда можете упростить его, удалить его и использовать createObj или анонимную запись, как я сделал для свойства marker:)

Вам нужно установить оба react-plotly.js plotly.js в вашем проекте.

open Fable.Core.JsInterop
open Fable.Core
open Fable.React

// Define props using DUs this helps create a typed version of the React props
// You can then transform a list of props into an object using `keyValueList`

[<RequireQualifiedAccess>]
type LayoutProps =
    | Title of string
    | Width of int
    | Height of int

// GraphType is marked as a `StringEnum` this means
// the value will be replace at compile time with
// their string representation so:
// `Scatter` becomes `"scatter"`
// You can customise the output by using `[<CompiledName("MyCustomName")>] 

[<RequireQualifiedAccess; StringEnum>]
type GraphType =
    | Scatter
    | Bar

[<RequireQualifiedAccess; StringEnum>]
type GraphMode =
    | Lines
    | Points
    | Markers
    | Text
    | None

[<RequireQualifiedAccess>]
type DataProps =
    | X of obj array
    | Y of obj array
    | Type of GraphType
    | Marker of obj

    // This is an helpers to generate the `flagList` waited by Plotly, if you don't like it you can just remove 
    // member and replace it with `| Mode of string` and so you have to pass the string by yourself
    static member Mode (modes : GraphMode seq) : DataProps =
        let flags =
            modes
            |> Seq.map unbox<string> // This is safe to do that because GraphMode is a StringEnum
            |> String.concat "+"

        unbox ("mode", flags)


[<RequireQualifiedAccess>]
type PlotProps =
    | Nothing // Should have real props here is there exist more than Data and Layout

    // Here notes that we are asking for an `Array` or Data
    // Array being the type expected by the JavaScript library
    // `DataProps seq` is our way to represents props
    static member Data (dataList : (DataProps seq) array) : PlotProps =
        let datas =
            dataList
            |> Array.map (fun v ->
                keyValueList CaseRules.LowerFirst v // Transform the list of props into a JavaScript object
            )

        unbox ("data", datas)

    static member Layout (props : LayoutProps seq) : PlotProps =
        unbox ("layout", keyValueList CaseRules.LowerFirst props)

// All the example I saw from react-plotly was using this factory function to transform the plotly library into a React component
// Even, the example you shown if you look at the Babel tab in the live example
let createPlotlyComponent (plotly : obj) = import "default" "react-plotly.js/factory"

// Immport the plotly.js library
let plotlyLib : obj = import "default" "plotly.js"

// Apply the factory on the plotly library
let Plot : obj = createPlotlyComponent plotlyLib

// Helper function to instantiate the react components
// This is really low level, in general we use `ofImport` like you did but if I use `ofImport` then I got a React error
let inline renderPlot (plot : obj) (props : PlotProps list) =
    ReactBindings.React.createElement(plot, (keyValueList CaseRules.LowerFirst props), [])

let root =
    // Here we can render the plot using our Typed DSL
    renderPlot
        Plot
        [
            PlotProps.Data
                [|
                    [
                        DataProps.X [| 1; 2; 3 |]
                        DataProps.Y [| 2; 6; 3 |]
                        DataProps.Type GraphType.Scatter
                        DataProps.Mode
                            [
                                GraphMode.Lines
                                GraphMode.Points
                            ]
                        DataProps.Marker {| color = "red" |}
                    ]
                    [
                        DataProps.Type GraphType.Bar
                        DataProps.X [| 1; 2; 3 |]
                        DataProps.Y [| 2; 5; 3 |]
                    ]
                |]
            PlotProps.Layout
                [
                    LayoutProps.Width 640
                    LayoutProps.Height 480
                    LayoutProps.Title "A Fancy Plot"
                ]
        ]
...