How to make legend for scatter plot in Makie plots? - julia

Suppose I have the following data:
using DataFrames, CairoMakie, RDatasets
iris_df = dataset("datasets", "iris")[:, 1:4];
preds = rand((0, 1), 150)
Now I want to draw a scatter plot with a legend and arbitrary labels:
p = scatter(
iris_df[:, 2], iris_df[:, 3],
color=preds,
dpi=300,
)
Now I want to add a legend for it, But I'm unsuccessful.
What I've tried:
julia> Legend(p, ["label2", "label1"])
ERROR: MethodError: no method matching _block(::Type{Legend}, ::Makie.FigureAxisPlot, ::Vector{String})
Closest candidates are:
_block(::Type{<:Makie.Block}, ::Union{GridPosition, GridSubposition}, ::Any...; kwargs...) at C:\Users\Shayan\.julia\packages\Makie\Ggejq\src\makielayout\blocks.jl:287
_block(::Type{<:Makie.Block}, ::Union{Figure, Scene}, ::Any...; bbox, kwargs...) at C:\Users\Shayan\.julia\packages\Makie\Ggejq\src\makielayout\blocks.jl:298
Or:
f = Figure()
Axis(f[1, 1])
p = scatter!(
iris_df[:, 2], iris_df[:, 3],
color=preds,
dpi=300,
)
Legend(p, ["label2", "label1"])
ERROR: MethodError: no method matching _block(::Type{Legend}, ::Scatter{Tuple{Vector{Point{2, Float32}}}}, ::Vector{String})
Closest candidates are:
_block(::Type{<:Makie.Block}, ::Union{GridPosition, GridSubposition}, ::Any...; kwargs...) at C:\Users\Shayan\.julia\packages\Makie\Ggejq\src\makielayout\blocks.jl:287
_block(::Type{<:Makie.Block}, ::Union{Figure, Scene}, ::Any...; bbox, kwargs...) at C:\Users\Shayan\.julia\packages\Makie\Ggejq\src\makielayout\blocks.jl:298

Based on the maintainers' claim, this can't be done automatically yet. But one way is to can make the legend manually:
fig, ax, p = scatter(
iris_df[:, 2], iris_df[:, 3],
color=preds, dpi=300,
size=(50, 50),
)
elem_1, elem_2 = (
[
MarkerElement(
color = :black,
marker = :square,
markersize = 15
)
],
[
MarkerElement(
color = :yellow,
marker = :square,
markersize = 15
)
]
)
Legend(
fig[1, 2],
[elem_1, elem_2],
["label 1", "label 2"],
"Legend"
)
display(fig)
Helpful links: [1], [2]

Related

The legends is going outside of the plot. How to avoid it

Drawing using code below
p = scatter(; title = "a", titlefont=font(12), xlabel = "message size",
xscale = :log10, xlims, xticks, ylabel = "time [sec]",
ylims, yscale = :log10, legend=:outertopright, left_margin=15Plots.mm, bottom_margin=15Plots.mm,
guidefont=font(8), xtickfont=font(5), width=2000,height=2000, legend_margin=5, legendfont=font(5))
scatter!( p, data1[:, 1], data2[:, 5], color=colors[index], ma=0.9 )
The legends is going outside of the plot. How to avoid it or increase the size of whole graph.
data1 = [0,4,8,16, ,32 ,64 ,128 ,256 ,512 ,1024 ,2048 ,4096 ,8192 ,16384 ,32768 ,65536 ,131072 ,262144 ,524288 ,1048576 ,2097152 ,4194304]
data2=[3.08E-06 ,6.20E-06 ,6.26E-06 ,3.75E-05 ,4.36E-05 ,4.37E-05 ,4.52E-05 ,4.70E-05 ,5.58E-05 ,5.83E-05 ,6.67E-05 ,8.77E-05 ,0.000134397 ,0.000229329 ,0.000424682 ,0.000868432 ,0.00035778 ,0.000452593 ,0.000561347 ,0.000750234 ,0.001281027 ,0.002102504]

Does Makie have linked brushing capability similar to Holoviews?

