how to create legend from neighboring column in 3d plot

Hi, I have a question, I'm wondering how I'd be able to create a legend based on a neighboring column
right now I have this code
And the pulled table doesn't necessarily extract the element names (i assume because it's not integer?)
but I'm wondering how I can make my scatter legend based on my column 1 in the excel sheet?

7 Comments

How many legend labels would you want, and what do you want them to say? Do you want one legend label string for every unique marker color?
would depend on the index that I selected. As many as the points that are being plotted. Each index corresponds to a single element which is already in the excel sheet. So I'm just trying to grab the information from that row.
OK, you want as many legend strings as points that are currently selected in your Excel instance. Some questions remain:
  1. So are you using ActiveX to figure out which cells in Excel are the currently selected cells? If not, how do you know which ones (cells, indexes) are selected?
  2. Where exactly does the legend label string come from (unanswered question from before)? Is it some column in the workbook and you want to use the strings in the same rows as the rows you selected?
I'm going to be busy traveling this weekend and might not be able to answer if you don't answer quickly.
the response below from dpb pretty much nailed it but incomplete, I'm just trying to give legends to each row that is being plotted and the label is available in each row.
OK, but his code does not determine how many cells you selected. Remember you said "depend on the index that I selected".
this command selects the index that I want
ix=(tT.AW>=21)&(tT.AW<=30);
It's based on the second column
"...but his code does not determine how many cells you selected..."
Au contraire, good buddy! <VBG> I did get that far before the phone rang...it's in
C=jet(nnz(ix));
although not at that point a separate variable...not sure if going to need that or not, yet, but is good possibility.
Will return to the field of battle; may need to look more at the structure of the spreadsheet; I didn't look at it specifically, to know what details may look like...

Sign in to comment.

 Accepted Answer

Why folks continue to beat head against wall with deprecated xlsread is beyond ken...
fn='https://www.mathworks.com/matlabcentral/answers/uploaded_files/1369019/PIP2.xlsx';
opt=detectImportOptions(fn,'ReadVariableNames',1,'VariableNamesRange','A1','DataRange','A2');
opt.SelectedVariableNames=opt.VariableNames(~startsWith(opt.VariableNames,'Var'));
tT=readtable(fn,opt);
Warning: Column headers from the file were modified to make them valid MATLAB identifiers before creating variable names for the table. The original column headers are saved in the VariableDescriptions property.
Set 'VariableNamingRule' to 'preserve' to use the original column headers as table variable names.
head(tT)
Element AtomicNumber E0 R CED Pvap Tm a b c _______ ____________ ______ ___ __________ __________ ____ ______ ______ ______ {'Al'} 13 -1.66 125 0.00045848 2.1021e-07 660 8.2407 14525 192.25 {'Si'} 14 -1.37 110 0.1877 0.035232 1410 8.7131 20614 267.17 {'Sc'} 21 -2.08 160 318.31 1.0132e+05 2836 7.7265 13961 45.18 {'Ti'} 22 -0.163 140 317.48 1.0079e+05 3287 8.9022 20949 190.76 {'V' } 23 -1.18 135 47.626 2268.2 2672 9.18 22303 133.69 {'Cr'} 24 -0.74 140 8.2415 67.923 1684 8.4871 15308 59.51 {'Mn'} 25 -1.185 140 11.645 135.6 1246 7.5051 9853.7 68.23 {'Fe'} 26 -0.04 140 1.8146 3.2928 1535 8.3793 16214 88.52
There's something really funky about the content of this spreadsheet that dreadfully screws up the MATLAB detection process in trying to find out where the data are and creating an appropriate internal import object.
Without all the help given above, it can't figure out the first row are variable names and that the real data begins in the second row -- it skips down to row 7 -- I've noticed in the past that having some odds 'n ends over to the side out of the way of the main data can confuse things; it seems to have done so royally here.
I'd be tempted to suggest this one be sent to TMW as a sample that their 'bot ought to do a much better job on and as a template to figure out what caused it to lose its way so badly....that's a sidelight; it should NOT be that much grief to read the table -- generally detectimportOptions does do a pretty good job; but "not so much!" on this one. That's frustrating to the newcomer who doesn't yet know about all the innards that one can play with...
Anyways, now on to the problem at hand...
ix=(tT.AtomicNumber>=21)&(tT.AtomicNumber<=30);
hSc3=scatter3(tT.CED(ix),tT.R(ix),tT.E0(ix),'o','filled');
NNZ=nnz(ix);
C=jet(NNZ);
hSc3.CData=C;
xlabel(tT.Properties.VariableNames(5))
ylabel(tT.Properties.VariableNames(4))
zlabel(tT.Properties.VariableNames(3))
OK, now we're back to where was before got so rudely interrupted before, but with the actual full table and names...
scatter3 handle hSc3 is still just one object; so now we'll fix things up to be able to make a legend object to match. This again is a pretty common "hack" one has to do with handle graphics when the base objects don't match what one really wants to do...
%tT(ix,:)
hold on % don't let added stuff wipe out what we've already got drawn
hL=plot(nan(2,NNZ),'o'); % create an array of NNZ columns to force NNZ line handles
set(hL,{'MarkerFaceColor'},num2cell(C,2),{'MarkerEdgeColor'},num2cell(C,2)) % force the same color map
hLg=legend(hL,tT.Element(ix)); % and add the legend on those

