I prematurely posted a code golf challenge to draw the Utah Teapot using this dataset (just the teapot). (Revised and Posted teapot challenge) But when I looked deeper at the data in order to whip up a little example, I realized I have no idea what's going on with that data. I have a good understanding of Bezier curves in 2D, implemented deCasteljau. But for 3D does it work the same?
Yes! It does!
The data contains patches containing 16 vertices each. Is there a standard ordering for how these are laid out? And if they correspond to the 2D curves, then the four corner points actually touch the surface and the remaining 12 are controls, right?
Yes!
My "original plan" was to simplify the shape to rectangles, project them to the canvas, and draw filled shapes in a grey computed by the magnitude of the dot product of the patch normal to a light vector. If I simplify it that far, will it even look like a teapot? Does one have to use raytracing to achieve a recognizable image?
That's subjective. :-(
While this may look like several questions, but they are all aspects of this one: "Please, kindly Guru, school me on some Bezier Patches? What do I need to know to draw the teapot?"
Here's the code I've written so far. (uses this matrix library: mat.ps)
%!
%%BoundingBox: 115 243 493 487
%-115 -243 translate
(mat.ps)run %include matrix library
/tok{ token pop exch pop }def
/s{(,){search{ tok 3 1 roll }{ tok exit }ifelse }loop }def
/f(teapot)(r)file def
/patch[ f token pop { [ f 100 string readline pop s ] } repeat ]def
/vert[ f token pop { [ f 100 string readline pop s ] } repeat ]def
%vert == patch == %test data input
/I3 3 ident def % 3D identity matrix
/Cam [ 0 0 10 ] def % world coords of camera center viewpoint
/Theta [ 0 0 0 ] def % y-rotation x-rotation z-rotation
/Eye [ 0 0 15 ] def % eye relative to camera vp
/Rot I3 def % initial rotation seq
/makerot {
Theta 0 get roty % pan
Theta 1 get rotx matmul % tilt
Theta 2 get rotz matmul % twist
} def
/proj {
Cam {sub} vop % translate to camera coords
Rot matmul % perform camera rotation
0 get aload pop Eye aload pop % extract dot x,y,z and eye xyz
4 3 roll div exch neg % perform perspective projection
4 3 roll add 1 index mul
4 1 roll 3 1 roll sub mul exch % (ez/dz)(dx-ex) (ez/dz)(dy-ey)
} def
/R 20 def
/H -3 def
/ang 0 def
{
300 700 translate
1 70 dup dup scale div setlinewidth
/Cam [ ang sin R mul H ang cos R mul ] def % camera revolves around Y axis at height H, dist R
/Theta [ ang H R atan 0 ] def % rotate camera back to origin
/Rot makerot def % squash rotation sequence into a matrix
patch {
% Four corners
%[ exch dup 0 get exch dup 3 get exch dup 12 get exch 15 get ]
% Boundary curves
[ exch
dup 8 get exch dup 4 get exch dup 0 get exch %curveto4
dup 14 get exch dup 13 get exch dup 12 get exch %curveto3
dup 7 get exch dup 11 get exch dup 15 get exch %curveto2
dup 1 get exch dup 2 get exch dup 3 get exch %curveto1
dup 0 get exch %moveto
pop ]
{ 1 sub vert exch get proj } forall
moveto
curveto curveto curveto curveto
stroke
%flushpage flush (%lineedit)(r)file pop
} forall
pstack
showpage
%exit
/ang ang 10 add def
} loop
Here's the original Newell Teapot dataset.
And here's my spectacularly bad image:
Update: bugfix. Maybe they are laid out 'normally' after all. Selecting the correct corners at least gives a symmetrical shape:
Update: boundary curves looks better.
A Bi-Cubic Bezier surface patch is a 4x4 array of 3D points. Yes, the four corners touch the surface; and the rows are Bezier curves, and columns are also Bezier curves. But the deCasteljau algorithm is based on calculating the median between two points, and is equally meaningful in 3D as in 2D.
The next step in completing the above code is subdividing the patches to cover smaller portions. Then the simple boundary curve extraction above becomes a suitable polygon mesh.
Start by flattening the patches, inserting the vertex data directly instead of using a separate cache. This code iterates through the patches, looking up points in the vertex array and constructs a new array of patches which is then redefined with the same name.
/patch[ patch{ [exch { 1 sub vert exch get }forall ] }forall ]def
Then we need the deCasteljau algorithm to split Bezier curves. vop comes from the matrix library and applies a binary operation upon corresponding elements of a vector and produces a new vector as the result.
/median { % [x0 y0 z0] [x1 y1 z1]
{add 2 div} vop % [ (x0+x1)/2 (y0+y1)/2 (z0+z1)/2 ]
} def
/decasteljau { % [P0] P1 P2 P3 . P0 P1' P2' P3' P3' P4' P5' P3
{p3 p2 p1 p0}{exch def}forall
/p01 p0 p1 median def
/p12 p1 p2 median def
/p23 p2 p3 median def
/p012 p01 p12 median def
/p123 p12 p23 median def
/p0123 p012 p123 median def
p0 p01 p012 p0123 % first half-curve
p0123 p123 p23 p3 % second half-curve
} def
Then some stack manipulation to apply to each row of a patch and assemble the results into 2 new patches.
/splitrows { % [b0 .. b15] . [c0 .. c15] [d0 .. d15]
aload pop % b0 .. b15
4 { % on each of 4 rows
16 12 roll decasteljau % roll the first 4 to the top
8 4 roll % exch left and right halves (probably unnecessary)
20 4 roll % roll new curve to below the patch (pushing earlier ones lower)
} repeat
16 array astore % pack the left patch
17 1 roll 16 array astore % roll, pack the right patch
} def
An ugly utility lets us reuse the row code for columns. The stack comments were necessary to write this procedure, so they're probably necessary to read it. n j roll rolls n elements (to the left), j times; == the top j elements above n-th element (counting from 1). So n steady decreases, selecting where to put the element, and j selects which element to put there (dragging everything else with it). If bind were applied, this procedure would be substantially faster than a dictionary-based procedure.
% [ 0 1 2 3
% 4 5 6 7
% 8 9 10 11
% 12 13 14 15 ]
/xpose {
aload pop % 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
15 12 roll % 0 4 5 6 7 8 9 10 11 12 13 14 15 1 2 3
14 11 roll % 0 4 8 9 10 11 12 13 14 15 1 2 3 5 6 7
13 10 roll % 0 4 8 12 13 14 15 1 2 3 5 6 7 9 10 11
12 9 roll % 0 4 8 12 1 2 3 5 6 7 9 10 11 13 14 15
11 9 roll % 0 4 8 12 1 5 6 7 9 10 11 13 14 15 2 3
10 8 roll % 0 4 8 12 1 5 9 10 11 13 14 15 2 3 6 7
9 7 roll % 0 4 8 12 1 5 9 13 14 15 2 3 6 7 10 11
8 6 roll % 0 4 8 12 1 5 9 13 2 3 6 7 10 11 14 15
7 6 roll % 0 4 8 12 1 5 9 13 2 6 7 10 11 14 15 3
6 5 roll % 0 4 8 12 1 5 9 13 2 6 10 11 14 15 3 7
5 4 roll % 0 4 8 12 1 5 9 13 2 6 10 14 15 3 7 11
4 3 roll % 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15
16 array astore
} def
% [ 0 4 8 12
% 1 5 9 13
% 2 6 10 14
% 3 7 11 15 ]
/splitcols {
xpose
splitrows
xpose
} def
Then apply these functions to the patch data. Again, redefining patch each time.
/patch[ patch{ splitrows }forall ]def
/patch[ patch{ splitrows }forall ]def
/patch[ patch{ splitcols }forall ]def
/patch[ patch{ splitcols }forall ]def
This gives the ability to deal with smaller fragments.
Add a visibility test.
/visible { % patch . patch boolean
dup % p p
dup 3 get exch dup 0 get exch 12 get % p p3 p0 p12
1 index {sub} vop % p p3 p0 v0->12
3 1 roll {sub} vop % p v0->12 v0->3
cross /normal exch def
dup
[ exch dup 0 get exch dup 3 get exch dup 12 get exch 15 get ]
{ Cam {sub} vop normal dot 0 ge } forall
%add add add 4 div 0 lt
or or or
} def
Producing
Update: test was backwards.
Update: Test is Useless! You can see from the image that the bottom piece is not oriented outward, and of course, backface-culling doesn't prevent the handle from showing through the pot. This calls for hidden surface removal. And since Postscript doesn't have support for a Z-buffer, I guess it'll have to be a Binary Space Partition. So it's back to the books for me.
Update: Add a Model->World transform to turn the thing upright.
/Model -90 rotx def % model->world transform
/proj {
Model matmul 0 get % perform model->world transform
Cam {sub} vop % translate to camera coords
...
Producing this.
Complete program so far. (uses matrix library:mat.ps.) In ghostscript, you can view an animated rotation by holding [enter].
%!
%%BoundingBox: 109 246 492 487
%-109 -246 translate
(mat.ps)run %include matrix library
(det.ps)run %supplementary determinant function
/tok{ token pop exch pop }def
/s{(,){search{ tok 3 1 roll }{ tok exit }ifelse }loop }def
/f(teapot)(r)file def
/patch[ f token pop { [ f 100 string readline pop s ] } repeat ]def
/vert[ f token pop { [ f 100 string readline pop s ] } repeat ]def
/patch[ patch{ [exch { 1 sub vert exch get }forall ] }forall ]def
%vert == patch == %test data input flush quit
/I3 3 ident def % 3D identity matrix
/Cam [ 0 0 10 ] def % world coords of camera center viewpoint
/Theta [ 0 0 0 ] def % y-rotation x-rotation z-rotation
/Eye [ 0 0 15 ] def % eye relative to camera vp
/Rot I3 def % initial rotation seq
/Model -90 rotx def % model->world transform
/makerot {
Theta 0 get roty % pan
Theta 1 get rotx matmul % tilt
Theta 2 get rotz matmul % twist
} def
/proj {
Model matmul 0 get % perform model->world transform
Cam {sub} vop % translate to camera coords
Rot matmul % perform camera rotation
0 get aload pop Eye aload pop % extract dot x,y,z and eye xyz
4 3 roll div exch neg % perform perspective projection
4 3 roll add 1 index mul
4 1 roll 3 1 roll sub mul exch % (ez/dz)(dx-ex) (ez/dz)(dy-ey)
} def
/median { % [x0 y0 z0] [x1 y1 z1]
{add 2 div} vop % [ (x0+x1)/2 (y0+y1)/2 (z0+z1)/2 ]
} def
/decasteljau { % [P0] P1 P2 P3 . P0 P1' P2' P3' P3' P4' P5' P3
{p3 p2 p1 p0}{exch def}forall
/p01 p0 p1 median def
/p12 p1 p2 median def
/p23 p2 p3 median def
/p012 p01 p12 median def
/p123 p12 p23 median def
/p0123 p012 p123 median def
p0 p01 p012 p0123
p0123 p123 p23 p3
} def
/splitrows { % [b0 .. b15] . [c0 .. c15] [d0 .. d15]
aload pop % b0 .. b15
4 {
16 12 roll decasteljau
%8 4 roll
20 4 roll
} repeat
16 array astore
17 1 roll 16 array astore
} def
/xpose {
aload pop % 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
15 12 roll % 0 4 5 6 7 8 9 10 11 12 13 14 15 1 2 3
14 11 roll % 0 4 8 9 10 11 12 13 14 15 1 2 3 5 6 7
13 10 roll % 0 4 8 12 13 14 15 1 2 3 5 6 7 9 10 11
12 9 roll % 0 4 8 12 1 2 3 5 6 7 9 10 11 13 14 15
11 9 roll % 0 4 8 12 1 5 6 7 9 10 11 13 14 15 2 3
10 8 roll % 0 4 8 12 1 5 9 10 11 13 14 15 2 3 6 7
9 7 roll % 0 4 8 12 1 5 9 13 14 15 2 3 6 7 10 11
8 6 roll % 0 4 8 12 1 5 9 13 2 3 6 7 10 11 14 15
7 6 roll % 0 4 8 12 1 5 9 13 2 6 7 10 11 14 15 3
6 5 roll % 0 4 8 12 1 5 9 13 2 6 10 11 14 15 3 7
5 4 roll % 0 4 8 12 1 5 9 13 2 6 10 14 15 3 7 11
4 3 roll % 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 14
16 array astore
} def
/splitcols {
xpose
splitrows
xpose exch xpose
} def
/patch[ patch{ splitrows }forall ]def
/patch[ patch{ splitrows }forall ]def
/patch[ patch{ splitrows }forall ]def
/patch[ patch{ splitrows }forall ]def
/patch[ patch{ splitcols }forall ]def
/patch[ patch{ splitcols }forall ]def
/color {normal light dot 1 add 4 div
%1 exch sub
setgray} def
/visible { % patch . patch boolean
dup % p p
dup 3 get exch dup 0 get exch 12 get % p p3 p0 p12
1 index {sub} vop % p p3 p0 v0->12
3 1 roll {sub} vop % p v0->12 v0->3
cross /normal exch def
dup
[ exch dup 0 get exch dup 3 get exch dup 12 get exch 15 get ]
{ Cam {sub} vop normal dot 0 ge } forall
%add add add 4 div 0 lt
or or or
} def
/drawpatch {
% Four corners
%[ exch dup 0 get exch dup 3 get exch dup 12 get exch 15 get ]
visible {
[ exch
% control rows
%dup 4 get exch dup 5 get exch dup 6 get exch dup 7 get exch
%dup 11 get exch dup 10 get exch dup 9 get exch dup 8 get exch
% control columns
%dup 1 get exch dup 5 get exch dup 9 get exch dup 13 get exch
%dup 14 get exch dup 10 get exch dup 6 get exch dup 2 get exch
% Boundary curves
dup 8 get exch dup 4 get exch dup 0 get exch %curveto4
dup 14 get exch dup 13 get exch dup 12 get exch %curveto3
dup 7 get exch dup 11 get exch dup 15 get exch %curveto2
dup 1 get exch dup 2 get exch dup 3 get exch %curveto1
dup 0 get exch %moveto
pop ]
{ proj } forall
moveto curveto curveto curveto curveto
%moveto lineto lineto lineto lineto lineto lineto lineto closepath
%moveto lineto lineto lineto lineto lineto lineto lineto closepath
stroke
%flushpage flush (%lineedit)(r)file pop
}{
pop
}ifelse
} def
/R 20 def
/H -3 def
/ang 10 def
{
300 700 translate
1 70 dup dup scale div setlinewidth
% camera revolves around Y axis at height H, dist R
/Cam [ ang sin R mul H ang cos R mul ] def
/Theta [ ang H R atan 0 ] def % rotate camera back to origin
/Rot makerot def % squash rotation sequence into a matrix
patch {
drawpatch
} forall
pstack
showpage
%exit
/ang ang 10 add def
} loop
Based on assistance on math.StackExchange, I've been led to a sub-goal of supplementing the matrix library with a function to compute determinants.
So, this code passes some clumsy initial tests, but it's damned ugly, I must admit:
GS>[[1 0][0 1]] det
GS<1>=
1
GS>[[0 1][1 0]] det =
-1
GS>(mat.ps) run
GS>3 ident
GS<1>det =
1
GS>[[1 2 3][4 5 6][7 8 9]] det =
0
GS>
Update. A little more readable.
Update. A lot more readable using dot and cross. Thanks again, MvG.
(mat.ps) run % use dot and cross from matrix library
/elem { % M i j
3 1 roll get exch get % M_i_j
} def
/det {
dup length 1 index 0 get length ne { /det cvx /typecheck signalerror } if
1 dict begin /M exch def
M length 2 eq {
M 0 0 elem
M 1 1 elem mul
M 0 1 elem
M 1 0 elem mul sub
}{
M length 3 eq {
M aload pop cross dot
}{ /det cvx /rangecheck signalerror } ifelse
} ifelse
end
} def
Related
If I want to check how many values in a vector or matrix are less than a given value
I can use +/ (a < 20). But what if I wanted to know both the specific value and it's index.
Something like (2(value) 5(index)) as a table. I looked at i., i: (which give first and last position) and I. Does sorting first help?
A very common pattern in J is the creation of a mask from a filter and applying an action on and/or using the masked data in a hook or fork:
((actions) (filter)) (data)
For example:
NB. Random array
a =: ? 20 $ 10
6 3 9 0 3 3 0 6 2 9 2 4 6 8 7 4 6 1 7 1
NB. Filter and mask
f =: 5 < ]
m =: f a
1 0 1 0 0 0 0 1 0 1 0 0 1 1 1 0 1 0 1 0
NB. Values of a on m
m # a
6 9 6 9 6 8 7 6 7
NB. Indices of a on m
I. m
0 2 7 9 12 13 14 16 18
NB. Joint results
(I.m) ,: (m # a)
0 2 7 9 12 13 14 16 18
6 9 6 9 6 8 7 6 7
In other words, in this case you have m&# and f acting on a and I. acting on m. Notice that the final result can be derived from an action on m alone by commuting the arguments of copy #~:
(I. ,: (a #~ ]) m
0 2 7 9 12 13 14 16 18
6 9 6 9 6 8 7 6 7
and a can be pulled out from the action on m like so:
a ( (]I.) ,: (#~ ])) m
But since m itself is derived from an action (f) on a, we can write:
a ( (]I.) ,: (#~ ])) (f a)
which is a simple monadic hook y v (f y) → (v f) y.
Therefore:
action =: (]I.) ,: (#~ ])
filter =: 5 < ]
data =: a
(action filter) data
0 2 7 9 12 13 14 16 18
6 9 6 9 6 8 7 6 7
The J primitive Copy (#) can be used as a filter function, such as
k =: i.8
(k>3) # k
4 5 6 7
That's essentially
0 0 0 0 1 1 1 1 # i.8
The question is if the right-hand side of # is 2-d or higher rank shaped array, how to make a selection using #, if possible. For example:
k =: 2 4 $ i.8
(k > 3) # k
I got length error
What is the right way to make such a selection?
You can use the appropriate verb rank to get something like a 2d-selection:
(2 | k) #"1 1 k
1 3
5 7
but the requested axes have to be filled with 0s (or !.) to keep the correct shape:
(k > 3) #("1 1) k
0 0 0 0
4 5 6 7
(k > 2) #("1 1) k
3 0 0 0
4 5 6 7
You have to better define select for dimensions > 1 because now you have a structure. How do you discard values? Do you keep empty "cells"? Do you replace with 0s? Is structure important for the result?
If, for example, you only need the "values where" then just ravel , the array:
(,k > 2) # ,k
3 4 5 6 7
If you need to "replace where", then you can use amend }:
u =: 5 :'I. , 5 > y' NB. indices where 5 > y
0 u } k
0 0 0 0
0 5 6 7
z =: 3 2 4 $ i.25
u =: 4 :'I. , (5 > y) +. (0 = 3|y)' NB. indices where 5>y or 3 divides y
_999 u } z
_999 _999 _999 _999
_999 5 _999 7
8 _999 10 11
_999 13 14 _999
16 17 _999 19
20 _999 22 23
I am trying to learn Python and I am trying to run a random walk that plots the points. I have tried de-bugging this myself but I cannot figure out where this is going wrong. I apologise since this seems like a really simple problem but I am getting frustrated.
One file rw_visual.py sets things up and then calls the other file random_walk.py to generate the points in the walk.
rw_visual.py:
enter image description here
random_walk.py:
enter image description here
In debugging, rw_visual.py seems to run until it tries to run the command "rw.fill_walk()" and then it hangs. This tells me that there is something wrong in the while loop in random_walk.py causing this. As hard as I try, I cannot figure it out thought.
Sorry for the very basic question.
Python indentation implies scope. By getting the indentation of your while loop (and all it should contain) correct, I think this is producing the results you're looking for I left out the "graphical" part and just printed the x and y coordinates as a result of the random walk. You can take over the graphical part from here.
from random import choice
class RandomWalk():
def __init__(self, num_points=50):
self.num_points = num_points
self.x_values = [0]
self.y_values = [0]
def fill_walk(self):
while len(self.x_values) < self.num_points:
x_direction = choice([1, -1])
x_distance = choice([0,1,2,3,4])
x_step = x_direction * x_distance
y_direction = choice([1, -1])
y_distance = choice([0,1,2,3,4])
y_step = y_direction * y_distance
if x_step == 0 and y_step == 0:
continue
next_x = self.x_values[-1] + x_step
next_y = self.y_values[-1] + y_step
print (str(next_x) + " " + str(next_y))
self.x_values.append(next_x)
self.y_values.append(next_y)
rw = RandomWalk()
rw.fill_walk()
RESULTS
-2 -3
1 0
-2 0
-1 1
-1 -3
1 -1
4 0
0 0
0 4
0 5
3 5
1 3
1 4
1 3
-2 4
-3 7
0 7
1 7
-2 5
-2 1
-3 1
-1 0
-4 3
-3 5
0 9
3 7
3 4
-1 5
1 8
4 10
6 11
6 7
9 9
13 10
12 10
12 11
9 9
12 10
16 11
15 7
14 6
14 3
16 2
18 2
15 0
13 -2
12 -1
8 1
12 1
I have found some code and I changed it for my purpose. The code goes to the given line number and adds a new line with a certain format. But it does not work if I have a sequence of numbers. I could not find out why.
This is my code:
import fileinput
x = [2, 4, 5, 6]
for line in fileinput.FileInput("1.txt", inplace=1):
print(line, end="")
for index, item in enumerate(x):
if line.startswith("ND "+str(x[index]-1)):
print("ND "+str(x[index])+" 0 0 0 0")
This is the input file "1.txt":
ND 1 12 11 8 9
ND 3 15 11 7 9
ND 7 8 9 2 3
ND 8 4 5 1 12
ND 9 2 3 6 10
This is the result now :
ND 1 12 11 8 9
ND 2 0 0 0 0
ND 3 15 11 7 9
ND 4 0 0 0 0
ND 7 8 9 2 3
ND 8 4 5 1 12
ND 9 2 3 6 10
What I need should be like this:
ND 1 12 11 8 9
ND 2 0 0 0 0
ND 3 15 11 7 9
ND 4 0 0 0 0
ND 5 0 0 0 0
ND 6 0 0 0 0
ND 7 8 9 2 3
ND 8 4 5 1 12
ND 9 2 3 6 10
Can you please give me a hint! How should I change my code?
Would something like that work for you ? (you don't need to maintain the list of missing lines in x). It's not the most elegant code but it can be improved later if that work for you.
import fileinput
n = 1
for line in fileinput.FileInput("1.txt", inplace=1):
while not line.startswith("ND %d" % n):
print("ND %d 0 0 0 0" % n)
n+=1
print(line)
n+=1
You are missing ND 4 and ND 5 lines in your 1.txt. That's why you cannot print the ND 5 0 0 0 0 and ND 6 0 0 0 0.
You can use the regex to extract line number from text and compare:
import fileinput
import re
x = [2, 4, 5, 6]
last = 0
for line in fileinput.FileInput("1.txt", inplace=1):
# using regex to extract the "current" line number from ND...
current = int(re.search(r'\d+', line).group())
for index, item in enumerate(x):
# "=" because there's a case that your given line already exists
if item > last and item <= current:
print("ND "+str(item)+" 0 0 0 0")
last = current
print(line, end="")
As I said in the comment, it needs a break with the maximum number of ND in the file. So in this case at n == 10.
n = 1
for line in fileinput.FileInput("1.txt", inplace=1):
while not line.startswith("ND %d" % n):
print("ND %d 0 0 0 0" % n)
n+=1
if n == 10:
break
print(line, end="")
n+=1
I'm trying to generate a stack plot of version data using matplotlib. I have that portion working and displaying properly, but I'm unable to get the legend to display anything other than an empty square in the corner.
ra_ys = np.asarray(ra_ys)
# Going to generate a stack plot of the version stats
fig = plt.figure()
ra_plot = fig.add_subplot(111)
# Our x axis is going to be the dates, but we need them as numbers
x = [date2num(date) for date in dates]
# Plot the data
ra_plot.stackplot(x, ra_ys)
# Setup our legends
ra_plot.legend(ra_versions) #Also tried converting to a tuple
ra_plot.set_title("blah blah words")
print(ra_versions)
# Only want x ticks on the dates we supplied, and want them to display AS dates
ra_plot.set_xticks(x)
ra_plot.set_xticklabels([date.strftime("%m-%d") for date in dates])
plt.show()
ra_ys is a multidimensional array:
[[ 2 2 2 2 2 2 2 2 2 2 1]
[ 1 1 1 1 1 1 1 1 1 1 1]
[ 1 1 1 1 1 1 1 1 1 1 1]
[53 52 51 50 50 49 48 48 48 48 47]
[18 19 20 20 20 20 21 21 21 21 21]
[ 0 0 12 15 17 18 19 19 19 19 22]
[ 5 5 3 3 3 3 3 3 3 3 3]
[ 4 4 3 3 2 2 2 2 2 2 2]
[14 14 6 4 3 3 2 2 2 2 2]
[ 1 1 1 1 1 1 1 1 1 1 1]
[ 1 1 1 1 1 1 1 1 1 1 1]
[ 1 1 1 1 1 1 1 1 1 1 1]
[ 2 2 2 2 2 2 2 2 2 2 2]
[ 1 1 1 1 1 1 1 1 1 1 1]
[ 1 1 1 1 1 1 1 1 1 1 1]
[ 3 3 2 2 2 2 2 2 2 2 2]]
x is some dates: [734969.0, 734970.0, 734973.0, 734974.0, 734975.0, 734976.0, 734977.0, 734978.0, 734979.0, 734980.0, 734981.0]
ra_versions is a list: ['4.5.2', '4.5.7', '4.5.8', '5.0.0', '5.0.1', '5.0.10', '5.0.7', '5.0.8', '5.0.9', '5.9.105', '5.9.26', '5.9.27', '5.9.29', '5.9.31', '5.9.32', '5.9.34']
Am I doing something wrong? Can stack plots not have legends?
EDIT: I tried to print the handles and labels for the plot and got two empty lists ([] []):
handles, labels = theplot.get_legend_handles_labels()
print(handles,labels)
I then tested the same figure using the follow code for a proxy handle and it worked. So it looks like the lack of handles is the problem.
p = plt.Rectangle((0, 0), 1, 1, fc="r")
theplot.legend([p], ['test'])
So now the question is, how can I generate a variable number of proxy handles that match the colors of my stack plot?
This is the final (cleaner) approach to getting the legend. Since there are no handles, I generate proxy artists for each line. It's theoretically capable of handling cases where colors are reused, but it'll be confusing.
def plot_version_data(title, dates, versions, version_ys, savename=None):
print("Prepping plot for \"{0}\"".format(title))
fig = plt.figure()
theplot = fig.add_subplot(111)
# Our x axis is going to be the dates, but we need them as numbers
x = [date2num(date) for date in dates]
# Use these colors
colormap = "bgrcmy"
theplot.stackplot(x, version_ys, colors=colormap)
# Make some proxy artists for the legend
p = []
i = 0
for _ in versions:
p.append(plt.Rectangle((0, 0), 1, 1, fc=colormap[i]))
i = (i + 1) % len(colormap)
theplot.legend(p, versions)
theplot.set_ylabel(versions) # Cheating way to handle the legend
theplot.set_title(title)
# Setup the X axis - rotate to keep from overlapping, display like Oct-16,
# make sure there's no random whitespace on either end
plt.xticks(rotation=315)
theplot.set_xticks(x)
theplot.set_xticklabels([date.strftime("%b-%d") for date in dates])
plt.xlim(x[0],x[-1])
if savename:
print("Saving output as \"{0}\"".format(savename))
fig.savefig(os.path.join(sys.path[0], savename))
else:
plt.show()