plotly: highlight (dim), rather than filter, when clicking on point in legend - event-handling

I am building plotly figures with R. The figures have legends. Each legend has a colored point that represents a level of the data. Here is a minimal example:
library(plotly)
data(iris)
plot_ly(
x = ~Petal.Length, y = ~Petal.Width,
color = ~Species,
data = iris)
By default, double-clicking on a point in the legend completely hides all unrelated points. For example, double-clicking on the "versicolor" point in the legend hides all "setosa" and "virginica" points in the plot. In plotly argot, it "filters" the data in the plot.
But I would rather that clicking on a point in the legend highlight points in the plot. For example, I would like clicking (or double-clicking) on the versicolor point in the legend to dim the "setosa" and "virginica" points in the plot, perhaps by reducing their opacity. The versicolor points in the plot would then be "highlighted." Can this behavior be implemented?
I've read through the plotly documentation and searched SO and the plotly forums for related questions. That search suggests two potential solutions, but they seem rather complicated:
Write a custom "click event" function in JS. https://plotly.com/javascript/plotlyjs-events/#legend-click-events seems to suggest that this approach can work. I don't know whether I can implement this approach from R.
Disable the default legend (showlegend = FALSE), then create a new legend by adding traces that have customized click events.
Are these the best approaches? If they are, and if more than one is workable, which one should I pursue?
Other notes: I'm not using Shiny. And I know about the itemclick and itemdoubleclick legend attributes, and about highlight_key(), but they don't seem relevant. (Please correct me if I'm wrong.)

The key is to disable legend-click events in R, and then to write a custom event handler that changes the figure when plotly_legendclick is triggered. Here is an example:
library(plotly)
data(iris)
myPlot <- plot_ly(
x = ~Petal.Length, y = ~Petal.Width,
color = ~Species,
data = iris)
# Disable default click-on-legend behavior
myPlot <- layout(
myPlot,
legend = list(itemclick = FALSE, itemdoubleclick = FALSE))
# Add an event handler
onRender(
myPlot,
"function (el) {
const OPACITY_START = el._fullData[0].marker.opacity;
const OPACITY_DIM = 0.3;
el.on('plotly_legendclick', function (data) {
// Get current opacity. The legend bubble on which we click is given by
// data.curveNumber: data.curveNumber == 0 means that we clicked on the
// first bubble, 1 means that we clicked on the second bubble, and so on.
var currentOpacity = data.fullData[data.curveNumber].marker.opacity
if (currentOpacity < OPACITY_START) { // if points already dimmed
var update = { 'marker.opacity' : OPACITY_START }; // could also set to null
} else { // if points not already dimmed
var update = { 'marker.opacity' : OPACITY_DIM };
}
Plotly.restyle(el, update, data.curveNumber);
} );
}"
)
When a user clicks on a legend bubble, this code toggles the corresponding bubbles in the plot between "normal" and "dim" states.
To instead toggle the other bubbles in the plot -- that is, to modify the other traces -- a small modification will be needed. In particular, data.curveNumber is always the number of the trace that corresponds to the clicked bubble in the legend. To instead modify the other traces, we need to pass the other trace numbers to Plotly.restyle(), not the trace that is indexed by data.curveNumber. See the Plotly.restyle() documentation for more on this point.

Related

How do I add a units label to a ILNumerics Colorbar

I would like to display the units in text for the values displayed on the colorbar. I have a colorbar added to my ILSurface and I'd like to show my units in text on the color bar along with the range.
Edit: I want to display text at the bottom of the color bar below the bottom tick just the one label.
I was able to get this to work this way
new ILColorbar()
{
Children = { new ILLabel("nm") {Position = new Vector3(.2f,.98f,0) } }
}
I have to say the Position coordinates are not very intuitive. I had to basically adjust the numbers by trial and error until it fit. I knew that the values range 0..1 so the X value was 1 at the bottom but I wanted it up from the border. And the Y value would need to be indented in some but I wasn't sure what was a good value but .2 works.
You can access the axis of ILColorbar and configure it in the usual way. Use the LabelTransformFunc on the ticks to set your own label text. You can use the default transform func and add your unit string.
Example:
var cb = scene.First<ILColorbar>();
cb.Axis.Ticks.LabelTransformFunc = (ind, val) =>
{
return ILTickCollection.DefaultLabelTransformFunc(ind, val) + "nm";
};
You can read more about the axis configuration here:
Axis Configuration
LabelTransformFunc in ApiDoc
Edit:
If only one label is needed, then add a new ILLabel object in ILColorbar group as follows:
new ILColorbar() {
new ILLabel("z(nm)") {
Position = new Vector3(0.5,1,0),
Anchor = new PointF(0.5f,0)
}
}
The ILColorbar area have the relative coordinates 0..1 over the width and the height of the color bar. So we set position x in the middle of the ILColorbar, and position y at the bottom.
The Anchor position is used as relative position in relation to the Position point.
ILLabel Documentation

