Asked by Koustubh Gohad
on 13 Nov 2019 at 21:36

I have a simple program to do the following.:

- Create a scatter plot of X and Y data.
- Highlight 3 points and fit a straight line through them. The user should be able to click on the figure and select any three points he or she wants to fit a line through.
- Calculate the X intercept of the fitted line.

I'm trying to do this using object oriented programming and hence have a simple data object which has these properties:

- X data
- Y data
- Number of points used for doing the line fit (3 in this case)
- Inital input of the interpolation (fit) points mentioned in 3
- X intercept (this is calculated in the constructor function using the inital fit points).

I can update the figure to display the latest 3 points selected and show the line fitted thorugh them, but my object (or instance of the object) does not get updated in the workspace with the latest interpolation points and the X interecept for the line fitted using those points.

Here is my object class definition:

classdef data_object

properties

x_data

y_data

num_points_linear_fit

interp_points

x_intercept

end

methods

function new_data_object = data_object(x_data, y_data, num_points_linear_fit, interp_points)

if nargin == 4

new_data_object.x_data = x_data;

new_data_object.y_data = y_data;

new_data_object.num_points_linear_fit = num_points_linear_fit;

new_data_object.interp_points = interp_points;

new_data_object.x_intercept = calc_x_intercept(new_data_object);

end

end

function calculated_x_intercept = calc_x_intercept(obj)

p = polyfit(obj.interp_points(:, 1), obj.interp_points(:, 2), 1);

calculated_x_intercept = -p(2)/p(1);

end

function obj = graphical_interface(obj)

X = obj.x_data;

Y = obj.y_data;

xy = obj.interp_points;

num_fit_points = obj.num_points_linear_fit;

num_data_points = numel(X);

f = figure; hold on; box on

points = gobjects(num_data_points, 1);

for I = 1:num_data_points

points(I) = plot(X(I), Y(I), 'bo', 'MarkerSize', 13);

end; clear I

ax = gca;

set(f, 'HitTest', 'on')

set(ax, 'HitTest', 'on')

set(points,'ButtonDownFcn',@(src,eventdata)getpoints(src, eventdata, obj),...

'PickableParts','all')

pp1 = plot(xy(:, 1), xy(:, 2), 'ro', 'MarkerSize', 8, 'MarkerFaceColor', 'r');

p = polyfit(xy(:, 1), xy(:, 2), 1);

temp = min(xy(:, 1)) : 0.01 : max(xy(:, 1));

pp2 = plot(temp, polyval(p, temp), 'r');

function getpoints(src, ~,~)

cp_x = get(src, 'xdata');

cp_y = get(src, 'ydata');

xy = [[cp_x, cp_y]; xy];

if size(xy, 1) > num_fit_points

xy = xy(1:num_fit_points, :);

end

delete(pp1)

pp1 = plot(xy(:, 1), xy(:, 2), 'ro', 'MarkerSize', 8, 'MarkerFaceColor', 'r');

if size(xy, 1) > 2

p = polyfit(xy(:, 1), xy(:, 2), 1);

obj.interp_points = xy;

obj.x_intercept = -p(2)/p(1);

temp = min(xy(:, 1)) : 0.01 : max(xy(:, 1));

if isempty(pp2)

pp2 = plot(temp, polyval(p, temp), 'r');

set(pp2, 'HitTest', 'off')

else

delete(pp2)

pp2 = plot(temp, polyval(p, temp), 'r');

set(pp2, 'HitTest', 'off')

end

end

end

end

end

end

This is how I call it to test:

X = (1:10)';

Y = rand(10, 1);

interp_points = [X([3, 4, 5]), Y([3, 4, 5])];

num_points_linear_fit = 3;

a = data_object(X, Y, num_points_linear_fit, interp_points);

graphical_interface(a);

I need my interpolation points and X intercept to update with every click (when new interpolation points are selected).

Any help would be greatly appreciated!

PS the 'interpolation points' should be called 'fit points'. Sorry for the confusion.

Thanks,

K

Answer by Guillaume
on 14 Nov 2019 at 14:54

Accepted Answer

As Thomas said, the kind of class you're developing would works better as a handle class.

Note that if you derive from handle you should also change the function signature of graphical_interface. It no longer needs to return obj (that's a value class pattern), so:

function graphical_interface(obj)

%... rest of the code unchanged.

end

While we're at it I'd change the code of the constructor slightly. As it is, if the user call the constructor with 1, 2, or 3 arguments, these arguments are discarded and every properties is set to 0. I would either error in that case or set the first 1, 2, or 3 properties to the value that have been passed.

Finally, the design of your callback getpoints is very odd. You create an anonymous function in order to pass the object to the callback in addition to the standard source and event arguments but your callback function completely ignores the object (because it doesn't need it as input as it's a nested function), so why did you bother with the anonymous function?

You should either create the ButtonDwn callback with:

set(points,'ButtonDownFcn',@getpoints,...

'PickableParts','all')

and have the callback signature as the nested function:

function getpoints(src, ~) %discard the event args as not needed

Or make the callback function an object method (non-nested) with signature:

function getpoints(obj, src)

and use it with:

set(points,'ButtonDownFcn',@(src, ~)getpoints(obj, src),...

'PickableParts','all')

But don't mix two different design patterns.

Koustubh Gohad
on 14 Nov 2019 at 20:57

All my experience has been procedural till now, this is the first time I'm using OOP.

Your input is much appreciated!

Sign in to comment.

Opportunities for recent engineering grads.

Apply Today
## 2 Comments

## Thomas Satterly (view profile)

## Direct link to this comment

https://fr.mathworks.com/matlabcentral/answers/490959-help-with-simple-oop-program#comment_767069

## Koustubh Gohad (view profile)

## Direct link to this comment

https://fr.mathworks.com/matlabcentral/answers/490959-help-with-simple-oop-program#comment_767272

Sign in to comment.