8 Comments

Sorry, I'm not super familiar with coding and was just trying to find something that works well. I was trying other stuffs as well like readtable here, but the problem that I encountered is it removed a lot of the first column from the extracted table (13-22) is gone for some reason
This one looks more robust and easier to work with though
nevermind I figured the missing rows, just had to add numheaderlines
wow, that's amazing. I'm gonna need some time to break this down and understand exactly how each line works. Thank you!
I got interrupted again, before adding some additional comments/amplifications, sorry...
The key thing is that, unfortunately, TMW didn't think ahead in the design of the scatter class objects to provide an array of handles nor in legend if going that route on some of the plotting routines to allow it to hold more than just what is associated with the axes to allow for such customizations. It's just not very flexible; it does what it does nicely, but it's hard sometimes to get the effects wanted...
Anyways, the key feature is that plot and friends, creates the HG2 graphics objects when passed NaN as plotting values, but it quietly ignores them on rendering the plot; hence you can create additional graphics objects on an axes without them being visible precisely for such purposes as this. Sneaky and not intuitive, but it's what one does with HG2. "When in Rome..."
Another "feature" used here is that HG2 draws a single line for a vector which returns a single handle -- so if we were to try to use just NNZ points, we'd be stuck with the same problem we had with the scatter -- only one line handle; and it can't have but one color, regardless however many points there might be; they didn't provide a color array for a line object.
BUT, if you plot a matrix; the vectorized behavior of plot, like most MATLAB builtin functions, is to operate by column -- so, plotting an array of NNZ columns with at least two rows draws NNZ lines; exactly what we needed to label in the legend. Then, it's just setting the properties to match; the 'o' marker for a line isn't filled by default and there isn't a high-level 'filled' meta-property, so have to specifically set the face and edge colors. The set() syntax is one of general use to set multiple handle objects at one time; use a cell array to contain the 'Property' and value pairs; the num2cell is to turn the 2D array into a 10x1 cell array each containing the color RGB three-vector; set passes each cell member in turn to the associate member in the handle array.
As is often the case, the end amount of code required is pretty small and concise; it may take quite a long time fiddling around to get to the desired end result, however. It comes only with experience and time; it really is not something an apprenctice will figure out on their own without having seen the concept demonstrated somewhere first...
can you tell me exactly what nnz is? or does?
NNZ=nnz(ix);
It's the count of nonzero elements in the lookup vector; ergo, the number of lines needed to add to label in the legend to match the plotted number of points/elements selected.
BTW, the compound logic expression is one for which I've written a small utility function for to put the clutter out of the toplevel code...
function flg=iswithin(x,lo,hi)
% returns T for values within range of input
% SYNTAX:
% [log] = iswithin(x,lo,hi)
% returns T for x between lo and hi values, inclusive
flg= (x>=lo) & (x<=hi);
end
Then one can write
ix=iswithin(tT.AtomicNumber,21,30);
instead of
ix=(tT.AtomicNumber>=21)&(tT.AtomicNumber<=30);
oh wow, thank you! I'm actually wondering what I'd need to do if I want to select multiple ranges because right now I can't do == for some reason.
Like what do I need to do If I want to select at# 21-23,49-52 and 82-83 at the same time? because right now I can't move vertically in the periodic table.
Thanks a lot! I'm learning
"...what do I need to do If I want to select at# 21-23,49-52 and 82-83 at the same time?"
The higher level comes in very handy there to reduce clutter...
ix=iswithin(tT.AtomicNumber,21,30)|iswithin(tT.AtomicNumber,49,52)|iswithin(tT.AtomicNumber,82,83);
Nota Bene the "or" operator, not "and". And, of course, you shouldn't write such code burying "magic numbers" inside the code itself, but use a set of low/high range arrays and set those instead. Then you can prompt the user for values desired and not change the code at all. Separate the logic from the data.
The problem with the above solution, however, is that it requires explicit code for the number of disparate selections. That's awfully inconvenient to code around; that solution works well only if the number of groups to be selected from is known a priori and doesn't change.
Alternatively, for disparate sets such as this, you can use some of the other MATLAB set operation functions or ismember
R=[21,30; 49,52; 82,83]; % set ranges desired in array by row, start-stop
rnge=cell2mat(arrayfun(@(i1,i2)i1:i2,R(:,1),R(:,2),'uni',0).'); % convert to a list of wanted values
ix=ismember(tT.AtomicNumber,rnge); % look 'em up in your table.
is one way. See intersect and friends as well...

Sign in to comment.

More Answers (0)

Categories

Find more on Creating, Deleting, and Querying Graphics Objects in Help Center and File Exchange

Products

Release

R2023a

Asked:

on 28 Apr 2023

Edited:

dpb
on 29 Apr 2023

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!