Simultaneous interaction with 2 figures in MATLAB GUI

I am writing a GUI in MATLAB (guide) where user will be shown 2 images(both images are positioned side by side in single gui window) from a series of images (but each drifted little bit) and will be allowed to select area of interest.
I want user to select working are in image 1 while simultaneously highlighting the selected area in image 2, so that it is easier to judge whether the feature of interest has drifted out of selected area or not. How to do that?
I am using following answer to select and crop area of interest(just FYI):
crop image with fixed x/y ratio
Here is a way to do it using imrect and its addNewPositionCallback method. Check here for a list of available methods.
In the following figure I create 2 axes. On the left that's the original image and on the right that's the "modified" image. By pressing the pushbutton, imrect is called and the addNewPositionCallback method executes a function, called GetROIPosition that is used to get the position of the rectangle defined by imrect. At the same time, in the 2nd axes, a rectangle is drawn with the same position as that in the 1st axes. To be even more fancy you can use the setConstrainedPosition to force the rectangle to be enclosed in a given axes. I'll let you do it :)
Here is the whole code with 2 screenshots:
function SelectROIs(~)
%clc
clear
close all
%//=========================
%// Create GUI components
hfigure = figure('Position',[300 300 900 600],'Units','Pixels');
handles.axesIm1 = axes('Units','Pixels','Position',[30,100,400 400],'XTick',[],'YTIck',[]);
handles.axesIm2 = axes('Units','Pixels','Position',[460,100,400,400],'XTick',[],'YTIck',[]);
handles.TextaxesIm1 = uicontrol('Style','Text','Position',[190 480 110 20],'String','Original image','FontSize',14);
handles.TextaxesIm2 = uicontrol('Style','Text','Position',[620 480 110 20],'String','Modified image','FontSize',14);
%// Create pushbutton and its callback
handles.SelectROIColoring_pushbutton = uicontrol('Style','pushbutton','Position',[380 500 120 30],'String','Select ROI','FontSize',14,'Callback',#(s,e) SelectROIListCallback);
%// ================================
%/ Read image and create 2nd image by taking median filter
handles.Im = imread('coins.png');
[Height,Width,~] = size(handles.Im);
handles.ModifIm = medfilt2(handles.Im,[3 3]);
imshow(handles.Im,'InitialMagnification','fit','parent',handles.axesIm1);
imshow(handles.ModifIm,'InitialMagnification','fit','parent',handles.axesIm2);
guidata(hfigure,handles);
%%
%// Pushbutton's callback. Create a draggable rectangle in the 1st axes and
%a rectangle in the 2nd axes. Using the addNewPositionCallback method of
%imrect, you can get the position in real time and update that of the
%rectangle.
function SelectROIListCallback(~)
hfindROI = findobj(handles.axesIm1,'Type','imrect');
delete(hfindROI);
hROI = imrect(handles.axesIm1,[Width/4 Height/4 Width/2 Height/2]); % Arbitrary size for initial centered ROI.
axes(handles.axesIm2)
rectangle('Position',[Width/4 Height/4 Width/2 Height/2],'EdgeColor','y','LineWidth',2);
id = addNewPositionCallback(hROI,#(s,e) GetROIPosition(hROI));
end
%// Function to fetch current position of the moving rectangle.
function ROIPos = GetROIPosition(hROI)
ROIPos = round(getPosition(hROI));
axes(handles.axesIm2)
hRect = findobj('Type','rectangle');
delete(hRect)
rectangle('Position',ROIPos,'EdgeColor','y','LineWidth',2);
end
end
The figure after pressing the button:
And after moving the rectangle around:
Yay! Hope that helps! Nota that since you're using GUIDE the syntax of the callbacks will look a bit different but the idea is exactly the same.

Drawing a resizeable box on an image

I'm working on a gui and using GUIDE. It loads and image and has the user draw an ROI around a point (the particle ROI). I would then like to have two sliders for creating a second ROI (the Scan ROI) where the user can use sliders to set the width and height of the second roi and see it updated on the image. The sliders seem to work ok but my gui keeps drawing a new roi on top of the image so it gets messy looking really fast. I would like to remove the user sizeable roi from the image before redrawing it (while still keeping the original particle ROI on the image. I currently do it the following way :
Inside the callback for the setroi size button (this should be for the particel ROI)
handles=guidata(hObject);
particleroiSize=imrect;% - draw a rectagle around the particle to get a meausr eof ROI size
roiPoints=getPosition(particleroiSize); %-get tha parameters fo the rectanlge
partX1 = round(roiPoints(1));
partY1 = round(roiPoints(2));
partX2 = round(partX1 + roiPoints(3));
partY2 = round(partY1 + roiPoints(4)); % these are the ROi positions in pixels
roiHeight = round(roiPoints(3)); % - these are just the ROI width and height
roiWidth = round(roiPoints(4));
handles=guidata(hObject); %_ update all the handles...
handles.partX1=partX1;
handles.partX2=partX2;
handles.partY1=partY1;
handles.partY2=partY2;
handles.roicenterX = (partX1 + round(roiPoints(3))/2);
handles.roicenterY= (partY1 + round(roiPoints(4))/2);
handles.roiHeight = roiHeight;
handles.roiWidth = roiWidth;
current_slice = round(get(handles.Image_Slider,'Value'));
particleImage=handles.Image_Sequence_Data(partY1:partY2,partX1:partX2,current_slice);
handles.particleImage=particleImage;
set(handles.RoiSizeDisplay,'String',strcat('Particle ROI is ',' ',num2str(roiHeight),' ', ' by ',num2str(roiWidth)) );
guidata(hObject,handles);
And then inside the call back for the sliders that set the Scan ROI size I have (this is inside two different sliders one adjusts the width and one the height :
handles=guidata(hObject);
try
delete(handles.ScanArea);
% plus any cleanup code you want
catch
end
WidthValue = get(handles.ScanAreaSliderWidth,'value');
HeightValue = get(handles.ScanAreaSliderHeight,'value');
set(handles.ScanAreaWidthDisplay,'String',strcat('Scan Area Width is ',' ', num2str(WidthValue))); % sets the display..now to do the drawing...
%h = imrect(hparent, position);
%position = [Xmin Ymin Width Heigth];
position = [ round(handles.roicenterX-WidthValue/2) round(handles.roicenterY-HeightValue/2) WidthValue HeightValue];
handles.ScanArea = imrect(handles.Image_Sequence_Plot,position);
%h = imrect(hparent, position)
handles=guidata(hObject);
guidata(hObject, handles);
But it never deletes the scan area ROI and keeps redrawign over it..I thought the try...catch would work but it doens't seem to. Am I making extra copies of the ROI or something? Please help..
Thanks.
If you need to delete the ROI drawn with imrect, you can use findobj to look for rectangle objects (which are of type "hggroup") and delete them:
hfindROI = findobj(gca,'Type','hggroup');
delete(hfindROI)
and that should do it. Since you first draw particleroiSize, which is of the hggroup type as well, you might not want to delete all the outputs from the call to findobj. If there are multiple rectangles in your current axis, then hfindROI will contain multiple entries. As such you might want to delete all of them but the first one, which corresponds to particleroiSize.
I hope i got your question right. If not please ask for clarifications!
Thanks. This worked perfectly except that I had to use
hfindROI = findobj(handles.Image_Sequence_Plot,'Type','hggroup');
delete(hfindROI(1:end-1))
to get rid of everything but the first ROI, so I guessteh hggoup objects are added at the start ? (I thought I would use deleted(hfindROI(2:end)) to delete all but the first. Also, why does hfindROI return a list of numbers? Do they represent the hggroup objects or something like that?
thanks..

Adding a slider removes the toolbar

I am facing a strange behavior on Matlab. I want to plot a simple surface in 3D with a variable parameter.
Simple example :
param = 0.5;
[x,y]=meshgrid(linspace(-1,1,10),linspace(-1,1,10));
z = param*x./y;
surf(x,y,z);
I get a classic 3D picture : http://i.imgur.com/2KrnWeH.png
I am now trying to add a slider on my figure to directly control the value of parameter :
function test()
figHandle=figure;
param = 0.5;
[x,y]=meshgrid(linspace(-1,1,10),linspace(-1,1,10));
z = param*x./y;
surfacePlotted = surf(x,y,z);
sliderPosition=[10 400 200 20];
hsl = uicontrol('Style','slider','Min',-2,'Max',2,'SliderStep',[1 1]./(10),'Value',param,'Position',sliderPosition,'Callback',{#updatePlot,surfacePlotted});
end
function updatePlot(hObject,~,eventdata)
surfacePlotted=eventdata;
param = get(hObject,'Value');
x=get(surfacePlotted,'XData');
y=get(surfacePlotted,'YData');
z = param*x./y;
set(surfacePlotted,'ZData',z);
end
I get a nice slider, and I can click on it to modify the parameter : http://i.imgur.com/TqkMSmH.png
i.imgur.com/i9xablF.png (second picture with an other position for the slide
However the main menu bar is not here anymore, especially the "Pan" icon which allow me to manipulate in 3D the figure.
I tried to add it manually after my slider definition
uicontrol('MenuBar','figure');
and I also tried :
set(figHandle, 'MenuBar', 'figure');
without success so far.
Anyone already encoutered this issue and found a work around ? Or Am I simply missing something easy ?
Thanks :)
Edit : Edited picture links, I put the one to manage them
It is addressed here in Yair's website (undocumentedmatlab):
uicontrol side-effect: removing figure toolbar
You only need to add this line into your code:
set(figHandle,'toolbar','figure');

Annotations on a JFreeChart Pie Chart

Specifically I am looking to add text annotations to specific locations to a JFreeChart that is being output to a png file for web use. Can/how do annotations get added to pie charts. I have been able to successfully add annotations to XYPlots, but don't know how to overlay or add one to a PiePlot.
My full task is to use the PiePlot to create a sort of clock. So far everything has worked great, but now I need to add labels at the 12, 3, 6, and 9 o'clock locations and completely stumped.
Adam
Bit of an old question, but here's how I did something similar (annotation at 1, 2, 3, ... o'clock positions) using a polar plot. It uses a ChoiceFormatter and the NumberTickUnit:
final JFreeChart chart = ChartFactory.createPolarChart(
"HAPI Hourly Usage (UTC)", ds, true, true, false);
final PolarPlot plot = (PolarPlot) chart.getPlot();
// Create a ChoiceFormat to map the degrees to clock positions
double[] limits = new double[12];
String[] formats = new String[12];
limits[0] = 0;
formats[0] = "12";
// degrees = 360/12
for (int i = 1; i < limits.length; i++) {
limits[i] = degrees * (i);
formats[i] = Integer.toString(i);
}
ChoiceFormat clock = new ChoiceFormat(limits, formats);
TickUnit tickUnit = new NumberTickUnit(degrees, clock);
// now configure the plot
plot.setAngleTickUnit(tickUnit); // sets the ticks
plot.setAngleLabelsVisible(true); // makes the labels visible
plot.setAngleLabelPaint(Color.LIGHT_GRAY); // user choice
plot.setAngleGridlinesVisible(true); // must set this to display the
// labels
plot.setAngleGridlinePaint(Color.BLACK); // plot's background color
// (makes lines invisible)
plot.setRadiusGridlinesVisible(false); //turn off the radius value circles
ValueAxis axis = plot.getAxis();
axis.setTickLabelsVisible(false); //turn off the radius value labels
winds up looking like http://img522.imageshack.us/img522/6693/hapihours.jpg
After a fairly strenuous search I don't believe this is currently possible (JFreeChart 1.0.13).
Possible options are:
Create a second chart with an XYPlot to generate a second image with needed annotations. Overlay this image on the page. This option is bad because it doubles the number of chart images to be uploaded.
Set the image as a background on the page and HTML the text over the image. Bad because it isn't maintainable and makes a headache of data transfer.
Personally I am just going to find another way to communicate the information in the title, but I wanted to post my findings for the next person.
Adam