livebook-dev / vega_lite Goto Github PK
View Code? Open in Web Editor NEWElixir bindings for Vega-Lite
License: Apache License 2.0
Elixir bindings for Vega-Lite
License: Apache License 2.0
Vl.new(width: 400, height: 80)
|> Vl.data_from_url("https://vega.github.io/editor/data/penguins.json")
|> Vl.transform(density: "Body Mass (g)", groupby: ["Species"], extent: [2500, 6500])
|> Vl.mark(:area)
|> Vl.encode_field(:x, "value", type: :quantitative, title: "Body mass (g)")
|> Vl.encode_field(:y, "density", type: :quantitative, stack: true)
|> Vl.encode_field(:color, "Species", type: :nominal)
https://vega.github.io/vega-lite/docs/density.html
The simplest approach would be to add a :text_color
argument
Can vega_lite be used to render graphics/charts outside of Livebook?
Like can I add this to a regular Phoenix application and generate/render a chart?
Looking at the docs I didn't see a way to do that.
Currently needed npm pacakges are searched in wrong local directory. Missing assets prefix (it is default standard setup in phoenix).
This is crude soluton. It might break it for others:
+local_bin = npm_bin(npm_path, ["--prefix", "./assets"])
-local_bin = npm_bin(npm_path)
global_bin = npm_bin(npm_path, ["--global"])
Probably better solution would be to provide some configuration options.
From the post on Elixirforum it seems that while rendering the chart vega_lite is placing the x axis at the top instead of the bottom while using the Vl.new/0
initializing function, this behavior is fixed by using VegaLite.new(width: 100, height: 100)
(any size, even smaller).
Even using something like
Vl.new()
|> Vl.encode_field(:x, "myaxis", axis: [orient: "bottom"])
does not work.
On the top without size in new
:
With size of 10x10:
With size of 100x100:
Livebook if needed: vegalite-issue.livemd.txt
Hi Team
Am working on vega lite 2 dimensional boxplot, and am thinking to submit this as a new mark (2DBoxplot) in vega_lite. Can i do this ? if yes then would like to know the process of how can i submit a new mark in vega_lite?
Thanks,
Saurabh
I'm hoping this is a simple question, but it's at least one I couldn't figure out.
I have some Vega-Lite JSON that I'd like to use to render a chart in livebook, but I can't figure out how to get it to work. I keep getting the VegaLite struct to render.
What am I missing here?
Thanks in advance for your time.
Hi there! I'm trying to conditionally set the stroke_width
by following the general structure here.
I've tried Vl.encode(spec, :stroke_width, value: 1)
and that works great. It's just unclear to me as to how to add the condition clause. I've tried adding a condition
keyword like Vl.encode(spec, :stroke_width, value: 1, condition: %{field: "field_name", type: :nominal, value: 5})
but no luck. I've tried a few permutations (with and without the type
, with and without the value
).
Any tips or direction would be greatly appreciated. ๐
the following code results in a labelangle property that the editor does not recognize. and if the case is changed to 'labelAngle:' it works fine in the editor. NOTE: The elixir code never changes the angle of the text; though it should since fixing the code in the editor works as expected. 'tickCount' has the same issue by the way,
`# elixir code
data = skill_stats
# data |> dbg
Vl.new(title: "Service Level By Skill", description: "")
|> Vl.data_from_values(data)
|> Vl.concat([
Vl.new(width: 700, height: 200)
|> Vl.encode_field(:x, "skill",type: :nominal,title: "",
axis: [ labelAngle: -45]
)
|> Vl.encode_field(:y, "value", type: :quantitative)
|> Vl.encode_field(:color, "series", scale: [scheme: "spectral"], legend: [orient: "left"] )
|> Vl.encode(:tooltip, [
[field: "series", type: :nominal, title: "category"],
[field: "value", type: :quantitative, title: "count"]
])
|> Vl.mark(:bar)
], :horizontal)
|> Vl.to_spec()
|> Jason.encode!()
`
[chart](Open the Chart in the Vega Editor)
I noticed that this does not render as desired, with the x
axis on top:
VegaLite.new
|> VegaLite.config(axisX: [orient: "top"])
|> VegaLite.mark(:square)
|> VegaLite.encode_field(:x, "x")
|> VegaLite.encode_field(:y, "y")
|> VegaLite.data_from_values([%{x: 0, y: 0}, %{x: 1, y: 1}])
VegaLite
is downcasing the axisX
atom in the config, becoming:
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"config": {"axisx": {"orient": "top"}},
"data": {"values": [{"x": 0, "y": 0}, {"x": 1, "y": 1}]},
"encoding": {"x": {"field": "x"}, "y": {"field": "y"}},
"mark": "square"
}
Which renders as:
Using a two-tuple list preserves the casing and works, so this is only a minor wart on the DSL:
VegaLite.new
|> VegaLite.config([{"axisX", [orient: "top"]}])
|> VegaLite.mark(:square)
|> VegaLite.encode_field(:x, "x")
|> VegaLite.encode_field(:y, "y")
|> VegaLite.data_from_values([%{x: 0, y: 0}, %{x: 1, y: 1}])
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"config": {"axisX": {"orient": "top"}},
"data": {"values": [{"x": 0, "y": 0}, {"x": 1, "y": 1}]},
"encoding": {"x": {"field": "x"}, "y": {"field": "y"}},
"mark": "square"
}
If I try add view
(https://vega.github.io/vega-lite/docs/spec.html for configuring background of the chart https://vega.github.io/vega-lite/docs/spec.html#view-background) to the spec like this
|> VegaLite.encode(:view,
fill: "#f3f3f3"
)
it results in an error unknown channel, expected one of :x, ...
We are limited by this collection of known channels:
https://github.com/livebook-dev/vega_lite/blob/v0.1.6/lib/vega_lite.ex#L374-L388
The workaround I can do is
|> VegaLite.to_spec()
|> Map.put("view", %{fill: "#f3f3f2"})
Would there be place for a non-channel function for adding to the spec?
I have a use case where I need to provide multiple datasets to a Vega spec.
It would be nice to support this:
https://vega.github.io/vega-lite/docs/data.html#datasets
Basically I'm looking for an API along the lines of:
Vl.new()
|> Vl.datasets_from_values([
main: my_main_data,
other: my_other_data,
])
|> Vl.data_from_name(:main)
...
# Example usage (already possible)
|> Vl.transform(:lookup, :some_key, from: [data: [name: :other], key: :some_key, fields: [:some_field]])
Here's a minimal example:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/editor/data/cars.json")
|> Vl.mark(:circle)
|> Vl.param("grid", select: :interval, bind: :scales)
|> Vl.param("brush", select: :interval, bind: [input: :range, min: 0, max: 1000])
|> Vl.encode_field(:x, "Horsepower", type: :quantitative)
|> Vl.encode_field(:y, "Miles_per_Gallon", type: :quantitative)
|> Vl.encode(:size, param: "brush", value: 10)
I've bashed my head against this for a while, trying all sorts of different things that seem to match up with the Vega-lite guides but.... no luck. Help? ๐ I'm sure it's me rather than a bug.
I've got the following scenario: I want to render two disjoint layers with different x,y values on top of each other. The first is a list of vectors from a tensor, the second is different sets of reference data.
No matter how I try to arrange Vl.layers()
or Vl.repeat()
, VegaLite will not render. Now this might be a Vega-Lite limitation, but it isn't clear from the docs.
This (just the repeat) works
{times, _nsamples} = Nx.shape(signal_tensor)
samples = for i <- 0..(times-1), do: {"#{:io_lib.format "s~3..0B", [i]}", Nx.to_flat_list(signal_tensor[i])}
y_names = for {name, _data} <- samples, do: name
plot_data = [x: x_data] ++ samples
Vl.new(width: 700, height: 400, autosize: [type: "fit", contains: "content"])
|> Vl.encode(:size, value: 1)
|> Vl.data_from_series(plot_data)
|> Vl.transform(filter: [field: "x", range: plot_range])
|> Vl.repeat(
[layer: y_names],
Vl.new()
|> Vl.mark(:line, clip: true)
|> Vl.encode_field(:x, "x", type: :quantitative, scale: [domain: plot_range])
|> Vl.encode_repeat(:y, :layer, type: :quantitative, scale: [zero: false], title: "signals")
|> Vl.encode(:color, type: :nominal, datum: [repeat: :layer], scale: [scheme: "turbo"])
|> Vl.encode(:opacity, value: 0.5)
)
Now what I'd like to do is plot a different signal on the same graph, but I cannot find a way to add Vl.layers()
in a meaningful way.
Any ideas?
Hi,
Is there a way to find width and length of bar chart in vegalite? Please find below explanation on why i need it:
If you refer "The Parts of Box Plots" section in below link then it tells us that boxplot mark is a composite mark that means made up of basic marks like bar, line, point etc.
https://vega.github.io/vega-lite/docs/boxplot.html#parts
This is where am trying to draw my own box plot instead of using inbuilt :boxplot mark. Please find below an example made up of dummy data.
Here am able to draw everything in livebook except the white line in bar chart. This line represents median inside bar and i want this line to go all the way up and down and touch the boundaries of bar on both side like we get in inbuilt boxplot.
basically i need help with highlighted section in below to dynamically get the height and length of bar and then accordingly draw the median line inside bar. Curious to know how inbuild boxplot mark does this?
Example:
Mix.install([
{:vega_lite, "> 0.1.0"},> 0.1.0"}
{:kino, "
])
alias VegaLite, as: Vl
data = [
%{
"atl_iqr" => 3.6189,
"atl_lo_outlier" => 1,
"atl_lo_whisker" => 3.176,
"atl_median" => 8.894,
"atl_q1" => 8.605,
"atl_q3" => 12.224,
"atl_up_outlier" => 20,
"atl_up_whisker" => 17.6525
}
]
Vl.new(height: 480, width: 500, title: "Composite Boxplot W ATLExecution & CPUUsage")
|> Vl.layers([
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:bar, tooltip: true)
|> Vl.encode(:size, value: 20)
|> Vl.encode_field(:x, "atl_q1", type: :quantitative, title: "ATLExecutionTime")
|> Vl.encode_field(:x2, "atl_q3"),
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:tick, color: :white, size: 10, tooltip: true)
|> Vl.encode_field(:x, "atl_median", type: :quantitative),
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:rule, size: 2, ticks: true, tooltip: true)
|> Vl.encode_field(:x, "atl_q1", type: :quantitative)
|> Vl.encode_field(:x2, "atl_lo_whisker"),
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:rule, size: 2, ticks: true, tooltip: true)
|> Vl.encode_field(:x, "atl_q3", type: :quantitative)
|> Vl.encode_field(:x2, "atl_up_whisker"),
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:tick, color: :black, size: 16, thickness: 2, tooltip: true)
|> Vl.encode_field(:x, "atl_lo_whisker", type: :quantitative),
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:tick, color: :black, size: 16, thickness: 2, tooltip: true)
|> Vl.encode_field(:x, "atl_up_whisker", type: :quantitative),
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:point, color: :red, tooltip: true)
|> Vl.encode_field(:x, "atl_lo_outlier", type: :quantitative),
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:point, color: :red, tooltip: true)
|> Vl.encode_field(:x, "atl_up_outlier", type: :quantitative)
])
e.g.
"encoding": {
"x": {"timeUnit": "yearmonthdate", "field": "date"},
"tooltip": [
{"timeUnit": "yearmonthdate", "field": "date"},
{"field": "temp_max", "type": "quantitative"},
{"field": "temp_min", "type": "quantitative"}
]
},
Hi Team
Am working in Livebook and using vega-lite library for creating charts. I noticed one issue wherein when am using "labelExpr" atom for customizing my chart labels then it is not working. Opening my visualization chart from livebook to vega-lite editor shows me that it is converting "labelExpr" to lower case "labelexpr" and this is what the issue is.
Here is a sample example for my data from Livebook
data = [
%{"event" => "A1", "value" => 3, "quartile" => 3.5},
%{"event" => "A1", "value" => 4, "quartile" => 3.5},
%{"event" => "A1", "value" => 5, "quartile" => 3.5},
%{"event" => "B2", "value" => 1, "quartile" => 1.5},
%{"event" => "B2", "value" => 2, "quartile" => 1.5},
%{"event" => "B2", "value" => 3, "quartile" => 1.5},
%{"event" => "C3", "value" => 5, "quartile" => 5.5},
%{"event" => "C3", "value" => 6, "quartile" => 5.5},
%{"event" => "C3", "value" => 7, "quartile" => 5.5}
]
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:boxplot, orient: "horizontal", color: :orange, ticks: true)
|> Vl.encode_field(:x, "value")
|> Vl.encode_field(:y, "event",
axis: [labelExpr: "datum.label[0]"],
sort: [op: :q1, field: "value"]
)
I also tried to explore through the code and seem like below code is having the problem? May you pls check and confirm i can initiate a PR for the same ?
https://github.com/livebook-dev/vega_lite/blob/main/lib/vega_lite.ex#L1111
This elixir code I have seems ok but does not produce a chart. I have commented out the line that causes the issue; The joinaggregate line.
it is not clear to me why that line causes a blank chart. My goal is to produce a chart like what is shown in the editor...not sure its a bug but can transform be used some other way?
`data = skill_stats
fit_bars = Enum.count(get_skills(skill_stats)) * 22
# will never be smaller than 200 but expand depending
# on data [ for each skill ] and overflow to a scroll
width = max(fit_bars - rem(fit_bars, 100), 200)
Vl.new(title: "Service Level By Skill", description: "")
|> Vl.data_from_values(data)
|> Vl.concat(
[
Vl.new(width: width, height: 200)
|> Vl.encode_field(:x, "skill",
type: :nominal,
title: "",
axis: [label_angle: -45, label_font_size: 15])
|> Vl.encode_field(:y, "value",
type: :quantitative,
aggregate: :sum,
stack: "normalize",
title: "percentage of calls",
axis: [format: ".0%"],
sort: [field: "value"]
)
|> Vl.encode_field(:color, "series", scale: [scheme: "spectral"], legend: [orient: "left"])
|> Vl.encode(:order, aggregate: "sum", field: "value")
#|> Vl.transform(joinaggregate: "value", op: :sum, groupby: "skill", as: "TotalCount")
|> Vl.transform(calculate: "datum.value/datum.TotalCount * 100", as: "PercentOfTotal")
|> Vl.encode(:tooltip, [
[field: "skill", type: :nominal],
[field: "series", type: :nominal, title: "category"],
[field: "value", type: :quantitative, title: "count"],
[field: "sl_percentage", type: :quantitative, title: "Service Level %"],
[field: "abandon_percentage", type: :quantitative, title: "abandon %"],
[field: "PercentOfTotal", title: "% of Calls per skill", type: :quantitative, format: "0.2f"]
])
|> Vl.mark(:bar)
],
:horizontal
)
|> Vl.to_spec()
|> Jason.encode!()`
I'm trying to create a heatmap that will contain text annotation for each cell. Is it possible to create this using VegaLite.mark
?
If not, is it possible to add text layer to a heatmap? How?
What I tried:
alias VegaLite, as: VL
items = ["a", "b", "c"]
interacting_items = [{"a", "b"}, {"a", "c"}]
interaction_data =
for x <- items, y <- items do
score =
cond do
{x, y} in interacting_items -> 1
true -> 0
end
%{"x" => x, "y" => y, "xy" => x <> y, "score" => score}
end
VL.new(width: 500, height: 500)
|> VL.Data.heatmap(interaction_data, x: "x", y: "y", text: "y",
color: [field: "score", scale: [scheme: "reds"]])
|> VL.mark("text", "xy")
The error I get:
** (ArgumentError) cannot add mark to the view, because it is already a multi-view specification (has the :layer key defined)
(vega_lite 0.1.8) lib/vega_lite.ex:1028: anonymous fn/4 in VegaLite.validate_blank_view!/2
(elixir 1.14.2) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
(vega_lite 0.1.8) lib/vega_lite.ex:1027: VegaLite.validate_blank_view!/2
(vega_lite 0.1.8) lib/vega_lite.ex:612: VegaLite.mark/3
(stdlib 3.15) erl_eval.erl:685: :erl_eval.do_apply/6
(elixir 1.14.2) lib/module/parallel_checker.ex:107: Module.ParallelChecker.verify/1
I'm trying to figure out a cumulative count aggregation and I'm stumbling over the aggregate and window transforms. Here's a Livebook where I'm trying to replicate the example here. I can't seem to get the correct incantation of transforms down. Any help would be greatly appreciated. ๐ I'm happy to add the examples back via a PR.
https://gist.github.com/cigrainger/9fe3eda3b74d207ead4401a1211ac937
Issue:
We've been unable to render charts on Windows 10. As far as we can tell, there are two problems.
The first is that Windows doesn't seem to support invoking *.cmd
files as executables. So when System.find_executable("npm") returns the path to npm.cmd
and npm_bin tries to run it, it fails with an eaccess
error. For *.cmd
, my understanding is that it would have to be something like System.cmd('cmd.exe', ['/c', path | args])
instead.
The second is that getting the path to the correct vega-lite binary fails on windows because the executable name is hard-coded (vl2svg
as opposed to vl2svg.cmd
on Windows).
In both cases, it seems like there would have a to be an OS check before searching and/or invoking the executables.
Versions:
OS: Windows 10
Node: 12.13.1
NPM: 6.12.1
vega_lite: 0.1.3
[email protected]
[email protected]
[email protected]
original discussion on the forums
VegaLite.Export
uses the npm bin
command to find where to look for executables, but that subcommand was removed in NPM 9.0:
but then there was some concern that the PR author's assumptions (like "the global bin location is always where node
is") aren't true across platforms, so now there's an RFC to re-add npm bin
but only for the global case:
The simplest workaround would be to ensure that users are on NPM 8 or lower, but I don't know if that's an easy thing to enforce.
Long-term, the various NPM discussions linked above suggest that this should be using npm run
or similar instead of manually searching for executables.
this json works fine in the editor but there are no examples of it in the documentation; or anywhere else that I can find. I know this is not a bug per say, But I would be glad to add documentation if I could figure this one out...
`{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {
"name": "data",
"values": [
{
"agent": "bart",
"avg_score": "3.0000000000000000",
"call_count": 5,
"label": "Answer Time",
"max_score": 5,
"percentage": "0.60000000000000000000",
"score_average": "3.0000000000000000",
"score_calc": "0.00",
"score_desc": "agent % from avg",
"score_percentage": "0.60000000000000000000"
},
{
"agent": "mabel",
"avg_score": "3.0000000000000000",
"call_count": 5,
"label": "Answer Time",
"max_score": 5,
"percentage": "0.60000000000000000000",
"score_average": "3.0000000000000000",
"score_calc": "0.00",
"score_desc": "agent % from avg",
"score_percentage": "0.60000000000000000000"
},
{
"agent": "bart",
"avg_score": "5.8000000000000000",
"call_count": 5,
"label": "Negative Words",
"max_score": 10,
"percentage": "0.58000000000000000000",
"score_average": "5.8000000000000000",
"score_calc": "0.00",
"score_desc": "agent % from avg",
"score_percentage": "0.58000000000000000000"
},
{
"agent": "mabel",
"avg_score": "5.8000000000000000",
"call_count": 5,
"label": "Negative Words",
"max_score": 10,
"percentage": "0.58000000000000000000",
"score_average": "5.8000000000000000",
"score_calc": "0.00",
"score_desc": "agent % from avg",
"score_percentage": "0.58000000000000000000"
}
]
},
"description": "",
"encoding": {
"y": {
"axis": {
"labels": true,
"tickBand": "center",
"tickOpacity": 0,
"title": ""
},
"field": "agent",
"type": "nominal"
}
},
"height": {"step": 16},
"layer": [
{
"encoding": {
"tooltip": [
{"field": "AgentCalls", "title": "calls", "type": "quantitative"},
{"field": "AvgAgtScore", "title": "avg score", "type": "quantitative"}
],
"x": {
"aggregate": "average",
"axis": null,
"field": "percentage",
"scale": {"domain": [0, 1]}
}
},
"mark": {"color": "#ddd", "tooltip": true, "type": "bar"},
"transform": [
{
"groupby": ["agent"],
"joinaggregate": [
{"as": "AvgAgtScore", "field": "avg_score", "op": "average"}
]
},
{
"groupby": ["agent"],
"joinaggregate": [
{"as": "AgentCalls", "field": "call_count", "op": "average"}
]
}
]
},
{
"encoding": {"text": {"field": "AgentPercentage", "format": ".2%"}},
"mark": {
"align": "left",
"tooltip": "the agent percentage",
"type": "text",
"x": 5
},
"transform": [
{
"groupby": ["agent"],
"joinaggregate": [
{"as": "AgentPercentage", "field": "percentage", "op": "average"}
]
}
]
},
{
"encoding": {
"color": {
"condition": {"test": "datum['ScorePercentageCalc'] < 0 ", "value": "Red"},
"value": "Green"
},
"text": {
"field": "ScorePercentageCalc",
"format": ".2%",
"type": "quantitative"
},
"tooltip": [
{"field": "score_desc", "title": "description"},
{
"field": "ScorePercentage",
"format": ".2%",
"title": "avg for all scores",
"type": "quantitative"
}
]
},
"mark": {
"align": "left",
"baseline": "middle",
"dx": 24,
"fontWeight": "bold",
"tooltip": "test",
"type": "text"
},
"transform": [
{
"joinaggregate": [
{
"as": "ScorePercentageCalc",
"field": "score_calc",
"op": "average"
}
]
},
{
"joinaggregate": [
{
"as": "ScorePercentage",
"field": "score_percentage",
"op": "average"
}
]
}
]
}
],
"title": "Agent Scores",
"width": 200
}`
How hard would it be to add the ability to pipe a transformed DataFrame right into a VegaLite chart without needing to create intermediary variables?
My idea is something like this...
df
|> DataFrame.group_by(...)
|> DataFrame.summarise_with(...)
|> Vl.new()
|> Vl.mark()
|> Vl.encode_with(:x, "x")
|> Vl.encode_with(:y, "y")
...and the result is a chart in Livebook without any unnecessary variables.
Thoughts?
the charts i have all use the same component; and I want to update a given chart when the parameters change on the address bar. I have it working so that the data is updated when the parameters change; I have fields and other widgets updating on the page but am unsure how to update a chart when the data changes.
Not so much expecting a detailed answer or tutorial, just maybe a nudge in the right direction...
this is my existing component [just for reference]
`attr(:id, :string, required: true)
attr(:spec, :string, required: true)
attr(:width, :string, required: false, default: "100")
attr(:height, :string, required: false, default: "100")
def chart(assigns) do
~H"""
Requires Erlang/OTP 24+ and webview support.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.