Maya: Get/Set vertex Normal in world space? - python

i'm working on small tool which bake custom pivot.Sounds like nothing special.
The thing is when I run comand bakeCustomPivot, it also brakes custom normals which must keep their orientation all time in my case. The first idea that came into mind is to save vertices normal before bake and restore them after they have changed.But now I have real problem.
cmds.polyNormalPerVertex( q=1, normalX=1) returns values which are equal to values I got after pivot was baket since normals change their orientation with object pivot orientation in same way.
My step was to get difference beetwen pivot orientation before and after pivot bake and next step is to add this difference to normal value. Unfortunatelly it did not help.
So after short brainstorm I decided to try another way and get vertex normal value in world space directly.
Actually this is where by brainstorm stopped)
This will help to undrestand more clearly what I mean. Just run it
import maya.cmds as cmds
import maya.mel as mel
# create test plane
plane = cmds. polyPlane(axis=[0,0,1], subdivisionsHeight=2, subdivisionsWidth=2)
cmds.select(plane[0]+'.vtx[*]')
objVertices = cmds.ls(sl=1,fl=1)
# Activate normals preview
cmds.select(plane)
mel.eval('ToggleVertexNormalDisplay')
# Change plane normals to any
cmds.polyNormalPerVertex(objVertices, normalXYZ=[0.3, 0.4, 1.0])
# get vtx normal vector for one vtx before bake
cmds.select(objVertices[0])
oldVtxNormalX = cmds.polyNormalPerVertex( q=1, normalX=1)
oldVtxNormalY = cmds.polyNormalPerVertex( q=1, normalY=1)
oldVtxNormalZ = cmds.polyNormalPerVertex( q=1, normalZ=1)
oldVtxValue = list(map(lambda x: x[0], [oldVtxNormalX] + [oldVtxNormalY] + [oldVtxNormalZ]))
# Warning. Why values are differ from custom values? They are smaller
print ( '{} --------- Old normal value '.format(oldVtxValue) ) # [0.26832816004753113, 0.3577708899974823, 0.8944271802902222]
# Rotate pivot, bake it (Modify/Bake Pivot)
cmds.select(plane)
cmds.manipPivot(o=[45.0, 0.0, 0.0]) # apply pivot
mel.eval('BakeCustomPivot')
# get vtx normal vector for one vtx after bake
cmds.select(objVertices[0])
newVtxNormalX = cmds.polyNormalPerVertex( q=1, normalX=1)
newVtxNormalY = cmds.polyNormalPerVertex( q=1, normalY=1)
newVtxNormalZ = cmds.polyNormalPerVertex( q=1, normalZ=1)
newVtxValue = list(map(lambda x: x[0], [newVtxNormalX] + [newVtxNormalY] + [newVtxNormalZ]))
print ( '{} --------- New normal value after bake. Same result '.format(newVtxValue) ) # [0.26832816004753113, 0.3577708899974823, 0.8944271802902222]
# !!!! As you can see normal values still same despite of fact pivot have changed

The polyNormalPerVertex cmd returns the values in objectspace. And baking the pivot sets the pivot to 0 but the rotation of the object to 45 degree. If you set the rotation to 0, the normals point exactly in the same direction.

Related

how to find Y face of the cube in Maya with Python

sorry for such specific question guys , I think people only with knowledge of Maya will answer tho. In Maya I have cubes different sizes and I need to find with python which face of cube is pointing Y axis down. (Pivot is in center) Any tips will be appreciated
Thanks a lot :)
import re
from maya import cmds
from pymel.core.datatypes import Vector, Matrix, Point
obj = 'pCube1'
# Get the world transformation matrix of the object
obj_matrix = Matrix(cmds.xform(obj, query=True, worldSpace=True, matrix=True))
# Iterate through all faces
for face in cmds.ls(obj + '.f[*]', flatten=True):
# Get face normal in object space
face_normals_text = cmds.polyInfo(face, faceNormals=True)[0]
# Convert to a list of floats
face_normals = [float(digit) for digit in re.findall(r'-?\d*\.\d*', face_normals_text)]
# Create a Vector object and multiply with matrix to get world space
v = Vector(face_normals) * obj_matrix
# Check if vector faces downwards
if max(abs(v[0]), abs(v[1]), abs(v[2])) == -v[1]:
print face, v
If you just need a quick solution without vector math and Pymel or the the API, you can use cmds.polySelectConstraint to find the faces aligned with a normal. All you need to do is select all the faces, then use the constraint to get only the ones pointing the right way. This will select all the faces in a mesh that are pointing along a given axis:
import maya.cmds as cmds
def select_faces_by_axis (mesh, axis = (0,1,0), tolerance = 45):
cmds.select(mesh + ".f[*]")
cmds.polySelectConstraint(mode = 3, type = 8, orient = 2, orientaxis = axis, orientbound = (0, tolerance))
cmds.polySelectConstraint(dis=True) # remember to turn constraint off!
The axis is the x,y,z axis you want and tolerance is the slop in degrees you'll tolerate. To get the downward faces you'd do
select_faces_by_axis ('your_mesh_here', (0,0,-1))
or
select_faces_by_axis ('your_mesh_here', (0,0,-1), 1)
# this would get faces only within 1 degree of downard
This method has the advantage of operating mostly in Maya's C++, it's going to be faster than python-based methods that loop over all the faces in a mesh.
With pymel the code can be a bit more compact. Selecting the faces pointing downwards:
n=pm.PyNode("pCubeShape1")
s = []
for f in n.faces:
if f.getNormal(space='world')[1] < 0.0:
s.append(f)
pm.select(s)

