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

Ternary diagram support #65

Open
exaexa opened this issue Jan 8, 2025 · 3 comments
Open

Ternary diagram support #65

exaexa opened this issue Jan 8, 2025 · 3 comments

Comments

@exaexa
Copy link
Contributor

exaexa commented Jan 8, 2025

Hello!

first, thanks for a great and lightweight package!

I was wondering to add support for ternary diagrams (mainly to plot stuff on Aitchison simplices), as seen e.g. here: https://yutannihilation.github.io/allYourFigureAreBelongToUs/ggtern/ . I saw some extension guides in documentation (esp. with the rose diagram) but I don't see how to extend the coordinate system to allow the x+y+z coords required for ternary plots.

Is there some good starting point/docs/recommendation for that?

Thanks!
-mk

@davibarreira
Copy link
Owner

Thanks for the interest in the package. These are very cool plots! Let me read a bit on them.

I do think Vizagrams can reproduce them, but it will take a bit of coding in order to compute the position with respect to the three axis. Let me think a bit about it, and I'll try to come up with something.

@exaexa
Copy link
Contributor Author

exaexa commented Jan 8, 2025

OK thanks a lot, any hints in where to start are super welcome!

Re the plots:

  • we mostly aim to plot Aitchison-transformed data (see clr() here https://en.m.wikipedia.org/wiki/Compositional_data )
  • one stupid outcome of how the stuff works is that "straight" lines between compositions and confidence intervals are very curved (thats usually plotted by just tracing the line by tiny segments). The above ggtern site should show the curved confidence interval.

@davibarreira
Copy link
Owner

Vizagrams has the grammar specification and the diagramming operations. At the moment, there is no specification implemented for the case of this ternary plot, but it seems like a nice example to port.

So, at the moment, an easy to use implementation for a ternary plot is not actually available. But, I've sketched a working solution using the diagramming tools, which is actually quite similar to how one would implement it in the package itself. The only thing missing would be to tie it to the plot specification. I intend to do it in the upcoming days, it shouldn't take very long (I think). In the mean time, here is how you could plot it using the tools available now.

First, the triangular grid

using Vizagrams
using DataFrames
using CSV
using StatsBase

# Producing the triangular grid
trigside = 200
n = 6

d =  S(:stroke=>"black",:fillOpacity=>0)Polygon([-trigside/2,0,trigside/2][0,sin/3)*trigside,0])
ax1 = mapreduce(+,range(0,1,n)range(0,trigside,n)) do x
    w = 3
    T(cos/3)*x[2],sin/3)*x[2])*(
        S(:fill=>:grey)T(-w/2,0)Rectangle(h=0.5,w=w)  
        (T(-2,0),TextMark(text=x[1],fontsize=5,anchor=:w))
    )
end

ax2 = mapreduce(+,range(1,0,n)range(0,trigside,n)) do x
    w = 3
    T(-cos/3)*x[2],sin/3)*x[2])*
    R/3)*(S(:fill=>:grey)T(w/2,0)Rectangle(h=0.5,w=w)  (T(2,0),TextMark(text=x[1],fontsize=5,anchor=:e)))
end

ax3 = mapreduce(+,range(1,0,n)range(0,trigside,n)) do x
    w = 3
    T(x[2],0)*
    R(-π/3)*(S(:fill=>:grey)T(w/2,0)Rectangle(h=0.5,w=w)  (T(2,0),TextMark(text=x[1],fontsize=5,anchor=:e)))
end

d = T(-trigside/2,0)ax1+
    T(trigside/2,0)ax2+
    T(-trigside/2,0)ax3+
    d


grid = T(-trigside/2,0)mapreduce(+,range(0,1,n)[2:end-1]range(0,trigside,n)[2:end-1]) do x
    w = 3
    h = trigside*(1-x[1])
    a = cos/3)*h * 2
    T(cos/3)*x[2],sin/3)*x[2])*
    S(:stroke=>:grey)Line([[0,0],[a,0]])
end

l = trigside/2
grid =  grid +
        T(cos/3)*l,sin/3)*l)M(0)R/3)grid + 
        T(-cos/3)*l,sin/3)*l)M(0)R(-π/3)grid

tguide = grid + d
draw(tguide)

image

The next step is to adjust the dataset using the clr transformation and the softmax in order to compute the x-y coordinates.

df = DataFrame(CSV.File("plotly_election.csv"))
df = df[!,Not(:Column1)]
df = mapreduce((x,y)->hcat(x,y,makeunique=true),["Joly", "Coderre", "Bergeron"],init=df) do c
    values = map(eachrow(df[!,["Joly", "Coderre", "Bergeron"]])) do row
        log(row[c]/geomean(row))
    end
    DataFrame(("clr_"*c=>values))
end
df = mapreduce((x,y)->hcat(x,y,makeunique=true),names(df[!,[:clr_Joly,:clr_Coderre,:clr_Bergeron]]),init=df) do c
    values = map(eachrow(df[!,[:clr_Joly,:clr_Coderre,:clr_Bergeron]])) do row
        exp(row[c]) / sum(exp.(collect(row)))
    end
    DataFrame(("sft_"*c=>values))
end
df = sort(df,:sft_clr_Bergeron)

function clr_scale(xs::Vector,trigside)
    w = map(x->exp(x) / sum(exp.(xs)),xs)
    v = [[0,sin/3)*trigside],[-trigside/2,0],[trigside/2,0]]
    mean(v,weights(w))
end
function clr_scale(xs::DataFrameRow,trigside)
    xs = collect(xs)
    clr_scale(xs,trigside)
end

data = map(eachrow(df)) do row
    clr_scale(row[[:clr_Joly,:clr_Coderre,:clr_Bergeron]],trigside)
end

df = hcat(df,DataFrame(x = [row[1] for row in data], y = [row[2] for row in data]))

sizescale = Linear(domain=(minimum(df[!,:total]),maximum(df[!,:total])), codomain=(2,7))
colorscale = Vizagrams.Categorical(domain=unique(df.winner),codomain=[:red,:blue,:green])

d = tguide + mapreduce(+,eachrow(df)) do row
    T(row[:x],row[:y])*
    U(sizescale(row[:total]))*
    S(:stroke=>:white,:fillOpacity=>0.5,:fill=>colorscale(row[:winner]))*
    Circle()
end

draw(d)

image

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

2 participants