In Holoviews it is possible to link two different plots, so that when we zoom in or select parts of one plot, the other plots reflects the same zooming in or selection. Are there similar functionality available in Makie?
Edit 1
Based on the answer I tried the code below, However, I get a static image. I tired it both in Jupyter lab notebook and VSCode both of which were installed in Windows Subsystem for Linux with Ubuntu. The code I tried is:
using CairoMakie
f = Figure(backgroundcolor = RGBf(0.98, 0.98, 0.98),
resolution = (1000, 700))
ga = f[1, 1] = GridLayout()
gb = f[2, 1] = GridLayout()
gcd = f[1:2, 2] = GridLayout()
gc = gcd[1, 1] = GridLayout()
gd = gcd[2, 1] = GridLayout()
axtop = Axis(ga[1, 1])
axmain = Axis(ga[2, 1], xlabel = "before", ylabel = "after")
axright = Axis(ga[2, 2])
linkyaxes!(axmain, axright)
linkxaxes!(axmain, axtop)
labels = ["treatment", "placebo", "control"]
data = randn(3, 100, 2) .+ [1, 3, 5]
for (label, col) in zip(labels, eachslice(data, dims = 1))
scatter!(axmain, col, label = label)
density!(axtop, col[:, 1])
density!(axright, col[:, 2], direction = :y)
end
f
Edit 2
After suggesting that I should use GLMakie for interactive plot, I tried it in VSCode and indeed I got an interactive plot. However, in Jupyter notebook, I got a static image. The solution is to use Makie.inline!(false) and display(f) rather than using f:
using GLMakie
Makie.inline!(false)
f = Figure(backgroundcolor = RGBf(0.98, 0.98, 0.98),
resolution = (1000, 700))
ga = f[1, 1] = GridLayout()
gb = f[2, 1] = GridLayout()
gcd = f[1:2, 2] = GridLayout()
gc = gcd[1, 1] = GridLayout()
gd = gcd[2, 1] = GridLayout()
axtop = Axis(ga[1, 1])
axmain = Axis(ga[2, 1], xlabel = "before", ylabel = "after")
axright = Axis(ga[2, 2])
linkyaxes!(axmain, axright)
linkxaxes!(axmain, axtop)
labels = ["treatment", "placebo", "control"]
data = randn(3, 100, 2) .+ [1, 3, 5]
for (label, col) in zip(labels, eachslice(data, dims = 1))
scatter!(axmain, col, label = label)
density!(axtop, col[:, 1])
density!(axright, col[:, 2], direction = :y)
end
display(f)
You can use linkxaxes! and linkyaxes!. Here's an example: https://makie.juliaplots.org/v0.17.13/tutorials/layout-tutorial/index.html#panel_a

Avg-min-max chart in plotly

I'm building a screener using plotly dash based on some plots generated through Microsoft Excel. One of those charts is the Avg-max-min, below an example of this type of graph.
I have no idea on how to "translate" this kind of graph in to a plotly figure, I've tried this so far, but I did not succeed:
fig = go.Figure(data=go.Scatter(
x=[0, 1, 2],
y=[6, 10, 2],
error_y=dict(
type='data', # value of error bar given in data coordinates
array=[1, 2, 3],
visible=True)
))
I think I've got really close to the real solution, is basically doing a errorbar plot without lines.
It doesn't seem to me that there is a prebuilt function to do so unless you are happy with box plots.
Data
import pandas as pd
import plotly.graph_obj as go
df = pd.DataFrame({"x": [0, 0, 0, 1, 1, 2],
"y":[1,2,4, 1, 10, 4]})
# here I calculate min, max and mean for every x
grp = df.groupby("x").agg({"y":{"min", "max", "mean"}})
grp.columns = ["_".join(col) for col in grp.columns]
grp = grp.reset_index()
Plot
fig = go.Figure()
# first I add a trace for every x
fig.add_trace(go.Scatter(x=grp["x"],
y=grp["y_min"],
mode="markers",
showlegend=False,
marker=dict(color="blue",
size=10)))
fig.add_trace(go.Scatter(x=grp["x"],
y=grp["y_mean"],
mode="markers",
showlegend=False,
marker=dict(color="blue",
size=20)))
fig.add_trace(go.Scatter(x=grp["x"],
y=grp["y_max"],
mode="markers",
showlegend=False,
marker=dict(color="blue",
size=10)))
# then I add a vertical line for
# every x where y_min!=y_max
for i, row in grp.iterrows():
if row["y_min"]!=row["y_max"]:
fig.add_shape(
dict(type="line",
x0=row["x"],
x1=row["x"],
y0=row["y_min"],
y1=row["y_max"],
line=dict(
color="blue",
width=2)
)
)
fig.update_layout(title="Avg-Max-Min Graph", title_x=0.5)
fig.show()

