mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 00:01:35 +03:00
Visualization Registry (https://github.com/enso-org/ide/pull/429)
* Implement registry.
* Refactor code to use a trait `Class` as the builder of `Visualization`s.
Original commit: 48b8d88dc3
This commit is contained in:
parent
2b5353b594
commit
de148f16b8
@ -1,29 +1,36 @@
|
|||||||
//! This module defines the visualization widgets and related functionality.
|
//! This module defines the visualization widgets and related functionality.
|
||||||
//!
|
//!
|
||||||
//! The overall architecture of visualizations consists of three parts:
|
//! The overall architecture of visualizations consists of four parts:
|
||||||
//!
|
//!
|
||||||
//! 1. The `DataRenderer` trait provides the functionality to render the actual visualization view
|
//! 1. The `DataRenderer` trait provides the functionality to render the actual visualization view
|
||||||
//! that implements `display::Object`. It is provided with data and itself provides frp streams
|
//! that implements `display::Object`. It is provided with data and itself provides frp streams
|
||||||
//! of its output if there is some, for example, if it acts as a widget.
|
//! of its output if there is some, for example, if it acts as a widget.
|
||||||
//!
|
//!
|
||||||
//! 2. The `Visualization` is a struct that wraps the `DataRenderer` and implements the generic
|
//! 2. The `Visualization` is a struct that wraps the `DataRenderer` and implements the generic
|
||||||
//! tasks that are the same for all visualisations. That is, interfacing with the other UI
|
//! tasks that are the same for all visualizations. That is, interfacing with the other UI
|
||||||
//! elements, the visualization registry, as well as propagating frp messages.
|
//! elements, the visualization registry, as well as propagating frp messages.
|
||||||
//!
|
//!
|
||||||
//! 3. The `Container` wraps the `Visualisation` and provides the UI elements that facilitate
|
//! 3. The `Class` is a struct that can instantiate multiple `Visualization` structs and provides
|
||||||
//! user interactions. For example, selecting a visualisation or connecting it to nodes in the
|
//! data about the visualization even before they are instantiated like input datatype and name.
|
||||||
|
//!
|
||||||
|
//! 4. The `Container` wraps the `Visualization` and provides the UI elements that facilitate
|
||||||
|
//! user interactions. For example, selecting a visualization or connecting it to nodes in the
|
||||||
//! graph editor scene.
|
//! graph editor scene.
|
||||||
//!
|
//!
|
||||||
//! In addition this module also contains a `Data` struct that provides a dynamically typed way to
|
//! In addition this module also contains a `Data` struct that provides a dynamically typed way to
|
||||||
//! handle data for visualisations. This allows the `Visualisation` struct to be without type
|
//! handle data for visualizations. This allows the `Visualization` struct to be without type
|
||||||
//! parameters and simplifies the FRP communication and complexity of the node system.
|
//! parameters and simplifies the FRP communication and complexity of the node system.
|
||||||
|
|
||||||
pub mod class;
|
pub mod class;
|
||||||
pub mod container;
|
pub mod container;
|
||||||
pub mod renderer;
|
|
||||||
pub mod data;
|
pub mod data;
|
||||||
|
pub mod js;
|
||||||
|
pub mod registry;
|
||||||
|
pub mod renderer;
|
||||||
|
|
||||||
pub use class::*;
|
pub use class::*;
|
||||||
pub use data::*;
|
|
||||||
pub use container::*;
|
pub use container::*;
|
||||||
|
pub use data::*;
|
||||||
|
pub use js::*;
|
||||||
|
pub use registry::*;
|
||||||
pub use renderer::*;
|
pub use renderer::*;
|
||||||
|
@ -5,8 +5,9 @@ use crate::prelude::*;
|
|||||||
use crate::frp;
|
use crate::frp;
|
||||||
use crate::visualization::*;
|
use crate::visualization::*;
|
||||||
|
|
||||||
|
use ensogl::display::Scene;
|
||||||
use ensogl::display;
|
use ensogl::display;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
|
||||||
// ====================
|
// ====================
|
||||||
@ -20,11 +21,31 @@ pub struct EnsoCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Type alias for a string representing an enso type.
|
/// Type alias for a string representing an enso type.
|
||||||
#[derive(Clone,CloneRef,Debug)]
|
#[derive(Clone,CloneRef,Debug,PartialEq,Eq,Hash)]
|
||||||
pub struct EnsoType {
|
pub struct EnsoType {
|
||||||
content: Rc<String>
|
content: Rc<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<String> for EnsoType {
|
||||||
|
fn from(source:String) -> Self {
|
||||||
|
EnsoType { content:Rc::new(source) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for EnsoType {
|
||||||
|
fn from(source:&str) -> Self {
|
||||||
|
EnsoType { content:Rc::new(source.to_string()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains general information about a visualization.
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct Signature {
|
||||||
|
pub name : String,
|
||||||
|
pub input_types : Vec<EnsoType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
@ -145,7 +166,6 @@ impl Visualization {
|
|||||||
frp.preprocess_change.emit(data.as_ref().map(|code|code.clone_ref()))
|
frp.preprocess_change.emit(data.as_ref().map(|code|code.clone_ref()))
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,3 +174,138 @@ impl Visualization {
|
|||||||
self.state.renderer.set_size(size)
|
self.state.renderer.set_size(size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// === Visualization Class ===
|
||||||
|
// ===========================
|
||||||
|
|
||||||
|
/// Indicates that instantiating a `Visualisation` from a `Class` has failed.
|
||||||
|
#[derive(Debug,Display)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum InstantiationError {
|
||||||
|
/// Indicates a problem with instantiating a class object.
|
||||||
|
InvalidClass { inner:Box<dyn Error> },
|
||||||
|
/// Indicates a problem with instantiating a visualisation from a valid class object.
|
||||||
|
InvalidVisualisation { inner:Box<dyn Error> },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of the attempt to instantiate a `Visualization` from a `Class`.
|
||||||
|
pub type InstantiationResult = Result<Visualization,InstantiationError>;
|
||||||
|
|
||||||
|
/// Specifies a trait that allows the instantiation of `Visualizations`.
|
||||||
|
///
|
||||||
|
/// The `Class` provides a way to implement structs that allow the instantiation of specific
|
||||||
|
/// visualizations, while already providing general information that doesn't require an
|
||||||
|
/// instantiated visualization, for example, the name or input type of the visualization.
|
||||||
|
///
|
||||||
|
/// There are two example implementations: The `JsSourceClass`, which is based on a JS snippet to
|
||||||
|
/// instantiate `JsRenderer`, and the fairly generic `NativeConstructorClass`, that only requires
|
||||||
|
/// a function that can create a InstantiationResult. The later can be used as a thin wrapper around
|
||||||
|
/// the constructor methods of native visualizations.
|
||||||
|
///
|
||||||
|
/// Example
|
||||||
|
/// --------
|
||||||
|
/// ```no_run
|
||||||
|
/// use graph_editor::component::visualization;
|
||||||
|
/// use graph_editor::component::visualization::Visualization;
|
||||||
|
/// use graph_editor::component::visualization::renderer::example::native::BubbleChart;
|
||||||
|
/// use ensogl::display::Scene;
|
||||||
|
/// use std::rc::Rc;
|
||||||
|
///
|
||||||
|
/// // Create a `visualization::Class` from a JS source code snippet.
|
||||||
|
/// let js_source_class = visualization::JsSourceClass::from_js_source_raw(r#"
|
||||||
|
///
|
||||||
|
/// class BubbleVisualization {
|
||||||
|
/// static inputTypes = ["[[Float,Float,Float]]"]
|
||||||
|
/// onDataReceived(root, data) {}
|
||||||
|
/// setSize(root, size) {}
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// return BubbleVisualization;
|
||||||
|
///
|
||||||
|
/// "#.into());
|
||||||
|
///
|
||||||
|
/// // Create a `visualization::Class` that instantiates a `BubbleChart`.
|
||||||
|
/// let native_bubble_vis_class = visualization::NativeConstructorClass::new(
|
||||||
|
/// visualization::Signature {
|
||||||
|
/// name : "Bubble Visualization (native)".to_string(),
|
||||||
|
/// input_types : vec!["[[Float,Float,Float]]".into()],
|
||||||
|
/// },
|
||||||
|
/// |scene:&Scene| Ok(Visualization::new(BubbleChart::new(scene)))
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub trait Class: Debug {
|
||||||
|
/// Provides additional information about the `Class`, for example, which `DataType`s can be
|
||||||
|
/// rendered by the instantiated visualization.
|
||||||
|
fn signature(&self) -> &Signature;
|
||||||
|
/// Create new visualization, that is initialised for the given scene. This can fail if the
|
||||||
|
/// `visualization::Class` contains invalid data, for example, JS code that fails to execute,
|
||||||
|
/// or if the scene is in an invalid state.
|
||||||
|
// TODO consider not providing the scene here, but hooking the the shapes/dom elements into the
|
||||||
|
// scene externally.
|
||||||
|
fn instantiate(&self, scene:&Scene) -> InstantiationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for `Class` objects, so they can be passed through the FRP system.
|
||||||
|
#[derive(Clone,Debug,Default)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct Handle {
|
||||||
|
class : Option<Rc<dyn Class>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handle {
|
||||||
|
/// Constructor.
|
||||||
|
pub fn new<T:Class+'static>(class:T) -> Handle {
|
||||||
|
let class = Rc::new(class);
|
||||||
|
Handle {class:Some(class)}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the inner class.
|
||||||
|
pub fn class(&self) -> Option<Rc<dyn Class>> {
|
||||||
|
self.class.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CloneRef for Handle {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// === Native Constructor Class ===
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
/// Type alias for a function that can create a `Visualization`.
|
||||||
|
pub trait VisualizationConstructor = Fn(&Scene) -> InstantiationResult;
|
||||||
|
|
||||||
|
/// Constructor that instantiates visualisations from a given `VisualizationConstructor`. Can be
|
||||||
|
/// used to wrap the constructor of visualizations defined in Rust.
|
||||||
|
#[derive(CloneRef,Clone,Derivative)]
|
||||||
|
#[derivative(Debug)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct NativeConstructorClass {
|
||||||
|
info : Rc<Signature>,
|
||||||
|
#[derivative(Debug="ignore")]
|
||||||
|
constructor : Rc<dyn VisualizationConstructor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NativeConstructorClass {
|
||||||
|
/// Create a visualization source from a closure that returns a `Visualization`.
|
||||||
|
pub fn new<T>(info:Signature, constructor:T) -> Self
|
||||||
|
where T: VisualizationConstructor + 'static {
|
||||||
|
let info = Rc::new(info);
|
||||||
|
let constructor = Rc::new(constructor);
|
||||||
|
NativeConstructorClass{info,constructor}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Class for NativeConstructorClass {
|
||||||
|
fn signature(&self) -> &Signature {
|
||||||
|
&self.info
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiate(&self, scene:&Scene) -> InstantiationResult {
|
||||||
|
self.constructor.call((scene,))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -46,7 +46,7 @@ impl Default for ContainerFrp {
|
|||||||
|
|
||||||
/// Container that wraps a `Visualization` for rendering and interaction in the GUI.
|
/// Container that wraps a `Visualization` for rendering and interaction in the GUI.
|
||||||
///
|
///
|
||||||
/// The API to interact with the visualisation is exposed through the `ContainerFrp`.
|
/// The API to interact with the visualization is exposed through the `ContainerFrp`.
|
||||||
#[derive(Clone,CloneRef,Debug,Shrinkwrap)]
|
#[derive(Clone,CloneRef,Debug,Shrinkwrap)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
@ -72,7 +72,7 @@ pub struct ContainerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerData {
|
impl ContainerData {
|
||||||
/// Set whether the visualisation should be visible or not.
|
/// Set whether the visualization should be visible or not.
|
||||||
pub fn set_visibility(&self, is_visible:bool) {
|
pub fn set_visibility(&self, is_visible:bool) {
|
||||||
if let Some(vis) = self.visualization.borrow().as_ref() {
|
if let Some(vis) = self.visualization.borrow().as_ref() {
|
||||||
if is_visible {
|
if is_visible {
|
||||||
@ -83,7 +83,7 @@ impl ContainerData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates whether the visualisation is visible.
|
/// Indicates whether the visualization is visible.
|
||||||
fn is_visible(&self) -> bool {
|
fn is_visible(&self) -> bool {
|
||||||
if let Some(vis) = self.visualization.borrow().as_ref() {
|
if let Some(vis) = self.visualization.borrow().as_ref() {
|
||||||
vis.has_parent()
|
vis.has_parent()
|
||||||
@ -99,8 +99,8 @@ impl ContainerData {
|
|||||||
|
|
||||||
/// Update the content properties with the values from the `ContainerData`.
|
/// Update the content properties with the values from the `ContainerData`.
|
||||||
///
|
///
|
||||||
/// Needs to called when a visualisation has been set.
|
/// Needs to called when a visualization has been set.
|
||||||
fn init_visualisation_properties(&self) {
|
fn init_visualization_properties(&self) {
|
||||||
let size = self.size.get();
|
let size = self.size.get();
|
||||||
if let Some(vis) = self.visualization.borrow().as_ref() {
|
if let Some(vis) = self.visualization.borrow().as_ref() {
|
||||||
vis.set_size(size);
|
vis.set_size(size);
|
||||||
@ -109,10 +109,10 @@ impl ContainerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set the visualization shown in this container..
|
/// Set the visualization shown in this container..
|
||||||
fn set_visualisation(&self, visualization:Visualization) {
|
fn set_visualization(&self, visualization:Visualization) {
|
||||||
self.add_child(&visualization);
|
self.add_child(&visualization);
|
||||||
self.visualization.replace(Some(visualization));
|
self.visualization.replace(Some(visualization));
|
||||||
self.init_visualisation_properties();
|
self.init_visualization_properties();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,9 +152,9 @@ impl Container {
|
|||||||
container_data.toggle_visibility()
|
container_data.toggle_visibility()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
def _f_set_vis = frp.set_visualization.map(f!([container_data](visualisation) {
|
def _f_set_vis = frp.set_visualization.map(f!([container_data](visualization) {
|
||||||
if let Some(visualisation) = visualisation.as_ref() {
|
if let Some(visualization) = visualization.as_ref() {
|
||||||
container_data.set_visualisation(visualisation.clone());
|
container_data.set_visualization(visualization.clone());
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -9,13 +9,13 @@ use serde::Deserialize;
|
|||||||
|
|
||||||
|
|
||||||
// ======================================
|
// ======================================
|
||||||
// === Wrapper for Visualisation Data ===
|
// === Wrapper for Visualization Data ===
|
||||||
// =======================================
|
// =======================================
|
||||||
|
|
||||||
/// Type indicator
|
/// Type indicator
|
||||||
pub type DataType = EnsoType;
|
pub type DataType = EnsoType;
|
||||||
|
|
||||||
/// Wrapper for data that can be consumed by a visualisation.
|
/// Wrapper for data that can be consumed by a visualization.
|
||||||
/// TODO[mm] consider static versus dynamic typing for visualizations and data!
|
/// TODO[mm] consider static versus dynamic typing for visualizations and data!
|
||||||
#[derive(Clone,CloneRef,Debug)]
|
#[derive(Clone,CloneRef,Debug)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
@ -70,7 +70,7 @@ impl Data {
|
|||||||
pub enum DataError {
|
pub enum DataError {
|
||||||
/// Indicates that that the provided data type does not match the expected data type.
|
/// Indicates that that the provided data type does not match the expected data type.
|
||||||
InvalidDataType,
|
InvalidDataType,
|
||||||
/// The data caused an error in the computation of the visualisation.
|
/// The data caused an error in the computation of the visualization.
|
||||||
InternalComputationError,
|
InternalComputationError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
133
gui/src/rust/lib/graph-editor/src/component/visualization/js.rs
Normal file
133
gui/src/rust/lib/graph-editor/src/component/visualization/js.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
//! This module contains functionality to create a `Class` object from a JS source strings.
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::component::visualization::JsVisualizationError;
|
||||||
|
use crate::component::visualization::InstantiationError;
|
||||||
|
use crate::component::visualization::JsRenderer;
|
||||||
|
use crate::component::visualization::JsResult;
|
||||||
|
use crate::component::visualization::InstantiationResult;
|
||||||
|
use crate::component::visualization::Class;
|
||||||
|
use crate::component::visualization::Visualization;
|
||||||
|
use crate::component::visualization::Signature;
|
||||||
|
use crate::component::visualization::EnsoType;
|
||||||
|
|
||||||
|
use ensogl::display::Scene;
|
||||||
|
use ensogl::system::web::JsValue;
|
||||||
|
use js_sys;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// === Visualization Class Wrapper ===
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
/// Internal wrapper for the a JS class that implements our visualization specification. Provides
|
||||||
|
/// convenience functions for accessing JS methods and signature.
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
struct VisualizationClassWrapper {
|
||||||
|
class: JsValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisualizationClassWrapper {
|
||||||
|
fn instantiate_class(source:&str) -> JsResult<VisualizationClassWrapper> {
|
||||||
|
let context = JsValue::NULL;
|
||||||
|
let constructor = js_sys::Function::new_no_args(source);
|
||||||
|
let class = constructor.call0(&context)?;
|
||||||
|
Ok(VisualizationClassWrapper{class})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> JsResult<Signature> {
|
||||||
|
let input_types = self.input_types().unwrap_or_default();
|
||||||
|
let name = self.name()?;
|
||||||
|
Ok(Signature {name,input_types})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn constructor(&self) -> JsResult<js_sys::Function> {
|
||||||
|
Ok(js_sys::Reflect::get(&self.prototype()?,&"constructor".into())?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prototype(&self) -> JsResult<JsValue> {
|
||||||
|
Ok(js_sys::Reflect::get(&self.class,&"prototype".into())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_types(&self) -> JsResult<Vec<EnsoType>> {
|
||||||
|
let input_types = js_sys::Reflect::get(&self.class, &"inputTypes".into())?;
|
||||||
|
let input_types = js_sys::Array::from(&input_types);
|
||||||
|
let js_string_to_enso_type = |value:JsValue| {Some(EnsoType::from(value.as_string()?))};
|
||||||
|
Ok(input_types.iter().filter_map(js_string_to_enso_type).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> JsResult<String> {
|
||||||
|
let constructor = self.constructor()?;
|
||||||
|
let name = js_sys::Reflect::get(&constructor,&"name".into())?;
|
||||||
|
Ok(name.as_string().unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiate(&self) -> JsResult<JsValue> {
|
||||||
|
let fn_wrapper = js_sys::Function::new_with_args("cls", "return new cls()");
|
||||||
|
let context = JsValue::NULL;
|
||||||
|
Ok(fn_wrapper.call1(&context, &self.class)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ========================
|
||||||
|
// === Js Source Class ===
|
||||||
|
// ========================
|
||||||
|
|
||||||
|
/// Implements the `visualization::Class` for a JS source string.
|
||||||
|
///
|
||||||
|
/// Example
|
||||||
|
/// -------
|
||||||
|
/// ```no_run
|
||||||
|
///
|
||||||
|
/// # use graph_editor::component::visualization::JsSourceClass;
|
||||||
|
///
|
||||||
|
/// JsSourceClass::from_js_source_raw(r#"
|
||||||
|
/// class Visualization {
|
||||||
|
/// static inputTypes = ["[[Float,Float,Float]]"]
|
||||||
|
/// onDataReceived(root, data) {}
|
||||||
|
/// setSize(root, size) {}
|
||||||
|
/// }
|
||||||
|
/// return Visualizations;
|
||||||
|
/// "#.into()).unwrap();
|
||||||
|
/// ```
|
||||||
|
#[derive(CloneRef,Clone,Debug)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct JsSourceClass {
|
||||||
|
js_class : Rc<VisualizationClassWrapper>,
|
||||||
|
signature: Rc<Signature>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsSourceClass {
|
||||||
|
/// Create a visualization source from piece of JS source code. Signature needs to be inferred.
|
||||||
|
pub fn from_js_source_raw(source:&str) -> Result<Self,JsVisualizationError> {
|
||||||
|
let js_class = VisualizationClassWrapper::instantiate_class(&source)?;
|
||||||
|
let signature = js_class.signature()?;
|
||||||
|
let js_class = Rc::new(js_class);
|
||||||
|
let signature = Rc::new(signature);
|
||||||
|
Ok(JsSourceClass{js_class,signature})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Class for JsSourceClass {
|
||||||
|
fn signature(&self) -> &Signature {
|
||||||
|
&self.signature
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiate(&self, scene:&Scene) -> InstantiationResult {
|
||||||
|
let obj = match self.js_class.instantiate() {
|
||||||
|
Ok(obj) => obj,
|
||||||
|
Err(err) => return Err(InstantiationError::InvalidClass {inner:err.into()}),
|
||||||
|
};
|
||||||
|
let renderer = match JsRenderer::from_object(obj) {
|
||||||
|
Ok(renderer) => renderer,
|
||||||
|
Err(err) => return Err(InstantiationError::InvalidClass {inner:err.into()}),
|
||||||
|
};
|
||||||
|
renderer.set_dom_layer(&scene.dom.layers.front);
|
||||||
|
Ok(Visualization::new(renderer))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
//! The `Registry` provides a mechanism to store `visualization::Class`es for all available visualizations. It
|
||||||
|
//! provides functionality to register new factories, as well as get suitable factories for
|
||||||
|
//! a specific data type.
|
||||||
|
//!
|
||||||
|
//! Example
|
||||||
|
//! --------
|
||||||
|
//! ```no_run
|
||||||
|
//! use graph_editor::component::visualization::Registry;
|
||||||
|
//! use graph_editor::component::visualization::EnsoType;
|
||||||
|
//! use graph_editor::component::visualization::JsSourceClass;
|
||||||
|
//!
|
||||||
|
//! // Instantiate a pre-populated registry.
|
||||||
|
//! let registry = Registry::with_default_visualizations();
|
||||||
|
//! // Add a new class that creates visualizations defined in JS.
|
||||||
|
//! registry.register_class(JsSourceClass::from_js_source_raw(r#"
|
||||||
|
//! class BubbleVisualization {
|
||||||
|
//! static inputTypes = ["[[Float,Float,Float]]"]
|
||||||
|
//! onDataReceived(root, data) {}
|
||||||
|
//! setSize(root, size) {}
|
||||||
|
//! }
|
||||||
|
//! return BubbleVisualization;
|
||||||
|
//! "#.into()).unwrap());
|
||||||
|
//!
|
||||||
|
//! // Get all factories that can render visualization for the type `[[Float,Float,Float]]`.
|
||||||
|
//! let target_type:EnsoType = "[[Float,Float,Float]]".to_string().into();
|
||||||
|
//! assert!(registry.valid_sources(&target_type).len() > 0);
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use crate::component::visualization::*;
|
||||||
|
use crate::component::visualization::renderer::example::js::get_bubble_vis_class;
|
||||||
|
use crate::component::visualization::renderer::example::native::BubbleChart;
|
||||||
|
|
||||||
|
use ensogl::display::scene::Scene;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ==============================
|
||||||
|
// === Visualization Registry ===
|
||||||
|
// ==============================
|
||||||
|
|
||||||
|
/// HashMap that contains the mapping from `EnsoType`s to a `Vec` of `Factories. This is meant to
|
||||||
|
/// map a `EnsoType` to all `visualization::Class`es that support visualising that type.
|
||||||
|
type RegistryTypeMap = HashMap<EnsoType, Vec<Rc<dyn Class>>>;
|
||||||
|
|
||||||
|
/// The registry struct. For more information see the module description.
|
||||||
|
#[derive(Clone,CloneRef,Default,Debug)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct Registry {
|
||||||
|
entries : Rc<RefCell<RegistryTypeMap>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Registry {
|
||||||
|
/// Return an empty `Registry`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a `Registry` prepopulated with default visualizations.
|
||||||
|
pub fn with_default_visualizations() -> Self {
|
||||||
|
let registry = Self::new();
|
||||||
|
// FIXME use proper enso types here.
|
||||||
|
registry.register_class(NativeConstructorClass::new(
|
||||||
|
Signature {
|
||||||
|
name : "Bubble Visualization (native)".to_string(),
|
||||||
|
input_types : vec!["[[Float,Float,Float]]".to_string().into()],
|
||||||
|
},
|
||||||
|
|scene:&Scene| Ok(Visualization::new(BubbleChart::new(scene)))
|
||||||
|
));
|
||||||
|
registry.register_class(get_bubble_vis_class());
|
||||||
|
|
||||||
|
registry
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a new visualization class with the registry.
|
||||||
|
pub fn register_class<T:Class+'static>(&self, class:T) {
|
||||||
|
self.register_class_rc(Rc::new(class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a new visualization class that's pre-wrapped in an `Rc` with the registry.
|
||||||
|
pub fn register_class_from_handle(&self, handle:&Handle) {
|
||||||
|
if let Some(class) = handle.class() {
|
||||||
|
self.register_class_rc(class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_class_rc(&self, class:Rc<dyn Class>) {
|
||||||
|
let spec = class.signature();
|
||||||
|
for dtype in &spec.input_types {
|
||||||
|
let mut entries = self.entries.borrow_mut();
|
||||||
|
let entry_vec = entries.entry(dtype.clone()).or_insert_with(default);
|
||||||
|
entry_vec.push(Rc::clone(&class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all `visualization::Class`es that can create a visualization for the given datatype.
|
||||||
|
pub fn valid_sources(&self, dtype:&EnsoType) -> Vec<Rc<dyn Class>>{
|
||||||
|
let entries = self.entries.borrow();
|
||||||
|
entries.get(dtype).cloned().unwrap_or_else(default)
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ use ensogl::display;
|
|||||||
pub struct DataRendererFrp {
|
pub struct DataRendererFrp {
|
||||||
pub network : frp::Network,
|
pub network : frp::Network,
|
||||||
/// This is emitted if the state of the renderer has been changed by UI interaction.
|
/// This is emitted if the state of the renderer has been changed by UI interaction.
|
||||||
/// It contains the output data of this visualisation if there is some.
|
/// It contains the output data of this visualization if there is some.
|
||||||
pub on_change : frp::Stream<Option<EnsoCode>>,
|
pub on_change : frp::Stream<Option<EnsoCode>>,
|
||||||
/// Will be emitted if the visualization changes it's preprocessor. Transmits the new
|
/// Will be emitted if the visualization changes it's preprocessor. Transmits the new
|
||||||
/// preprocessor code.
|
/// preprocessor code.
|
||||||
@ -53,7 +53,7 @@ impl Default for DataRendererFrp {
|
|||||||
/// producing a `display::Object` that will be shown in the scene. It will create FRP events to
|
/// producing a `display::Object` that will be shown in the scene. It will create FRP events to
|
||||||
/// indicate updates to its output data (e.g., through user interaction).
|
/// indicate updates to its output data (e.g., through user interaction).
|
||||||
///
|
///
|
||||||
/// A DataRenderer can indicate what kind of data it can use to create a visualisation through the
|
/// A DataRenderer can indicate what kind of data it can use to create a visualization through the
|
||||||
/// `valid_input_types` method. This serves as a hint, it will also reject invalid input in the
|
/// `valid_input_types` method. This serves as a hint, it will also reject invalid input in the
|
||||||
/// `set_data` method with a `DataError`. The owner of the `DataRenderer` is in charge of producing
|
/// `set_data` method with a `DataError`. The owner of the `DataRenderer` is in charge of producing
|
||||||
/// UI feedback to indicate a problem with the data.
|
/// UI feedback to indicate a problem with the data.
|
||||||
@ -67,7 +67,7 @@ pub trait DataRenderer: display::Object + Debug {
|
|||||||
/// processed by this `DataRenderer`, if the data is of an invalid data type, it violates other
|
/// processed by this `DataRenderer`, if the data is of an invalid data type, it violates other
|
||||||
/// assumptions of this `DataRenderer`, a `DataError` is returned.
|
/// assumptions of this `DataRenderer`, a `DataError` is returned.
|
||||||
fn receive_data(&self, data:Data) -> Result<(), DataError>;
|
fn receive_data(&self, data:Data) -> Result<(), DataError>;
|
||||||
/// Set the size of viewport of the visualization. The visualisation must not render outside of
|
/// Set the size of viewport of the visualization. The visualization must not render outside of
|
||||||
/// this viewport.
|
/// this viewport.
|
||||||
fn set_size(&self, size:Vector2<f32>);
|
fn set_size(&self, size:Vector2<f32>);
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! The `example` modules contains some examples of visualisations.
|
//! The `example` modules contains some examples of visualizations.
|
||||||
//! TODO further describe the examples, how they work and how to extend them.
|
//! TODO further describe the examples, how they work and how to extend them.
|
||||||
|
|
||||||
pub mod native;
|
pub mod native;
|
||||||
|
@ -1,104 +1,15 @@
|
|||||||
//! Example of the visualisation JS wrapper API usage
|
//! Example of the visualization JS wrapper API usage
|
||||||
// TODO remove once we have proper visualizations or replace with a nice d3 example.
|
// TODO remove once we have proper visualizations or replace with a nice d3 example.
|
||||||
// These implementations are neither efficient nor pretty, but get the idea across.
|
// These implementations are neither efficient nor pretty, but get the idea across.
|
||||||
|
|
||||||
use crate::component::visualization::JsRenderer;
|
use crate::component::visualization::JsSourceClass;
|
||||||
|
|
||||||
/// Returns a simple bubble chart implemented in vanilla JS. uses single functions to implement the
|
/// Return an `JsSourceClass` that creates example Bubble Visualizations implemented in JS.
|
||||||
/// visualization.
|
pub fn get_bubble_vis_class() -> JsSourceClass {
|
||||||
pub fn function_sample_js_bubble_chart() -> JsRenderer {
|
|
||||||
let fn_set_data = r#"{
|
|
||||||
const xmlns = "http://www.w3.org/2000/svg";
|
|
||||||
const root = arguments[0];
|
|
||||||
while (root.firstChild) {
|
|
||||||
root.removeChild(root.lastChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
const svgElem = document.createElementNS(xmlns, "svg");
|
|
||||||
svgElem.setAttributeNS(null, "id" , "vis-svg");
|
|
||||||
svgElem.setAttributeNS(null, "viewBox", "0 0 " + 100 + " " + 100);
|
|
||||||
svgElem.setAttributeNS(null, "width" , 100);
|
|
||||||
svgElem.setAttributeNS(null, "height" , 100);
|
|
||||||
root.appendChild(svgElem);
|
|
||||||
|
|
||||||
const data = arguments[1];
|
|
||||||
data.forEach(data => {
|
|
||||||
const bubble = document.createElementNS(xmlns,"circle");
|
|
||||||
bubble.setAttributeNS(null,"stroke", "black");
|
|
||||||
bubble.setAttributeNS(null,"fill" , "red");
|
|
||||||
bubble.setAttributeNS(null,"r" , data[2]);
|
|
||||||
bubble.setAttributeNS(null,"cx" , data[0]);
|
|
||||||
bubble.setAttributeNS(null,"cy" , data[1]);
|
|
||||||
svgElem.appendChild(bubble);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let fn_set_size = r#"{
|
|
||||||
const root = arguments[0];
|
|
||||||
const width = arguments[1][0];
|
|
||||||
const height = arguments[1][1];
|
|
||||||
const svgElem = root.firstChild;
|
|
||||||
svgElem.setAttributeNS(null, "viewBox", "0 0 " + width + " " + height);
|
|
||||||
svgElem.setAttributeNS(null, "width" , width);
|
|
||||||
svgElem.setAttributeNS(null, "height" , height);
|
|
||||||
}"#;
|
|
||||||
JsRenderer::from_functions(fn_set_data, fn_set_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a simple bubble chart implemented in vanilla JS. Uses an object to implement the
|
|
||||||
/// visualization logic.
|
|
||||||
pub fn object_sample_js_bubble_chart() -> JsRenderer {
|
|
||||||
let fn_prototype = r#"
|
|
||||||
(() => {
|
|
||||||
class BubbleVisualisation {
|
|
||||||
onDataReceived(root, data) {
|
|
||||||
const xmlns = "http://www.w3.org/2000/svg";
|
|
||||||
while (root.firstChild) {
|
|
||||||
root.removeChild(root.lastChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
const svgElem = document.createElementNS(xmlns, "svg");
|
|
||||||
svgElem.setAttributeNS(null, "id" , "vis-svg");
|
|
||||||
svgElem.setAttributeNS(null, "viewBox", "0 0 " + 100 + " " + 100);
|
|
||||||
svgElem.setAttributeNS(null, "width" , 100);
|
|
||||||
svgElem.setAttributeNS(null, "height" , 100);
|
|
||||||
root.appendChild(svgElem);
|
|
||||||
|
|
||||||
data.forEach(data => {
|
|
||||||
const bubble = document.createElementNS(xmlns,"circle");
|
|
||||||
bubble.setAttributeNS(null,"stroke", "black");
|
|
||||||
bubble.setAttributeNS(null,"fill" , "red");
|
|
||||||
bubble.setAttributeNS(null,"r" , data[2]);
|
|
||||||
bubble.setAttributeNS(null,"cx" , data[0]);
|
|
||||||
bubble.setAttributeNS(null,"cy" , data[1]);
|
|
||||||
svgElem.appendChild(bubble);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setSize(root, size) {
|
|
||||||
const width = size[0];
|
|
||||||
const height = size[1];
|
|
||||||
const svgElem = root.firstChild;
|
|
||||||
svgElem.setAttributeNS(null, "viewBox", "0 0 " + width + " " + height);
|
|
||||||
svgElem.setAttributeNS(null, "width" , width);
|
|
||||||
svgElem.setAttributeNS(null, "height" , height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BubbleVisualisation();
|
|
||||||
})()
|
|
||||||
"#;
|
|
||||||
JsRenderer::from_object(fn_prototype).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Returns a simple bubble chart implemented in vanilla JS. uses single functions to implement the
|
|
||||||
/// visualization.
|
|
||||||
pub fn constructor_sample_js_bubble_chart() -> JsRenderer {
|
|
||||||
let fn_constructor = r#"
|
let fn_constructor = r#"
|
||||||
class BubbleVisualisation {
|
class BubbleVisualization {
|
||||||
|
static inputTypes = ["[[Float,Float,Float]]"]
|
||||||
|
|
||||||
onDataReceived(root, data) {
|
onDataReceived(root, data) {
|
||||||
const xmlns = "http://www.w3.org/2000/svg";
|
const xmlns = "http://www.w3.org/2000/svg";
|
||||||
while (root.firstChild) {
|
while (root.firstChild) {
|
||||||
@ -133,7 +44,8 @@ pub fn constructor_sample_js_bubble_chart() -> JsRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BubbleVisualisation();
|
return BubbleVisualization;
|
||||||
"#;
|
"#;
|
||||||
JsRenderer::from_constructor(fn_constructor).unwrap()
|
|
||||||
|
JsSourceClass::from_js_source_raw(fn_constructor).unwrap()
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Examples of defining visualisation in Rust using web_sys or ensogl.
|
//! Examples of defining visualization in Rust using web_sys or ensogl.
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! This module contains functionality that allows the usage of JavaScript to define visualizations.
|
//! This module contains functionality that allows the usage of JavaScript to define visualizations.
|
||||||
//!
|
//!
|
||||||
//! The `JsRenderer` defines a generic way to wrap JS function calls and allow interaction with
|
//! The `JsRenderer` defines a generic way to wrap JS function calls and allow interaction with
|
||||||
//! JS code and the visualisation system.
|
//! JS code and the visualization system.
|
||||||
//!
|
//!
|
||||||
//! There are at the moment three way to generate a `JsRenderer`:
|
//! There are at the moment three way to generate a `JsRenderer`:
|
||||||
//! 1. `JsRenderer::from_functions` where the bodies of the required functions are provided as
|
//! 1. `JsRenderer::from_functions` where the bodies of the required functions are provided as
|
||||||
@ -11,12 +11,15 @@
|
|||||||
//! 3. `JsRenderer::from_constructor`where the body of a constructor function needs to be
|
//! 3. `JsRenderer::from_constructor`where the body of a constructor function needs to be
|
||||||
//! provided. The returned object needs to fulfill the same specification as in (2).
|
//! provided. The returned object needs to fulfill the same specification as in (2).
|
||||||
//!
|
//!
|
||||||
//! Right now the only functions required on the wrapped object are
|
//! Right now the only functions supported on the wrapped object are
|
||||||
//! * `onDataReceived(root, data)`, which receives the html element that the visualisation should be
|
//! * `onDataReceived(root, data)`, which receives the html element that the visualization should be
|
||||||
//! appended on, as well as the data that should be rendered.
|
//! appended on, as well as the data that should be rendered.
|
||||||
//! * `setSize(root, size)`, which receives the node that the visualisation should be appended on,
|
//! * `setSize(root, size)`, which receives the node that the visualization should be appended on,
|
||||||
//! as well as the intended size.
|
//! as well as the intended size.
|
||||||
//!
|
//!
|
||||||
|
//! All functions on the class are optional, and methods that are not present, will be handled as
|
||||||
|
//! no-op by the `JsRenderer`.
|
||||||
|
//!
|
||||||
//! TODO: refine spec and add functions as needed, e.g., init, callback hooks or type indicators.
|
//! TODO: refine spec and add functions as needed, e.g., init, callback hooks or type indicators.
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@ -32,6 +35,7 @@ use ensogl::display;
|
|||||||
use ensogl::system::web::JsValue;
|
use ensogl::system::web::JsValue;
|
||||||
use ensogl::system::web;
|
use ensogl::system::web;
|
||||||
use js_sys;
|
use js_sys;
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -42,27 +46,49 @@ use js_sys;
|
|||||||
/// Errors that can occur when transforming JS source to a visualization.
|
/// Errors that can occur when transforming JS source to a visualization.
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum JsVisualisationError {
|
pub enum JsVisualizationError {
|
||||||
NotAnObject { inner:JsValue },
|
NotAnObject { inner:JsValue },
|
||||||
NotAFunction { inner:JsValue },
|
NotAFunction { inner:JsValue },
|
||||||
/// An unknown error occurred on the JS side. Inspect the content for more information. .
|
/// An unknown error occurred on the JS side. Inspect the content for more information.
|
||||||
Unknown { inner:JsValue }
|
Unknown { inner:JsValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<JsValue> for JsVisualisationError {
|
impl Display for JsVisualizationError {
|
||||||
fn from(value:JsValue) -> Self {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
// TODO add differentiation if we encounter specific errors and return new variants.
|
// TODO find a nice way to circumvent the fact that `JsValue` does not implement `Display`.
|
||||||
JsVisualisationError::Unknown {inner:value}
|
match self {
|
||||||
|
JsVisualizationError::NotAnObject { inner } => {
|
||||||
|
f.write_fmt(format_args!("Not an object: {:?}",inner))
|
||||||
|
},
|
||||||
|
JsVisualizationError::NotAFunction { inner } => {
|
||||||
|
f.write_fmt(format_args!("Not a function: {:?}",inner))
|
||||||
|
},
|
||||||
|
JsVisualizationError::Unknown { inner } => {
|
||||||
|
f.write_fmt(format_args!("Unknown: {:?}",inner))
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for JsVisualizationError {}
|
||||||
|
|
||||||
|
impl From<JsValue> for JsVisualizationError {
|
||||||
|
fn from(value:JsValue) -> Self {
|
||||||
|
// TODO add differentiation if we encounter specific errors and return new variants.
|
||||||
|
JsVisualizationError::Unknown {inner:value}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal helper type to propagate results that can fail due to `JsVisualizationError`s.
|
||||||
|
pub(crate) type JsResult<T> = Result<T, JsVisualizationError>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==================
|
// ==================
|
||||||
// === JsRenderer ===
|
// === JsRenderer ===
|
||||||
// ==================
|
// ==================
|
||||||
|
|
||||||
/// `JsVisualizationGeneric` allows the use of arbitrary javascript to create visualisations. It
|
/// `JsVisualizationGeneric` allows the use of arbitrary javascript to create visualizations. It
|
||||||
/// takes function definitions as strings and proved those functions with data.
|
/// takes function definitions as strings and proved those functions with data.
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
@ -81,11 +107,11 @@ impl JsRenderer {
|
|||||||
/// code will be executed as the function body of the respective functions.
|
/// code will be executed as the function body of the respective functions.
|
||||||
///
|
///
|
||||||
/// `fn_set_data` will be called with two arguments: the first argument (`root) )will be the
|
/// `fn_set_data` will be called with two arguments: the first argument (`root) )will be the
|
||||||
/// root node that the visualisation should use to build its output, the second argument
|
/// root node that the visualization should use to build its output, the second argument
|
||||||
/// (`data`)will be the data that it should visualise.
|
/// (`data`)will be the data that it should visualise.
|
||||||
///
|
///
|
||||||
/// `fn_set_size` will be called with a tuple of floating point values indicating the desired
|
/// `fn_set_size` will be called with a tuple of floating point values indicating the desired
|
||||||
/// width and height. This can be used by the visualisation to ensure proper scaling.
|
/// width and height. This can be used by the visualization to ensure proper scaling.
|
||||||
///
|
///
|
||||||
/// For a full example see
|
/// For a full example see
|
||||||
/// `crate::component::visualization::renderer::example::function_sample_js_bubble_chart`
|
/// `crate::component::visualization::renderer::example::function_sample_js_bubble_chart`
|
||||||
@ -103,14 +129,14 @@ impl JsRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Internal helper that tries to convert a JS object into a `JsRenderer`.
|
/// Internal helper that tries to convert a JS object into a `JsRenderer`.
|
||||||
fn from_object_js(object:js_sys::Object) -> Result<JsRenderer,JsVisualisationError> {
|
fn from_object_js(object:js_sys::Object) -> Result<JsRenderer,JsVisualizationError> {
|
||||||
let set_data = js_sys::Reflect::get(&object,&"onDataReceived".into())?;
|
let set_data = js_sys::Reflect::get(&object,&"onDataReceived".into())?;
|
||||||
let set_size = js_sys::Reflect::get(&object,&"setSize".into())?;
|
let set_size = js_sys::Reflect::get(&object,&"setSize".into())?;
|
||||||
if !set_data.is_function() {
|
if !set_data.is_function() {
|
||||||
return Err(JsVisualisationError::NotAFunction { inner:set_data })
|
return Err(JsVisualizationError::NotAFunction { inner:set_data })
|
||||||
}
|
}
|
||||||
if !set_size.is_function() {
|
if !set_size.is_function() {
|
||||||
return Err(JsVisualisationError::NotAFunction { inner:set_size })
|
return Err(JsVisualizationError::NotAFunction { inner:set_size })
|
||||||
}
|
}
|
||||||
let set_data:js_sys::Function = set_data.into();
|
let set_data:js_sys::Function = set_data.into();
|
||||||
let set_size:js_sys::Function = set_size.into();
|
let set_size:js_sys::Function = set_size.into();
|
||||||
@ -132,22 +158,30 @@ impl JsRenderer {
|
|||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use graph_editor::component::visualization::JsRenderer;
|
/// use graph_editor::component::visualization::JsRenderer;
|
||||||
///
|
///
|
||||||
/// let renderer = JsRenderer::from_object("function() {
|
/// let renderer = JsRenderer::from_object_source(r#"function() {
|
||||||
/// class Visualization {
|
/// class Visualization {
|
||||||
|
/// static inputTypes = ["[[Float,Float,Float]]"]
|
||||||
/// onDataReceived(root, data) {};
|
/// onDataReceived(root, data) {};
|
||||||
/// setSize(root, size) {};
|
/// setSize(root, size) {};
|
||||||
/// }
|
/// }
|
||||||
/// return new Visualisation();
|
/// return Visualization;
|
||||||
/// }()").unwrap();
|
/// }()"#).unwrap();
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// For a full example see
|
/// For a full example see
|
||||||
/// `crate::component::visualization::renderer::example::object_sample_js_bubble_chart`
|
/// `crate::component::visualization::renderer::example::object_sample_js_bubble_chart`
|
||||||
pub fn from_object(source: &str) -> Result<JsRenderer,JsVisualisationError> {
|
pub fn from_object_source(source: &str) -> Result<JsRenderer,JsVisualizationError> {
|
||||||
let object = js_sys::eval(source)?;
|
let object = js_sys::eval(source)?;
|
||||||
if !object.is_object() {
|
if !object.is_object() {
|
||||||
return Err(JsVisualisationError::NotAnObject { inner:object } )
|
return Err(JsVisualizationError::NotAnObject { inner:object } )
|
||||||
|
}
|
||||||
|
Self::from_object_js(object.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_object(object:JsValue) -> Result<JsRenderer,JsVisualizationError> {
|
||||||
|
if !object.is_object() {
|
||||||
|
return Err(JsVisualizationError::NotAnObject { inner:object } )
|
||||||
}
|
}
|
||||||
Self::from_object_js(object.into())
|
Self::from_object_js(object.into())
|
||||||
}
|
}
|
||||||
@ -165,26 +199,25 @@ impl JsRenderer {
|
|||||||
/// onDataReceived(root, data) {};
|
/// onDataReceived(root, data) {};
|
||||||
/// setSize(root, size) {};
|
/// setSize(root, size) {};
|
||||||
/// }
|
/// }
|
||||||
/// return new Visualisation();
|
/// return Visualization;
|
||||||
/// ").unwrap();
|
/// ").unwrap();
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// For a full example see
|
/// For a full example see
|
||||||
/// `crate::component::visualization::renderer::example::constructor_sample_js_bubble_chart`
|
/// `crate::component::visualization::renderer::example::constructor_sample_js_bubble_chart`
|
||||||
pub fn from_constructor(source:&str) -> Result<JsRenderer,JsVisualisationError> {
|
pub fn from_constructor(source:&str) -> Result<JsRenderer,JsVisualizationError> {
|
||||||
let context = JsValue::NULL;
|
let context = JsValue::NULL;
|
||||||
let constructor = js_sys::Function::new_no_args(source);
|
let constructor = js_sys::Function::new_no_args(source);
|
||||||
let object = constructor.call0(&context)?;
|
let object = constructor.call0(&context)?;
|
||||||
if !object.is_object() {
|
if !object.is_object() {
|
||||||
return Err(JsVisualisationError::NotAnObject { inner:object } )
|
return Err(JsVisualizationError::NotAnObject { inner:object } )
|
||||||
}
|
}
|
||||||
Self::from_object_js(object.into())
|
Self::from_object_js(object.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hooks the root node into the given scene.
|
/// Hooks the root node into the given scene.
|
||||||
///
|
///
|
||||||
/// MUST be called to make this visualisation visible.
|
/// MUST be called to make this visualization visible.
|
||||||
// TODO[mm] find a better mechanism to ensure this. Probably through the registry later on.
|
|
||||||
pub fn set_dom_layer(&self, scene:&DomScene) {
|
pub fn set_dom_layer(&self, scene:&DomScene) {
|
||||||
scene.manage(&self.root_node);
|
scene.manage(&self.root_node);
|
||||||
}
|
}
|
||||||
|
@ -56,9 +56,7 @@ use nalgebra::Vector2;
|
|||||||
use ensogl::display::Scene;
|
use ensogl::display::Scene;
|
||||||
use crate::component::visualization::Visualization;
|
use crate::component::visualization::Visualization;
|
||||||
use crate::component::visualization;
|
use crate::component::visualization;
|
||||||
use crate::component::visualization::example::js::constructor_sample_js_bubble_chart;
|
|
||||||
use crate::component::visualization::MockDataGenerator3D;
|
use crate::component::visualization::MockDataGenerator3D;
|
||||||
use crate::component::visualization::example::native;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -247,7 +245,7 @@ ensogl::def_command_api! { Commands
|
|||||||
remove_selected_nodes,
|
remove_selected_nodes,
|
||||||
/// Remove all nodes from the graph.
|
/// Remove all nodes from the graph.
|
||||||
remove_all_nodes,
|
remove_all_nodes,
|
||||||
/// Toggle the visibility of the selected visualisations
|
/// Toggle the visibility of the selected visualizations
|
||||||
toggle_visualization_visibility,
|
toggle_visualization_visibility,
|
||||||
|
|
||||||
|
|
||||||
@ -288,7 +286,7 @@ ensogl::def_command_api! { Commands
|
|||||||
debug_set_data_for_selected_node,
|
debug_set_data_for_selected_node,
|
||||||
|
|
||||||
/// Cycle the visualization for the selected nodes. TODO only has dummy functionality at the moment.
|
/// Cycle the visualization for the selected nodes. TODO only has dummy functionality at the moment.
|
||||||
debug_cycle_visualisation_for_selected_node,
|
debug_cycle_visualization_for_selected_node,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Commands {
|
impl Commands {
|
||||||
@ -318,7 +316,7 @@ impl Commands {
|
|||||||
def toggle_node_inverse_select = source();
|
def toggle_node_inverse_select = source();
|
||||||
|
|
||||||
def debug_set_data_for_selected_node = source();
|
def debug_set_data_for_selected_node = source();
|
||||||
def debug_cycle_visualisation_for_selected_node = source();
|
def debug_cycle_visualization_for_selected_node = source();
|
||||||
|
|
||||||
}
|
}
|
||||||
Self {add_node,add_node_at_cursor,remove_selected_nodes,remove_all_nodes
|
Self {add_node,add_node_at_cursor,remove_selected_nodes,remove_all_nodes
|
||||||
@ -327,7 +325,7 @@ impl Commands {
|
|||||||
,enable_node_merge_select,disable_node_merge_select,toggle_node_merge_select
|
,enable_node_merge_select,disable_node_merge_select,toggle_node_merge_select
|
||||||
,enable_node_subtract_select,disable_node_subtract_select,toggle_node_subtract_select
|
,enable_node_subtract_select,disable_node_subtract_select,toggle_node_subtract_select
|
||||||
,enable_node_inverse_select,disable_node_inverse_select,toggle_node_inverse_select
|
,enable_node_inverse_select,disable_node_inverse_select,toggle_node_inverse_select
|
||||||
,debug_set_data_for_selected_node,debug_cycle_visualisation_for_selected_node}
|
,debug_set_data_for_selected_node,debug_cycle_visualization_for_selected_node}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,6 +356,7 @@ pub struct FrpInputs {
|
|||||||
pub translate_selected_nodes : frp::Source<Position>,
|
pub translate_selected_nodes : frp::Source<Position>,
|
||||||
pub cycle_visualization : frp::Source<NodeId>,
|
pub cycle_visualization : frp::Source<NodeId>,
|
||||||
pub set_visualization : frp::Source<(NodeId,Option<Visualization>)>,
|
pub set_visualization : frp::Source<(NodeId,Option<Visualization>)>,
|
||||||
|
pub register_visualization_class : frp::Source<Option<Rc<visualization::Handle>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrpInputs {
|
impl FrpInputs {
|
||||||
@ -380,13 +379,16 @@ impl FrpInputs {
|
|||||||
def translate_selected_nodes = source();
|
def translate_selected_nodes = source();
|
||||||
def cycle_visualization = source();
|
def cycle_visualization = source();
|
||||||
def set_visualization = source();
|
def set_visualization = source();
|
||||||
|
def register_visualization_class = source();
|
||||||
}
|
}
|
||||||
let commands = Commands::new(&network);
|
let commands = Commands::new(&network);
|
||||||
Self {commands,remove_edge,press_node_input,remove_all_node_edges
|
Self {commands,remove_edge,press_node_input,remove_all_node_edges
|
||||||
,remove_all_node_input_edges,remove_all_node_output_edges,set_visualization_data
|
,remove_all_node_input_edges,remove_all_node_output_edges,set_visualization_data
|
||||||
,connect_detached_edges_to_node,connect_edge_source,connect_edge_target
|
,connect_detached_edges_to_node,connect_edge_source,connect_edge_target
|
||||||
,set_node_position,select_node,translate_selected_nodes,set_node_expression
|
,set_node_position,select_node,translate_selected_nodes,set_node_expression
|
||||||
,connect_nodes,deselect_all_nodes,cycle_visualization,set_visualization}
|
,connect_nodes,deselect_all_nodes,cycle_visualization,set_visualization
|
||||||
|
,register_visualization_class
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -789,17 +791,8 @@ impl GraphEditorModelWithNetwork {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let chart = constructor_sample_js_bubble_chart();
|
|
||||||
let dom_layer = model.scene.dom.layers.front.clone_ref();
|
|
||||||
chart.set_dom_layer(&dom_layer);
|
|
||||||
|
|
||||||
let vis = Visualization::new(chart);
|
|
||||||
node.view.frp.set_visualization.emit(Some(vis));
|
|
||||||
|
|
||||||
|
|
||||||
self.nodes.insert(node_id,node);
|
self.nodes.insert(node_id,node);
|
||||||
|
|
||||||
|
|
||||||
node_id
|
node_id
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1132,7 +1125,7 @@ impl application::shortcut::DefaultShortcutProvider for GraphEditor {
|
|||||||
, Self::self_shortcut(shortcut::Action::press (&[Key::Shift,Key::Alt]) , "toggle_node_inverse_select")
|
, Self::self_shortcut(shortcut::Action::press (&[Key::Shift,Key::Alt]) , "toggle_node_inverse_select")
|
||||||
, Self::self_shortcut(shortcut::Action::release (&[Key::Shift,Key::Alt]) , "toggle_node_inverse_select")
|
, Self::self_shortcut(shortcut::Action::release (&[Key::Shift,Key::Alt]) , "toggle_node_inverse_select")
|
||||||
, Self::self_shortcut(shortcut::Action::press (&[Key::Character("d".into())]) , "debug_set_data_for_selected_node")
|
, Self::self_shortcut(shortcut::Action::press (&[Key::Character("d".into())]) , "debug_set_data_for_selected_node")
|
||||||
, Self::self_shortcut(shortcut::Action::press (&[Key::Character("f".into())]) , "debug_cycle_visualisation_for_selected_node")
|
, Self::self_shortcut(shortcut::Action::press (&[Key::Character("f".into())]) , "debug_cycle_visualization_for_selected_node")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1208,6 +1201,9 @@ fn new_graph_editor(world:&World) -> GraphEditor {
|
|||||||
let inputs = &model.frp;
|
let inputs = &model.frp;
|
||||||
let mouse = &scene.mouse.frp;
|
let mouse = &scene.mouse.frp;
|
||||||
let touch = &model.touch_state;
|
let touch = &model.touch_state;
|
||||||
|
let visualization_registry = visualization::Registry::with_default_visualizations();
|
||||||
|
let logger = &model.logger;
|
||||||
|
|
||||||
|
|
||||||
let outputs = UnsealedFrpOutputs::new();
|
let outputs = UnsealedFrpOutputs::new();
|
||||||
let sealed_outputs = outputs.seal(); // Done here to keep right eval order.
|
let sealed_outputs = outputs.seal(); // Done here to keep right eval order.
|
||||||
@ -1414,12 +1410,8 @@ fn new_graph_editor(world:&World) -> GraphEditor {
|
|||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// === Vis Cycling ===
|
// === Vis Cycling ===
|
||||||
|
def _cycle_vis = inputs.debug_cycle_visualization_for_selected_node.map(f!([inputs,nodes](_) {
|
||||||
def _cycle_vis= inputs.debug_cycle_visualisation_for_selected_node.map(f!([inputs,nodes](_) {
|
|
||||||
nodes.selected.for_each(|node| inputs.cycle_visualization.emit(node));
|
nodes.selected.for_each(|node| inputs.cycle_visualization.emit(node));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -1432,8 +1424,9 @@ fn new_graph_editor(world:&World) -> GraphEditor {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// === Vis Update Data ===
|
||||||
|
|
||||||
// TODO remove this once real data is available.
|
// TODO remove this once real data is available.
|
||||||
let dummy_switch = Rc::new(Cell::new(false));
|
|
||||||
let sample_data_generator = MockDataGenerator3D::default();
|
let sample_data_generator = MockDataGenerator3D::default();
|
||||||
def _set_dumy_data = inputs.debug_set_data_for_selected_node.map(f!([nodes](_) {
|
def _set_dumy_data = inputs.debug_set_data_for_selected_node.map(f!([nodes](_) {
|
||||||
nodes.selected.for_each(|node_id| {
|
nodes.selected.for_each(|node_id| {
|
||||||
@ -1446,21 +1439,21 @@ fn new_graph_editor(world:&World) -> GraphEditor {
|
|||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
def _set_dumy_data = inputs.cycle_visualization.map(f!([scene,nodes](node_id) {
|
let cycle_count = Rc::new(Cell::new(0));
|
||||||
// TODO remove dummy cycling once we have the visualization registry.
|
def _cycle_visualization = inputs.cycle_visualization.map(f!([scene,nodes,visualization_registry,logger](node_id) {
|
||||||
let dc = dummy_switch.get();
|
let visualizations = visualization_registry.valid_sources(&"[[Float,Float,Float]]".into());
|
||||||
dummy_switch.set(!dc);
|
cycle_count.set(cycle_count.get() % visualizations.len());
|
||||||
let vis = if dc {
|
let vis = &visualizations[cycle_count.get()];
|
||||||
Visualization::new(native::BubbleChart::new(&scene))
|
let vis = vis.instantiate(&scene);
|
||||||
} else {
|
let node = nodes.get_cloned_ref(node_id);
|
||||||
let chart = constructor_sample_js_bubble_chart();
|
match (vis, node) {
|
||||||
let dom_layer = scene.dom.layers.front.clone_ref();
|
(Ok(vis), Some(node)) => {
|
||||||
chart.set_dom_layer(&dom_layer);
|
|
||||||
Visualization::new(chart)
|
|
||||||
};
|
|
||||||
if let Some(node) = nodes.get_cloned_ref(node_id) {
|
|
||||||
node.view.visualization_container.frp.set_visualization.emit(Some(vis));
|
node.view.visualization_container.frp.set_visualization.emit(Some(vis));
|
||||||
}
|
},
|
||||||
|
(Err(e), _) => logger.warning(|| format!("Failed to cycle visualization: {}", e)),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
cycle_count.set(cycle_count.get() + 1);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
def _toggle_selected = inputs.toggle_visualization_visibility.map(f!([nodes](_) {
|
def _toggle_selected = inputs.toggle_visualization_visibility.map(f!([nodes](_) {
|
||||||
@ -1469,8 +1462,16 @@ fn new_graph_editor(world:&World) -> GraphEditor {
|
|||||||
node.view.visualization_container.frp.toggle_visibility.emit(());
|
node.view.visualization_container.frp.toggle_visibility.emit(());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// === Register Visualization ===
|
||||||
|
|
||||||
|
def _register_visualization = inputs.register_visualization_class.map(f!([visualization_registry](handle) {
|
||||||
|
if let Some(handle) = handle {
|
||||||
|
visualization_registry.register_class_from_handle(&handle);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
// === OUTPUTS REBIND ===
|
// === OUTPUTS REBIND ===
|
||||||
|
Loading…
Reference in New Issue
Block a user