Maya API Python symmetry table with MRichSelection?

I was wondering if there is a way to access the symmetry table of the MRichSelection having as a result the positive, the seam and the negative side with the positive and the negative ordered by vertex id correspondence. ie: vertex id 15 is the symmetry correlated to vert id 350. They are both at index 5 in the positive and negative list.
I know I can achieve something similar using the filterXpand, but I believe the lists are not ordered in the way I can access the opposite vertex.
I don't know if you ever found a solution to this, but I will post mine for future TD's looking for a solution.
So let's assume you want to get the corresponding verts between left and right on the YZ plane. you have 2 different options. Using the MRichSelection to handle you symmetry table. Or calculate the vert yourself, by getting the smallest distance vector on the opposite side. Note: if you use the MRichSelection method, you will need to make sure that symmetry mode is enbaled in the viewport.
I will show both answers, so lets get started:
Also note: I will be calculating the YZ Plane, as mentioned earlier. So adjust to your liking if needed.
Solution 1(Calculating yourself):
#importing the OpenMaya Module
from maya.api import OpenMaya as om
#converting selected object into MObject and MFnMesh functionset
mSel=om.MSelectionList()
mSel.add(cmds.ls(sl=1)[0])
mObj=mSel.getDagPath(0)
mfnMesh=om.MFnMesh(mObj)
#getting our basePoints
baseShape = mfnMesh.getPoints()
#this function can be used to revert the object back to the baseShape
mfnMesh.setPoints(baseShape)
#getting l and r verts
mtol=0.02# this will be our mid tolerance, if the mesh is not completely symmetric on the mid
lVerts=[]#for storing left Verts
rVerts=[]#for storing right Verts
mVerts=[]#for storing mid Verts
corrVerts={} #for storing correspondign verts
for i in range(mfnMesh.numVertices): #iteratign through all the verts on the mesh
thisPoint = mfnMesh.getPoint(i) #getting current point position
if thisPoint.x>0+mtol: # if pointValue on x axis is bigger than 0+midTolerance
lVerts.append((i, thisPoint))#append to left vert storage list(i = vert index, thisPoint = vert Mpoint position)
elif thisPoint.x<0-mtol: #opposite of left vert calculation
rVerts.append((i, thisPoint))
else: #if none of the above, assign to mid verts
mVerts.append((i, thisPoint))
rVertspoints=[i for v,i in rVerts] #getting the vert mPoint positions of the right side
for vert, mp in lVerts: #going through our left points, unpacking our vert index and mPoint position()
nmp=om.MPoint(-mp.x, mp.y, mp.z) #storing the reversed mpoint of the left side vert
rp = mfnMesh.getClosestPoint(nmp)#getting the closest point on the mesh
if rp[0] in rVertspoints: #cheking if the point is in the right side
corrVerts[vert] = rVerts[rVertspoints.index(rp[0])][0] #adding it if it is true
else:#if it is not, calculate closest vert
#iterating through rVertspoints and find smallest distance
dList=[nmp.distanceTo(rVert) for rVert in rVertspoints]#distance list for each vert based on input point
mindist = min(dList)#getting the closest distance
corrVerts[vert] = rVerts[dList.index(mindist)][0]#adding the vert
#now the corrVerts will have stored the corresponding vertices from left to right
Solution 2(using MRichSelection):
#MAKE SURE SYMMETRY IN THE VIEWPORT IS TURNED ON TO WORK! (will also work with topological symmetry)
#importing the OpenMaya Module
from maya.api import OpenMaya as om
#converting selected object into MObject and MFnMesh functionset
mSel=om.MSelectionList()
mSel.add(cmds.ls(sl=1)[0])
mObj=mSel.getDagPath(0)
mfnMesh=om.MFnMesh(mObj)
#getting our basePoints
baseShape = mfnMesh.getPoints()
#this function can be used to revert the object back to the baseShape
mfnMesh.setPoints(baseShape)
#getting l and r verts
mtol=0.02# this will be our mid tolerance, if the mesh is not completely symmetric on the mid
lVerts=[]#for storing left Verts
corrVerts={} #for storing correspondign verts
for i in range(mfnMesh.numVertices): #iteratign through all the verts on the mesh
thisPoint = mfnMesh.getPoint(i) #getting current point position
if thisPoint.x>0+mtol: # if pointValue on x axis is bigger than 0+midTolerance
lVerts.append((i, thisPoint))#append to left vert storage list(i = vert index, thisPoint = vert Mpoint position)
#selecting our verts with symmetry on
SymSelection = cmds.select(["%s.vtx[%s]"%(mObj,i) for i,v in lVerts], sym=True)
#getting the rich selection. it will store the symmetry iformation for us
mRichBase = om.MGlobal.getRichSelection()
lCor = mRichBase.getSelection()#this will store our lSide verts as an MSelectionList
rCor = mRichBase.getSymmetry()#this will symmetry verts as an MSelectionList
mitL = om.MItSelectionList(lCor)#creating iterative lists so we can get the components
mitR = om.MItSelectionList(rCor)
while not mitL.isDone():#iterating through the left list
mitLComp = mitL.getComponent()#getting dag path and components of leftside
mitRComp = mitR.getComponent()#getting dag path and components of rightside
mitLCorVert = om.MItMeshVertex(mitLComp[0], mitLComp[1]) #creating our iterative vertex lists
mitRCorVert = om.MItMeshVertex(mitRComp[0], mitRComp[1])
while not mitLCorVert.isDone():#iterating through our verts
corrVerts[mitLCorVert.index()] = mitRCorVert.index()#adding corresponding verts to our dictionary
mitLCorVert.next()#go to next vert. needed to stop loop
mitRCorVert.next()#go to next vert. needed to stop loop
mitL.next()#go to next selection in list if more. needed to stop loop
mitR.next()#go to next selection in list if more. needed to stop loop
cmds.select(cl=1)#deseleting our verts
#now the corrVerts will have stored the corresponding vertices from left to right
Hope it will help you all, looking for a few solutions.
Cheers
Bjarke Rauff, Rigging TD.
The answer by #Bjarke Rauff was very helpful, wanted to add a note about speed.
MFnMesh.getClosestPoint() builds an octree to efficiently find the point, but it will do that on every call. A mesh with 100k points can take up to 45s to process.
Use a MMeshIntersector() to cache the data between lookups. This speeds up the table creation by 900x for 100k points to .05s.
mesh # MDagpath obj to poly
flip_matrix # MTransformMatrix to flop the point
itMesh = om.MItMeshPolygon(mesh)
mesh.extendToShape()
matrix = mesh.inclusiveMatrix()
node = mesh.node()
intersector = om.MMeshIntersector()
intersector.create(node, matrix)
if not (intersector.isCreated):
print("Failed to create mesh intersector")
return
flipped_ids={}
while not itMesh.isDone():
id = itMesh.index()
face_center = itMesh.center()
# flop the point across the axis
flipped_point = face_center*flip_matrix
MpointOnMesh = intersector.getClosestPoint(flipped_point)
if MpointOnMesh is not None:
# get face id property from MPointOnMesh
flipped_id = MpointOnMesh.face
flipped_ids[id] = flipped_id
else:
print("No intersection")
itMesh.next()
NOTE
I tried hash tables with a tuple of the point as the key, but the point positions had slight variations, even with rounding, which created different hashes.
I've tested the MRichSelection approach and it doesn't actually work consistently in practice. It seems like it works when you have a perfectly mirrored mesh, but that can't be assumed. The component lists are not necessarily in sync.

