From e8e8b5704a2da6398093918a22ff42dca432264b Mon Sep 17 00:00:00 2001 From: acalejos Date: Mon, 22 Jan 2024 00:05:45 -0500 Subject: [PATCH] Add Style gallery in docs --- lib/exgboost/plotting.ex | 529 ++++++++++++++++++++++++++++---- lib/exgboost/plotting/style.ex | 16 + lib/exgboost/plotting/styles.ex | 303 +----------------- mix.exs | 32 ++ test/data/model.json | Bin 0 -> 31162 bytes 5 files changed, 526 insertions(+), 354 deletions(-) create mode 100644 lib/exgboost/plotting/style.ex create mode 100644 test/data/model.json diff --git a/lib/exgboost/plotting.ex b/lib/exgboost/plotting.ex index e38679a..05a147a 100644 --- a/lib/exgboost/plotting.ex +++ b/lib/exgboost/plotting.ex @@ -1,60 +1,397 @@ defmodule EXGBoost.Plotting do - @moduledoc """ - Functions for plotting EXGBoost `Booster` models using [Vega](https://vega.github.io/vega/) + use EXGBoost.Plotting.Style - Fundamentally, all this module does is convert a `Booster` into a format that can be - ingested by Vega, and apply some default configuations that only account for a subset of the configurations - that can be set by a Vega spec directly. The functions provided in this module are designed to have opinionated - defaults that can be used to quickly visualize a model, but the full power of Vega is available by using the - `to_tabular/1` function to convert the model into a tabular format, and then using the `to_vega/2` function - to convert the tabular format into a Vega specification. + @doc """ + A light theme based on the [Solarized](https://ethanschoonover.com/solarized/) color palette + """ + style :solarized_light do + [ + # base3 + background: "#fdf6e3", + leaves: [ + # base01 + text: [fill: "#586e75", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + # base2, base1 + rect: [fill: "#eee8d5", stroke: "#93a1a1", strokeWidth: 1] + ], + splits: [ + # base01 + text: [fill: "#586e75", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + # base1, base00 + rect: [fill: "#93a1a1", stroke: "#657b83", strokeWidth: 1], + # base00 + children: [fill: "#657b83", stroke: "#657b83", strokeWidth: 1] + ], + yes: [ + # green + text: [fill: "#859900"], + # base00 + path: [stroke: "#657b83", strokeWidth: 1] + ], + no: [ + # red + text: [fill: "#dc322f"], + # base00 + path: [stroke: "#657b83", strokeWidth: 1] + ] + ] + end - ## Default Vega Specification + @doc """ + A dark theme based on the [Solarized](https://ethanschoonover.com/solarized/) color palette + """ + style :solarized_dark do + # base03 + [ + background: "#002b36", + leaves: [ + # base0 + text: [fill: "#839496", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + # base02, base01 + rect: [fill: "#073642", stroke: "#586e75", strokeWidth: 1] + ], + splits: [ + # base0 + text: [fill: "#839496", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + # base01, base00 + rect: [fill: "#586e75", stroke: "#657b83", strokeWidth: 1], + # base00 + children: [fill: "#657b83", stroke: "#657b83", strokeWidth: 1] + ], + yes: [ + # green + text: [fill: "#859900"], + # base00 + path: [stroke: "#657b83", strokeWidth: 1] + ], + no: [ + # red + text: [fill: "#dc322f"], + # base00 + path: [stroke: "#657b83", strokeWidth: 1] + ] + ] + end - The default Vega specification is designed to be a good starting point for visualizing a model, but it is - possible to customize the specification by passing in a map of Vega properties to the `to_vega/2` function. - Refer to `Custom Vega Specifications` for more details on how to do this. + @doc """ + A light and playful theme + """ + style :playful_light do + [ + background: "#f0f0f0", + padding: 10, + leaves: [ + text: [fill: "#000", font_size: 12, font_style: "italic", font_weight: "bold"], + rect: [fill: "#e91e63", stroke: "#000", stroke_width: 1, radius: 5] + ], + splits: [ + text: [fill: "#000", font_size: 12, font_style: "normal", font_weight: "bold"], + children: [ + fill: "#000", + font_size: 12, + font_style: "normal", + font_weight: "bold" + ], + rect: [fill: "#8bc34a", stroke: "#000", stroke_width: 1, radius: 10] + ], + yes: [ + path: [stroke: "#4caf50", stroke_width: 2] + ], + no: [ + path: [stroke: "#f44336", stroke_width: 2] + ] + ] + end - By default, the Vega specification includes the following entities to use for rendering the model: - * `:width` - The width of the plot in pixels - * `:height` - The height of the plot in pixels - * `:padding` - The padding in pixels to add around the visualization. If a number, specifies padding for all sides. If an object, the value should have the format `[left: value, right: value, top: value, bottom: value]` - * `:leafs` - Specifies characteristics of leaf nodes - * `:inner_nodes` - Specifies characteristics of inner nodes - * `:links` - Specifies characteristics of links between nodes + @doc """ + A dark and playful theme + """ + style :playful_dark do + [ + background: "#333", + padding: 10, + leaves: [ + text: [fill: "#fff", font_size: 12, font_style: "italic", font_weight: "bold"], + rect: [fill: "#e91e63", stroke: "#fff", stroke_width: 1, radius: 5] + ], + splits: [ + text: [fill: "#fff", font_size: 12, font_style: "normal", font_weight: "bold"], + rect: [fill: "#8bc34a", stroke: "#fff", stroke_width: 1, radius: 10] + ], + yes: [ + text: [fill: "#4caf50"], + path: [stroke: "#4caf50", stroke_width: 2] + ], + no: [ + text: [fill: "#f44336"], + path: [stroke: "#f44336", stroke_width: 2] + ] + ] + end - ## Custom Vega Specifications + @doc """ + A dark theme + """ + style :dark do + [ + background: "#333", + padding: 10, + leaves: [ + text: [fill: "#fff", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#666", stroke: "#fff", strokeWidth: 1] + ], + splits: [ + text: [fill: "#fff", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#444", stroke: "#fff", strokeWidth: 1], + children: [fill: "#fff", stroke: "#fff", strokeWidth: 1] + ] + ] + end - The default Vega specification is designed to be a good starting point for visualizing a model, but it is - possible to customize the specification by passing in a map of Vega properties to the `to_vega/2` function. - You can find the full list of Vega properties [here](https://vega.github.io/vega/docs/specification/). + @doc """ + A high contrast theme + """ + style :high_contrast do + [ + background: "#000", + padding: 10, + leaves: [ + text: [fill: "#fff", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#333", stroke: "#fff", strokeWidth: 1] + ], + splits: [ + text: [fill: "#fff", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#666", stroke: "#fff", strokeWidth: 1] + ] + ] + end - It is suggested that you use the data attributes provided by the default specification as a starting point, since they - provide the necessary data transformation to convert the model into a tree structure that can be visualized by Vega. - If you would like to customize the default specification, you can use `EXGBoost.Plotting.to_vega/1` to get the default - specification, and then modify it as needed. + @doc """ + A light theme + """ + style :light do + [ + background: "#f0f0f0", + padding: 10, + leaves: [ + text: [fill: "#000", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#ddd", stroke: "#000", strokeWidth: 1] + ], + splits: [ + text: [fill: "#000", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#bbb", stroke: "#000", strokeWidth: 1] + ] + ] + end - Once you have a custom specification, you can pass it to `VegaLite.from_json/1` to create a new `VegaLite` struct, after which - you can use the functions provided by the `VegaLite` module to render the model. + @doc """ + A theme based on the [Monokai](https://monokai.pro/) color palette + """ + style :monokai do + [ + background: "#272822", + leaves: [ + text: [fill: "#f8f8f2", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#3e3d32", stroke: "#66d9ef", strokeWidth: 1] + ], + splits: [ + text: [fill: "#f8f8f2", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#66d9ef", stroke: "#f8f8f2", strokeWidth: 1], + children: [fill: "#f8f8f2", stroke: "#f8f8f2", strokeWidth: 1] + ], + yes: [ + text: [fill: "#a6e22e"], + path: [stroke: "#f8f8f2", strokeWidth: 1] + ], + no: [ + text: [fill: "#f92672"], + path: [stroke: "#f8f8f2", strokeWidth: 1] + ] + ] + end - ## Specification Validation - You can optionally validate your specification against the Vega schema by passing the `validate: true` option to `to_vega/2`. - This will raise an error if the specification is invalid. This is useful if you are creating a custom specification and want - to ensure that it is valid. Note that this will only validate the specification against the Vega schema, and not against the - VegaLite schema. This requires the [`ex_json_schema`] package to be installed. + @doc """ + A theme based on the [Dracula](https://draculatheme.com/) color palette + """ + style :dracula do + [ + background: "#282a36", + leaves: [ + text: [fill: "#f8f8f2", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#44475a", stroke: "#ff79c6", strokeWidth: 1] + ], + splits: [ + text: [fill: "#f8f8f2", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#ff79c6", stroke: "#f8f8f2", strokeWidth: 1], + children: [fill: "#f8f8f2", stroke: "#f8f8f2", strokeWidth: 1] + ], + yes: [ + text: [fill: "#50fa7b"], + path: [stroke: "#f8f8f2", strokeWidth: 1] + ], + no: [ + text: [fill: "#ff5555"], + path: [stroke: "#f8f8f2", strokeWidth: 1] + ] + ] + end - ## Livebook Integration + @doc """ + A theme based on the [Nord](https://www.nordtheme.com/) color palette + """ + style :nord do + [ + background: "#2e3440", + leaves: [ + text: [fill: "#d8dee9", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#3b4252", stroke: "#88c0d0", strokeWidth: 1] + ], + splits: [ + text: [fill: "#d8dee9", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#88c0d0", stroke: "#d8dee9", strokeWidth: 1], + children: [fill: "#d8dee9", stroke: "#d8dee9", strokeWidth: 1] + ], + yes: [ + text: [fill: "#a3be8c"], + path: [stroke: "#d8dee9", strokeWidth: 1] + ], + no: [ + text: [fill: "#bf616a"], + path: [stroke: "#d8dee9", strokeWidth: 1] + ] + ] + end - This module also provides a `Kino.Render` implementation for `EXGBoost.Booster` which allows - models to be rendered directly in Livebook. This is done by converting the model into a Vega specification - and then using the `Kino.Render` implementation for Elixir's [`VegaLite`](https://hexdocs.pm/vega_lite/VegaLite.html) API - to render the model. + @doc """ + A theme based on the [Material](https://material.io/design/color/the-color-system.html#tools-for-picking-colors) color palette + """ + style :material do + [ + background: "#263238", + leaves: [ + text: [fill: "#eceff1", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#37474f", stroke: "#80cbc4", strokeWidth: 1] + ], + splits: [ + text: [fill: "#eceff1", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#80cbc4", stroke: "#eceff1", strokeWidth: 1], + children: [fill: "#eceff1", stroke: "#eceff1", strokeWidth: 1] + ], + yes: [ + text: [fill: "#c5e1a5"], + path: [stroke: "#eceff1", strokeWidth: 1] + ], + no: [ + text: [fill: "#ef9a9a"], + path: [stroke: "#eceff1", strokeWidth: 1] + ] + ] + end + @doc """ + A theme based on the One Dark color palette + """ + style :one_dark do + [ + background: "#282c34", + leaves: [ + text: [fill: "#abb2bf", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#3b4048", stroke: "#98c379", strokeWidth: 1] + ], + splits: [ + text: [fill: "#abb2bf", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#98c379", stroke: "#abb2bf", strokeWidth: 1], + children: [fill: "#abb2bf", stroke: "#abb2bf", strokeWidth: 1] + ], + yes: [ + text: [fill: "#98c379"], + path: [stroke: "#abb2bf", strokeWidth: 1] + ], + no: [ + text: [fill: "#e06c75"], + path: [stroke: "#abb2bf", strokeWidth: 1] + ] + ] + end - . The Vega specification is then passed to [VegaLite](https://hexdocs.pm/vega_lite/readme.html) + @doc """ + A theme based on the [Gruvbox](https://github.com/morhetz/gruvbox) color palette + """ + style :gruvbox do + [ + background: "#282828", + leaves: [ + text: [fill: "#ebdbb2", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#3c3836", stroke: "#b8bb26", strokeWidth: 1] + ], + splits: [ + text: [fill: "#ebdbb2", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#b8bb26", stroke: "#ebdbb2", strokeWidth: 1], + children: [fill: "#ebdbb2", stroke: "#ebdbb2", strokeWidth: 1] + ], + yes: [ + text: [fill: "#b8bb26"], + path: [stroke: "#ebdbb2", strokeWidth: 1] + ], + no: [ + text: [fill: "#fb4934"], + path: [stroke: "#ebdbb2", strokeWidth: 1] + ] + ] + end + @doc """ + A dark theme based on the [Horizon](https://www.horizon.io/) color palette + """ + style :horizon_dark do + [ + background: "#1C1E26", + leaves: [ + text: [fill: "#E3E6EE", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#232530", stroke: "#F43E5C", strokeWidth: 1] + ], + splits: [ + text: [fill: "#E3E6EE", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#F43E5C", stroke: "#E3E6EE", strokeWidth: 1], + children: [fill: "#E3E6EE", stroke: "#E3E6EE", strokeWidth: 1] + ], + yes: [ + text: [fill: "#48B685"], + path: [stroke: "#E3E6EE", strokeWidth: 1] + ], + no: [ + text: [fill: "#F43E5C"], + path: [stroke: "#E3E6EE", strokeWidth: 1] + ] + ] + end + @doc """ + A light theme based on the [Horizon](https://www.horizon.io/) color palette """ + style :horizon_light do + [ + background: "#FDF0ED", + leaves: [ + text: [fill: "#1A2026", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], + rect: [fill: "#F7E3D3", stroke: "#F43E5C", strokeWidth: 1] + ], + splits: [ + text: [fill: "#1A2026", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], + rect: [fill: "#F43E5C", stroke: "#1A2026", strokeWidth: 1], + children: [fill: "#1A2026", stroke: "#1A2026", strokeWidth: 1] + ], + yes: [ + text: [fill: "#48B685"], + path: [stroke: "#1A2026", strokeWidth: 1] + ], + no: [ + text: [fill: "#F43E5C"], + path: [stroke: "#1A2026", strokeWidth: 1] + ] + ] + end + HTTPoison.start() @schema HTTPoison.get!("https://vega.github.io/schema/vega/v5.json").body @@ -136,27 +473,10 @@ defmodule EXGBoost.Plotting do @plotting_params [ style: [ - doc: "The style to use for the visualization.", + doc: + "The style to use for the visualization. Refer to `EXGBoost.Plotting.Styles` for a list of available styles.", default: :solarized_light, - type: - {:in, - [ - :solarized_light, - :solarized_dark, - :playful_light, - :playful_dark, - :dark, - :light, - :nord, - :dracula, - :gruvbox, - :high_contrast, - :monokai, - :material, - :one_dark, - nil, - false - ]} + type: {:in, Keyword.keys(@styles)} ], rankdir: [ doc: "Determines the direction of the graph.", @@ -343,13 +663,89 @@ defmodule EXGBoost.Plotting do ] @plotting_schema NimbleOptions.new!(@plotting_params) - @defaults NimbleOptions.validate!([], @plotting_schema) + @moduledoc """ + Functions for plotting EXGBoost `Booster` models using [Vega](https://vega.github.io/vega/) + + Fundamentally, all this module does is convert a `Booster` into a format that can be + ingested by Vega, and apply some default configuations that only account for a subset of the configurations + that can be set by a Vega spec directly. The functions provided in this module are designed to have opinionated + defaults that can be used to quickly visualize a model, but the full power of Vega is available by using the + `to_tabular/1` function to convert the model into a tabular format, and then using the `to_vega/2` function + to convert the tabular format into a Vega specification. + + ## Default Vega Specification + + The default Vega specification is designed to be a good starting point for visualizing a model, but it is + possible to customize the specification by passing in a map of Vega properties to the `to_vega/2` function. + Refer to `Custom Vega Specifications` for more details on how to do this. + + By default, the Vega specification includes the following entities to use for rendering the model: + * `:width` - The width of the plot in pixels + * `:height` - The height of the plot in pixels + * `:padding` - The padding in pixels to add around the visualization. If a number, specifies padding for all sides. If an object, the value should have the format `[left: value, right: value, top: value, bottom: value]` + * `:leafs` - Specifies characteristics of leaf nodes + * `:inner_nodes` - Specifies characteristics of inner nodes + * `:links` - Specifies characteristics of links between nodes + + ## Custom Vega Specifications + + The default Vega specification is designed to be a good starting point for visualizing a model, but it is + possible to customize the specification by passing in a map of Vega properties to the `to_vega/2` function. + You can find the full list of Vega properties [here](https://vega.github.io/vega/docs/specification/). + + It is suggested that you use the data attributes provided by the default specification as a starting point, since they + provide the necessary data transformation to convert the model into a tree structure that can be visualized by Vega. + If you would like to customize the default specification, you can use `EXGBoost.Plotting.to_vega/1` to get the default + specification, and then modify it as needed. + + Once you have a custom specification, you can pass it to `VegaLite.from_json/1` to create a new `VegaLite` struct, after which + you can use the functions provided by the `VegaLite` module to render the model. + + ## Specification Validation + You can optionally validate your specification against the Vega schema by passing the `validate: true` option to `to_vega/2`. + This will raise an error if the specification is invalid. This is useful if you are creating a custom specification and want + to ensure that it is valid. Note that this will only validate the specification against the Vega schema, and not against the + VegaLite schema. This requires the [`ex_json_schema`] package to be installed. + + ## Livebook Integration + + This module also provides a `Kino.Render` implementation for `EXGBoost.Booster` which allows + models to be rendered directly in Livebook. This is done by converting the model into a Vega specification + and then using the `Kino.Render` implementation for Elixir's [`VegaLite`](https://hexdocs.pm/vega_lite/VegaLite.html) API + to render the model. + + . The Vega specification is then passed to [VegaLite](https://hexdocs.pm/vega_lite/readme.html) + + ## Plotting Parameters + + This module exposes a high-level API for customizing the EXGBoost model visualization, but it is also possible to + customize the Vega specification directly. You can also choose to pass in Vega Mark specifications to customize the + appearance of the nodes and links in the visualization outside of the parameteres specified below. Refer to the + [Vega documentation](https://vega.github.io/vega/docs/marks/) for more details on how to do this. + + #{NimbleOptions.docs(@plotting_params)} + + + ## Styles + + Styles are a keyword-map that adhere to the plotting schema as defined in `EXGBoost.Plotting`. + `EXGBoost.Plotting.Styles` provides a set of predefined styles that can be used to quickly customize the appearance of the visualization. + + + Refer to the `EXGBoost.Plotting.Styles` module for a list of available styles. You can pass a style to the `:style` + option as an atom or string, and it will be applied to the visualization. Styles will override any other options that are passed + for each option where the style defined a value. For example, if you pass `:solarized_light` as the style, and also pass + `:background` as an option, the `:background` option will be ignored since the `:solarized_light` style defines its own value for `:background`. + """ + def get_schema(), do: @schema def get_defaults(), do: @defaults + def get_styles(), do: @styles + defp validate_spec(spec) do case ExJsonSchema.Validator.validate(get_schema(), spec) do :ok -> @@ -385,7 +781,7 @@ defmodule EXGBoost.Plotting do end end - def deep_merge_kw(a, b) do + defp deep_merge_kw(a, b) do Keyword.merge(a, b, fn _key, val_a, val_b when is_list(val_a) and is_list(val_b) -> deep_merge_kw(val_a, val_b) @@ -463,7 +859,7 @@ defmodule EXGBoost.Plotting do opts = unless opts[:style] in [nil, false] do - style = apply(EXGBoost.Plotting.Styles, opts[:style], []) + style = apply(__MODULE__, opts[:style], []) deep_merge_kw(opts, style) else @@ -789,7 +1185,7 @@ defmodule EXGBoost.Plotting do opts = unless opts[:style] in [nil, false] do - style = apply(EXGBoost.Plotting.Styles, opts[:style], []) + style = apply(__MODULE__, opts[:style], []) deep_merge_kw(opts, style) else @@ -1209,7 +1605,6 @@ defmodule EXGBoost.Plotting do }) spec = Map.merge(spec, tlk) |> Map.delete("style") - File.write!("/Users/andres/Documents/exgboost/spec.json", Jason.encode!(spec)) spec = if opts[:validate], do: validate_spec(spec), else: spec Jason.encode!(spec) |> VegaLite.from_json() @@ -1270,6 +1665,10 @@ defmodule EXGBoost.Plotting do defp capitalize(<>) when first in ?a..?z, do: <> defp capitalize(rest), do: rest + + def __after_compile__(env) do + IO.inspect(env) + end end defimpl Kino.Render, for: EXGBoost.Booster do diff --git a/lib/exgboost/plotting/style.ex b/lib/exgboost/plotting/style.ex new file mode 100644 index 0000000..002a62b --- /dev/null +++ b/lib/exgboost/plotting/style.ex @@ -0,0 +1,16 @@ +defmodule EXGBoost.Plotting.Style do + @moduledoc false + defmacro __using__(_opts) do + quote do + import EXGBoost.Plotting.Style + Module.register_attribute(__MODULE__, :styles, accumulate: true) + end + end + + defmacro style(style_name, do: body) do + quote do + def unquote(style_name)(), do: unquote(body) + Module.put_attribute(__MODULE__, :styles, {unquote(style_name), unquote(body)}) + end + end +end diff --git a/lib/exgboost/plotting/styles.ex b/lib/exgboost/plotting/styles.ex index 62d4807..cd6fdf8 100644 --- a/lib/exgboost/plotting/styles.ex +++ b/lib/exgboost/plotting/styles.ex @@ -1,293 +1,18 @@ defmodule EXGBoost.Plotting.Styles do + @bst File.cwd!() |> Path.join("test/data/model.json") |> EXGBoost.read_model() + @moduledoc """ - A style is a keyword-map that adheres to the plotting schema - as defined in `EXGBoost.Plotting`. +
+ #{Enum.map(EXGBoost.Plotting.get_styles(), fn {name, _style} -> """ +
+

#{name}

+
+        
+        #{EXGBoost.Plotting.to_vega(@bst, style: name, height: 200, width: 300).spec |> Jason.encode!()}
+        
+      
+
+ """ end) |> Enum.join("\n\n")} +
""" - - def solarized_light(), - do: [ - # base3 - background: "#fdf6e3", - leaves: [ - # base01 - text: [fill: "#586e75", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - # base2, base1 - rect: [fill: "#eee8d5", stroke: "#93a1a1", strokeWidth: 1] - ], - splits: [ - # base01 - text: [fill: "#586e75", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - # base1, base00 - rect: [fill: "#93a1a1", stroke: "#657b83", strokeWidth: 1], - # base00 - children: [fill: "#657b83", stroke: "#657b83", strokeWidth: 1] - ], - yes: [ - # green - text: [fill: "#859900"], - # base00 - path: [stroke: "#657b83", strokeWidth: 1] - ], - no: [ - # red - text: [fill: "#dc322f"], - # base00 - path: [stroke: "#657b83", strokeWidth: 1] - ] - ] - - def solarized_dark(), - do: [ - # base03 - background: "#002b36", - leaves: [ - # base0 - text: [fill: "#839496", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - # base02, base01 - rect: [fill: "#073642", stroke: "#586e75", strokeWidth: 1] - ], - splits: [ - # base0 - text: [fill: "#839496", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - # base01, base00 - rect: [fill: "#586e75", stroke: "#657b83", strokeWidth: 1], - # base00 - children: [fill: "#657b83", stroke: "#657b83", strokeWidth: 1] - ], - yes: [ - # green - text: [fill: "#859900"], - # base00 - path: [stroke: "#657b83", strokeWidth: 1] - ], - no: [ - # red - text: [fill: "#dc322f"], - # base00 - path: [stroke: "#657b83", strokeWidth: 1] - ] - ] - - def playful_light(), - do: [ - background: "#f0f0f0", - padding: 10, - leaves: [ - text: [fill: "#000", font_size: 12, font_style: "italic", font_weight: "bold"], - rect: [fill: "#e91e63", stroke: "#000", stroke_width: 1, radius: 5] - ], - splits: [ - text: [fill: "#000", font_size: 12, font_style: "normal", font_weight: "bold"], - children: [ - fill: "#000", - font_size: 12, - font_style: "normal", - font_weight: "bold" - ], - rect: [fill: "#8bc34a", stroke: "#000", stroke_width: 1, radius: 10] - ], - yes: [ - path: [stroke: "#4caf50", stroke_width: 2] - ], - no: [ - path: [stroke: "#f44336", stroke_width: 2] - ] - ] - - def playful_dark(), - do: [ - background: "#333", - padding: 10, - leaves: [ - text: [fill: "#fff", font_size: 12, font_style: "italic", font_weight: "bold"], - rect: [fill: "#e91e63", stroke: "#fff", stroke_width: 1, radius: 5] - ], - splits: [ - text: [fill: "#fff", font_size: 12, font_style: "normal", font_weight: "bold"], - rect: [fill: "#8bc34a", stroke: "#fff", stroke_width: 1, radius: 10] - ], - yes: [ - text: [fill: "#4caf50"], - path: [stroke: "#4caf50", stroke_width: 2] - ], - no: [ - text: [fill: "#f44336"], - path: [stroke: "#f44336", stroke_width: 2] - ] - ] - - def dark(), - do: [ - background: "#333", - padding: 10, - leaves: [ - text: [fill: "#fff", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - rect: [fill: "#666", stroke: "#fff", strokeWidth: 1] - ], - splits: [ - text: [fill: "#fff", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - rect: [fill: "#444", stroke: "#fff", strokeWidth: 1], - children: [fill: "#fff", stroke: "#fff", strokeWidth: 1] - ] - ] - - def high_contrast(), - do: [ - background: "#000", - padding: 10, - leaves: [ - text: [fill: "#fff", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - rect: [fill: "#333", stroke: "#fff", strokeWidth: 1] - ], - splits: [ - text: [fill: "#fff", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - rect: [fill: "#666", stroke: "#fff", strokeWidth: 1] - ] - ] - - def light(), - do: [ - background: "#f0f0f0", - padding: 10, - leaves: [ - text: [fill: "#000", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - rect: [fill: "#ddd", stroke: "#000", strokeWidth: 1] - ], - splits: [ - text: [fill: "#000", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - rect: [fill: "#bbb", stroke: "#000", strokeWidth: 1] - ] - ] - - def monokai(), - do: [ - background: "#272822", - leaves: [ - text: [fill: "#f8f8f2", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - rect: [fill: "#3e3d32", stroke: "#66d9ef", strokeWidth: 1] - ], - splits: [ - text: [fill: "#f8f8f2", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - rect: [fill: "#66d9ef", stroke: "#f8f8f2", strokeWidth: 1], - children: [fill: "#f8f8f2", stroke: "#f8f8f2", strokeWidth: 1] - ], - yes: [ - text: [fill: "#a6e22e"], - path: [stroke: "#f8f8f2", strokeWidth: 1] - ], - no: [ - text: [fill: "#f92672"], - path: [stroke: "#f8f8f2", strokeWidth: 1] - ] - ] - - def dracula(), - do: [ - background: "#282a36", - leaves: [ - text: [fill: "#f8f8f2", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - rect: [fill: "#44475a", stroke: "#ff79c6", strokeWidth: 1] - ], - splits: [ - text: [fill: "#f8f8f2", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - rect: [fill: "#ff79c6", stroke: "#f8f8f2", strokeWidth: 1], - children: [fill: "#f8f8f2", stroke: "#f8f8f2", strokeWidth: 1] - ], - yes: [ - text: [fill: "#50fa7b"], - path: [stroke: "#f8f8f2", strokeWidth: 1] - ], - no: [ - text: [fill: "#ff5555"], - path: [stroke: "#f8f8f2", strokeWidth: 1] - ] - ] - - def nord(), - do: [ - background: "#2e3440", - leaves: [ - text: [fill: "#d8dee9", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - rect: [fill: "#3b4252", stroke: "#88c0d0", strokeWidth: 1] - ], - splits: [ - text: [fill: "#d8dee9", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - rect: [fill: "#88c0d0", stroke: "#d8dee9", strokeWidth: 1], - children: [fill: "#d8dee9", stroke: "#d8dee9", strokeWidth: 1] - ], - yes: [ - text: [fill: "#a3be8c"], - path: [stroke: "#d8dee9", strokeWidth: 1] - ], - no: [ - text: [fill: "#bf616a"], - path: [stroke: "#d8dee9", strokeWidth: 1] - ] - ] - - def material(), - do: [ - background: "#263238", - leaves: [ - text: [fill: "#eceff1", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - rect: [fill: "#37474f", stroke: "#80cbc4", strokeWidth: 1] - ], - splits: [ - text: [fill: "#eceff1", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - rect: [fill: "#80cbc4", stroke: "#eceff1", strokeWidth: 1], - children: [fill: "#eceff1", stroke: "#eceff1", strokeWidth: 1] - ], - yes: [ - text: [fill: "#c5e1a5"], - path: [stroke: "#eceff1", strokeWidth: 1] - ], - no: [ - text: [fill: "#ef9a9a"], - path: [stroke: "#eceff1", strokeWidth: 1] - ] - ] - - def one_dark(), - do: [ - background: "#282c34", - leaves: [ - text: [fill: "#abb2bf", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - rect: [fill: "#3b4048", stroke: "#98c379", strokeWidth: 1] - ], - splits: [ - text: [fill: "#abb2bf", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - rect: [fill: "#98c379", stroke: "#abb2bf", strokeWidth: 1], - children: [fill: "#abb2bf", stroke: "#abb2bf", strokeWidth: 1] - ], - yes: [ - text: [fill: "#98c379"], - path: [stroke: "#abb2bf", strokeWidth: 1] - ], - no: [ - text: [fill: "#e06c75"], - path: [stroke: "#abb2bf", strokeWidth: 1] - ] - ] - - def gruvbox(), - do: [ - background: "#282828", - leaves: [ - text: [fill: "#ebdbb2", fontSize: 12, fontStyle: "normal", fontWeight: "normal"], - rect: [fill: "#3c3836", stroke: "#b8bb26", strokeWidth: 1] - ], - splits: [ - text: [fill: "#ebdbb2", fontSize: 12, fontStyle: "normal", fontWeight: "bold"], - rect: [fill: "#b8bb26", stroke: "#ebdbb2", strokeWidth: 1], - children: [fill: "#ebdbb2", stroke: "#ebdbb2", strokeWidth: 1] - ], - yes: [ - text: [fill: "#b8bb26"], - path: [stroke: "#ebdbb2", strokeWidth: 1] - ], - no: [ - text: [fill: "#fb4934"], - path: [stroke: "#ebdbb2", strokeWidth: 1] - ] - ] end diff --git a/mix.exs b/mix.exs index 568dc1f..eb55c5b 100644 --- a/mix.exs +++ b/mix.exs @@ -126,6 +126,38 @@ defmodule EXGBoost.MixProject do } }); + + + + + + + """ end diff --git a/test/data/model.json b/test/data/model.json new file mode 100644 index 0000000000000000000000000000000000000000..6f3a37853464eee5baca47559960a4f361dd048f GIT binary patch literal 31162 zcmdsA3wTpS+D@BZNYj+wxNW&Bf++WkP?DK5-Q^~Ag?~XrkdU@%8@5eKl5$azo4U9l za`6YNsN6*4CJI)x?I|lFEW!f1ii&sv1dFl)%OWgA|C!T%lausJ3R?3M=Xp*}PBJs+ z`)1zv``$C>G<`5rS5sns?{elk@*afa*aExF?X=FI0;|hubvP&63LH7s zyrN>OBZt?G=ngewoYsl1ERV)Fu}*Kk+iufp6z{e;3|)4}w_Jhv^!VRw41SuU4{*5X5xoR{Tw z+wImuSB||PVAztJ#f8d81qJjwR?TNZ59r#!DzWc;CywmZou3){_quHkXF&h)MQ&HN z-Q%%(rxp3Sj;)<+IDxUoKD#&HRHyb0{dipfkvf7btNaA-1_PW zrdMzG`V2I7@7k-oQhM|R@1pZ;g@wKn>m~e z;Mok})@+y4qYRYShbA4=j47}cX62|(#Ye=X1Pq5W&q|r%^Hj^+*h1S>YnH=DCVkI7 zec=pA?8|JM*UISZ$#)g_02kS}cimbp8aM3uF4ZLQN(rnhN-3|6Qr}+02f#NzhMF35 zIDB7nOXAp<)T_xVTKSqL7E&%{=Q|2=tW)fcynK}bd`uEp9Zyk#!)s*`(svFX&r%Cr zPM4Q@pgHW3oo)q(ZknX6LgBcDQ^ta2O_fXvp{2HD)fQd)J;dpyjVV%RR+eHB|C->5G~WSH|81 zRz&c)naF|)jeB*YT2hWZt2ocu#Dk~h6qAYOa`QlUxqlK&3TU6{-k}!*o;GMyhHPKGv)nwne)O3^X zapihxPRru1#MTHFj~0Ub(+QU{yshsv9CZFo0!|9%HD^{Zcqy(aQxuSQHRo=+4?nSV z);%;F`>fVGA{{zMB)2X4E+!YaU3COooXtyxT2`^w?s5|(Ztc*k>+R(vtv-i zfWw*V@-=Bqn?xuh{4-vm<7n2gYS3Eq!H8EFUy~?2V{gvk(M)g2>a-VqdTxnqJ@O&+ z6G!u(J(2U4HGiVlL37g8G90wCb{-#KhP8T>`rkWNMh4cc4 zR#zEBjfo62D6XbDBbaCyjUF1j^QkO%*yy;aM`F~dk>u&)3o;rVUjRcAr@-0&YD&qYL=+ifolHZp%P09GdQ6{QKjMuQO?RD@?>qMt z+B*0P{Op8ol^tGu32*As#r&gL|C)Z?0D1JQi_zg%7a{$+bnc@M%j)svlYC<_!U$7N z_uv6zMg$y)nZcSW%%FS5l4!cc(#-%cRqvrYA#;o*Z+tv$ts|GNjFb9AUBYR}-=Gnm zne4N`0=;Q5%Q%CcSJi-lnr+NPwnf!$%-m?`C(`TsH5+@+&l`#e5ZQwZ&tDn>qRW@=s;<0c+-9_AOuTG8`x@#u z;#=-aSW%q;-k;==;E0&ytWgBr^nh@-hh{=DM`!dqeNKAhp|$LcP@t6-X`?A@)TCvn z`gf0w)xZinZ|#yogTGj7}Lz zltzDPIVmG<{d#M*MN*hE0>X#QLm8+5B--s*}s#NFm4oHwPhyByyY}MTS)R%e>mMQ%~yu@Wk}oku^2k2kG-@VW4m$* zr*;G0LqEaUt7a{`YzqdK;Oteimc6f2z@Ua;@1qZ9L~dJ7o?I!D^PNXYEq-W@<<>uu zIh6;{L43n4OqL(g zA-QM&PAJzi19!>Esf^t497bm^nGfztGPk_rTe;iia?7ga7ty3gx^W-FiV<2XQH&X} z;EtFXtZ5zHNk99U3zO0z-DL(o(z9teszMTKvVRBrEUp~MEQaI2f#cCnz-%ozh!}3n=sjQt8MC($xw7aAS$^9Jg7%e?zqaZt zC3^0}YhvFforlTzKc%;k;yEwDjD_TSvjG<-jb(>7ov%z>o`P>}{hoZs;I#w3UpUFK zyZ;g_HJxg?G7D8W`>e6dX}=y#8cMj6zljjg2;8YB1W=4I*hPc7qwh8EPe6A<5o;v# zhc-pkJ0{>+g)?a{Nr$?fXxHjpfJblgaR?Hbj@y275WN%A9VKp(*k|g37WQ2T$Iz=5=S(x`xuGK6Gq|&j!I^D-x9aI} z9NVD7AZd}gUuUJ3wFOsUPd|%?_t0<9^k|YKx^hbas3>Hrj9FS_3ONvfQcAC0fdCY9 zT``y%!wT@8bHCoCPQovwAOwb09C_0!j(xS(Z?st`tqsLE@1^7$SW=H~=le{0l zcBnmGQ@WAFz1k0-c26bwhcofo)ThbF)X-i%NA_sA%jYuVsu7jZi-+KZT~7J7H(ciN z2gX^prp(1Rz4@u-#JhJ@OgQj}#zYv)>6aqTRCl{hGXxQA#6gY@fb z=igPYA}iy@lc)Z5k}MmLi`UTaJeth5_|Otw+SypXe4bfOI~Ip}WXR~sv?BTN@PRmS{2p`K=O34i zuV^Q?Je!Wvr(H3he~)L{@Y2kpq-TQp6*EIgdQ1vZG%!RccRcC-=dKwK@BIjEt&^S` zF$c#@+=4cIw;efp&Sakj7BEhWS%FE9i9)t96WQjckfEa>%2>%MwQeelMzjyj#UfS* z`U|RvYaV8$u;OdRJ9IkbVb&n95;GT)9<@tx5L7U8woU;91~1RZ?J5>8iNy$63d~lhE)vNv(eJ4odfm%1ONM_-$n>XfH|ut1W=4A z^k?{Ig&zOhHDi5mGy2vv0qc^s0CLd_m2V;Amu=w~_F&w#gn%1e_twIS>s|q@>JI@B z<6w|}eccz-xmeC#MNrfpn6dEF zy**bgMz`NQPj2)yuThM3w6O4p%#rRo#20d>&17BxeqdjqCROdaMYWK1q< z1=*3+1?NOrQBU5xzYQ7G>H-B*?3FITV1pTGY_L<7(NBobRV#!3UyWfXCJCh>6 ztt&=XT6QUadGHnUg8aU6Vq&Cu+wC}bo7l@Wc7v@$(Z8zr6X^jCmHYm8^7L^KB*m@FKr`F<9AACkz=f<(49nm2J^d^zYXoo=S& zzpGHY#LIHC!zZ{CVWkk%qL1l_q}I~&U<7e6l>`>sT$HLKBw2P)-~=}l3Gj=d|= z&vdUyyYGq|oxDkonDU}!$22$hF{~J&73CN+QotQCGnAxPyGXsR}Uc8rF;{tl)33S@Hu|vE-)(D=~8+=}~_a2f<->%!*&~0|N!j z)`EkG;YRYtQ|+bAUAB|?jkb~79^Nc{(!oO#ayR2|DyyZg!{3u;T}{BxIPSr3_mlW} zLlFTQaLw3c9ylUZK74f)+PP!5oO&$PT;8#RWpr8=`u==#%cj#~D~2ri0>zjR>M(J6 zMGHrnWk=N|?rd0v0`cW@DmWw#2Fm3#1)CBGRP69uJ*MCD$E{c$=vEjMOwx>Pm(kwM zmr!Zx3ffvJXma|)s89NrwTC)ndG+t`p6#Jmt>6%N`5aUc)4tfB5Aw^IAVC7IMwOdN zD);?vF44aD<5p_-g9Zze-xJBT0900&cj$Bgz?uM37l7iR0R}@T4kPgsEkeES++eG4`m*S%F{s()MRM}(Tlp}CRd`@X0n3>}XaWb~pun13k9K~1 z$wdY;R-kh&5{F7V2TYfkOw3PC6tG-C67?{OsG%pbZ$i@5!ekQt(QL)VV0--iJ}*ys;I%Z@SE#3oGht z|Exqaa7Ijg){to|mInM*ia|80g`rRv8>HU1&BRYNdDF7;(oEWuQczX(F?i4RV2l>g z1Ai1oQwKu_L3A&3hh@Gxx>w7cpgPx}CNzMB5n2l@VnQ>6ARY6ROh5C}wcwZ_Mc18N zQKB}Gl5Pfa(+eh&P#Hs_cK5~?<-O9tkDE$wE!N>}bK>#gE}?x~DxyC)_e*aa>&w1+ z<8##b_^opMyfOP8(yR>lmY{<#kWvN)_~~}^8`5JXSsk6tS&ew zVuGIh@!Ofwp1mEU595zO<&Y$-hLo&NKXa+#;^~=|!~L(K&2x{)lfKxBk_{It68`Lx zUw*Tr`LVs<$??QIU~{*&+{duWTO!HY0^AWZqoF11n2mp9;KR$hnsLew6Us>CH%`#_ zf3SqY3mB)xtT2(R3|2hDW$xP#u#!HY4?SqWO3Yj>$?BKg_^{D8FmeKBYr#Rpa3h&H z>SJlN^Br>MfuE%BtHwz$madYvkLrg%AHG;}SNFqPjy0EVE-1#&wRw!6Hxw7G4Y=HE zH1BSfFE5+06_x*|ryRe*Xqh+g7IbuHf#u>u52H4%H(GAJC$jul*=Eazk}&2V#*X$G^@JeYv)|F z-PEl zPQYv}4J2Z?k=*xWuGD$;XQbOn7l}V`Ns1rynN)smy3~K%DQWSOP4LT)ufj_*C*Us% zo&3C^hyV?k^o-{3r!1A7Rm0F*UmZoJ^dICsTTYi5Ca*yWzaL#bYjCV(%~Q|HjRy`Y zzcTv_ck(xJ>Dvn2sV4+bm?`w9drj&GawA9xc(vV7obuYcXjbVq%ilZvU@7mzAM>TX z`xP+mT0%ge?q%k(jnSF^Uir1^UfM>J`hji+4*|+E|Lo_}cF>4NH|g&s&(sI2;30rH z1axQbg#|{gK|;XTZJ$WhDKAK6_oPTIiocd#xpxzO{kB%p0%InAcFcIZY;l1!<@}v^ k|HJ%$m)%±YZm|Nn|t`~Lv>w_*Oz5O2i)0yzEu0MuZx`Tzg` literal 0 HcmV?d00001