Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

geom.vector? #791

Closed
gaechter opened this issue Jan 14, 2016 · 14 comments
Closed

geom.vector? #791

gaechter opened this issue Jan 14, 2016 · 14 comments

Comments

@gaechter
Copy link

i think up to now its not possible to draw a vector in Gadfly. It were nice to have in geometry an option geom.vector.
Thank You for your great job!

Albert

@Mattriks
Copy link
Member

Mattriks commented Apr 6, 2016

Here is a quick implementation of vectors/arrows/segments. For this initial implementation, I've simply tried to make use of the aesthetic args that already exist. Suggestions for improvements welcome! I think the following example shows how to use it.
You can turn off the arrows by removing the shape=true argument from a layer. Line_style is controlled through Theme (and is not currently an aesthetic).

You'll need this file:
Geom_segments.txt
Rename it: Geom_segments.jl

And here is an example:

using DataFrames
include("Geom_segments.jl")
function expand_grid(n) 
    q = linspace(-10, 10, n)
    return vcat([[i j] for i in q, j in q]...)
end

function dsinc(x,y, sc) 
    z = hypot(x,y)
    dz = cos(z)./z  - sin(z)./(z.^2)
    θ = atan2(y,x)
    dx = sc*abs(dz).*cos(θ)
    dy = sc*abs(dz).*sin(θ)
    return DataFrame(xa=x-dx, ya=y-dy, xb=x+dx, yb=y+dy, dz=dz)
end

function sincf(x, y) 
    z = hypot(x,y)
    z = sin(z)./(z)
    return DataFrame(x=x, y=y, z=(z))
end


X = expand_grid(50)
X2 = expand_grid(20)

D1 = sincf(X[:,1], X[:,2])
D2 = dsinc(X2[:,1], X2[:,2], 2.0)

theme1 = Gadfly.Theme(default_color=colorant"black", line_style=[1mm, 0.0mm])
theme2 = Gadfly.Theme(line_style = [0.75mm, 0.5mm])

coord = Coord.cartesian(xmin=-10.0, xmax=10.0, ymin=-10.0, ymax=10.0)
l1 = layer(D1, x=:x, y=:y, color=:z, Geom.rectbin, order=1)
l2 = layer(D2, x=:xa, xmax=:xb, y=:ya, ymax=:yb, shape=true,
            Geom_segment, theme1, order=2 )
l3 = layer(D2, x=:xa, xmax=:xb, y=:ya, ymax=:yb, color=:dz, shape=true,
        Geom_segment, theme1, order=2)


p1 = plot(coord, l1, l2,
    Guide.title("f(x,y)=sin(hypot(x,y))/hypot(x,y)"),
)

p2 = plot(coord, l3,
    Scale.color_continuous(minvalue=-0.5, maxvalue=0.1),
    Guide.title("f'(x,y)"), Guide.xlabel("x"), Guide.ylabel("")
)

# hstack(p1, p2)

vector1

Note in this example I have plotted the gradient field in a particular way.

@AStupidBear
Copy link

@Mattriks This is what I need. Why not merge?

@Mattriks
Copy link
Member

Mattriks commented Aug 5, 2016

See the conversation starting from March 30 at #815 about the current status of Gadfly development.

@AStupidBear
Copy link

Oh, I see. It's a pitty! No wonder Plots.jl discards gadfly recently.

@vchuravy
Copy link

vchuravy commented Apr 7, 2017

Since the development of Gadfly has been moving forward it would be great to have functionality like this inbuilt!

@bjarthur
Copy link
Member

bjarthur commented Apr 7, 2017

#823 was merged last august. Geom.segment. perhaps we should close this issue?

@vchuravy
Copy link

vchuravy commented Apr 7, 2017

Yes maybe that is already powerful enough, I was looking for a way to draw vectorfields and I don't found Geom.segment particularly intuitive.

@tlnagy
Copy link
Member

tlnagy commented Apr 7, 2017

Maybe @Mattriks can give some insight since he was the original developer of Geom.segment

@Mattriks
Copy link
Member

Mattriks commented Apr 8, 2017

Note you can use Geom.segment(arrow=true) or Geom.vector as the example shows, and this provides basic functionality for segments/arrows/vectors. Doing a Geom.vectorfield(z, x, y) where z is 2D function or matrix would also be possible to implement. You can think about Geom.vectorfield(z, x, y) as being similar to Geom.contour(z, x, y). Just as Geom.contour returns a LineGeometry, so Geom.vectorfield would return a SegmentGeometry. Geom.contour requires the external package Contour to calculate the contours, so Geom.vectorfield would require an external package to calculate the vectors. What Julia package calculates vectorfields from z,x,y data?: my Julia package CoupledFields.

@Mattriks
Copy link
Member

Mattriks commented Apr 8, 2017

Here is an example. Like Geom.contour the function below takes the volcano z,x,y data, but turns it into a vectorfield.

using CoupledFields, DataFrames, RDatasets

function VecField{T<:Float64}(X::Matrix{T}, Z::Vector{T}, sc::T)
    # sc controls the size of the vectors
    kpars = GaussianKP(X)
    ∇g = hcat(gradvecfield([1.0 -7.0], X, Z[:,1:1], kpars)...)'
    vecf = [X-∇g*sc X+∇g*sc] 
    return DataFrame(x=X[:,1], y=X[:,2], x1=vecf[:,1], y1=vecf[:,2], x2=vecf[:,3], y2=vecf[:,4], col=Z[:,1])
end

volcano = Matrix{Float64}(dataset("datasets", "volcano"))
volc = volcano[1:4:end, 1:4:end]
Z = vec(volc) 
X = vcat([[x y] for x in 1.0:size(volc,1), y in 1.0:size(volc,2)]...)

 D = VecField(X, Z, 0.05)
