Bar Charts
Mix.install([
{:vega_lite, "~> 0.1.5"},
{:kino_vega_lite, "~> 0.1.1"},
:jason
])
alias VegaLite, as: Vl
Simple bar chart
A bar chart encodes quantitative values as the extent of rectangular bars.
data = [
%{"a" => "A", "b" => 28},
%{"a" => "B", "b" => 55},
%{"a" => "C", "b" => 43},
%{"a" => "D", "b" => 91},
%{"a" => "E", "b" => 81},
%{"a" => "F", "b" => 53},
%{"a" => "G", "b" => 19},
%{"a" => "H", "b" => 87},
%{"a" => "I", "b" => 52}
]
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:bar)
|> Vl.encode(:x, field: :a, type: :nominal, axis: [label_angle: 40])
|> Vl.encode(:y, field: :b, type: :quantitative)
Responsive Bar Chart
The bar gets automatically resized based on container size. To see how the bar gets automatically resized try this in the editor.
I could´t use “width”: “container”. Is this possible in livebook?
Vl.new(width: 300, heigh: 250)
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/cars.json")
|> Vl.mark(:bar)
|> Vl.encode(:x, field: "Origin")
|> Vl.encode(:y, aggregate: :count, title: "Number of Cars")
Section
Aggregate Bar Chart
A bar chart showing the US population distribution of age groups in 2000.
Vl.new(
height: [step: 17],
description: "A bar chart showing the US population distribution of age groups in 2000."
)
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/population.json")
|> Vl.transform(filter: "datum.year == 2000")
|> Vl.mark(:bar)
|> Vl.encode(:y, field: "age")
|> Vl.encode(:x, field: "people", aggregate: :sum, title: "population")
Aggregate Bar Chart (Sorted)
A bar chart that sorts the y-values by the x-values.
Vl.new(
height: [step: 17],
description: "A bar chart that sorts the y-values by the x-values."
)
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/population.json")
|> Vl.transform(filter: "datum.year == 2000")
|> Vl.mark(:bar)
|> Vl.encode(:y, field: "age", type: :ordinal, sort: "-x")
|> Vl.encode(:x, field: "people", aggregate: :sum, title: "population")
Grouped Bar Chart
Read here for more details about how to set step size for grouped bar.
data = [
%{"category" => "A", "group" => "x", "value" => 0.1},
%{"category" => "A", "group" => "y", "value" => 0.6},
%{"category" => "A", "group" => "z", "value" => 0.9},
%{"category" => "B", "group" => "x", "value" => 0.7},
%{"category" => "B", "group" => "y", "value" => 0.2},
%{"category" => "B", "group" => "z", "value" => 1.1},
%{"category" => "C", "group" => "x", "value" => 0.6},
%{"category" => "C", "group" => "y", "value" => 0.1},
%{"category" => "C", "group" => "z", "value" => 0.2}
]
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:bar)
|> Vl.encode(:x, field: "category")
|> Vl.encode(:y, field: "value", type: :quantitative)
|> Vl.encode(:x_offset, field: "group")
|> Vl.encode(:color, field: "group")
Grouped Bar Chart (Multiple Measure with Repeat)
Read here for more details about how to set step size for grouped bar.
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/movies.json")
|> Vl.repeat(
[layer: ["Worldwide Gross", "US Gross"]],
Vl.new()
|> Vl.mark(:bar)
|> Vl.encode(:x, field: "Major Genre", type: :nominal)
|> Vl.encode_repeat(:y, :layer, aggregate: :sum, type: :quantitative, title: "Total Gross")
|> Vl.encode(:color, datum: [repeat: :layer], type: :nominal, title: "Gross")
|> Vl.encode(:x_offset, datum: [repeat: :layer])
)
Stacked Bar Chart
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/seattle-weather.csv")
|> Vl.mark(:bar)
|> Vl.encode_field(:x, "date", type: :ordinal, time_unit: :month, title: "Month of the year")
|> Vl.encode(:y, aggregate: :count, type: :quantitative)
|> Vl.encode_field(:color, "weather",
type: :nominal,
scale: [
domain: ["sun", "fog", "drizzle", "rain", "snow"],
range: ["#e7ba52", "#c7c7c7", "#aec7e8", "#1f77b4", "#9467bd"]
]
)
Stacked Bar Chart with Rounded Corners
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/seattle-weather.csv")
|> Vl.mark(:bar, corner_radius_top_left: 10, corner_radius_top_right: 10)
|> Vl.encode_field(:x, "date", type: :ordinal, time_unit: :month, title: "Month of the year")
|> Vl.encode(:y, aggregate: :count, type: :quantitative)
|> Vl.encode_field(:color, "weather")
Horizontal Stacked Bar Chart
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/barley.json")
|> Vl.mark(:bar)
|> Vl.encode_field(:y, "variety")
|> Vl.encode_field(:x, "yield", aggregate: :sum)
|> Vl.encode_field(:color, "site")
Normalized (Percentage) Stacked Bar Chart
Vl.new(width: [step: 13])
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/population.json")
|> Vl.transform(filter: "datum.year == 2000")
|> Vl.transform(calculate: "datum.sex == 2 ? 'Female' : 'Male'", as: "gender")
|> Vl.mark(:bar)
|> Vl.encode_field(:x, "age")
|> Vl.encode_field(:y, "people", aggregate: :sum, stack: :normalize)
|> Vl.encode_field(:color, "gender", scale: [range: ["#675193", "#ca8861"]])
Normalized (Percentage) Stacked Bar Chart With Labels
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/population.json")
|> Vl.transform(filter: "datum.year == 2000")
|> Vl.transform(calculate: "datum.sex == 2 ? 'Female' : 'Male'", as: "gender")
|> Vl.encode_field(:y, "age")
|> Vl.layers([
Vl.new()
|> Vl.mark(:bar)
|> Vl.encode_field(:x, "people", aggregate: :sum, stack: :normalize, title: "population")
|> Vl.encode_field(:color, "gender", scale: [range: ["#675193", "#ca8861"]]),
Vl.new()
|> Vl.mark(:text, opacity: 0.9, color: :white)
|> Vl.encode_field(:x, "people", aggregate: :sum, stack: :normalize, band_position: 0.5)
|> Vl.encode_field(:text, "people", aggregate: :sum, title: "population")
|> Vl.encode_field(:detail, "gender")
])
Gantt Chart (Ranged Bar Marks)
A simple bar chart with ranged data (aka Gantt Chart).
data = [
%{"task" => "A", "start" => 1, "end" => 3},
%{"task" => "B", "start" => 3, "end" => 8},
%{"task" => "C", "start" => 8, "end" => 10}
]
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:bar)
|> Vl.encode_field(:y, "task", type: :ordinal)
|> Vl.encode_field(:x, "start", type: :quantitative)
|> Vl.encode_field(:x2, "end")
A Bar Chart Encoding Color Names in the Data
A bar chart that directly encodes color names in the data.
data = [
%{"color" => "red", "b" => 28},
%{"color" => "green", "b" => 55},
%{"color" => "blue", "b" => 43}
]
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:bar)
|> Vl.encode_field(:x, "color", type: :nominal)
|> Vl.encode_field(:y, "b", type: :quantitative)
|> Vl.encode_field(:color, "color", type: :nominal, scale: false)
Layered Bar Chart
A bar chart showing the US population distribution of age groups and gender in 2000.
Vl.new(width: [step: 17])
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/population.json")
|> Vl.transform(filter: "datum.year == 2000")
|> Vl.transform(calculate: "datum.sex == 2 ? 'Female' : 'Male'", as: "gender")
|> Vl.mark(:bar)
|> Vl.encode_field(:x, "age", type: :ordinal)
|> Vl.encode_field(:y, "people", aggregate: "sum", title: "population", stack: false)
|> Vl.encode_field(:color, "gender", scale: [range: ["#675193", "#ca8861"]])
|> Vl.encode(:opacity, value: 0.6)
###
Diverging Stacked Bar Chart (Population Pyramid)
A population pyramid for the US in 2000, created using stack. See https://vega.github.io/vega-lite/examples/concat_population_pyramid.html for a variant of this created using concat.
Vl.new(width: 300, height: 200)
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/population.json")
|> Vl.transform(filter: "datum.year == 2000")
|> Vl.transform(calculate: "datum.sex == 2 ? 'Female' : 'Male'", as: "gender")
|> Vl.transform(calculate: "datum.sex == 2 ? -datum.people : datum.people", as: "signed_people")
|> Vl.mark(:bar)
|> Vl.encode_field(:y, "age", sort: :descending, axis: false)
|> Vl.encode_field(:x, "signed_people", aggregate: :sum, axis: [format: :s])
|> Vl.encode_field(
:color,
"gender",
scale: [range: ["#675193", "#ca8861"]],
legend: [orient: :top, title: false]
)
|> Vl.config(view: [stroke: false], axis: [grid: false])
Diverging Stacked Bar Chart (with Neutral Parts)
A diverging stacked bar chart for sentiments towards a set of eight questions, displayed as percentages with neutral responses straddling the 0% mark
TODO set correct x
data = [
%{
"question" => "Question 1",
"type" => "Strongly disagree",
"value" => 24,
"percentage" => 0.7
},
%{"question" => "Question 1", "type" => "Disagree", "value" => 294, "percentage" => 9.1},
%{
"question" => "Question 1",
"type" => "Neither agree nor disagree",
"value" => 594,
"percentage" => 18.5
},
%{"question" => "Question 1", "type" => "Agree", "value" => 1927, "percentage" => 59.9},
%{"question" => "Question 1", "type" => "Strongly agree", "value" => 376, "percentage" => 11.7},
%{
"question" => "Question 2",
"type" => "Strongly disagree",
"value" => 2,
"percentage" => 18.2
},
%{"question" => "Question 2", "type" => "Disagree", "value" => 2, "percentage" => 18.2},
%{
"question" => "Question 2",
"type" => "Neither agree nor disagree",
"value" => 0,
"percentage" => 0
},
%{"question" => "Question 2", "type" => "Agree", "value" => 7, "percentage" => 63.6},
%{"question" => "Question 2", "type" => "Strongly agree", "value" => 11, "percentage" => 0},
%{"question" => "Question 3", "type" => "Strongly disagree", "value" => 2, "percentage" => 20},
%{"question" => "Question 3", "type" => "Disagree", "value" => 0, "percentage" => 0},
%{
"question" => "Question 3",
"type" => "Neither agree nor disagree",
"value" => 2,
"percentage" => 20
},
%{"question" => "Question 3", "type" => "Agree", "value" => 4, "percentage" => 40},
%{"question" => "Question 3", "type" => "Strongly agree", "value" => 2, "percentage" => 20},
%{"question" => "Question 4", "type" => "Strongly disagree", "value" => 0, "percentage" => 0},
%{"question" => "Question 4", "type" => "Disagree", "value" => 2, "percentage" => 12.5},
%{
"question" => "Question 4",
"type" => "Neither agree nor disagree",
"value" => 1,
"percentage" => 6.3
},
%{"question" => "Question 4", "type" => "Agree", "value" => 7, "percentage" => 43.8},
%{"question" => "Question 4", "type" => "Strongly agree", "value" => 6, "percentage" => 37.5},
%{"question" => "Question 5", "type" => "Strongly disagree", "value" => 0, "percentage" => 0},
%{"question" => "Question 5", "type" => "Disagree", "value" => 1, "percentage" => 4.2},
%{
"question" => "Question 5",
"type" => "Neither agree nor disagree",
"value" => 3,
"percentage" => 12.5
},
%{"question" => "Question 5", "type" => "Agree", "value" => 16, "percentage" => 66.7},
%{"question" => "Question 5", "type" => "Strongly agree", "value" => 4, "percentage" => 16.7},
%{"question" => "Question 6", "type" => "Strongly disagree", "value" => 1, "percentage" => 6.3},
%{"question" => "Question 6", "type" => "Disagree", "value" => 1, "percentage" => 6.3},
%{
"question" => "Question 6",
"type" => "Neither agree nor disagree",
"value" => 2,
"percentage" => 12.5
},
%{"question" => "Question 6", "type" => "Agree", "value" => 9, "percentage" => 56.3},
%{"question" => "Question 6", "type" => "Strongly agree", "value" => 3, "percentage" => 18.8},
%{"question" => "Question 7", "type" => "Strongly disagree", "value" => 0, "percentage" => 0},
%{"question" => "Question 7", "type" => "Disagree", "value" => 0, "percentage" => 0},
%{
"question" => "Question 7",
"type" => "Neither agree nor disagree",
"value" => 1,
"percentage" => 20
},
%{"question" => "Question 7", "type" => "Agree", "value" => 4, "percentage" => 80},
%{"question" => "Question 7", "type" => "Strongly agree", "value" => 0, "percentage" => 0},
%{"question" => "Question 8", "type" => "Strongly disagree", "value" => 0, "percentage" => 0},
%{"question" => "Question 8", "type" => "Disagree", "value" => 0, "percentage" => 0},
%{
"question" => "Question 8",
"type" => "Neither agree nor disagree",
"value" => 0,
"percentage" => 0
},
%{"question" => "Question 8", "type" => "Agree", "value" => 0, "percentage" => 0},
%{"question" => "Question 8", "type" => "Strongly agree", "value" => 2, "percentage" => 100}
]
Vl.new()
|> Vl.data_from_values(data)
|> Vl.transform(
calculate:
"if(datum.type === 'Strongly disagree',-2,0) + if(datum.type==='Disagree',-1,0) + if(datum.type =='Neither agree nor disagree',0,0) + if(datum.type ==='Agree',1,0) + if(datum.type ==='Strongly agree',2,0)",
as: "q_order"
)
|> Vl.transform(
calculate:
"if(datum.type === 'Disagree' || datum.type === 'Strongly disagree', datum.percentage,0) + if(datum.type === 'Neither agree nor disagree', datum.percentage / 2,0)",
as: "signed_percentage"
)
|> Vl.transform(stack: "percentage", as: ["v1", "v2"], groupby: ["question"])
|> Vl.transform(joinaggregate: [[field: "signed_percentage", op: "sum", as: "offset"]])
|> Vl.transform(groupby: ["question"])
|> Vl.transform(calculate: "datum.v1 - datum.offset", as: "nx")
|> Vl.transform(calculate: "datum.v2 - datum.offset", as: "nx2")
|> Vl.mark(:bar)
|> Vl.encode_field(:x, "nx", type: :quantitative, title: "percentage")
|> Vl.encode_field(:x2, "nx2")
|> Vl.encode_field(:y, "question",
type: :nominal,
title: "Question",
axis: [offset: 5, ticks: false, min_extent: 60, domain: false]
)
|> Vl.encode_field(:color, "type",
title: "Response",
scale: [
domain: [
"Strongly disagree",
"Disagree",
"Neither agree nor disagree",
"Agree",
"Strongly agree"
],
range: ["#c30d24", "#f3a583", "#cccccc", "#94c6da", "#1770ab"],
type: :ordinal
]
)
# |> Vl.to_spec()
Simple Bar Chart with Labels
Bar chart with text labels. Set domain to make the frame cover the labels.
data = [
%{"a" => "A", "b" => 28},
%{"a" => "B", "b" => 55},
%{"a" => "C", "b" => 43}
]
Vl.new()
|> Vl.data_from_values(data)
|> Vl.encode_field(:y, "a", type: :nominal)
|> Vl.encode_field(:x, "b", type: :quantitative, scale: [domain: [0, 60]])
|> Vl.layers([
Vl.new()
|> Vl.mark(:bar),
Vl.new()
|> Vl.mark(:text, align: :left, baseline: :middle, dx: 3)
|> Vl.encode_field(:text, "b", type: :quantitative)
])
Bar Chart with Label Overlays
TODO font looks like superposed
Vl.new(width: 300, height: [step: 20])
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/movies.json")
|> Vl.encode_field(:y, "Major Genre", type: :nominal, axis: false)
|> Vl.layers([
Vl.new()
|> Vl.mark(:bar, color: "#ddd")
|> Vl.encode_field(:x, "IMDB Rating",
aggregate: :mean,
scale: [domain: [0, 10]],
title: "Mean IMDB Ratings"
),
Vl.new()
|> Vl.mark(:text, align: :left, x: 5)
|> Vl.encode_field(:text, "Major Genre", detail: [aggregate: :count])
])
Bar Chart showing Initials of Month Names
Using labelExpr to show only initial letters of month names.
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/seattle-weather.csv")
|> Vl.mark(:bar)
|> Vl.encode_field(:x, "date",
time_unit: "month",
axis: [
label_align: "left",
label_expr: "datum.label[0]"
]
)
|> Vl.encode_field(:y, "precipitation", aggregate: :mean)
Bar Chart with Negative Values and a Zero-Baseline
A bar chart with negative values. We can hide the axis domain line, and instead use a conditional grid color to draw a zero baseline.
data = [
%{"a" => "A", "b" => -28},
%{"a" => "B", "b" => 55},
%{"a" => "C", "b" => -33},
%{"a" => "D", "b" => 91},
%{"a" => "E", "b" => 81},
%{"a" => "F", "b" => 53},
%{"a" => "G", "b" => -19},
%{"a" => "H", "b" => 87},
%{"a" => "I", "b" => 52}
]
Vl.new()
|> Vl.data_from_values(data)
|> Vl.mark(:bar)
|> Vl.encode_field(:x, "a",
type: :nominal,
axis: [domain: false, ticks: false, label_angle: 0, label_padding: 4]
)
|> Vl.encode_field(:y, "b",
type: :quantitative,
axis: [
grid_color: [
condition: [test: "datum.value ===0", value: "black"],
value: "#ddd"
]
]
)
Horizontal Bar Chart with Negative Values and Labels
A bar chart with negative values. We can hide the axis domain line, and instead use a conditional grid color to draw a zero baseline.
data = [
%{"a" => "A", "b" => -28},
%{"a" => "B", "b" => 55},
%{"a" => "C", "b" => -33},
%{"a" => "D", "b" => 91},
%{"a" => "E", "b" => 81},
%{"a" => "F", "b" => 53},
%{"a" => "G", "b" => -19},
%{"a" => "H", "b" => 87},
%{"a" => "I", "b" => 52}
]
Vl.new()
|> Vl.data_from_values(data)
|> Vl.encode_field(:y, "a",
type: :nominal,
axis: [domain: false, ticks: false, label_angle: 0, label_padding: 4]
)
|> Vl.encode_field(:x, "b",
type: :quantitative,
scale: [padding: 20],
axis: [
grid_color: [
condition: [test: "datum.value ===0", value: "black"],
value: "#ddd"
]
]
)
|> Vl.layers([
Vl.new()
|> Vl.mark(:bar),
Vl.new()
|> Vl.mark(:text,
align: [expr: "datum.b < 0 ? 'right' : 'left'"],
dx: [expr: "datum.b < 0 ? -2 : 2"]
)
|> Vl.encode_field(:text, "b", type: :quantitative)
])
Bar Chart with a Spacing-Saving Y-Axis
Bar Chart with a spacing-saving y-axis
Vl.new(height: [step: 50])
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/cars.json")
|> Vl.mark(:bar, y_offset: 5, corner_radius_end: 2, height: [band: 0.5])
|> Vl.encode_field(:y, "Origin",
scale: [padding: 0],
axis: [
band_position: 0,
grid: true,
domain: false,
ticks: false,
label_align: "left",
label_baseline: "middle",
label_padding: -5,
label_offset: -15,
title_x: 5,
title_y: -5,
title_angle: 0,
title_align: "left"
]
)
|> Vl.encode(:x, aggregate: :count, axis: [grid: false], title: "Number of cars")
Heat Lane Chart
Heat lane chart based on https://www.smashingmagazine.com/2022/07/accessibility-first-approach-chart-visual-design/
Vl.new(height: 150, width: 400)
|> Vl.data_from_url("https://vega.github.io/vega-lite/examples/data/cars.json")
|> Vl.transform(
bin: true,
field: "Horsepower",
as: ["bin_Horsepower_start", "bin_Horsepower_end"]
)
|> Vl.transform(
aggregate: [[op: :count, as: :count]],
groupby: ["bin_Horsepower_start", "bin_Horsepower_end"]
)
|> Vl.transform(bin: true, field: :count, as: ["bin_count_start", "bin_count_end"])
|> Vl.transform(calculate: "-datum.bin_count_end/2", as: "y2")
|> Vl.transform(calculate: "datum.bin_count_end/2", as: "y")
|> Vl.transform(joinaggregate: [[field: "bin_count_end", op: "max", as: "max_bin_count_end"]])
|> Vl.encode_field(:x, "bin_Horsepower_start",
type: :quantitative,
title: "Horsepower",
axis: [grid: false]
)
|> Vl.encode_field(:x2, "bin_Horsepower_end")
|> Vl.encode_field(:y, "y", axis: false)
|> Vl.encode_field(:y2, "y2")
|> Vl.layers([
Vl.new()
|> Vl.mark(:bar, x_offset: 2, x2_offset: -2, corner_radius: 3)
|> Vl.encode_field(:color, "max_bin_count_end",
type: :ordinal,
title: "count",
scale: [scheme: "lighttealblue"]
),
Vl.new()
|> Vl.mark(:bar, x_offset: 2, x2_offset: -2, y_offset: -3, y2_offset: 3)
|> Vl.encode_field(:color, "bin_count_end", type: :ordinal, title: "count")
])