maya python iterating a big number of vertex

I am writing a script in python for maya to swap vertex position from one side to another.
Since I want the flipping to be topology based I am using the topological symmetry selection tool to find the vertex correspondence.
I managed to do that using filterExpand and xform.
The problem is that it is quite slow on a large poly count mesh and I was wondering how this could be done using openMaya instead.
import maya.cmds as cmds
def flipMesh():
sel=cmds.ls(sl=1)
axis={'x':0,'y':1,'z':2}
reverse=[1.0,1.0,1.0]
#quring the axtive symmetry axis
activeaxis=cmds.symmetricModelling(q=1, axis=1)
reverse[axis[activeaxis]]=-1.0
#getting the vertex count
verts=cmds.polyEvaluate(v=1)
#selecting all vertex
cmds.select(sel[0]+'.vtx[0:'+str(verts)+']')
#getting all the positive vertex
posit=cmds.filterExpand(sm=31,ex=1,smp=1)
seam=cmds.filterExpand(sm=31,ex=1,sms=1)
#swapping position on the positive side with the negative side
for pos in posit:
cmds.select(pos, sym=True)
neg=cmds.filterExpand(sm=31,ex=1,smn=1)
posT=cmds.xform(pos, q=1, t=1)
negT=cmds.xform(neg[0], q=1, t=1)
cmds.xform(pos,t=[a*b for a,b in zip(negT,reverse)])
cmds.xform(neg[0],t=[a*b for a,b in zip(posT,reverse)])
#inverting position on the seam
for each in seam:
seamP=cmds.xform(each, q=1, t=1)
seaminvP=[a*b for a,b in zip(seamP,reverse)]
cmds.xform(each, t=(seaminvP))
cmds.select(sel)
Thanks
Maurizio
You can try out OpenMaya.MFnMesh to get and set your vertices.
Here's an example that will simply mirror all points of a selected object along their z axis:
import maya.OpenMaya as OpenMaya
# Get selected object
mSelList = OpenMaya.MSelectionList()
OpenMaya.MGlobal.getActiveSelectionList(mSelList)
sel = OpenMaya.MItSelectionList(mSelList)
path = OpenMaya.MDagPath()
sel.getDagPath(path)
# Attach to MFnMesh
MFnMesh = OpenMaya.MFnMesh(path)
# Create empty point array to store new points
newPointArray = OpenMaya.MPointArray()
for i in range( MFnMesh.numVertices() ):
# Create a point, and mirror it
newPoint = OpenMaya.MPoint()
MFnMesh.getPoint(i, newPoint)
newPoint.z = -newPoint.z
newPointArray.append(newPoint)
# Set new points to mesh all at once
MFnMesh.setPoints(newPointArray)
Instead of moving them one at at time you can use MFnMesh.setPoints to set them all at once. You'll have to adapt your logic to this, but hopefully this will help you out manipulating with Maya's api. I should also note that you would also have to resolve normals afterwards.