Providing markershapes as Int in Julia

In Julia, I can provide a color as an Int. For example, this works:
Using Plots()
# Using gr backend
gr()
x = [1,2,3]
y = [1,2,3]
cols = [1,2,3]
scatter(x,y, markercolor = cols, leg = false)
If I want to change the shape, I can provide the following:
shapes = [:hex, :circle, :hex]
scatter(x, y, markershape = shapes, markercolor = cols, leg = false)
But it seems I cannot provide marker shapes as an Int!
shapes = [1, 2, 3]
scatter(x, y, markershape = shapes, markercolor = cols, leg = false)
Is there any easy way to provide Int's for shapes in Plots? Or nice way to convert Ints to shapes?
Using an integer as an index into Plots.Supported_markers might work:
julia> Plots.supported_markers()
24-element Array{Symbol,1}:
:none
:auto
:circle
:rect
:star5
:diamond
:hexagon
:cross
:xcross
:utriangle
:dtriangle
:rtriangle
:ltriangle
:pentagon
:heptagon
:octagon
:star4
:star6
:star7
:star8
:vline
:hline
:+
:x
julia> Plots.supported_markers()[6]
:diamond

plot contour in different panels of sp plot

I have 3 different rasters in a stack. I need to plot a panel plot and add different shapefiles on each panel. So far I managed to do the following;
## read the libraries
library(raster)
library(rgdal)
library(sp)
library(rworldmap)
library(OceanView)
##random raster object
r <- raster(ncol=40, nrow=20)
r[] <- rnorm(n=ncell(r))
# Create a RasterStack object with 3 layers
s <- stack(x=c(r, r*2, r**2))
##coordinate system
wgs<-CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
##reading the additional shape files
w <- spTransform(getMap(), wgs)
poly <- list(list("sp.lines", as(w, 'SpatialLines'), lwd =
0.5,col="black"))
##plotting with spplot
plot(spplot(s,layout=c(3,1),sp.layout=poly,
colorkey =list(space = "right"),
names.attr = c("a","b","c")))
so far I plotted the 3 rasters with a shapefile overlayed on it. Now I need to plot the 3 different contours one each on the panel plot. And also need to plot the windspeed arrows on each of the plot. I know to do this I need to use contour() and quiver() functions. However, I am unable to plot these.
## different raster stack for the contour plot
s1 <- stack(x=c(r/2, r*10, r**5))
##differnt wind components
lat= matrix(rep(seq(-90,90,length.out=20),each=20), ncol=20, byrow=TRUE)
lon=matrix(rep(seq(-180,180,length.out=20),each=20), ncol=20, byrow=F)
u=matrix(rep(sample(seq(-2,2,length.out=1000),20),each=20), ncol=20, byrow=TRUE)
v=matrix(rep(sample(seq(-2,2,length.out=1000),20),each=20), ncol=20, byrow=TRUE)
##plot the arrows
quiver2D(u = u,v=v,x = lon, y = lat,add=T,type="simple")
can any one help me with this? Any help would be appreciated.
First you need to figure out which function in the lattice paradigm is being called. That requires either looking at the ?spplot` help page (which I stupidly failed to do first), or following the classes and functions along the calling tree (which is what I actually did). And then look at the help page of the final function to see if there is a parameter you can pass to add contour lines:
showMethods("spplot",class="Raster", includeDefs=TRUE) # last argument need to see code
Function: spplot (package sp)
obj="Raster"
function (obj, ...)
{
.local <- function (obj, ..., maxpixels = 50000, as.table = TRUE,
zlim)
{
obj <- sampleRegular(obj, maxpixels, asRaster = TRUE,
useGDAL = TRUE)
if (!missing(zlim)) {
if (length(zlim) != 2) {
warning("zlim should be a vector of two elements")
}
if (length(zlim) >= 2) {
obj[obj < zlim[1] | obj > zlim[2]] <- NA
}
}
obj <- as(obj, "SpatialGridDataFrame")
spplot(obj, ..., as.table = as.table)
}
.local(obj, ...)
}
> showMethods("spplot",class="SpatialGridDataFrame",includeDefs=TRUE)
Function: spplot (package sp)
obj="SpatialGridDataFrame"
function (obj, ...)
spplot.grid(as(obj, "SpatialPixelsDataFrame"), ...)
> showMethods("spplot.grid",class="SpatialPixelsDataFrame",includeDefs=TRUE)
Function "spplot.grid":
<not an S4 generic function>
> spplot.grid
Error: object 'spplot.grid' not found
> getAnywhere(spplot.grid)
A single object matching ‘spplot.grid’ was found
It was found in the following places
namespace:sp
with value
function (obj, zcol = names(obj), ..., names.attr, scales = list(draw = FALSE),
xlab = NULL, ylab = NULL, aspect = mapasp(obj, xlim, ylim),
panel = panel.gridplot, sp.layout = NULL, formula, xlim = bbox(obj)[1,
], ylim = bbox(obj)[2, ], checkEmptyRC = TRUE, col.regions = get_col_regions())
{
if (is.null(zcol))
stop("no names method for object")
if (checkEmptyRC)
sdf = addNAemptyRowsCols(obj)
else sdf = as(obj, "SpatialPointsDataFrame")
if (missing(formula))
formula = getFormulaLevelplot(sdf, zcol)
if (length(zcol) > 1) {
sdf = spmap.to.lev(sdf, zcol = zcol, names.attr = names.attr)
zcol2 = "z"
}
else zcol2 = zcol
if (exists("panel.levelplot.raster")) {
opan <- lattice.options("panel.levelplot")[[1]]
lattice.options(panel.levelplot = "panel.levelplot.raster")
}
scales = longlat.scales(obj, scales, xlim, ylim)
args = append(list(formula, data = as(sdf, "data.frame"),
aspect = aspect, panel = panel, xlab = xlab, ylab = ylab,
scales = scales, sp.layout = sp.layout, xlim = xlim,
ylim = ylim, col.regions = col.regions), list(...))
if (all(unlist(lapply(obj#data[zcol], is.factor)))) {
args$data[[zcol2]] = as.numeric(args$data[[zcol2]])
if (is.null(args$colorkey) || (is.logical(args$colorkey) &&
args$colorkey) || (is.list(args$colorkey) && is.null(args$colorkey$at) &&
is.null(args$colorkey$labels))) {
if (!is.list(args$colorkey))
args$colorkey = list()
ck = args$colorkey
args$colorkey = NULL
args = append(args, colorkey.factor(obj[[zcol[1]]],
ck))
}
else args = append(args, colorkey.factor(obj[[zcol[1]]],
ck, FALSE))
}
ret = do.call(levelplot, args)
if (exists("panel.levelplot.raster"))
lattice.options(panel.levelplot = opan)
ret
}
<bytecode: 0x7fae5e6b7878>
<environment: namespace:sp>
You can see that it supports additional arguments passed via , list(...)). As it happens it's therefore fairly easy to add contour lines to a levelplot with contour=TRUE although that only appears in the Arguments list, but not in the named arguments in the Usage section for levelplot. Nonetheless testing in the example on ?levelplot page shows that it succeeds. Your example is not a particularly good one to illustrate with, since it is so fine-grained and has no pattern of ascending or descending levels. Nonetheless adding contour=TRUE, to the arguments to spplot does produce black contour lines. (The timestamp is due to lattice code in my Rprofile setup so won't show up on your device).
png(); plot(spplot(s,layout=c(3,1),sp.layout=poly, contour=TRUE,
colorkey =list(space = "right"),
names.attr = c("a","b","c"))) ; dev.off()
If one gets around to hacking spplot.grid or perhaps sp::panel.gridplot, then this material from the author of levelplot might be of use:
https://markmail.org/search/?q=list%3Aorg.r-project.r-help+lattice+add+contours+to+levelplot#query:list%3Aorg.r-project.r-help%20lattice%20add%20contours%20to%20levelplot%20from%3A%22Deepayan%20Sarkar%22+page:1+mid:w7q4l7dh6op2lfmt+state:results

Resources