All Downloads are FREE. Search and download functionalities are using the official Maven repository.

viz.package.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2024 quafadas
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package viz

import scala.scalajs.js
import com.raquo.laminar.api.L.*
import scalajs.js.JSON

import viz.vega.facades.EmbedOptions
import viz.vega.facades.VegaView
import viz.vega.facades.EmbedResult

import viz.vega.facades.Helpers.*
import org.scalajs.dom.ResizeObserverEntry
import org.scalajs.dom.ResizeObserver

object LaminarViz:

  def dataClickBus: (EventStream[js.UndefOr[js.Dynamic]], (x: js.Dynamic, y: js.Dynamic) => Unit) =
    val (dataClickedBus, callback) = EventStream.withJsCallback[js.UndefOr[js.Dynamic]]
    val fromFct = (x: js.Dynamic, y: js.Dynamic) => callback(dataClickHandler.apply(x, y))
    (dataClickedBus, fromFct)
  end dataClickBus

  def signalBus: (EventStream[js.Dynamic], (x: String, y: js.Dynamic) => Unit) =
    val (hoverBus, callback) = EventStream.withJsCallback[js.Dynamic]
    val handler: js.Function2[String, js.Dynamic, js.Dynamic] = (str: String, dyn: js.Dynamic) => dyn
    val fromFct = (x: String, y: js.Dynamic) => callback(handler.apply(x, y))
    (hoverBus, fromFct)
  end signalBus

  /** Embed a chart in a div. This method is a good choice if you are not at all worried about performance (mostly you
    * won't be), you want to get started quickly, and you want things to "just work".
    *
    * There is one "gotcha". If _you_ choose to provide your own div, it _must_ have a well defined width and height.
    *
    * If it does not the chart will not initialize properly, and it will not recover from this failure.
    *
    * @param chart
    *   \- the chart you wish to plot
    * @param inDivOpt
    *   \- optionally, the div you wish to plot into. If you don't provide one, one will be created for you.
    * @param embedOpt
    *   \- optionally, the embed options you wish to use
    */
  def viewEmbed(
      chart: Spec,
      inDivOpt: Option[Div] = None,
      embedOpt: Option[EmbedOptions] = None
  ): (Div, Signal[Option[VegaView]]) =

    val specObj = JSON.parse(chart.spec).asInstanceOf[js.Object]

    val (embeddedIn, embedResult) = (inDivOpt, embedOpt) match
      case (Some(thisDiv), Some(opts)) =>
        val p: js.Promise[EmbedResult] = viz.vega.facades.embed(thisDiv.ref, specObj, opts)
        (thisDiv, p)
      case (Some(thisDiv), None) =>
        val specObj = JSON.parse(chart.spec).asInstanceOf[js.Object]
        val p: js.Promise[EmbedResult] = viz.vega.facades.embed(thisDiv.ref, specObj, EmbedOptions())
        (thisDiv, p)
      case (None, Some(opts)) =>
        val newDiv = div(
          width := "100%",
          height := "100%"
        )
        val p: js.Promise[EmbedResult] = viz.vega.facades.embed(newDiv.ref, specObj, opts)
        (newDiv, p)
      case (None, None) =>
        val newDiv = div(
          width := "100%",
          height := "100%"
        )
        val p: js.Promise[EmbedResult] = viz.vega.facades.embed(newDiv.ref, specObj, EmbedOptions())
        (newDiv, p)

    val view: Signal[Option[VegaView]] = Signal.fromJsPromise(embedResult).map(er => er.map(_.view))

    val resizeMontitor = new EventBus[ResizeObserverEntry]
    val resizer = inDivOpt match
      case None =>
        val resizeObserver = new ResizeObserver((entries: scala.scalajs.js.Array[ResizeObserverEntry], _) =>
          entries.foreach(entry => resizeMontitor.emit(entry))
        )
        resizeObserver.observe(embeddedIn.ref)
        Some(resizeObserver)

      case Some(embeddedIn) => None

    embedResult.`then`(in =>
      embeddedIn.amend(
        resizeMontitor.events.debounce(100).combineWith(view.changes) --> Observer {
          (valu: (ResizeObserverEntry, Option[VegaView])) =>
            valu._2.foreach { view =>
              view.width(valu._1.contentBoxSize.head.blockSize.toInt)
              view.height(valu._1.contentBoxSize.head.blockSize.toInt)
              view.runAsync()
            }
        },
        onUnmountCallback { _ =>
          in.view.finalize()
          resizer.foreach(_.disconnect())
        }
      )
    )
    (embeddedIn, view)
  end viewEmbed

  /** Embed a chart in a div. This method is a good choice if you are not at all worried about performance (mostly you
    * won't be), you want to get started quickly, and you want things to "just work".
    *
    * There is one "gotcha". If _you_ choose to provide your own div, it _must_ have a well defined width and height.
    *
    * If it does not the chart will not initialize properly, and it will not recover from this failure.
    *
    * @param chart
    *   \- the chart you wish to plot
    * @param inDivOpt
    *   \- optionally, the div you wish to plot into. If you don't provide one, one will be created for you.
    * @param embedOpt
    *   \- optionally, the embed options you wish to use
    */
  def simpleEmbed(chart: Spec, inDivOpt: Option[Div] = None, embedOpt: Option[EmbedOptions] = None): Div =
    viewEmbed(chart, inDivOpt, embedOpt)._1

  end simpleEmbed

end LaminarViz




© 2015 - 2024 Weber Informatics LLC | Privacy Policy