Detect loops/intersections in matplotlib scatter plot

At some point in my work, I came up with that kind of scatter plot.
I would like for my script to be able to detect the fact that it "loops" and to give me the point (or an approximation thereof) where it does so : for instance, in this case it would be about [0.2,0.1].
I tried to play around with some representative quantities of my points, like norm and/or argument, like in the following piece of code.
import numpy as np
x,y = np.genfromtxt('points.dat',unpack=True)
norm = np.sqrt(x**2+y**2)
arg = np.arctan2(y,x)
left,right = np.meshgrid(norm,norm)
norm_diff = np.fabs(left - right)
mask = norm_diff == 0.
norm_diff_ma = np.ma.masked_array(norm_diff,mask)
left,right = np.meshgrid(arg,arg)
arg_diff = np.fabs(left - right)
mask = arg_diff == 0.
arg_diff_ma = np.ma.masked_array(arg_diff,mask)
list_of_indices = np.ma.where((norm_diff_ma<1.0e-04)*(arg_diff_ma<1.0e-04))
But, it does not work as intended : might be because the dataset contains too many points and the distance between two aligned points is anyhow of the same order of magnitude as the distance between the points in the "loop cluster" ...
I was thinking about detecting clusters, or maybe even detecting lines in the scatter plot and then see if there are any intersections between any two lines, but I am afraid my skills in image processing only go so far.
Is there any algorithm, any trick that any of you can think about would work here ?
A representative data sample can be found here.
Edit 08/13/2015 16h18 : after the short discussion with #DrBwts I took a closer look at the data I obtained after a pyplot.contour() call. Using the following routine to extract all the vertices :
def contour_points(contour, steps=1):
try:
loc_arr = np.row_stack([path.interpolated(steps).vertices for linecol in contour.collections for path in linecol.get_paths()])
except ValueError:
loc_arr = np.empty((0,2))
finally:
return loc_arr
y,x = contour_points(CS,steps=1).T
it turns out the points of coordinates (x,y) are ordered, in the sense where a call to pyplot.plot() connects the dots correctly.