coord = Coord.cartesian(xmin=1, xmax=22, ymin=1, ymax=16)

p = plot(D, coord,
        layer(x=:x1, y=:y1, xend=:x2, yend=:y2, color=:col, Geom.vector),
        layer(z=volc, x=1:22, y=1:16, Geom.contour(levels=7)),
        Scale.x_continuous(minvalue=1.0, maxvalue=22.0),
        Scale.y_continuous(minvalue=1.0, maxvalue=16.0),
        Guide.xlabel("x"), Guide.ylabel("y"),
        Theme(key_position=:none)
)

issue791a
Geom.vectorfield(z,x,y) should definitely be implemented in Gadfly, just so that we can use that awesome plot in the documentation!

@bjarthur
Copy link
Member

bjarthur commented Apr 8, 2017

please submit a PR!

is vectorfield the terminology used by ggplot?

@Mattriks
Copy link
Member

Mattriks commented Apr 8, 2017

In ggplot you can plot vectors using geom_segment, but I don't think that ggplot2 currently has a function that takes a dataset of z,x,y values and turns it into a vectorfield.
In Gadfly, the first step in implementing this is to write a custom statistic (#894), which returns a new aes for the vectorfield, which is then passed to Geom.vector. I've done this, but I'm getting errors elsewhere in Gadfly, which means that the new aes is doing something that Gadfly finds atypical. So now I need to resolve those issues.

@Mattriks
Copy link
Member

Mattriks commented Apr 10, 2017

Here is my custom statistic Stat.vectorfield (it can be used as is):

module Stat

using CoupledFields, Gadfly

type VecFieldStatistic <: Gadfly.StatisticElement
    smoothness::Float64
    scale::Float64
    samples::Int
    function VecFieldStatistic(; smoothness=1.0, scale=1.0, samples=20)
        new(smoothness, scale, samples)
    end
end

function Gadfly.input_aesthetics(stat::VecFieldStatistic)
    return [:z, :x, :y, :color]
end

function Gadfly.output_aesthetics(stat::VecFieldStatistic)
    return [:x, :y, :xend, :yend, :color]
end

function Gadfly.default_scales(stat::VecFieldStatistic)
    return [Gadfly.Scale.x_continuous(), Gadfly.Scale.y_continuous()]
end

const vectorfield = VecFieldStatistic

function Gadfly.Stat.apply_statistic(stat::VecFieldStatistic,
                         scales::Dict{Symbol, Gadfly.ScaleElement},
                         coord::Gadfly.CoordinateElement,
                         aes::Gadfly.Aesthetics)

    xs = aes.x === nothing ? nothing : convert(Vector{Float64}, aes.x)
    ys = aes.y === nothing ? nothing : convert(Vector{Float64}, aes.y)

    if typeof(aes.z) <: Function
        if xs == nothing && aes.xmin != nothing && aes.xmax != nothing
            xs = linspace(aes.xmin[1], aes.xmax[1], stat.samples)
        end
        if ys == nothing && aes.ymin != nothing && aes.ymax != nothing
            ys = linspace(aes.ymin[1], aes.ymax[1], stat.samples)
        end

        zs = Float64[aes.z(x, y) for x in xs, y in ys]

    elseif typeof(aes.z) <: Matrix
        zs = convert(Matrix{Float64}, aes.z)

        if xs == nothing
            xs = collect(Float64, 1:size(zs)[1])
        end
        if ys == nothing
            ys = collect(Float64, 1:size(zs)[2])
        end
        if size(zs) != (length(xs), length(ys))
            error("Stat.vectorfield requires dimension of z to be length(x) by length(y)")
        end
    else
        error("Stat.vectorfield requires either a matrix or a function")
    end
    
    X = vcat([[x y] for x in aes.x, y in aes.y]...)
    Z = vec(zs)
    kpars = GaussianKP(X)
    ∇g = hcat(gradvecfield([stat.smoothness -7.0], X, Z[:,1:1], kpars)...)'
    vecf = [X-∇g*stat.scale X+∇g*stat.scale] 
    
    aes.z = nothing
    aes.x = vecf[:,1]
    aes.y = vecf[:,2]
    aes.xend = vecf[:,3]
    aes.yend = vecf[:,4]
    color_scale = get(scales, :color, Gadfly.Scale.color_continuous_gradient())
    Scale.apply_scale(color_scale, [aes], Gadfly.Data(color=Z))
end

end

using Stat

Now z can be a Matrix or a function. The sinc function is a good test case:

function sincf(x, y) 
    z = hypot(x,y)
    z = sin(z)./z
    return z
end

xlr = linspace(-14, 14, 22)
xhr = linspace(-15, 15, 50) 

# Note contour and vectorfield are statistics:
# Geom.contour is a short expression for Geom.path + Stat.contour
layer1 = layer(z=(x,y)->sincf(x,y), x=xhr, y=xhr, Geom.path, Gadfly.Stat.contour)
layer2 = layer(z=(x,y)->sincf(x,y), x=xlr, y=xlr, Geom.vector, 
            Stat.vectorfield(smoothness=0.2, scale=5.0), order=2 )

p = plot(layer1, layer2,
        Scale.x_continuous(minvalue=-15.0, maxvalue=15.0),
        Scale.y_continuous(minvalue=-15.0, maxvalue=15.0),
        Guide.xlabel("x"), Guide.ylabel("y"),
        Guide.colorkey("z"),
        Theme(panel_fill=colorant"black")
)

issue791b

The next step is to implement Geom.vectorfield, which is straightforward because Geom.vectorfield is Stat.vectorfield + Geom.vector.

@bjarthur
Copy link
Member

see #992

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants