Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions jsoo/jsoo.ml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ type plotly =
data Js.t Js.js_array Js.t ->
layout Js.t ->
config Js.t -> unit Js.meth;

validate :
data Js.t Js.js_array Js.t ->
layout Js.t ->
Js.js_string Js.t Js.js_array Js.t Js.meth;
>

let plotly : plotly Js.t = Js.Unsafe.pure_js_expr "Plotly"
Expand All @@ -72,3 +77,10 @@ let create div fig =
@@ List.map obj_of_graph_object fig.Figure.graphs)
(obj_of_layout fig.layout)
(Js.Unsafe.obj [||])
let validate fig =
let data_array = Js.array (Array.of_list (List.map obj_of_graph_object fig.Figure.graphs)) in
let layout = obj_of_layout fig.layout in
plotly##validate data_array layout
|> Js.to_array
|> Array.to_list
|> List.map Js.to_string
9 changes: 9 additions & 0 deletions jsoo/jsoo.mli
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,17 @@ type plotly =
data Js.t Js.js_array Js.t ->
layout Js.t ->
config Js.t -> unit Js.meth;

validate :
data Js.t Js.js_array Js.t ->
layout Js.t ->
Js.js_string Js.t Js.js_array Js.t Js.meth;
>

val plotly : plotly Js.t

val create : Dom_html.divElement Js.t -> Plotly.Figure.t -> unit

(** Validate a figure using Plotly.js validate function.
Returns a list of error messages (empty list means valid). *)
val validate : Plotly.Figure.t -> string list
15 changes: 15 additions & 0 deletions python/python.ml
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,18 @@ let write_image figure path =
| Some f ->
let f = Py.Callable.to_function_with_keywords f in
ignore @@ f [| Py.String.of_string path |] []

let validate (figure : figure t) : (string list, string) result =
try
let plotly_io = Py.Import.import_module "plotly.io" in
match Py.Object.get_attr_string plotly_io "to_json" with
| None -> Error "plotly.io.to_json not found"
| Some to_json_fn ->
let to_json_fn = Py.Callable.to_function_with_keywords to_json_fn in
(try
let _ = to_json_fn [| figure |] ["validate", Py.Bool.of_bool true] in
Ok []
with Py.E (_, _) ->
Error "Figure validation failed")
with _ ->
Ok []
3 changes: 3 additions & 0 deletions python/python.mli
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ val python_figure_to_json : figure t -> Ezjsonm.value

val show : ?renderer:string -> figure t -> unit
val write_image : figure t -> string -> unit
val validate : figure t -> (string list, string) result
(** Validate a figure using Plotly's Python validate function.
Returns Ok [] if valid, or Error with description if invalid. *)
90 changes: 50 additions & 40 deletions test/test_all.ml
Original file line number Diff line number Diff line change
Expand Up @@ -33,31 +33,37 @@ let test_json_figure figure =
let current_json = Figure.to_json figure in
let test_name = filename in

if Sys.file_exists ref_path then begin
let ref_json = Ezjsonm.value_from_channel (open_in ref_path) in

if not (json_equal current_json ref_json) then begin
Printf.printf " ✗ %s - JSON doesn't match reference\n" test_name;
(* Validate that the generated JSON produces a valid Plotly figure by deserializing it *)
match Figure.of_json current_json with
| None ->
Printf.printf " ✗ %s - Invalid Plotly figure (failed to deserialize)\n" test_name;
false
end else begin
match Figure.of_json current_json with
| Some fig' ->
let roundtrip_json = Figure.to_json fig' in
if not (json_equal current_json roundtrip_json) then begin
Printf.printf " ✗ %s - Round-trip failed\n" test_name;
false
end else begin
Printf.printf " ✓ %s (JSON)\n" test_name;
true
end
| None ->
Printf.printf " ✗ %s - Failed to parse generated JSON\n" test_name;
| Some _ ->
if Sys.file_exists ref_path then begin
let ref_json = Ezjsonm.value_from_channel (open_in ref_path) in

if not (json_equal current_json ref_json) then begin
Printf.printf " ✗ %s - JSON doesn't match reference\n" test_name;
false
end
end else begin
Printf.printf " ? %s - No JSON reference\n" test_name;
false
end
end else begin
match Figure.of_json current_json with
| Some fig' ->
let roundtrip_json = Figure.to_json fig' in
if not (json_equal current_json roundtrip_json) then begin
Printf.printf " ✗ %s - Round-trip failed\n" test_name;
false
end else begin
Printf.printf " ✓ %s (JSON)\n" test_name;
true
end
| None ->
Printf.printf " ✗ %s - Failed to parse generated JSON\n" test_name;
false
end
end else begin
Printf.printf " ? %s - No JSON reference\n" test_name;
false
end

let test_python_backend figure =
let module Python = Plotly_python.Python in
Expand All @@ -74,24 +80,28 @@ let test_python_backend figure =
(* Convert figure to Python object *)
let py_fig = Python.of_figure figure in

(* Extract JSON from the actual Python figure object *)
let py_json = Python.python_figure_to_json py_fig in

(* Verify it matches the Python-specific reference *)
if Sys.file_exists ref_path then begin
let ref_json = Ezjsonm.value_from_channel (open_in ref_path) in

if not (json_equal py_json ref_json) then begin
Printf.printf " ✗ %s - Python figure JSON doesn't match reference\n" filename;
(* Validate the figure using Plotly's Python validate function *)
match Python.validate py_fig with
| Error err ->
Printf.printf " ✗ %s - Invalid Plotly figure (validation error: %s)\n" filename err;
false
end else begin
Printf.printf " ✓ %s (Python backend)\n" filename;
true
end
end else begin
Printf.printf " ? %s - No Python reference (run: dune exec test/generate_python_references.exe)\n" filename;
false
end
| Ok _errors ->
let py_json = Python.python_figure_to_json py_fig in

if Sys.file_exists ref_path then begin
let ref_json = Ezjsonm.value_from_channel (open_in ref_path) in

if not (json_equal py_json ref_json) then begin
Printf.printf " ✗ %s - Python figure JSON doesn't match reference\n" filename;
false
end else begin
Printf.printf " ✓ %s (Python backend)\n" filename;
true
end
end else begin
Printf.printf " ? %s - No Python reference (run: dune exec test/generate_python_references.exe)\n" filename;
false
end
with e ->
Printf.printf " ✗ %s - Python backend error: %s\n" filename (Printexc.to_string e);
false
Expand Down