Skip to content

Commit 4581c93

Browse files
committed
#441: First go at implementing encoded array data
1 parent a4949f1 commit 4581c93

File tree

6 files changed

+380
-25
lines changed

6 files changed

+380
-25
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
namespace Plotly.NET
2+
3+
open DynamicObj
4+
open System
5+
6+
/// Data type tag for encoded typed arrays accepted by plotly.js (>= 2.28.0).
7+
/// Serializes to the shorthand strings documented in the plotly.js release notes
8+
/// (e.g. "f8" for Float64, "u1c" for UInt8Clamped).
9+
[<RequireQualifiedAccess>]
10+
type TypedArrayDType =
11+
| Float64
12+
| Float32
13+
| Int32
14+
| UInt32
15+
| Int16
16+
| UInt16
17+
| Int8
18+
| UInt8
19+
| UInt8Clamped
20+
21+
static member toString =
22+
function
23+
| Float64 -> "f8"
24+
| Float32 -> "f4"
25+
| Int32 -> "i4"
26+
| UInt32 -> "u4"
27+
| Int16 -> "i2"
28+
| UInt16 -> "u2"
29+
| Int8 -> "i1"
30+
| UInt8 -> "u1"
31+
| UInt8Clamped -> "u1c"
32+
33+
static member convert = TypedArrayDType.toString >> box
34+
override this.ToString() = this |> TypedArrayDType.toString
35+
member this.Convert() = this |> TypedArrayDType.convert
36+
37+
38+
/// <summary>
39+
/// Base64-encoded typed array representation accepted by plotly.js (>= 2.28.0) for trace data_array fields.
40+
///
41+
/// Carries a base64-encoded byte payload (<c>bdata</c>), the underlying value data type (<c>dtype</c>),
42+
/// and an optional <c>shape</c> for multi-dimensional arrays. See the plotly.js v2.28.0 release notes.
43+
/// </summary>
44+
type EncodedTypedArray() =
45+
inherit DynamicObj()
46+
47+
/// <summary>
48+
/// Returns a new EncodedTypedArray with the given base64 payload, dtype tag, and optional shape.
49+
/// Use this overload when you already own the base64 encoding (e.g. from numpy-style pipelines).
50+
/// </summary>
51+
/// <param name="bdata">The base64-encoded contents of the typed array.</param>
52+
/// <param name="dtype">The data type of individual values in the payload.</param>
53+
/// <param name="shape">Optional array shape. Required for multi-dimensional arrays, omittable for 1-D. Serialized as a comma-separated string.</param>
54+
static member init
55+
(
56+
bdata: string,
57+
dtype: TypedArrayDType,
58+
?shape: seq<int>
59+
) =
60+
EncodedTypedArray()
61+
|> EncodedTypedArray.style (
62+
bdata = bdata,
63+
dtype = dtype,
64+
?shape = shape
65+
)
66+
67+
/// <summary>
68+
/// Returns a function that applies the given bdata/dtype/shape to an existing EncodedTypedArray.
69+
/// </summary>
70+
static member style
71+
(
72+
bdata: string,
73+
dtype: TypedArrayDType,
74+
?shape: seq<int>
75+
) =
76+
fun (eta: EncodedTypedArray) ->
77+
eta
78+
|> DynObj.withProperty "bdata" bdata
79+
|> DynObj.withProperty "dtype" (TypedArrayDType.convert dtype)
80+
|> DynObj.withOptionalPropertyBy "shape" shape (fun s -> String.Join(",", s |> Seq.map string))
81+
82+
/// <summary>
83+
/// Creates an EncodedTypedArray from a 1-D Float64 array, base64-encoding the underlying bytes.
84+
/// Pass <paramref name="shape"/> to declare a multi-dimensional layout over the flat data.
85+
/// </summary>
86+
static member ofFloat64Array (data: float[], ?shape: seq<int>) =
87+
let bytes = Array.zeroCreate<byte> (data.Length * sizeof<float>)
88+
Buffer.BlockCopy(data, 0, bytes, 0, bytes.Length)
89+
EncodedTypedArray.init (Convert.ToBase64String bytes, TypedArrayDType.Float64, ?shape = shape)
90+
91+
/// <summary>
92+
/// Creates an EncodedTypedArray from a 1-D Float32 array.
93+
/// </summary>
94+
static member ofFloat32Array (data: single[], ?shape: seq<int>) =
95+
let bytes = Array.zeroCreate<byte> (data.Length * sizeof<single>)
96+
Buffer.BlockCopy(data, 0, bytes, 0, bytes.Length)
97+
EncodedTypedArray.init (Convert.ToBase64String bytes, TypedArrayDType.Float32, ?shape = shape)
98+
99+
/// <summary>
100+
/// Creates an EncodedTypedArray from a 1-D Int32 array.
101+
/// </summary>
102+
static member ofInt32Array (data: int32[], ?shape: seq<int>) =
103+
let bytes = Array.zeroCreate<byte> (data.Length * sizeof<int32>)
104+
Buffer.BlockCopy(data, 0, bytes, 0, bytes.Length)
105+
EncodedTypedArray.init (Convert.ToBase64String bytes, TypedArrayDType.Int32, ?shape = shape)
106+
107+
/// <summary>
108+
/// Creates an EncodedTypedArray from a 1-D UInt32 array.
109+
/// </summary>
110+
static member ofUInt32Array (data: uint32[], ?shape: seq<int>) =
111+
let bytes = Array.zeroCreate<byte> (data.Length * sizeof<uint32>)
112+
Buffer.BlockCopy(data, 0, bytes, 0, bytes.Length)
113+
EncodedTypedArray.init (Convert.ToBase64String bytes, TypedArrayDType.UInt32, ?shape = shape)
114+
115+
/// <summary>
116+
/// Creates an EncodedTypedArray from a 1-D Int16 array.
117+
/// </summary>
118+
static member ofInt16Array (data: int16[], ?shape: seq<int>) =
119+
let bytes = Array.zeroCreate<byte> (data.Length * sizeof<int16>)
120+
Buffer.BlockCopy(data, 0, bytes, 0, bytes.Length)
121+
EncodedTypedArray.init (Convert.ToBase64String bytes, TypedArrayDType.Int16, ?shape = shape)
122+
123+
/// <summary>
124+
/// Creates an EncodedTypedArray from a 1-D UInt16 array.
125+
/// </summary>
126+
static member ofUInt16Array (data: uint16[], ?shape: seq<int>) =
127+
let bytes = Array.zeroCreate<byte> (data.Length * sizeof<uint16>)
128+
Buffer.BlockCopy(data, 0, bytes, 0, bytes.Length)
129+
EncodedTypedArray.init (Convert.ToBase64String bytes, TypedArrayDType.UInt16, ?shape = shape)
130+
131+
/// <summary>
132+
/// Creates an EncodedTypedArray from a 1-D Int8 (signed byte) array.
133+
/// </summary>
134+
static member ofInt8Array (data: sbyte[], ?shape: seq<int>) =
135+
let bytes = Array.zeroCreate<byte> data.Length
136+
Buffer.BlockCopy(data, 0, bytes, 0, bytes.Length)
137+
EncodedTypedArray.init (Convert.ToBase64String bytes, TypedArrayDType.Int8, ?shape = shape)
138+
139+
/// <summary>
140+
/// Creates an EncodedTypedArray from a 1-D UInt8 (byte) array.
141+
/// </summary>
142+
static member ofUInt8Array (data: byte[], ?shape: seq<int>) =
143+
EncodedTypedArray.init (Convert.ToBase64String data, TypedArrayDType.UInt8, ?shape = shape)
144+
145+
/// <summary>
146+
/// Creates an EncodedTypedArray from a byte array, tagged as UInt8Clamped (maps to JS Uint8ClampedArray).
147+
/// </summary>
148+
static member ofUInt8ClampedArray (data: byte[], ?shape: seq<int>) =
149+
EncodedTypedArray.init (Convert.ToBase64String data, TypedArrayDType.UInt8Clamped, ?shape = shape)

