Hi I'm looking for a clean way to produce a slider in matlab that allows me to adjust both ends of the range. Rather than dragging a single value I would like to be able to control the end points of the slider.
I can accomplish this using two sliders with Matlab but am wondering if there is any way that this can be combined into a single control
Example showing slider that I would like.
http://demos.jquerymobile.com/1.3.0-rc.1/docs/demos/widgets/sliders/rangeslider.html
This can be accomplished through the use of a third party java jar using Swing along with Matlabs handle and javacomponent functions. I've adapted this from the JIDE Common Layer (Open Source)
Apparently Matlab can use standard Swing components. This is discussed on Undocumented Matlab.
You will end up with a slider that looks like so:
Here is the code:
function [hcomponent, hcontainer] = createSlider
% Add the 3rd Party Jar, should use static path but for the example, we
% use dynamic
javaaddpath('C:\PathToJars\jide_demo.jar')
import com.jidesoft.plaf.LookAndFeelFactory;
import com.jidesoft.swing.JideButton;
import com.jidesoft.swing.JideSwingUtilities;
import com.jidesoft.swing.RangeSlider;
import com.jidesoft.swing.SelectAllUtils;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
minField = JTextField();
maxField = JTextField();
SelectAllUtils.install(minField);
SelectAllUtils.install(maxField);
rangeSlider = RangeSlider(-100, 100, -100, 100);
rangeSlider.setPaintTicks(true);
rangeSlider.setPaintLabels(true);
rangeSlider.setPaintTrack(true);
rangeSlider.setRangeDraggable(false);
rangeSlider.setMajorTickSpacing(25);
rangeSlider = handle(rangeSlider, 'CallbackProperties');
function updateValues(~, ~)
minField.setText(num2str(rangeSlider.getLowValue()));
maxField.setText(num2str(rangeSlider.getHighValue()));
end
rangeSlider.StateChangedCallback = #updateValues;
minField.setText(num2str(rangeSlider.getLowValue()));
maxField.setText(num2str(rangeSlider.getHighValue()));
minPanel = JPanel(BorderLayout());
minPanel.add(JLabel('Min'), BorderLayout.BEFORE_FIRST_LINE);
minField.setEditable(false);
minPanel.add(minField);
maxPanel = JPanel(BorderLayout());
maxPanel.add(JLabel('Max', SwingConstants.TRAILING), BorderLayout.BEFORE_FIRST_LINE);
maxField.setEditable(false);
maxPanel.add(maxField);
textFieldPanel = JPanel(GridLayout(1, 3));
textFieldPanel.add(minPanel);
textFieldPanel.add(JPanel());
textFieldPanel.add(maxPanel);
panel = JPanel(BorderLayout());
panel.add(rangeSlider, BorderLayout.CENTER);
panel.add(textFieldPanel, BorderLayout.AFTER_LAST_LINE);
% hcontainer can be used to interact with panel like uicontrol
[hcomponent, hcontainer] = javacomponent(panel, [50, 50, 200, 100], gcf);
end
First you need to create edit boxes on either side of your slider. Assuming that f is the handle for the figure and h is the handle for the slider you can do something like this:
endval1 = uicontrol('Parent',f,'Style','Edit','Position',[180,10,200,23],'String','Your number here', 'Callback', #(src,event)callback(src,event,h));
Of course change the position to what suits your needs and create two of them for top and bottom.
Notice how I have a callback with the following #(src,event)callback(src,event,h). Here I'm passing in the handle for the slider (h) so I can modify it when I need.
Now for the callback:
function callback(src, event, h)
n = num2str(src.String);
set(h, 'Min', n);
end
That's an example of a callback. You will need another for the other end of course. Keep in mind that if your new min value is larger than either the current slider value or max value, it will not render until the remaining two are updated accordingly. The same of course applies to the max value.
Hope this helps.
Related
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.
I would like to add labels to some points plotted using the command scatter. For the sake of simplicity, let's say I have only one point:
x = 10;
pointSize = 100;
fontSize = 20;
P = scatter(x, 0, pointSize, [0,0,0], 'filled');
text(x, 0, 'pointLabel',...
'HorizontalAlignment', 'center',...
'VerticalAlignment', 'bottom',...
'FontSize', fontSize);
The problem with the previous commands is that the text pointLabel overlaps with the point P depending on the values assigned to the properties pointsize and fontSize.
I have read the documentation of the text command, but the examples only show how to put a label horizontally aligned with a specific point in the diagram. If the alignment needs to be horizontal it is easy, but I could not find a general way to compute the y coordinate of the label pointLabel from the values of the other dimensions.
Clearly I can reach a good alignment by testing various combinations of values, but I am looking for a general solution.
Is there anyone who can help me?
This assumes you are using >=R2014b, though it can also be accomplished in older versions using set and get commands.
When a text object is created, its default units are data coordinates, but those can be changed. In your case, I'd go with points.
x = 10;
pointSize = 100;
fontSize = 20;
P = scatter(x, 0, pointSize, [0,0,0], 'filled');
t = text(x, 0, 'pointLabel',...
'HorizontalAlignment', 'center',...
'VerticalAlignment', 'bottom',...
'FontSize', fontSize);
% It's always a good idea to switch back to the default units, so remember them.
originalUnits = t.Units;
t.Units = 'points';
% Shift the text up by the sqrt(pi)/2 times the radius of the point
t.Position(2) = t.Position(2) + sqrt(pointSize)/2;
t.Units = originalUnits;
Check out Text Properties for more info. If you want to get really sophisticated, you can use the read-only property Extent and your known marker size and position to calculate when a label is overlapping one of your points. Since the default unit is in data space, no conversions are necessary.
If you're working with an older version of MATLAB, all of these options and properties are still available, you just have to work a little harder to use them. For instance, you can't direction set the position as above, but you would instead use get to assign it to a temporary variable, change it, and then use set to update. More lines of code, but ultimately the same effect.
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.
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');
As the title says I am using the GUIDE toolbox in Matlab and I would firstly like to know how I can display the left/right arrows at either end of the slider?
Also how can I get the slider to automatically move every 1 second?
As far as I understand it I need to first create a timer object and set the execution mode and period as follows:
time = timer;
set(time,'executionMode','fixedRate','period',1);
Now I know I need to set the timerFcn to something like:
set(handles.slider1,'Value',x);
in order to change the position of the slider.
Also I understand I need to increment the x variable first by the slider step which in my case is 0.00520833. For example:
x = x + 0.00520833;
So I have some code as follows:
time = timer;
set(time,'executionMode','fixedRate','period',1);
time.timerFcn = set(handles.slider1,'Value', x = x + 0.00520833);
start(time);
However this doesn't work, and i'm sure it's because of something stupid that I am doing.
Thanks!
EDIT:
Now I can move the slider every second but what I would like to do is run a function of my own every second instead. For example:
time.timerFcn = #slider_increment;
function slider_increment
set(handles.slider1,'Value', get(handles.slider1,'Value') + 0.00520833)
slider = get(handles.slider1,'Value');
set(handles.text4,'String', slider);
I know this is a little messy but I will sort that later. The problem i'm facing is how to declare my own function inside the GUI script created by guide, and allow the function to access the handles to the GUI objects.
First, this
time.timerFcn = set(handles.slider1,'Value', x = x + 0.00520833);
Definitely produces an error...
I think you want something like this:
h = uicontrol;
time = timer;
set(time,'executionMode','fixedRate','period',1);
%Note: set(h,val,get(h,val) + change)
time.timerFcn = #(x,y)set(h,'position', get(h,'position') + 10);
start(time);