Related
I have the following code, with which I intend to read and plot many curves from many different files. The "reading and plotting" is already working pretty good.
The problem is that now I want to make a fitting for all those curves in the same plot. This code already manages to fit the curves, but the output is all in one array and I can not plot it, since I could not separate it.
#!/usr/bin/python
import matplotlib.pyplot as plt
from numpy import exp
from lmfit import Model
def read_files(arquivo):
x = []
y = []
abscurrent = []
time = []
data = open(arquivo, 'r')
headers = data.readlines()[60:]
for line in headers:
line = line.strip()
X, Y, ABS, T = line.split('\t')
x.append(X)
y.append(Y)
abscurrent.append(ABS)
time.append(T)
data.close()
def J(x, j, n):
return j*((exp((1.6e-19*x)/(n*1.38e-23*333)))-1)
gmod = Model(J)
result = gmod.fit(abscurrent, x=x, j=10e-10, n=1)
return x, y, abscurrent, time
print(result.fit_report())
When I ask to print the "file" result.best_fit, which in the lmfit would give the best fit for that curve, I get 12 times this result (I have 12 curves) , with different values:
- Adding parameter "j"
- Adding parameter "n"
[ 4.30626925e-17 3.25367918e-14 9.60736218e-14 2.20310475e-13
4.63245638e-13 9.38169958e-13 1.86480698e-12 3.67881758e-12
7.22634738e-12 1.41635088e-11 2.77290634e-11 5.42490983e-11
1.06108942e-10 2.07520542e-10 4.05768903e-10 7.93323537e-10
1.55126521e-09 3.03311029e-09 5.93085363e-09 1.16032067e-08
2.26884736e-08 4.43641560e-08 8.67362753e-08 1.69617697e-07
3.31685858e-07 6.48478168e-07]
- Adding parameter "j"
- Adding parameter "n"
[ 1.43571772e-16 1.00037588e-13 2.92349492e-13 6.62623404e-13
This means that the code is calculating the fit correctly, I just have to separate this output somehow in order to plot each of them with the their curve. Each set of values between [] is what I want to separate in a way I can plot it.
I do not see how the code you posted could possibly produce your output. I do not see a print() function that prints out the array of 26 values, but would imagine that could be the length of your lists x, y and abscurrent -- it is not the output of your print(result.fit_report()), and I do not see that result.
I do not see anything to suggest you have 12 independent curves.
Also, result.best_fit is not a file, it is an array.
I am writing a code to find the least squares of best fit line of some data in an imported file. The equation of the line is ax+b where I have calculated a and b already. To plot the line I have tried:
LS_fit_ydata = []
for i in x_data:
y_new = ((i*b) + a)
LS_fit_ydata.append(y_new)
I am using matplotlib.pyplot as plt to plot my graph.
There are no error messages but the line does not appear on my graph. Does anyone know what's going wrong? Thank you for any help you can provide.
What you're missing is the plotting part in the code:
# The code you provided
LS_fit_ydata = []
for i in x_data:
y_new = ((i*b) + a)
LS_fit_ydata.append(y_new)
# What happens here is you're plotting x against y one by one via the list
plt.plot(x_data, y_new)
# Show the graph
plt.show()
I am trying to plot contour lines of pressure level. I am using a netCDF file which contain the higher resolution data (ranges from 3 km to 27 km). Due to higher resolution data set, I get lot of pressure values which are not required to be plotted (rather I don't mind omitting certain contour line of insignificant values). I have written some plotting script based on the examples given in this link http://matplotlib.org/basemap/users/examples.html.
After plotting the image looks like this
From the image I have encircled the contours which are small and not required to be plotted. Also, I would like to plot all the contour lines smoother as mentioned in the above image. Overall I would like to get the contour image like this:-
Possible solution I think of are
Find out the number of points required for plotting contour and mask/omit those lines if they are small in number.
or
Find the area of the contour (as I want to omit only circled contour) and omit/mask those are smaller.
or
Reduce the resolution (only contour) by increasing the distance to 50 km - 100 km.
I am able to successfully get the points using SO thread Python: find contour lines from matplotlib.pyplot.contour()
But I am not able to implement any of the suggested solution above using those points.
Any solution to implement the above suggested solution is really appreciated.
Edit:-
# Andras Deak
I used print 'diameter is ', diameter line just above del(level.get_paths()[kp]) line to check if the code filters out the required diameter. Here is the filterd messages when I set if diameter < 15000::
diameter is 9099.66295612
diameter is 13264.7838257
diameter is 445.574234531
diameter is 1618.74618114
diameter is 1512.58974168
However the resulting image does not have any effect. All look same as posed image above. I am pretty sure that I have saved the figure (after plotting the wind barbs).
Regarding the solution for reducing the resolution, plt.contour(x[::2,::2],y[::2,::2],mslp[::2,::2]) it works. I have to apply some filter to make the curve smooth.
Full working example code for removing lines:-
Here is the example code for your review
#!/usr/bin/env python
from netCDF4 import Dataset
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import numpy as np
import scipy.ndimage
from mpl_toolkits.basemap import interp
from mpl_toolkits.basemap import Basemap
# Set default map
west_lon = 68
east_lon = 93
south_lat = 7
north_lat = 23
nc = Dataset('ncfile.nc')
# Get this variable for later calucation
temps = nc.variables['T2']
time = 0 # We will take only first interval for this example
# Draw basemap
m = Basemap(projection='merc', llcrnrlat=south_lat, urcrnrlat=north_lat,
llcrnrlon=west_lon, urcrnrlon=east_lon, resolution='l')
m.drawcoastlines()
m.drawcountries(linewidth=1.0)
# This sets the standard grid point structure at full resolution
x, y = m(nc.variables['XLONG'][0], nc.variables['XLAT'][0])
# Set figure margins
width = 10
height = 8
plt.figure(figsize=(width, height))
plt.rc("figure.subplot", left=.001)
plt.rc("figure.subplot", right=.999)
plt.rc("figure.subplot", bottom=.001)
plt.rc("figure.subplot", top=.999)
plt.figure(figsize=(width, height), frameon=False)
# Convert Surface Pressure to Mean Sea Level Pressure
stemps = temps[time] + 6.5 * nc.variables['HGT'][time] / 1000.
mslp = nc.variables['PSFC'][time] * np.exp(9.81 / (287.0 * stemps) * nc.variables['HGT'][time]) * 0.01 + (
6.7 * nc.variables['HGT'][time] / 1000)
# Contour only at 2 hpa interval
level = []
for i in range(mslp.min(), mslp.max(), 1):
if i % 2 == 0:
if i >= 1006 and i <= 1018:
level.append(i)
# Save mslp values to upload to SO thread
# np.savetxt('mslp.txt', mslp, fmt='%.14f', delimiter=',')
P = plt.contour(x, y, mslp, V=2, colors='b', linewidths=2, levels=level)
# Solution suggested by Andras Deak
for level in P.collections:
for kp,path in enumerate(level.get_paths()):
# include test for "smallness" of your choice here:
# I'm using a simple estimation for the diameter based on the
# x and y diameter...
verts = path.vertices # (N,2)-shape array of contour line coordinates
diameter = np.max(verts.max(axis=0) - verts.min(axis=0))
if diameter < 15000: # threshold to be refined for your actual dimensions!
#print 'diameter is ', diameter
del(level.get_paths()[kp]) # no remove() for Path objects:(
#level.remove() # This does not work. produces ValueError: list.remove(x): x not in list
plt.gcf().canvas.draw()
plt.savefig('dummy', bbox_inches='tight')
plt.close()
After the plot is saved I get the same image
You can see that the lines are not removed yet. Here is the link to mslp array which we are trying to play with http://www.mediafire.com/download/7vi0mxqoe0y6pm9/mslp.txt
If you want x and y data which are being used in the above code, I can upload for your review.
Smooth line
You code to remove the smaller circles working perfectly. However the other question I have asked in the original post (smooth line) does not seems to work. I have used your code to slice the array to get minimal values and contoured it. I have used the following code to reduce the array size:-
slice = 15
CS = plt.contour(x[::slice,::slice],y[::slice,::slice],mslp[::slice,::slice], colors='b', linewidths=1, levels=levels)
The result is below.
After searching for few hours I found this SO thread having simmilar issue:-
Regridding regular netcdf data
But none of the solution provided over there works.The questions similar to mine above does not have proper solutions. If this issue is solved then the code is perfect and complete.
General idea
Your question seems to have 2 very different halves: one about omitting small contours, and another one about smoothing the contour lines. The latter is simpler, since I can't really think of anything else other than decreasing the resolution of your contour() call, just like you said.
As for removing a few contour lines, here's a solution which is based on directly removing contour lines individually. You have to loop over the collections of the object returned by contour(), and for each element check each Path, and delete the ones you don't need. Redrawing the figure's canvas will get rid of the unnecessary lines:
# dummy example based on matplotlib.pyplot.clabel example:
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)
plt.figure()
CS = plt.contour(X, Y, Z)
for level in CS.collections:
for kp,path in reversed(list(enumerate(level.get_paths()))):
# go in reversed order due to deletions!
# include test for "smallness" of your choice here:
# I'm using a simple estimation for the diameter based on the
# x and y diameter...
verts = path.vertices # (N,2)-shape array of contour line coordinates
diameter = np.max(verts.max(axis=0) - verts.min(axis=0))
if diameter<1: # threshold to be refined for your actual dimensions!
del(level.get_paths()[kp]) # no remove() for Path objects:(
# this might be necessary on interactive sessions: redraw figure
plt.gcf().canvas.draw()
Here's the original(left) and the removed version(right) for a diameter threshold of 1 (note the little piece of the 0 level at the top):
Note that the top little line is removed while the huge cyan one in the middle doesn't, even though both correspond to the same collections element i.e. the same contour level. If we didn't want to allow this, we could've called CS.collections[k].remove(), which would probably be a much safer way of doing the same thing (but it wouldn't allow us to differentiate between multiple lines corresponding to the same contour level).
To show that fiddling around with the cut-off diameter works as expected, here's the result for a threshold of 2:
All in all it seems quite reasonable.
Your actual case
Since you've added your actual data, here's the application to your case. Note that you can directly generate the levels in a single line using np, which will almost give you the same result. The exact same can be achieved in 2 lines (generating an arange, then selecting those that fall between p1 and p2). Also, since you're setting levels in the call to contour, I believe the V=2 part of the function call has no effect.
import numpy as np
import matplotlib.pyplot as plt
# insert actual data here...
Z = np.loadtxt('mslp.txt',delimiter=',')
X,Y = np.meshgrid(np.linspace(0,300000,Z.shape[1]),np.linspace(0,200000,Z.shape[0]))
p1,p2 = 1006,1018
# this is almost the same as the original, although it will produce
# [p1, p1+2, ...] instead of `[Z.min()+n, Z.min()+n+2, ...]`
levels = np.arange(np.maximum(Z.min(),p1),np.minimum(Z.max(),p2),2)
#control
plt.figure()
CS = plt.contour(X, Y, Z, colors='b', linewidths=2, levels=levels)
#modified
plt.figure()
CS = plt.contour(X, Y, Z, colors='b', linewidths=2, levels=levels)
for level in CS.collections:
for kp,path in reversed(list(enumerate(level.get_paths()))):
# go in reversed order due to deletions!
# include test for "smallness" of your choice here:
# I'm using a simple estimation for the diameter based on the
# x and y diameter...
verts = path.vertices # (N,2)-shape array of contour line coordinates
diameter = np.max(verts.max(axis=0) - verts.min(axis=0))
if diameter<15000: # threshold to be refined for your actual dimensions!
del(level.get_paths()[kp]) # no remove() for Path objects:(
# this might be necessary on interactive sessions: redraw figure
plt.gcf().canvas.draw()
plt.show()
Results, original(left) vs new(right):
Smoothing by resampling
I've decided to tackle the smoothing problem as well. All I could come up with is downsampling your original data, then upsampling again using griddata (interpolation). The downsampling part could also be done with interpolation, although the small-scale variation in your input data might make this problem ill-posed. So here's the crude version:
import scipy.interpolate as interp #the new one
# assume you have X,Y,Z,levels defined as before
# start resampling stuff
dN = 10 # use every dN'th element of the gridded input data
my_slice = [slice(None,None,dN),slice(None,None,dN)]
# downsampled data
X2,Y2,Z2 = X[my_slice],Y[my_slice],Z[my_slice]
# same as X2 = X[::dN,::dN] etc.
# upsampling with griddata over original mesh
Zsmooth = interp.griddata(np.array([X2.ravel(),Y2.ravel()]).T,Z2.ravel(),(X,Y),method='cubic')
# plot
plt.figure()
CS = plt.contour(X, Y, Zsmooth, colors='b', linewidths=2, levels=levels)
You can freely play around with the grids used for interpolation, in this case I just used the original mesh, as it was at hand. You can also play around with different kinds of interpolation: the default 'linear' one will be faster, but less smooth.
Result after downsampling(left) and upsampling(right):
Of course you should still apply the small-line-removal algorithm after this resampling business, and keep in mind that this heavily distorts your input data (since if it wasn't distorted, then it wouldn't be smooth). Also, note that due to the crude method used in the downsampling step, we introduce some missing values near the top/right edges of the region under consideraton. If this is a problem, you should consider doing the downsampling based on griddata as I've noted earlier.
This is a pretty bad solution, but it's the only one that I've come up with. Use the get_contour_verts function in this solution you linked to, possibly with the matplotlib._cntr module so that nothing gets plotted initially. That gives you a list of contour lines, sections, vertices, etc. Then you have to go through that list and pop the contours you don't want. You could do this by calculating a minimum diameter, for example; if the max distance between points is less than some cutoff, throw it out.
That leaves you with a list of LineCollection objects. Now if you make a Figure and Axes instance, you can use Axes.add_collection to add all of the LineCollections in the list.
I checked this out really quick, but it seemed to work. I'll come back with a minimum working example if I get a chance. Hope it helps!
Edit: Here's an MWE of the basic idea. I wasn't familiar with plt._cntr.Cntr, so I ended up using plt.contour to get the initial contour object. As a result, you end up making two figures; you just have to close the first one. You can replace checkDiameter with whatever function works. I think you could turn the line segments into a Polygon and calculate areas, but you'd have to figure that out on your own. Let me know if you run into problems with this code, but it at least works for me.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
def checkDiameter(seg, tol=.3):
# Function for screening line segments. NB: Not actually a proper diameter.
diam = (seg[:,0].max() - seg[:,0].min(),
seg[:,1].max() - seg[:,1].min())
return not (diam[0] < tol or diam[1] < tol)
# Create testing data
x = np.linspace(-1,1, 21)
xx, yy = np.meshgrid(x,x)
z = np.exp(-(xx**2 + .5*yy**2))
# Original plot with plt.contour
fig0, ax0 = plt.subplots()
# Make sure this contour object actually has a tiny contour to remove
cntrObj = ax0.contour(xx,yy,z, levels=[.2,.4,.6,.8,.9,.95,.99,.999])
# Primary loop: Copy contours into a new LineCollection
lineNew = list()
for lineOriginal in cntrObj.collections:
# Get properties of the original LineCollection
segments = lineOriginal.get_segments()
propDict = lineOriginal.properties()
propDict = {key: value for (key,value) in propDict.items()
if key in ['linewidth','color','linestyle']} # Whatever parameters you want to carry over
# Filter out the lines with small diameters
segments = [seg for seg in segments if checkDiameter(seg)]
# Create new LineCollection out of the OK segments
if len(segments) > 0:
lineNew.append(mpl.collections.LineCollection(segments, **propDict))
# Make new plot with only these line collections; display results
fig1, ax1 = plt.subplots()
ax1.set_xlim(ax0.get_xlim())
ax1.set_ylim(ax0.get_ylim())
for line in lineNew:
ax1.add_collection(line)
plt.show()
FYI: The bit with propDict is just to automate bringing over some of the line properties from the original plot. You can't use the whole dictionary at once, though. First, it contains the old plot's line segments, but you can just swap those for the new ones. But second, it appears to contain a number of parameters that are in conflict with each other: multiple linewidths, facecolors, etc. The {key for key in propDict if I want key} workaround is my way to bypass that, but I'm sure someone else can do it more cleanly.
Suppose I've been driving a set route with a 3g modem and GPS on my laptop, while my computer back at home records the ping delay. I've correlated ping with GPS lat/long, and now I'd like to visualise this data.
I've got about 80,000 points of data per day, and I'd like to display several month's worth. I'm especially interested in displaying areas where ping consistently times out (ie ping == 1000).
Scatter plot
My first attempt was with a scatter plot, with one point per data entry. I made the size of the point 5x larger if it was a timeout, so it was obvious where these areas were. I also dropped the alpha to 0.1, for a crude way to see overlaid points.
# Colour
c = pings
# Size
s = [2 if ping < 1000 else 10 for ping in pings]
# Scatter plot
plt.scatter(longs, lats, s=s, marker='o', c=c, cmap=cm.jet, edgecolors='none', alpha=0.1)
The obvious problem with this is that it displays one marker per data point, which is a very poor way to display large amounts of data. If I've drive past the same area twice, then the first pass data is just displayed on top of the second pass.
Interpolate over an even grid
I then had a try at using numpy and scipy to interpolate over an even grid.
# Convert python list to np arrays
x = np.array(longs, dtype=float)
y = np.array(lats, dtype=float)
z = np.array(pings, dtype=float)
# Make even grid (200 rows/cols)
xi = np.linspace(min(longs), max(longs), 200)
yi = np.linspace(min(lats), max(lats), 200)
# Interpolate data points to grid
zi = griddata((x, y), z, (xi[None,:], yi[:,None]), method='linear', fill_value=0)
# Plot contour map
plt.contour(xi,yi,zi,15,linewidths=0.5,colors='k')
plt.contourf(xi,yi,zi,15,cmap=plt.cm.jet)
From this example
This looks interesting (lots of colours and shapes), but it extrapolates too far around areas I haven't explored. You can't see the routes I've travelled, just red/blue blotches.
If I've driven in a large curve, it'll interpolate for the area between (see below):
Interpolate over an uneven grid
I then had a try at using meshgrid (xi, yi = np.meshgrid(lats, longs)) instead of a fixed grid, but I'm told my array is too big.
Is there an easy way I can create a grid from my points?
My requirements:
Handle large data sets (80,000 x 60 = ~5m points)
Display duplicate data for each point either by averaging (I assume interpolation will do this), or by taking a minimum value for each point.
Don't extrapolate too far from data points
I'm happy with a scatter plot (top), but I need some way to average the data before I display it.
(Apologies for the dodgy mspaint drawings, I can't upload actual data)
Solution:
# Get sum
hsum, long_range, lat_range = np.histogram2d(longs, lats, bins=(res_long,res_lat), range=((a,b),(c,d)), weights=pings)
# Get count
hcount, ignore1, ignore2 = np.histogram2d(longs, lats, bins=(res_long,res_lat), range=((a,b),(c,d)))
# Get average
h = hsum/hcount
x, y = np.where(h)
average = h[x, y]
# Make scatter plot
scatterplot = ax.scatter(long_range[x], lat_range[y], s=3, c=average, linewidths=0, cmap="jet", vmin=0, vmax=1000)
To simplify your question, you have two set of points, one for ping<1000, one for ping>=1000.
Since the count of points is very large, you can't plot them directly by scatter(). I created some sample data by:
longs = (np.random.rand(60, 1) + np.linspace(-np.pi, np.pi, 80000)).reshape(-1)
lats = np.sin(longs) + np.random.rand(len(longs)) * 0.1
bad_index = (longs>0) & (longs<1)
bad_longs = longs[bad_index]
bad_lats = lats[bad_index]
(longs, lats) is points for ping<1000, (bad_longs, bad_lats) is points for ping>1000
You can use numpy.histogram2d() to count the points:
ranges = [[np.min(lats), np.max(lats)], [np.min(longs), np.max(longs)]]
h, lat_range, long_range = np.histogram2d(lats, longs, bins=(400,400), range=ranges)
bad_h, lat_range2, long_range2 = np.histogram2d(bad_lats, bad_longs, bins=(400,400), range=ranges)
h and bad_h are the points count in every little squere area.
Then you can choose many methods to visualize it. For example, you can plot it by scatter():
y, x = np.where(h)
count = h[y, x]
pl.scatter(long_range[x], lat_range[y], s=count/20, c=count, linewidths=0, cmap="Blues")
count = bad_h[y, x]
pl.scatter(long_range2[x], lat_range2[y], s=count/20, c=count, linewidths=0, cmap="Reds")
pl.show()
Here is the full code:
import numpy as np
import pylab as pl
longs = (np.random.rand(60, 1) + np.linspace(-np.pi, np.pi, 80000)).reshape(-1)
lats = np.sin(longs) + np.random.rand(len(longs)) * 0.1
bad_index = (longs>0) & (longs<1)
bad_longs = longs[bad_index]
bad_lats = lats[bad_index]
ranges = [[np.min(lats), np.max(lats)], [np.min(longs), np.max(longs)]]
h, lat_range, long_range = np.histogram2d(lats, longs, bins=(300,300), range=ranges)
bad_h, lat_range2, long_range2 = np.histogram2d(bad_lats, bad_longs, bins=(300,300), range=ranges)
y, x = np.where(h)
count = h[y, x]
pl.scatter(long_range[x], lat_range[y], s=count/20, c=count, linewidths=0, cmap="Blues")
count = bad_h[y, x]
pl.scatter(long_range2[x], lat_range2[y], s=count/20, c=count, linewidths=0, cmap="Reds")
pl.show()
The output figure is:
The GDAL libraries including the Python API and associated utilities, particularly gdal_grid should work for you. It includes a number of interpolation and averaging methods and options for generating gridded data from scattered points. You should be able to manipulate the grid cell size to get a pleasing resolution.
GDAL handles a number of data formats, but you should be able to pass your coordinates and ping values as CSV and get back a PNG or JPEG without much trouble.
Keep in mind lat/lon data is not a planar coordinate system. If you intend to incorporate you results with other map data you'll have to figure out what map projection, units, etc. to use.
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.