src/Plotly.NET/Plotly.NET.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<Compile Include="InternalUtils.fs" />
5151
<Compile Include="CommonAbstractions\ColorKeyword.fs" />
5252
<Compile Include="CommonAbstractions\Colors.fs" />
53+
<Compile Include="CommonAbstractions\EncodedTypedArray.fs" />
5354
<Compile Include="CommonAbstractions\StyleParams.fs" />
5455
<Compile Include="CommonAbstractions\AutoRangeOptions.fs" />
5556
<Compile Include="CommonAbstractions\TickFormatStop.fs" />

src/Plotly.NET/Traces/Trace2D.fs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ type Trace2DStyle() =
176176
/// <param name="XCalendar">Sets the calendar system to use with `x` date data.</param>
177177
/// <param name="YCalendar">Sets the calendar system to use with `y` date data.</param>
178178
/// <param name="UIRevision">Controls persistence of some user-driven changes to the trace: `constraintrange` in `parcoords` traces, as well as some `editable: true` modifications such as `name` and `colorbar.title`. Defaults to `layout.uirevision`. Note that other user-driven trace attribute changes are controlled by `layout` attributes: `trace.visible` is controlled by `layout.legend.uirevision`, `selectedpoints` is controlled by `layout.selectionrevision`, and `colorbar.(x|y)` (accessible with `config: {editable: true}`) is controlled by `layout.editrevision`. Trace changes are tracked by `uid`, which only falls back on trace index if no `uid` is provided. So if your app can add/remove traces before the end of the `data` array, such that the same trace has a different index, you can still preserve user-driven changes if you give each trace a `uid` that stays with it as it moves.</param>
179+
/// <param name="XEncoded">Sets the x coordinates as a base64-encoded typed array (plotly.js &gt;= 2.28.0). If provided, overrides <c>X</c>/<c>MultiX</c>.</param>
180+
/// <param name="YEncoded">Sets the y coordinates as a base64-encoded typed array (plotly.js &gt;= 2.28.0). If provided, overrides <c>Y</c>/<c>MultiY</c>.</param>
179181
static member Scatter
180182
(
181183
?Name: string,
@@ -242,25 +244,29 @@ type Trace2DStyle() =
242244
?StackGaps: StyleParam.StackGaps,
243245
?XCalendar: StyleParam.Calendar,
244246
?YCalendar: StyleParam.Calendar,
245-
?UIRevision: string
247+
?UIRevision: string,
248+
?XEncoded: EncodedTypedArray,
249+
?YEncoded: EncodedTypedArray
246250
) =
247251
fun (trace: ('T :> Trace)) ->
248252
trace
249-
|> DynObj.withOptionalProperty "name" Name
253+
|> DynObj.withOptionalProperty "name" Name
250254
|> DynObj.withOptionalPropertyBy "visible" Visible StyleParam.Visible.convert
251-
|> DynObj.withOptionalProperty "showlegend" ShowLegend
255+
|> DynObj.withOptionalProperty "showlegend" ShowLegend
252256
|> DynObj.withOptionalPropertyBy "legend" Legend StyleParam.SubPlotId.convert
253-
|> DynObj.withOptionalProperty "legendrank" LegendRank
254-
|> DynObj.withOptionalProperty "legendgroup" LegendGroup
255-
|> DynObj.withOptionalProperty "legendgrouptitle" LegendGroupTitle
256-
|> DynObj.withOptionalProperty "opacity" Opacity
257+
|> DynObj.withOptionalProperty "legendrank" LegendRank
258+
|> DynObj.withOptionalProperty "legendgroup" LegendGroup
259+
|> DynObj.withOptionalProperty "legendgrouptitle" LegendGroupTitle
260+
|> DynObj.withOptionalProperty "opacity" Opacity
257261
|> DynObj.withOptionalPropertyBy "mode" Mode StyleParam.Mode.convert
258-
|> DynObj.withOptionalProperty "ids" Ids
259-
|> DynObj.withOptionalSingleOrMultiProperty "x" (X, MultiX)
260-
|> DynObj.withOptionalProperty "x0" X0
261-
|> DynObj.withOptionalProperty "dx" DX
262-
|> DynObj.withOptionalSingleOrMultiProperty "y" (Y, MultiY)
263-
|> DynObj.withOptionalProperty "y0" Y0
262+
|> DynObj.withOptionalProperty "ids" Ids
263+
|> DynObj.withOptionalSingleOrMultiProperty "x" (X, MultiX)
264+
|> DynObj.withOptionalProperty "x" XEncoded
265+
|> DynObj.withOptionalProperty "x0" X0
266+
|> DynObj.withOptionalProperty "dx" DX
267+
|> DynObj.withOptionalSingleOrMultiProperty "y" (Y, MultiY)
268+
|> DynObj.withOptionalProperty "y" YEncoded
269+
|> DynObj.withOptionalProperty "y0" Y0
264270
|> DynObj.withOptionalProperty "dy" DY
265271
|> DynObj.withOptionalSingleOrMultiProperty "text" (Text, MultiText)
266272
|> DynObj.withOptionalSingleOrMultiPropertyBy"textposition" (TextPosition, MultiTextPosition) StyleParam.TextPosition.convert
Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,35 @@
11
open System
2-
open Deedle
3-
open System.IO
42
open Plotly.NET
5-
open Plotly.NET.LayoutObjects
6-
open Plotly.NET.TraceObjects
7-
open Plotly.NET.ConfigObjects
8-
open DynamicObj
9-
open Giraffe.ViewEngine
10-
open Newtonsoft.Json
113

124
[<EntryPoint>]
13-
let main args =
14-
15-
ChartDomainTestCharts.Sankey.``Styled sankey chart``
5+
let main args =
166

7+
// Low-level demo: construct a scatter trace whose x/y come from base64-encoded
8+
// typed arrays (plotly.js >= 2.28.0 "data_array" form).
9+
let n = 500
10+
let xs = [| for i in 0 .. n - 1 -> float i * 2.0 * Math.PI / float (n - 1) |]
11+
let ys = xs |> Array.map sin
12+
13+
let xEncoded = EncodedTypedArray.ofFloat64Array xs
14+
let yEncoded = EncodedTypedArray.ofFloat64Array ys
15+
16+
// The encoded typed-array form was introduced in plotly.js 2.28.0; pin the CDN
17+
// explicitly so this demo renders regardless of the bundled default version.
18+
let displayOpts =
19+
DisplayOptions.init(
20+
PlotlyJSReference = CDN "https://cdn.plot.ly/plotly-2.28.0.min.js"
21+
)
22+
23+
Trace2D.initScatter (
24+
Trace2DStyle.Scatter(
25+
Name = "sin (encoded)",
26+
Mode = StyleParam.Mode.Lines_Markers,
27+
XEncoded = xEncoded,
28+
YEncoded = yEncoded
29+
)
30+
)
31+
|> GenericChart.ofTraceObject true
32+
|> Chart.withDisplayOptions displayOpts
1733
|> Chart.show
18-
0
34+
35+
0

0 commit comments

Comments
 (0)