The sample data is as follows:
unique_list = ['home0', 'page_a0', 'page_b0', 'page_a1', 'page_b1',
'page_c1', 'page_b2', 'page_a2', 'page_c2', 'page_c3']
sources = [0, 0, 1, 2, 2, 3, 3, 4, 4, 7, 6]
targets = [3, 4, 4, 3, 5, 6, 8, 7, 8, 9, 9]
values = [2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2]
Using the sample code from the documentation
fig = go.Figure(data=[go.Sankey(
node = dict(
pad = 15,
thickness = 20,
line = dict(color = "black", width = 0.5),
label = unique_list,
color = "blue"
),
link = dict(
source = sources,
target = targets,
value = values
))])
fig.show()
This outputs the following sankey diagram
However, I would like to get all the values which end in the same number in the same vertical column, just like how the leftmost column has all of it's nodes ending with a 0. I see in the docs that it is possible to move the node positions, however I was wondering if there was a cleaner way to do it other than manually inputting x and y values. Any help appreciated.
In go.Sankey() set arrangement='snap' and adjust x and y positions in x=<list> and y=<list>. The following setup will place your nodes as requested.
Plot:
Please note that the y-values are not explicitly set in this example. As soon as there are more than one node for a common x-value, the y-values will be adjusted automatically for all nodes to be displayed in the same vertical position. If you do want to set all positions explicitly, just set arrangement='fixed'
Edit:
I've added a custom function nodify() that assigns identical x-positions to label names that have a common ending such as '0' in ['home0', 'page_a0', 'page_b0']. Now, if you as an example change page_c1 to page_c2 you'll get this:
Complete code:
import plotly.graph_objects as go
unique_list = ['home0', 'page_a0', 'page_b0', 'page_a1', 'page_b1',
'page_c1', 'page_b2', 'page_a2', 'page_c2', 'page_c3']
sources = [0, 0, 1, 2, 2, 3, 3, 4, 4, 7, 6]
targets = [3, 4, 4, 3, 5, 6, 8, 7, 8, 9, 9]
values = [2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2]
def nodify(node_names):
node_names = unique_list
# uniqe name endings
ends = sorted(list(set([e[-1] for e in node_names])))
# intervals
steps = 1/len(ends)
# x-values for each unique name ending
# for input as node position
nodes_x = {}
xVal = 0
for e in ends:
nodes_x[str(e)] = xVal
xVal += steps
# x and y values in list form
x_values = [nodes_x[n[-1]] for n in node_names]
y_values = [0.1]*len(x_values)
return x_values, y_values
nodified = nodify(node_names=unique_list)
# plotly setup
fig = go.Figure(data=[go.Sankey(
arrangement='snap',
node = dict(
pad = 15,
thickness = 20,
line = dict(color = "black", width = 0.5),
label = unique_list,
color = "blue",
x=nodified[0],
y=nodified[1]
),
link = dict(
source = sources,
target = targets,
value = values
))])
fig.show()
I am looking for a Python library which would support mesh queries. For now, I have looked at openmesh, but I am a bit afraid that would be an overkill for my small master thesis project. The features which I need is:
to iterate over vertices around a given vertex
iterate over all edges, faces, vertices
easily associate function values with each vertex, face, edge (I picture that these geometric entities are indexed)
And if I am really successful, I might need also to:
change the topology of the mesh, like adding or removing a vertex
Is it possible to do this with numpy so I could keep my depedency list small? For now I plan that the initial mesh will be generated with distmesh (pydistmesh). Does it have parts which could be useful for my mesh queries?
Theese kinds of queries became quite easy and effiecient with improved face based data structure which is used by CGAL. Here I have implemented code to valk around one specific vertex:
# The demonstration of improved face based data structure
from numpy import array
triangles = array([[ 5, 7, 10],
[ 7, 5, 6],
[ 4, 0, 3],
[ 0, 4, 6],
[ 4, 7, 6],
[ 4, 9, 10],
[ 7, 4, 10],
[ 0, 2, 1],
[ 2, 0, 6],
[ 2, 5, 1],
[ 5, 2, 6],
[ 8, 4, 3],
[ 4, 11, 9],
[ 8, 11, 4],
[ 9, 11, 3],
[11, 8, 3]], dtype=int)
points = array([[ 0.95448092, 0.45655774],
[ 0.86370317, 0.02141752],
[ 0.53821089, 0.16915935],
[ 0.97218064, 0.72769053],
[ 0.55030382, 0.70878147],
[ 0.34692982, 0.08765148],
[ 0.46289581, 0.29827649],
[ 0.21159925, 0.39472549],
[ 0.61679844, 0.79488884],
[ 0.4272861 , 0.93375762],
[ 0.12451604, 0.54267654],
[ 0.45974728, 0.91139648]])
import pylab as plt
fig = plt.figure()
pylab.triplot(points[:,0],points[:,1],triangles)
for i,tri in enumerate(triangles):
v1,v2,v3 = points[tri]
vavg = (v1 + v2 + v3)/3
plt.text(vavg[0],vavg[1],i)
#plt.show()
## constructing improved face based data structure
def edge_search(v1,v2,skip):
"""
Which triangle has edge with verticies i and j and aren't triangle <skip>?
"""
neigh = -1
for i,tri in enumerate(triangles):
if (v1 in tri) and (v2 in tri):
if i is skip:
continue
else:
neigh = i
break
return(neigh)
def triangle_search(i):
"""
For given vertex with index i return any triangle from neigberhood
"""
for i,tri in enumerate(triangles):
if i in tri:
return(i)
neighberhood = []
for i,tri in enumerate(triangles):
v1, v2, v3 = tri
t3 = edge_search(v1,v2,i)
t1 = edge_search(v2,v3,i)
t2 = edge_search(v3,v1,i)
neighberhood.append([t1,t2,t3])
neighberhood = array(neighberhood,dtype=int)
faces = []
for vi,_ in enumerate(points):
faces.append(triangle_search(vi))
## Now walking over first ring can be implemented
def triangle_ring(vertex):
tri_start = faces[vertex]
tri = tri_start
## with asumption that vertex is not on the boundary
for i in range(10):
yield tri
boolindx = triangles[tri]==vertex
# permutating to next and previous vertex
w = boolindx[[0,1,2]]
cw = boolindx[[2,0,1]]
ccw = boolindx[[1,2,0]]
ct = neighberhood[tri][cw][0]
if ct==tri_start:
break
else:
tri=ct
for i in triangle_ring(6):
print(i)
## Using it for drawing lines on plot
vertex = 6
ring_points = []
for i in triangle_ring(vertex):
vi = triangles[i]
cw = (vi==vertex)[[2,0,1]]
print("v={}".format(vi[cw][0]))
ring_points.append(vi[cw][0])
data = array([points[i] for i in ring_points])
plt.plot(data[:,0],data[:,1],"ro")
#plt.savefig("topology.png")
plt.show()
input("Press Enter to continue...")
plt.close("all")
I am having a strange issue when setting up pie charts using matplotlib. For some reason, it does not seem to be handling my labels argument correctly.
A little background...I am working on a tool that will allow us to create tables that summarize the hits on our web map services. This basically just loops through all the log files and and grabs username, site name, and date\time info. I have got this first part working nicely and it creates some good summary tables. However, it would be a nice cherry on top to generate pie charts showing the who uses each site the most (by username). I have also got the pie chart creation to work where each chart is named after the site.
What is not working correctly is the labels.
I have admittedly not worked with matplotlib very much at all, but according to the code samples I have seen online, my code seems sound. Just to double check that the number of labels is matching the number of slices for the pie I print the results out for each chart and the input arguments seem to be correct, but maybe I am missing something?
The input table I am using looks good and that is the data being used for the pie chart. Here is the particular function I am working with:
#! /usr/local/bin/python
import arcpy, os
from pylab import *
import numpy as np
def CreatePieChart(table, out, case_field, name_field, data_field):
# Make new folder
_dir = os.path.join(out, 'Figures')
if not os.path.exists(_dir):
os.makedirs(_dir)
# Grab unique values
rows = arcpy.SearchCursor(table)
cases = sorted(list(set(r.getValue(case_field) for r in rows)))
del rows
# vals dictionary
vals_dict = {}
# Make table views
tv = arcpy.MakeTableView_management(table, 'temp_table')
for case in cases:
query = ''' {0} = '{1}' '''.format(arcpy.AddFieldDelimiters(tv, case_field), case)
arcpy.SelectLayerByAttribute_management(tv, 'NEW_SELECTION', query)
# Get total number for pie slice
rows = arcpy.SearchCursor(tv)
vals_dict[case] = [[r.getValue(name_field),r.getValue(data_field)] for r in rows]
del rows
# Populate Pie Chart
for case,vals in vals_dict.iteritems():
the_fig = figure(1, figsize=(8,8))
axes([0.1, 0.1, 0.8, 0.8])
fig = os.path.join(_dir, '{0}.png'.format(case.replace(' ','_')))
ax = the_fig.add_subplot(111)
label = [v[0] for v in vals]
fracs = [v[1] for v in vals]
print '%s %s %s' %(case,label,fracs)
if len(label) == len(fracs): # to make sure same number of labels as slices
cmap = plt.cm.prism
color = cmap(np.linspace(0., 1., len(fracs)))
pie_wedges = pie(fracs,colors=color,labels=label,pctdistance=0.5, labeldistance=1.05)
for wedge in pie_wedges[0]:
wedge.set_edgecolor('white')
ax.set_title(case)
savefig(fig)
print 'Created: %s' %fig
del the_fig,label,pie_wedges,fracs
return fig
if __name__ == '__main__':
table = r'C:\TEMP\Usage_Summary.dbf'
out = r'C:\TEMP'
case = 'Site_ID'
name = 'Username'
data = 'Count'
CreatePieChart(table, out, case, name, data)
And here is what is printed out to my Python window (slice count does indeed match the number of labels):
ElkRiver [u'elkriver', u'jasonco', u'jenibr', u'johnsh', u'nickme'] [731, 1, 2, 55, 58]
Created: C:\TEMP\Figures\ElkRiver.png
LongPrairie [u'brianya', u'chuckde', u'johnsh', u'longprairie', u'nickme', u'scottja'] [6, 7, 61, 129, 25, 3]
Created: C:\TEMP\Figures\LongPrairie.png
Madison [u'deanhe', u'johnsh', u'kathrynst', u'madison', u'scottja'] [7, 9, 1, 39, 1]
Created: C:\TEMP\Figures\Madison.png
NorthMankato [u' ', u'adamja', u'brianma', u'johnsh', u'johnvo', u'marksc', u'mattme', u'nickme', u'nmankato', u'scottja'] [20, 13, 65, 64, 8, 2, 4, 63, 64, 1]
Created: C:\TEMP\Figures\NorthMankato.png
Orono [u'arroned', u'davidma', u'dionsc', u'dougkl', u'gis_guest', u'jenibr', u'johnsh', u'kenad', u'kevinfi', u'kylele', u'marksc', u'natest', u'nickme', u'orono', u'samel', u'scottja', u'sethpe', u'sueda'] [2, 11, 1, 3, 5, 1, 40, 6, 1, 1, 5, 1, 37, 819, 8, 5, 2, 2]
Created: C:\TEMP\Figures\Orono.png
BellePlaine [u'billhe', u'billsc', u'bplaine', u'christopherga', u'craigla', u'dennisst', u'elkriver', u'empire', u'gis_guest', u'jasonfe', u'joedu', u'johnsh', u'joshst', u'lancefr', u'nickme', u'richardde', u'scottja', u'teresabu', u'travisje', u'wadena'] [3, 1, 331, 1, 1, 40, 1, 1, 12, 1, 27, 61, 4, 1, 47, 3, 12, 2, 2, 1]
Created: C:\TEMP\Figures\BellePlaine.png
Osseo [u'johnsh', u'karlan', u'kevinbi', u'marcusth', u'nickme', u'osseo', u'scottja'] [22, 2, 23, 11, 66, 54, 3]
Created: C:\TEMP\Figures\Osseo.png
EmpireTownship [u'empire', u'johnsh', u'lancefr', u'lanile', u'marksc', u'nickme', u'richardde', u'scottja', u'travisje'] [96, 10, 1, 14, 2, 224, 1, 1, 3]
Created: C:\TEMP\Figures\EmpireTownship.png
Courtland [u'courtland', u'empire', u'joedu', u'johnsh', u'nickme', u'scottja'] [24, 3, 3, 10, 27, 2]
Created: C:\TEMP\Figures\Courtland.png
LeSueur [u' ', u'johnsh', u'marksc', u'nickme'] [8, 6, 1, 98]
Created: C:\TEMP\Figures\LeSueur.png
Stratford [u'johnsh', u'neilgu', u'scottja', u'stratford'] [9, 3, 2, 47]
Created: C:\TEMP\Figures\Stratford.png
>>>
Something funky is happening behind the scenes because the charts come out with way more labels than what I pass into the pie() function.
I do not yet have a reputation to post pictures, but I have posted a picture here on gis stack exchange. Here is the link where the picture can be viewed. The picture is from the "Osseo" Chart that is created and you can see the from my print statements that these are the slices and lables:
Osseo [u'johnsh', u'karlan', u'kevinbi', u'marcusth', u'nickme', u'osseo', u'scottja'] [22, 2, 23, 11, 66, 54, 3]
I am not sure why so many extra labels are being created. Am I missing something here?
Here is a clean version of the code so others can test:
#! /usr/local/bin/python
import os
from pylab import *
import numpy as np
_dir = os.path.join(os.getcwd(), 'Figures')
if not os.path.exists(_dir):
os.makedirs(_dir)
vals_dict = {u'ElkRiver': [[u'elkriver', 731], [u'jasonco', 1], [u'jenibr', 2], [u'johnsh', 55], [u'nickme', 58]],
u'LongPrairie': [[u'brianya', 6], [u'chuckde', 7], [u'johnsh', 61], [u'longprairie', 129], [u'nickme', 25],
[u'scottja', 3]], u'Madison': [[u'deanhe', 7], [u'johnsh', 9], [u'kathrynst', 1], [u'madison', 39],
[u'scottja', 1]], u'NorthMankato': [[u' ', 20], [u'adamja', 13],[u'brianma', 65], [u'johnsh', 64],
[u'johnvo', 8], [u'marksc', 2], [u'mattme', 4], [u'nickme', 63], [u'nmankato', 64], [u'scottja', 1]],
u'Orono': [[u'arroned', 2], [u'davidma', 11], [u'dionsc', 1], [u'dougkl', 3], [u'gis_guest', 5], [u'jenibr', 1],
[u'johnsh', 40], [u'kenad', 6], [u'kevinfi', 1], [u'kylele', 1], [u'marksc', 5], [u'natest', 1], [u'nickme', 37],
[u'orono', 819], [u'samel', 8], [u'scottja', 5], [u'sethpe', 2], [u'sueda', 2]], u'BellePlaine': [[u'billhe', 3],
[u'billsc', 1], [u'bplaine', 331], [u'christopherga', 1], [u'craigla', 1], [u'dennisst', 40], [u'elkriver', 1],
[u'empire', 1], [u'gis_guest', 12], [u'jasonfe', 1], [u'joedu', 27], [u'johnsh', 61], [u'joshst', 4], [u'lancefr', 1],
[u'nickme', 47], [u'richardde', 3], [u'scottja', 12], [u'teresabu', 2], [u'travisje', 2], [u'wadena', 1]],
u'Osseo': [[u'johnsh', 22], [u'karlan', 2], [u'kevinbi', 23], [u'marcusth', 11], [u'nickme', 66], [u'osseo', 54],
[u'scottja', 3]], u'EmpireTownship': [[u'empire', 96], [u'johnsh', 10], [u'lancefr', 1], [u'lanile', 14], [u'marksc', 2],
[u'nickme', 224], [u'richardde', 1], [u'scottja', 1], [u'travisje', 3]], u'Courtland': [[u'courtland', 24], [u'empire', 3],
[u'joedu', 3], [u'johnsh', 10], [u'nickme', 27], [u'scottja', 2]], u'LeSueur': [[u' ', 8], [u'johnsh', 6], [u'marksc', 1],
[u'nickme', 98]], u'Stratford': [[u'johnsh', 9], [u'neilgu', 3], [u'scottja', 2], [u'stratford', 47]]}
# Populate Pie Chart
for case,vals in vals_dict.iteritems():
the_fig = figure(1, figsize=(8,8))
axes([0.1, 0.1, 0.8, 0.8])
fig = os.path.join(_dir, '{0}.png'.format(case.replace(' ','_')))
ax = the_fig.add_subplot(111)
label = [v[0] for v in vals]
fracs = [v[1] for v in vals]
print '%s %s %s' %(case,label,fracs)
if len(label) == len(fracs): # to make sure same number of labels as slices
cmap = plt.cm.prism
color = cmap(np.linspace(0., 1., len(fracs)))
pie_wedges = pie(fracs,colors=color,labels=label,pctdistance=0.5, labeldistance=1.1)
for wedge in pie_wedges[0]:
wedge.set_edgecolor('white')
ax.set_title(case)
savefig(fig)
print 'Created: %s' %fig
del label,pie_wedges,fracs
the_fig.clf()
From what I can tell without being able to run your example, the problem is that you're using the same figure in your loop over the cases
for case,vals in vals_dict.iteritems():
the_fig = figure(1, figsize=(8,8))
This gets figure 1 each time, and this figure never gets cleared. So try either making a new figure (call figure(figsize=(8,8)) instead of figure(1, figsize=(8,8))), or clearing the figure right after you create it (with the_fig.clf()). Let me know if that helps.
Edit: here's a version that doesn't show the axes, and uses legend instead of label so that you don't have labels being squished together. I also threw percentages on the labels, especially since with your color scheme you sometimes end up with some slices the same color (I tried to fix that but didn't have any luck). Matplotlib is actually doing a pretty decent job of putting small slices as far from each other as possible, the problem is in some of your charts, you just have too many small slices, so it's just not possible to get them all separated. That's why I switched to using a legend.
for case,vals in vals_dict.iteritems():
the_fig = figure(figsize=(8,8))
axes([0.1, 0.1, 0.8, 0.8])
fig = os.path.join(_dir, '{0}.png'.format(case.replace(' ','_')))
label = [v[0] for v in vals]
fracs = [v[1] for v in vals]
print '%s %s %s' %(case,label,fracs)
if len(label) == len(fracs): # to make sure same number of labels as slices
cmap = plt.cm.prism
color = cmap(np.linspace(0., 1., len(fracs)))
pie_wedges = pie(fracs,colors=color,pctdistance=0.5, labeldistance=1.1)
for wedge in pie_wedges[0]:
wedge.set_edgecolor('white')
legend(map(lambda x, f: '%s (%0.0f%%)' % (x, f), label, fracs), loc=4)
title(case)
savefig(fig)
print 'Created: %s' %fig