Library/tool for drawing ternary/triangle plots [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking us to recommend or find a tool, library or favorite off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead, describe the problem and what has been done so far to solve it.
Closed 9 years ago.
Improve this question
I need to draw ternary/triangle plots representing mole fractions (x, y, z) of various substances/mixtures (x + y + z = 1). Each plot represents iso-valued substances, e.g. substances which have the same melting point. The plots need to be drawn on the same triangle with different colors/symbols and it would be nice if I could also connect the dots.
I have looked at matplotlib, R and gnuplot, but they don't seem to be able to draw this kind of plot. The 3rd party ade4 package for R seems to be able to draw it, but I'm not sure if I can draw multiple plots on the same triangle.
I need something that runs under Linux or Windows. I'm open to any suggestions, including libraries for other languages, e.g. Perl, PHP, Ruby, C# and Java.
Created a very basic script for generating ternary (or more) plots. No gridlines or ticklines, but those wouldn't be too hard to add using the vectors in the "basis" array.
from pylab import *
def ternaryPlot(
data,
# Scale data for ternary plot (i.e. a + b + c = 1)
scaling=True,
# Direction of first vertex.
start_angle=90,
# Orient labels perpendicular to vertices.
rotate_labels=True,
# Labels for vertices.
labels=('one','two','three'),
# Can accomodate more than 3 dimensions if desired.
sides=3,
# Offset for label from vertex (percent of distance from origin).
label_offset=0.10,
# Any matplotlib keyword args for plots.
edge_args={'color':'black','linewidth':2},
# Any matplotlib keyword args for figures.
fig_args = {'figsize':(8,8),'facecolor':'white','edgecolor':'white'},
):
'''
This will create a basic "ternary" plot (or quaternary, etc.)
'''
basis = array(
[
[
cos(2*_*pi/sides + start_angle*pi/180),
sin(2*_*pi/sides + start_angle*pi/180)
]
for _ in range(sides)
]
)
# If data is Nxsides, newdata is Nx2.
if scaling:
# Scales data for you.
newdata = dot((data.T / data.sum(-1)).T,basis)
else:
# Assumes data already sums to 1.
newdata = dot(data,basis)
fig = figure(**fig_args)
ax = fig.add_subplot(111)
for i,l in enumerate(labels):
if i >= sides:
break
x = basis[i,0]
y = basis[i,1]
if rotate_labels:
angle = 180*arctan(y/x)/pi + 90
if angle > 90 and angle <= 270:
angle = mod(angle + 180,360)
else:
angle = 0
ax.text(
x*(1 + label_offset),
y*(1 + label_offset),
l,
horizontalalignment='center',
verticalalignment='center',
rotation=angle
)
# Clear normal matplotlib axes graphics.
ax.set_xticks(())
ax.set_yticks(())
ax.set_frame_on(False)
# Plot border
ax.plot(
[basis[_,0] for _ in range(sides) + [0,]],
[basis[_,1] for _ in range(sides) + [0,]],
**edge_args
)
return newdata,ax
if __name__ == '__main__':
k = 0.5
s = 1000
data = vstack((
array([k,0,0]) + rand(s,3),
array([0,k,0]) + rand(s,3),
array([0,0,k]) + rand(s,3)
))
color = array([[1,0,0]]*s + [[0,1,0]]*s + [[0,0,1]]*s)
newdata,ax = ternaryPlot(data)
ax.scatter(
newdata[:,0],
newdata[:,1],
s=2,
alpha=0.5,
color=color
)
show()
R has an external package called VCD which should do what you want.
The documentation is very good (122 page manual distributed w/ the package); there's also a book by the same name, Visual Display of Quantitative Information, by the package's author (Prof. Michael Friendly).
To create ternary plots using vcd, just call ternaryplot() and pass in an m x 3 matrix, i.e., a matrix with three columns.
The method signature is very simple; only a single parameter (the m x 3 data matrix) is required; and all of the keyword parameters relate to the plot's aesthetics, except for scale, which when set to 1, normalizes the data column-wise.
To plot data points on the ternary plot, the coordinates for a given point are calculated as the gravity center of mass points in which each feature value comprising the data matrix is a separate weight, hence the coordinates of a point V(a, b, c) are
V(b, c/2, c * (3^.5)/2
To generate the diagram below, i just created some fake data to represent four different chemical mixtures, each comprised of varying fractions of three substances (x, y, z). I scaled the input (so x + y + z = 1) but the function will do it for you if you pass in a value for its 'scale' parameter (in fact, the default is 1, which i believe is what your question requires). I used different colors & symbols to represent the four data points, but you can also just use a single color/symbol and label each point (via the 'id' argument).
A package I have authored in R has just been accepted for CRAN, webpage is www.ggtern.com:
It is based off ggplot2, which I have used as a platform. The driving force for me, was a desire to have consistency in my work, and, since I use ggplot2 heavily, development of the package was a logical progression.
For those of you who use ggplot2, use of ggtern should be a breeze, and, here is a couple of demonstrations of what can be achieved.
Produced with the following code:
# Load data
data(Feldspar)
# Sort it by decreasing pressure
# (so small grobs sit on top of large grobs
Feldspar <- Feldspar[with(Feldspar, order(-P.Gpa)), ]
# Build and Render the Plot
ggtern(data = Feldspar, aes(x = An, y = Ab, z = Or)) +
#the layer
geom_point(aes(fill = T.C,
size = P.Gpa,
shape = Feldspar)) +
#scales
scale_shape_manual(values = c(21, 24)) +
scale_size_continuous(range = c(2.5, 7.5)) +
scale_fill_gradient(low = "green", high = "red") +
#theme tweaks
theme_tern_bw() +
theme(legend.position = c(0, 1),
legend.justification = c(0, 1),
legend.box.just = "left") +
#tweak guides
guides(shape= guide_legend(order =1,
override.aes=list(size=5)),
size = guide_legend(order =2),
fill = guide_colourbar(order=3)) +
#labels and title
labs(size = "Pressure/GPa",
fill = "Temperature/C") +
ggtitle("Feldspar - Elkins and Grove 1990")
Contour plots have also been patched for the ternary environment, and, an inclusion of a new geometry for representing confidence intervals via the Mahalanobis Distance.
Produced with the following code:
ggtern(data=Feldspar,aes(An,Ab,Or)) +
geom_confidence(aes(group=Feldspar,
fill=..level..,
alpha=1-..level..),
n=2000,
breaks=c(0.01,0.02,0.03,0.04,
seq(0.05,0.95,by=0.1),
0.99,0.995,0.9995),
color=NA,linetype=1) +
geom_density2d(aes(color=..level..)) +
geom_point(fill="white",aes(shape=Feldspar),size=5) +
theme_tern_bw() +
theme_tern_nogrid() +
theme(ternary.options=element_ternary(padding=0.2),
legend.position=c(0,1),
legend.justification=c(0,1),
legend.box.just="left") +
labs(color="Density",fill="Confidence",
title="Feldspar - Elkins and Grove 1990 + Confidence Levels + Density") +
scale_color_gradient(low="gray",high="magenta") +
scale_fill_gradient2(low="red",mid="orange",high="green",
midpoint=0.8) +
scale_shape_manual(values=c(21,24)) +
guides(shape= guide_legend(order =1,
override.aes=list(size=5)),
size = guide_legend(order =2),
fill = guide_colourbar(order=3),
color= guide_colourbar(order=4),
alpha= "none")
Veusz supports ternary plots. Here is an example from the documentation:
Chloƫ Lewis developed a triangle-plot general class, meant to support the soil texture triangle
with Python and Matplotlib. It's available here http://nature.berkeley.edu/~chlewis/Sourcecode.html https://github.com/chlewissoil/TernaryPlotPy
Chloe editing to add: Moved it to a more reliable host! Also, it's a public repo, so if you want to request library-ization, you could add an issue. Hope it's useful to someone.
I just discovered a tool which uses Python/Matplotlib to generate ternary plots called wxTernary. It's available via http://wxternary.sourceforge.net/ -- I was able to successfully generate a ternary plot on the first try.
There seems to be an implementation at work here in gnuplot:
(source: ugm.ac.id)
There is a R package named soiltexture. It's aimed at soil texture triangle plot, but can be customized for some aspects.
Find a vector drawing library and draw it from scratch if you can't find an easier way to do it.

Categories