From bfb9c216fbad5b4e93536f2f4002a1edf03e98f1 Mon Sep 17 00:00:00 2001 From: Hannah White Date: Mon, 5 Nov 2018 16:37:41 -0500 Subject: [PATCH 01/15] turns visibility for intermediate plots off --- Plot.m | 2 +- checkPlots.m | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Plot.m b/Plot.m index 5e2471b..5bcfb9a 100644 --- a/Plot.m +++ b/Plot.m @@ -137,7 +137,7 @@ this.Limits = round([pHandle.XLim, pHandle.YLim, pHandle.ZLim], ... Plot.ROUNDOFF_ERROR); - tmp = figure(); + tmp = figure('Visible','off'); par = pHandle.Parent; pHandle.Parent = tmp; imgstruct = getframe(tmp); diff --git a/checkPlots.m b/checkPlots.m index 954d28c..93169a1 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -59,6 +59,7 @@ % run solution function try + figure('Visible','off') soln(varargin{:}); catch e warning(e.message); @@ -72,6 +73,7 @@ close('all', 'force'); % run student function and collect PLOT try + figure('Visible','off') fun(varargin{:}); catch e warning(e.message); @@ -503,8 +505,10 @@ % for the subplot checking pHandles = findobj(0, 'type', 'axes'); if numel(pHandles) ~= 0 + figure('Visible','off') plots(numel(pHandles)) = Plot(pHandles(end)); for i = 1:(numel(pHandles) - 1) + figure('Visible','off') plots(i) = Plot(pHandles(i)); end else From 6d1354f85f75840739363f862519a70c85d2bbe1 Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Sat, 10 Nov 2018 11:50:38 -0500 Subject: [PATCH 02/15] Add new Plot Feedback engine --- Plot.m | 420 ++++++++++++++++++++++++-------------- Point.m | 146 ++++++++++++++ Segment.m | 193 ++++++++++++++++++ checkPlots.m | 558 ++++++++++++++++++++++----------------------------- 4 files changed, 854 insertions(+), 463 deletions(-) create mode 100644 Point.m create mode 100644 Segment.m diff --git a/Plot.m b/Plot.m index 5bcfb9a..683e888 100644 --- a/Plot.m +++ b/Plot.m @@ -57,14 +57,12 @@ Points; Segments; Limits; + isAlien logical = false; end properties (Constant) POSITION_MARGIN = 0.05; ROUNDOFF_ERROR = 5; end - properties (Access=public) - isAlien logical = false; - end methods function this = Plot(pHandle) %% Constructor @@ -79,7 +77,7 @@ % % This class takes in student plot information and compares it with % the solution plot information to return feedback for each - % student. + % Plot. % % If the plot does not have a title, xlabel, ylabel, or zlabel, the % appropriate field will contain an empty string. @@ -126,10 +124,26 @@ 'Given input to Plot Constructor is not Axes Handle'); throw(ME); end - this.Title = pHandle.Title.String; - this.XLabel = pHandle.XLabel.String; - this.YLabel = pHandle.YLabel.String; - this.ZLabel = pHandle.ZLabel.String; + if iscell(pHandle.Title.String) + this.Title = strjoin(pHandle.Title.String, newline); + else + this.Title = pHandle.Title.String; + end + if iscell(pHandle.XLabel.String) + this.XLabel = strjoin(pHandle.XLabel.String, newline); + else + this.XLabel = pHandle.XLabel.String; + end + if iscell(pHandle.YLabel.String) + this.YLabel = strjoin(pHandle.YLabel.String, newline); + else + this.YLabel = pHandle.YLabel.String; + end + if iscell(pHandle.ZLabel.String) + this.ZLabel = strjoin(pHandle.ZLabel.String, newline); + else + this.ZLabel = pHandle.ZLabel.String; + end this.Position = round(pHandle.Position, ... Plot.ROUNDOFF_ERROR); this.PlotBox = round(pHandle.PlotBoxAspectRatio, ... @@ -137,7 +151,7 @@ this.Limits = round([pHandle.XLim, pHandle.YLim, pHandle.ZLim], ... Plot.ROUNDOFF_ERROR); - tmp = figure('Visible','off'); + tmp = figure('Visible', 'off'); par = pHandle.Parent; pHandle.Parent = tmp; imgstruct = getframe(tmp); @@ -149,8 +163,10 @@ lines = allchild(pHandle); if isempty(lines) - this.Points = []; - this.Segments = []; + tmp = Point(); + this.Points = tmp(false); + tmp = Segment(); + this.Segments = tmp(false); return; end for i = length(lines):-1:1 @@ -159,6 +175,13 @@ this.isAlien = true; end end + if isempty(lines) + tmp = Point(); + this.Points = tmp(false); + tmp = Segment(); + this.Segments = tmp(false); + return; + end xcell = {lines.XData}; ycell = {lines.YData}; zcell = {lines.ZData}; @@ -173,10 +196,10 @@ xdata = xcell{i}; ydata = ycell{i}; zdata = zcell{i}; - points = max([length(xdata), length(ydata), length(zdata)]); % number of data points - xNaN = false(1,points); - yNaN = false(1,points); - zNaN = false(1,points); + pts = max([length(xdata), length(ydata), length(zdata)]); % number of data points + xNaN = false(1,pts); + yNaN = false(1,pts); + zNaN = false(1,pts); if ~isempty(xdata) xNaN = isnan(xdata); end @@ -198,7 +221,6 @@ end end - legend = {lines.DisplayName}; color = {lines.Color}; marker = {lines.Marker}; marker(strcmp(marker, 'none')) = {''}; @@ -227,36 +249,22 @@ end segments = cell(1, totalSegs); segmentColors = cell(size(segments)); - segmentMarkers = cell(size(segments)); segmentStyles = cell(size(segments)); - segmentLegends = cell(size(segments)); counter = 1; for i = 1:length(xcell) if ~isempty(linestyle{i}) tmp = line2segments(xcell{i}, ycell{i}, zcell{i}); segments(counter:(counter+length(tmp)-1)) = tmp; segmentColors(counter:(counter+length(tmp)-1)) = color(i); - segmentMarkers(counter:(counter+length(tmp)-1)) = marker(i); segmentStyles(counter:(counter+length(tmp)-1)) = linestyle(i); - segmentLegends(counter:(counter+length(tmp)-1)) = legend(i); counter = counter + length(tmp); end end - % Find Uniqueness: - % for each one, iterate over others; each one, if equal, - % delete. - c = 1; - while c <= length(segments) - % iterate over rest of segments - seg = segments(c); - for j = (c+1):length(segments) - if isequal(seg, segments(j)) - segments(j) = []; - break; - end - end - c = c + 1; - end + % get rid of empty extra + segments((counter):end) = []; + segmentColors((counter):end) = []; + segmentStyles((counter):end) = []; + % % Sorting this would make comparison faster - but would the % sorting actually be slower than just comparing unsorted? @@ -264,13 +272,18 @@ % make it faster. So our sort algorithm doesn't actually have % to be fully unique, so just sorting by X values should be % good enough, while still being quite spritely - - this.Segments = struct('Segment', segments, ... - 'Color', segmentColors, ... - 'Marker', segmentMarkers, ... - 'LineStyle', segmentStyles, ... - 'Legend', segmentLegends); - segXPts = arrayfun(@(s)(s.Segment{1}(1)), this.Segments); + for s = numel(segments):-1:1 + segs(s) = Segment(segments{s}{:}, ... + segmentColors{s}, ... + segmentStyles{s}); + end + if isempty(segments) + segs = Segment(); + segs = segs(false); + end + segs = unique(segs); + this.Segments = segs; + segXPts = arrayfun(@(s)(s.Start(1)), this.Segments); [~, inds] = sort(segXPts); this.Segments = this.Segments(inds); function segments = line2segments(xx, yy, zz) @@ -278,107 +291,84 @@ % style, etc. - that's why it's a line! if ~isempty(zz) segments = cell(1, numel(xx) - 1); + mask = false(1, numel(xx) - 1); for idx = 1:length(xx)-1 - first = [num2str(xx(idx)) ' ' num2str(yy(idx)) ' ' num2str(zz(idx))]; - last = [num2str(xx(idx+1)) ' ' num2str(yy(idx+1)) ' ' num2str(zz(idx+1))]; - [~, order] = sort({first last}); - if order(1) == 1 - segments{idx} = {[xx(idx) xx(idx+1)], ... - [yy(idx) yy(idx+1)], ... - [zz(idx) zz(idx+1)]}; - else - segments{idx} = {[xx(idx+1) xx(idx)], ... - [yy(idx+1) yy(idx)], ... - [zz(idx+1) zz(idx)]}; + if xx(idx) ~= xx(idx+1) || ... + yy(idx) ~= yy(idx+1) || ... + zz(idx) ~= zz(idx+1) + mask(idx) = true; + first = [num2str(xx(idx)) ' ' num2str(yy(idx)) ' ' num2str(zz(idx))]; + last = [num2str(xx(idx+1)) ' ' num2str(yy(idx+1)) ' ' num2str(zz(idx+1))]; + [~, order] = sort({first last}); + if order(1) == 1 + segments{idx} = {[xx(idx) yy(idx) zz(idx)], ... + [xx(idx+1) yy(idx+1) zz(idx+1)]}; + else + segments{idx} = {[xx(idx+1) yy(idx+1) zz(idx+1)], ... + [xx(idx) yy(idx) zz(idx)]}; + end end end + segments = segments(mask); else segments = cell(1, numel(xx) - 1); + mask = false(1, numel(xx) - 1); for idx = 1:length(xx)-1 - first = [num2str(xx(idx)) ' ' num2str(yy(idx))]; - last = [num2str(xx(idx+1)) ' ' num2str(yy(idx+1))]; - [~, order] = sort({first last}); - if order(1) == 1 - segments{idx} = {[xx(idx) xx(idx+1)], ... - [yy(idx) yy(idx+1)], ... - []}; - else - segments{idx} = {[xx(idx+1) xx(idx)], ... - [yy(idx+1) yy(idx)], ... - []}; + if xx(idx) ~= xx(idx+1) || yy(idx) ~= yy(idx+1) + mask(idx) = true; + first = [num2str(xx(idx)) ' ' num2str(yy(idx))]; + last = [num2str(xx(idx+1)) ' ' num2str(yy(idx+1))]; + [~, order] = sort({first last}); + if order(1) == 1 + segments{idx} = {[xx(idx) yy(idx)], ... + [xx(idx+1) yy(idx+1)]}; + else + segments{idx} = {[xx(idx+1) yy(idx+1)], ... + [xx(idx) yy(idx)]}; + end end end + segments = segments(mask); end end - % for every line that has no line style, we should sort it. - % every point should be documented; EACH point is it's own - % structure - % for each line that has no line style, get it - mask = strcmp(linestyle, ''); + % Plots are connections between points and the points + % themselves. Every POINT is its own thing as well % get X, Y, Z, Marker, Legend, Color - ptXData = xcell(mask); - ptYData = ycell(mask); - if ~all(cellfun(@isempty, zcell)) - ptZData = zcell(mask); - else - ptZData = cell(1, sum(mask)); - end - % z data? - ptMarker = marker(mask); - ptColor = color(mask); - ptLegend = legend(mask); + % get total amnt of points totalPoints = 0; - for p = 1:numel(ptXData) - totalPoints = totalPoints + numel(xcell{p}); - end - ptData = cell(1, totalPoints); - points = struct('X', ptData, ... - 'Y', ptData, ... - 'Z', ptData, ... - 'Marker', ptData, ... - 'LineStyle', '', ... - 'Legend', ptData, ... - 'Color', ptData); - counter = 1; - for i = 1:length(ptXData) - % just separate X, Y, Z points - xx = num2cell(ptXData{i}); - yy = num2cell(ptYData{i}); - zz = ptZData{i}; - mark = ptMarker{i}; - col = ptColor{i}; - leg = ptLegend{i}; - if isempty(zz) - zz = {[]}; - else - zz = num2cell(zz); + for p = 1:numel(xcell) + if ~isempty(marker{p}) + totalPoints = totalPoints + numel(xcell{p}); end - [points(counter:(counter+length(xx)-1)).X] = deal(xx{:}); - [points(counter:(counter+length(xx)-1)).Y] = deal(yy{:}); - [points(counter:(counter+length(xx)-1)).Z] = deal(zz{:}); - [points(counter:(counter+length(xx)-1)).Marker] = deal(mark); - [points(counter:(counter+length(xx)-1)).Color] = deal(col); - [points(counter:(counter+length(xx)-1)).Legend] = deal(leg); - counter = counter + length(xx); end - % Unique check - % for all pts, if any point is identical, kill it - while p <= length(points) - pt = points(p); - for j = (p+1):length(points) - if isequal(pt, points(j)) - points(j) = []; + + n = totalPoints; + for i = 1:length(xcell) + if ~isempty(marker{i}) + % just separate X, Y, Z points + xx = xcell{i}; + yy = ycell{i}; + zz = zcell{i}; + if isempty(zz) + zz = zeros(1,length(xx)); + end + mark = marker{i}; + col = color{i}; + for j = length(xx):-1:1 + points(n) = Point([xx(j) yy(j) zz(j)], ... + mark, col); + n = n - 1; end end - p = p + 1; end - - % Sort, just like we did with Segments: - [~, inds] = sort([points.X]); - points = points(inds); - - + if totalPoints == 0 + points = Point(); + points = points(false); + end + % Unique check + % for all pts, if any point is identical, kill it + points = unique(points); this.Points = points; end end @@ -435,22 +425,22 @@ areEqual = false; return; end - if ~strcmp(strjoin(cellstr(this.Title), newline), strjoin(cellstr(that.Title), newline)) + if ~strcmp(this.Title, that.Title) areEqual = false; return; end - if ~strcmp(strjoin(cellstr(this.XLabel), newline), strjoin(cellstr(that.XLabel), newline)) + if ~strcmp(this.XLabel, that.XLabel) areEqual = false; return; end - if ~strcmp(strjoin(cellstr(this.YLabel), newline), strjoin(cellstr(that.YLabel), newline)) + if ~strcmp(this.YLabel, that.YLabel) areEqual = false; return; end - if ~strcmp(strjoin(cellstr(this.ZLabel), newline), strjoin(cellstr(that.ZLabel), newline)) + if ~strcmp(this.ZLabel, that.ZLabel) areEqual = false; return; end @@ -475,13 +465,57 @@ % for each point set, see if found in this thatPoints = that.Points; thisPoints = this.Points; + % use ismember! If all of student points are member, AND all of + % soln points are member, then yes + if ~all(ismember(thisPoints, thatPoints)) ... + || ~all(ismember(thatPoints, thisPoints)) + areEqual = false; + return; + end + + % Nothing should be left in either set; if both sets are + % non-empty, then false + + % Roll Call + % for each line segment in that, see if found in this + % Since they are unique, remove from both sets when found. + % Then, at end, if both are empty, equal; otherwise, unequal. + thatSegs = that.Segments; + thisSegs = this.Segments; + + if ~all(ismember(thisSegs, thatSegs)) ... + || ~all(ismember(thatSegs, thisSegs)) + areEqual = false; + return; + end + areEqual = true; + end + %% pointEquals: Check Plotted Equality + % + % pointEquals is like dataEquals, except it only checks exact + % points plotted - i.e., is the raw plotted data the same + function areEqual = pointEquals(this, that) + extractor = @(seg)([seg.Start, seg.Stop]); + thisPoints = [this.Points, extractor(this.Segments)]; + thatPoints = [that.Points, extractor(that.Segments)]; + areEqual = all(ismember(thisPoints, thatPoints)); + end + %% dataEquals: Check Data Equality + % + % dataEquals is the same as equals, except it strictly checks point + % and segment data - raw coordinates. + function areEqual = dataEquals(this, that) + % Point Call + % for each point set, see if found in this + thatPoints = that.Points; + thisPoints = this.Points; for i = numel(thatPoints):-1:1 thatPoint = thatPoints(i); % look through thisSegs; once found, delete from both isFound = false; for j = numel(thisPoints):-1:1 - if isequal(thatPoint, thisPoints(j)) + if thatPoint.dataEquals(thisPoints(j)) isFound = true; thisPoints(j) = []; thatPoints(i) = []; @@ -499,21 +533,6 @@ end % Nothing should be left in either set; if both sets are % non-empty, then false - thatPoints = that.Points; - thisPoints = this.Points; - for i = 1:numel(thatPoints) - isFound = false; - for j = 1:numel(thisPoints) - if isequal(thatPoints(i), thisPoints(j)) - isFound = true; - break; - end - end - if ~isFound - areEqual = false; - return; - end - end % Roll Call % for each line segment in that, see if found in this @@ -527,7 +546,7 @@ % look through thisSegs; once found, delete from both isFound = false; for j = numel(thisSegs):-1:1 - if isequal(thatSeg, thisSegs(j)) + if thatSeg.dataEquals(thisSegs(j)) isFound = true; thisSegs(j) = []; thatSegs(i) = []; @@ -590,13 +609,118 @@ 'input is not a valid instance of Plot'); throw(ME); end + + % Find out why + % title + % x,y,zlabel + % position + % plotbox + % segs + % pts + % limits + msg = ''; + if ~strcmp(this.Title, that.Title) + msg = sprintf('You gave title "%s", but we expected "%s"', ... + this.Title, that.Title); + elseif ~strcmp(this.XLabel, that.XLabel) + msg = sprintf('You have an X Label of "%s", but we expected "%s"', ... + this.XLabel, that.XLabel); + elseif ~strcmp(this.YLabel, that.YLabel) + msg = sprintf('You have a Y Label of "%s", but we expected "%s"', ... + this.YLabel, that.YLabel); + elseif ~strcmp(this.ZLabel, that.ZLabel) + msg = sprintf('You have a Z Label of "%s", but we expected "%s"', ... + this.ZLabel, that.ZLabel); + elseif any(this.Position < (that.Position - Plot.POSITION_MARGIN)) ... + || any(this.Position > (that.Position + Plot.POSITION_MARGIN)) + msg = sprintf(['Your plot has a position of [%0.2f, %0.2f, %0.2f, %0.2f], ', ... + 'but we expected [%0.2f, %0.2f, %0.2f, %0.2f] (Did you call subplot correctly?)'], ... + this.Position(1), this.Position(2), this.Position(3), this.Position(4), ... + that.Position(1), that.Position(2), that.Position(3), that.Position(4)); + elseif any(this.PlotBox < (that.PlotBox - Plot.POSITION_MARGIN)) ... + || any(this.PlotBox > (that.PlotBox + Plot.POSITION_MARGIN)) + msg = sprintf(['Your plot has a Plot Box of [%0.2f, %0.2f, %0.2f], ', ... + 'but we expected [%0.2f, %0.2f, %0.2f] (Did you call subplot correctly?)'], ... + this.PlotBox(1), this.PlotBox(2), this.PlotBox(3), ... + that.PlotBox(1), that.PlotBox(2), that.PlotBox(3)); + else + % we need to check Segs and Points + % do roll call + solnSegs = that.Segments; + studSegs = this.Segments; + isFound = false; + for i = numel(solnSegs):-1:1 + solnSeg = solnSegs(i); + isFound = false; + for j = numel(studSegs):-1:1 + if solnSeg.equals(studSegs(j)) + solnSegs(i) = []; + studSegs(j) = []; + isFound = true; + break; + end + end + if ~isFound + msg = sprintf('You didn''t plot segment (%0.2f, %0.2f)(%0.2f, %0.2f)', ... + solnSeg.Start.X, ... + solnSeg.Start.Y, ... + solnSeg.Stop.X, ... + solnSeg.Stop.Y); + break; + end + end + if isFound && ~isempty(studSegs) + seg = studSegs(1); + msg = sprintf('You plotted segment (%0.2f, %0.2f)(%0.2f, %0.2f) when you shouldn''t have', ... + seg.Start.X, ... + seg.Start.Y, ... + seg.Stop.X, ... + seg.Stop.Y); + end + if isempty(msg) + % look at points + solnPoints = that.Points; + studPoints = this.Points; + isFound = false; + for i = numel(solnPoints):-1:1 + solnPoint = solnPoints(i); + isFound = false; + for j = numel(studPoints):-1:1 + if solnPoint.equals(studPoints(j)) + solnPoints(i) = []; + studPoints(j) = []; + isFound = true; + break; + end + end + if ~isFound + msg = sprintf('You didn''t plot point (%0.2f, %0.2f)', ... + solnPoint.X, solnPoint.Y); + break; + end + end + if isFound && ~isempty(studPoints) + pt = studPoints(1); + msg = sprintf('You plotted point (%0.2f, %0.2f) when you shouldn''t have', ... + pt.X, pt.Y); + end + end + end + + if isempty(msg) && ~isequal(this.Limits(1:4), that.Limits(1:4)) + msg = sprintf(['Your plot has Limits of [%0.2f, %0.2f, %0.2f, %0.2f], ', ... + 'but we expected [%0.2f, %0.2f, %0.2f, %0.2f] (Did you call axis or xlim/ylim correctly?)'], ... + this.Limits(1), this.Limits(2), this.Limits(3), this.Limits(4), ... + that.Limits(1), that.Limits(2), that.Limits(3), that.Limits(4)); + end + studPlot = img2base64(this.Image); solnPlot = img2base64(that.Image); html = sprintf(['
', ... '

Your Plot

', ... '

Solution Plot

', ... - '
'],... - studPlot, solnPlot); + '

%s

'],... + studPlot, solnPlot, msg); end end diff --git a/Point.m b/Point.m new file mode 100644 index 0000000..420b452 --- /dev/null +++ b/Point.m @@ -0,0 +1,146 @@ +%% Point: Contains the Data for a Point on a Plot +% +% stores the coordinate, marker, and color for a point on a plot +% +%%% Fields +% +% * X: +% +% * Y: +% +% * Z: +% +% * Marker: +% +% * Color: +% +%%% Methods +% +% * Point: makes the points. Give it a 1x2 vec or a 1x3 vec. Doesnt matter. +% Give it one input if there is no marker. give it 3 if there is bc color +% matter +% +% * Equals: obviously returns if two points are the same +% +% * dataEquals: does equals but ignores color and marker. +% +%%% Remarks +% +% hey + +classdef Point < handle + properties (Access = public) + X; + Y; + Z; + Marker = ''; + Color; + end + methods (Access = public) + function this = Point(coord,marker,color) + %% Constructor + % + % creates an instance of Points from a vector containing + % coordinate data, char vec of marker, and char vec of color + % + %%% Remarks + % makes the points. Give it a 1x2 vec or a 1x3 vec. Doesnt + % matter. Give it one input if there is no marker. give it 3 if + % there is bc color matter + if nargin == 0 + return; + end + this.X = coord(1); + this.Y = coord(2); + if length(coord) == 3 + this.Z = coord(3); + else + this.Z = 0; + end + if nargin > 1 + this.Marker = marker; + this.Color = color; + else + this.Marker = ''; + this.Color = [0 0 0]; + end + end + function areEqual = equals(this,that) + %% Equals: does the equals thing + % you know this + if isempty(this) + areEqual = []; + return; + elseif isempty(that) + areEqual = false; + return; + end + orig = this; + this = reshape(this, 1, []); + that = reshape(that, 1, []); + if isscalar(that) + tmp(numel(this)) = that; + tmp(:) = that; + that = tmp; + tmp = tmp(false); + end + if isscalar(this) + tmp(numel(that)) = this; + tmp(:) = this; + this = tmp; + end + areEqual = this.dataEquals(that) ... + & strcmp({this.Marker},{that.Marker}) ... + & cellfun(@isequal, {this.Color}, {that.Color}); + if isscalar(orig) + areEqual = reshape(areEqual, size(that)); + else + areEqual = reshape(areEqual, size(orig)); + end + end + function tf = ne(this, that) + tf = ~this.equals(that); + end + function tf = eq(this, that) + tf = this.equals(that); + end + function areEqual = dataEquals(this,that) + %% dataEquals: does the equals thing + % you know this too + areEqual = [this.X] == [that.X] ... + & [this.Y] == [that.Y] ... + & [this.Z] == [that.Z]; + areEqual = reshape(areEqual, size(this)); + end + + function [sorted, inds] = sort(points, varargin) + if isempty(points) + sorted = points; + inds = []; + return; + elseif isscalar(points) + sorted = points; + inds = 1; + return; + end + xx = reshape([points.X], [], 1); + yy = reshape([points.Y], [], 1); + zz = reshape([points.Z], [], 1); + colors = vertcat(points.Color); + markers = reshape(string({points.Marker}), [], 1); + tmp = compose('%0.5f %0.5f %0.5f %d %d %d %s', ... + [xx, yy, zz], ... + colors, ... + markers); + [~, inds] = sort(tmp, varargin{:}); + sorted = points(inds); + end + + + end + + methods (Static) + end +end + + diff --git a/Segment.m b/Segment.m new file mode 100644 index 0000000..09c4289 --- /dev/null +++ b/Segment.m @@ -0,0 +1,193 @@ +%% Segment: A single connection between two points +% +% A single connection between two points without marker! +% +%%% Fields +% +% * Start: A Point that represents where this segment starts +% +% * Stop: A Point that represents where this segment ends +% +% * Color: A 1x3 vector that represents the color +% +% * Style: A character vector that represents the style (i.e., dashed. +% etc.) +% +%%% Methods +% +% * Segment +% +% * equals +% +% * dataEquals +% +%%% Remarks +% +% This class keeps data for a single segment - a connection. It does not +% care about what the endpoints look like - only where they are. +% +% The Point will not have a style - just coordinates +% +classdef Segment < handle + properties (Access = public) + Start; + Stop; + Color double; + Style char; + end + + methods + %% Constructor + % + % Segment(S, E, C, L) will use start S and stop E to make a + % segment, with color C and line style L. + % + %%% Remarks + % + % S and E can be Points or coordinates - if the latter, they will + % be constructed into points + function this = Segment(start, stop, color, style) + if nargin == 0 + return; + end + if isa(start, 'Point') + this.Start = start; + this.Stop = stop; + else + this.Start = Point(start); + this.Stop = Point(stop); + end + pts = sort([this.Start, this.Stop]); + this.Start = pts(1); + this.Stop = pts(2); + this.Color = color; + if strcmp(style, 'none') + this.Style = ''; + else + this.Style = style; + end + end + end + methods (Access = public) + function tf = equals(this, that) + if isempty(this) + tf = []; + return; + elseif isempty(that) + tf = false; + return; + end + orig = this; + this = reshape(this, 1, []); + that = reshape(that, 1, []); + if isscalar(that) + tmp(numel(this)) = that; + tmp(:) = that; + that = tmp; + tmp = tmp(false); + end + if isscalar(this) + tmp(numel(that)) = this; + tmp(:) = this; + this = tmp; + + end + tf = this.dataEquals(that) ... + & cellfun(@isequal, {this.Color}, {that.Color}) ... + & strcmp({this.Style}, {that.Style}); + if isscalar(orig) + tf = reshape(tf, size(that)); + else + tf = reshape(tf, size(orig)); + end + end + + function tf = eq(this, that) + tf = this.equals(that); + end + + function tf = ne(this, that) + tf = ~this.equals(that); + end + + function tf = dataEquals(this, that) + tf = [this.Start] == [that.Start] ... + & [this.Stop] == [that.Stop]; + tf = reshape(tf, size(this)); + end + + function [sorted, inds] = sort(segments, varargin) + if isempty(segments) + sorted = segments; + inds = []; + return; + elseif isscalar(segments) + sorted = segments; + inds = 1; + return; + end + % sort by Point start -> stop + starts = [segments.Start]; + xx1 = reshape([starts.X], [], 1); + yy1 = reshape([starts.Y], [], 1); + zz1 = reshape([starts.Z], [], 1); + stops = [segments.Stop]; + xx2 = reshape([stops.X], [], 1); + yy2 = reshape([stops.Y], [], 1); + zz2 = reshape([stops.Z], [], 1); + styles = reshape(string({segments.Style}), [], 1); + tmp = compose('%0.5f %0.5f %0.5f %0.5f %0.5f %0.5f %s', ... + [xx1, yy1, zz1, xx2, yy2, zz2], styles); + [~, inds] = sort(tmp, varargin{:}); + sorted = segments(inds); + sorted = reshape(sorted, size(segments)); + end + end +end +%% Plot: Class Containing Data for a Plot +% +% Holds data needed for each plot in fields. +% +% Has methods to check if a student's plot matches the solution, and to +% give feedback for the student plot. +% +%%% Fields +% +% * Segments: A structure array of all the segments in the plot +% +% * Points: A structure array of all the points in the plot +% +% * Title: A String of the title used for the plot +% +% * XLabel: A String of the xLabel used for the plot +% +% * YLabel: A String of the yLabel used for the plot +% +% * ZLabel: A String of the zLabel used for the plot +% +% * Position: A 1X4 double vector of the position of the axes in the figure +% window +% +% * PlotBox: A 1X3 vector representing the relative axis scale factors +% +% * Image: An image taken of the plot, as an MxNx3 uint8 array. +% +% * Legend: A string array of all the names in the legend +% +% * Limits: A 1x6 double vector representing the axes limits +% +%%% Methods +% +% * Plot +% +% * equals +% +% * generateFeedback +% +%%% Remarks +% +% The Plot class keeps all relevant data about a specific plot; note that +% a subplot is considered a single plot. Like the File class, the Plot +% class copies over any data necessary to recreate the plot entirely; as +% such, the plot can be deleted once a Plot object is created! +% \ No newline at end of file diff --git a/checkPlots.m b/checkPlots.m index 93169a1..d410c0a 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -11,10 +11,6 @@ % the plots were incorrect as a character vector in M. If E is true, M is % empty. % -% [E, M, D] = checkPlots(___) will do the same as above, and also return -% the differing data, if possible. Data is given if it's possible to -% quantitatively differentiate; XData, titles, etc. -% %%% Remarks % % F is flexible. You can pass in a character vector that represents the @@ -23,11 +19,6 @@ % You must ensure that the function and its solution (fun_soln.p) exist in % the current folder. % -% The function will report as soon as it finds something - it is not -% comprehensive. For example, if your plot is wrong in color AND -% coordinates, then the first time you check it will ONLY say "expected -% color to be ___, but got ____". You should run it after every fix to -% ensure you've fixed all problems. % % The offending plot will be shown side-by-side with its % corresponding solution in a new figure window. @@ -38,6 +29,10 @@ % warnings. % function [eq, msg, data] = checkPlots(fun, varargin) +ERROR_COLOR = [.45 0 0]; +BAD_MARKER_SIZE = 12; +BAD_LINE_WIDTH = 2; +BAD_FONT_FACTOR = 3; %#ok<*LAXES> % try to convert to function handle if ischar(fun) @@ -103,8 +98,16 @@ msg = sprintf('Expected %d plots, but you produced %d plots', ... numel(solns), numel(studs)); else + solutionFigure = figure('Visible', 'off', ... + 'NumberTitle', 'off', ... + 'Name', 'Solution Plot(s)'); + studentFigure = figure('Visible', 'off', ... + 'NumberTitle', 'off', ... + 'Name', 'Student Plot(s)'); + msg = cell(1, numel(solns)); % same number of plots; now loop through for n = numel(solns):-1:1 + msg{n} = cell(0); solnPlot = solns(n); isFound = false; for s = numel(studs):-1:1 @@ -124,111 +127,35 @@ % we couldn't find an equal one. Loop through and find % "closest" one, compare and report - % Priorities: - % Position (subplot) - % PointData - % SegmentData - % # Points - % # Segments - % PlotBox - % Title - % Xlabel, Ylabel, Zlabel - % if all fail, then just pick one at random + % * First, plots with the exact same segments and points + % should match up + % + % * If no, then recheck - this time, just look at raw X, Y, + % and Z data. If it matches, then that's our plot + % + % * If no, then we can't really be sure what they meant. + % Look at the Title + % + % * If no, then look at position (subplot) + % + % * If all else fails, pick one at random isFound = false; for s = numel(studs):-1:1 studPlot = studs(s); - if isequal(studPlot.Position, solnPlot.Position) + % Check all segments and points, separately. + if studPlot.dataEquals(solnPlot) + % exact match! isFound = true; break; end end if ~isFound - % positions never matched; try next (Segments exact) - % Roll Call - % for each line segment in that, see if found in this - - for s = numel(studs):-1:1 - studSegs = studs(s).Segments; - solnSegs = solnPlot.Segments; - % search through - for i = numel(solnSegs):-1:1 - seg = solnSegs(i); - for j = numel(studSegs):-1:1 - if isequal(seg, studSegs(j)) - solnSegs(i) = []; - studSegs(j) = []; - isFound = true; - end - end - if ~isFound - break; - end - end - % if any left, still not equal! - if ~isempty(studSegs) || ~isempty(solnSegs) - isFound = false; - end - end - end - - if ~isFound - % Segments (Exact) never matched; try next (Points exact) - % Point Call - % for each point set, see if found in this - for s = numel(studs):-1:1 - studPoints = studs(s).Points; - solnPoints = solnPlot.Points; - % search through - for i = numel(solnPoints):-1:1 - pt = solnPoints(i); - for j = numel(studPoints):-1:1 - if isequal(pt, studPoints(j)) - solnPoints(i) = []; - studPoints(j) = []; - isFound = true; - end - end - if ~isFound - break; - end - end - % if any left, still not equal! - if ~isempty(studPoints) || ~isempty(solnPoints) - isFound = false; - end - end - end - - if ~isFound - % Points (Exact) never matched; try next (# segs) - for s = numel(studs):-1:1 - studPlot = studs(s); - if numel(studPlot.Segments) == numel(solnPlot.Segments) - isFound = true; - break; - end - end - end - - if ~isFound - % # segments never matched; try next (# points) - for s = numel(studs):-1:1 - studPlot = studs(s); - if numel(studPlot.Points) == numel(solnPlot.Points) - isFound = true; - break; - end - end - end - - - - if ~isFound - % # of lines never matched; try next (PlotBox) + % Data Match failed; check raw X, Y, Z for s = numel(studs):-1:1 studPlot = studs(s); - if isequal(studPlot.PlotBox, solnPlot.PlotBox) + % if ismember is all true, match! + if studPlot.pointEquals(solnPlot) isFound = true; break; end @@ -236,7 +163,7 @@ end if ~isFound - % PlotBox never matched; try next (Title) + % Data never matched; try next (Title) for s = numel(studs):-1:1 studPlot = studs(s); if strcmpi(studPlot.Title, solnPlot.Title) @@ -245,20 +172,11 @@ end end end - if ~isFound - % Title never matched; try next (X, Y, Z label) + % Title never matched; look for plot in same position for s = numel(studs):-1:1 studPlot = studs(s); - if strcmpi(studPlot.XLabel, solnPlot.XLabel) - isFound = true; - break; - end - if strcmpi(studPlot.YLabel, solnPlot.YLabel) - isFound = true; - break; - end - if strcmpi(studPlot.ZLabel, solnPlot.ZLabel) + if isequal(studPlot.Position, solnPlot.Position) isFound = true; break; end @@ -271,233 +189,243 @@ warning('We couldn''t find a good match for this plot, so take any feedback with a grain of salt'); end eq = false; - % for each soln segment, check if stud seg exists; delete - % from both if necessary - solnSegs = solnPlot.Segments; - studSegs = studPlot.Segments; - segmentMismatch = false; - for i = numel(solnSegs):-1:1 - seg = solnSegs(i); - isFound = false; - for j = numel(studSegs):-1:1 - if isequal(seg, studSegs(j)) - solnSegs(i) = []; - studSegs(j) = []; - isFound = true; - break; - end - end - if ~isFound - segmentMismatch = true; - solnSeg = seg; - break; - end + % + % Feedback is given as bolding and/or changing colors. The + % following should be considered: + % * Title + % * Labels + % * Line Colors + % * Point Colors + % * Point Markers + % * Line Styles + % * Points that exist in one, but not other + % * Segments that exist in one, but not other + % + % Do the following: + % + % 1. If the title is messed up, color with ERROR_COLOR; + % 2. If the labels are messed up, color with ERROR_COLOR; + % 3. If a line is otherwise equal except for style or + % color, expand line width to BAD_SEGMENT_WIDTH + % 4. If a point is otherwise equal except for marker or + % color, expand marker size to BAD_MARKER_SIZE + % 5. If a point exists in only one, plot with + % BAD_MARKER_SIZE. + % 6. If a segment exists in only one, plot with + % BAD_LINE_WIDTH. + % 7. If the position is wrong, add text in middle of axes + % 8. If the axis style is wrong, add text in middle of axes + % 9. If the axis limits are wrong, color the Axis itself + % 10. If alien, add text in middle of axes + % + % Start by creating two plots. + %%% Title + solutionAxes = axes(solutionFigure, ... + 'Position', solnPlot.Position, ... + 'XLim', solnPlot.Limits(1:2), ... + 'YLim', solnPlot.Limits(3:4), ... + 'ZLim', solnPlot.Limits(5:6), ... + 'PlotBoxAspectRatio', solnPlot.PlotBox); + solutionAxes.XLabel.String = solnPlot.XLabel; + solutionAxes.YLabel.String = solnPlot.YLabel; + solutionAxes.ZLabel.String = solnPlot.ZLabel; + solutionAxes.Title.String = sprintf('[%d] %s', n, solnPlot.Title); + hold(solutionAxes, 'on'); + studentAxes = axes(studentFigure, ... + 'Position', solnPlot.Position, ... + 'XLim', studPlot.Limits(1:2), ... + 'YLim', studPlot.Limits(3:4), ... + 'ZLim', studPlot.Limits(5:6), ... + 'PlotBoxAspectRatio', studPlot.PlotBox); + studentAxes.XLabel.String = studPlot.XLabel; + studentAxes.YLabel.String = studPlot.YLabel; + studentAxes.ZLabel.String = studPlot.ZLabel; + studentAxes.Title.String = studPlot.Title; + hold(studentAxes, 'on'); + if ~studPlot.dataEquals(solnPlot) + msg{n}{end+1} = sprintf('Plot %d: Incorrect Data', n); end - if segmentMismatch - segMsg = sprintf('Segment (%d, %d) -> (%d, %d) is missing!', ... - solnSeg.Segment{1}(1), ... - solnSeg.Segment{2}(1), ... - solnSeg.Segment{1}(2), ... - solnSeg.Segment{2}(2)); - studSeg = []; - elseif ~isempty(studSegs) - segmentMismatch = true; - studSeg = studSegs(1); - solnSeg = []; - segMsg = sprintf('Segment (%d, %d) -> (%d, %d) should not have been plotted!', ... - studSeg.Segment{1}(1), ... - studSeg.Segment{2}(1), ... - studSeg.Segment{1}(2), ... - studSeg.Segment{2}(2)); + %%% Title + if ~isequal(studPlot.Title, solnPlot.Title) + studentAxes.Title.Color = ERROR_COLOR; + studentAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; + solutionAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; + msg{n}{end+1} = sprintf('Plot %d: Incorrect Title', n); end - - pointMismatch = false; - studPoints = studs(s).Points; - solnPoints = solnPlot.Points; - % search through - for i = numel(solnPoints):-1:1 - pt = solnPoints(i); - for j = numel(studPoints):-1:1 - if isequal(pt, studPoints(j)) - solnPoints(i) = []; - studPoints(j) = []; - isFound = true; - end - end - if ~isFound - pointMismatch = true; - solnPoint = pt; - break; - end + if ~isequal(studPlot.XLabel, solnPlot.XLabel) + studentAxes.XLabel.Color = ERROR_COLOR; + studentAxes.XLabel.FontSize = ... + studentAxes.XLabel.FontSize * BAD_FONT_FACTOR; + solutionAxes.XLabel.FontSize = ... + solutionAxes.XLabel.FontSize * BAD_FONT_FACTOR; + msg{n}{end+1} = sprintf('Plot %d: Incorrect XLabel', n); end - if pointMismatch - ptMsg = sprintf('Point (%0.2f, %0.2f) is missing!', ... - solnPoint.X, solnPoint.Y); - studPoint = []; - elseif ~isempty(studPoints) - pointMismatch = true; - studPoint = studPoints(1); - solnPoint = []; - ptMsg = sprintf('Point (%02.f, %0.2f) should not be plotted!', ... - studPoint.X, studPoint.Y); + if ~isequal(studPlot.YLabel, solnPlot.YLabel) + studentAxes.YLabel.Color = ERROR_COLOR; + studentAxes.YLabel.FontSize = ... + studentAxes.YLabel.FontSize * BAD_FONT_FACTOR; + solutionAxes.YLabel.FontSize = ... + solutionAxes.YLabel.FontSize * BAD_FONT_FACTOR; + msg{n}{end+1} = sprintf('Plot %d: Incorrect YLabel', n); end - % get right message - % Priority for checking: - % Position - % Data mismatch - % Colors - % Linestyles - % Marker Styles - % axes - % Title - % Labels - data.student = []; - data.solution = []; - if ~isequal(studPlot.Position, solnPlot.Position) - msg = 'Your plot is wrongly positioned (did you remember to call subplot?)'; - elseif segmentMismatch - msg = segMsg; - data.student = studSeg; - data.solution = solnSeg; - elseif pointMismatch - msg = ptMsg; - data.student = studPoint; - data.solution = solnPoint; - elseif ~isequal(studPlot.PlotBox, solnPlot.PlotBox) - msg = ['Your axes (limits and/or scaling) aren''t correct ', ... - '(axes limits and scaling are affected by things ', ... - '"axis square", "axis equal", and "axes([#, #, #, #]). "', ... - 'You might want to make sure you''ve set the axes correctly)']; - elseif ~isequal(studPlot.Title, solnPlot.Title) - msg = 'Your title is incorrect'; - data.student = studPlot.Title; - data.solution = solnPlot.Title; - elseif ~isequal(studPlot.XLabel, solnPlot.XLabel) - msg = 'Your x label is incorrect'; - data.student = studPlot.XLabel; - data.solution = solnPlot.XLabel; - elseif ~isequal(studPlot.YLabel, solnPlot.YLabel) - msg = 'Your y label is incorrect'; - data.student = studPlot.YLabel; - data.solution = solnPlot.YLabel; - elseif ~isequal(studPlot.ZLabel, solnPlot.ZLabel) - msg = 'Your z label is incorrect'; - data.student = studPlot.ZLabel; - data.solution = solnPlot.ZLabel; - elseif studPlot.isAlien - msg = ['Your plots are nearly identical; however, you''ve ', ... - 'Plotted something that isn''t a line and/or point. (', ... - 'functions like bar(), pie(), imshow(), and area() ', ... - 'can plot things that are not lines. Ensure you ', ... - 'haven''t used anything like that. In general, ', ... - 'plot() and plot3() should be all you need!)']; - else - % we can't find anything wrong; tell them to talk to a - % TA! - msg = ['We can''t seem to find anything wrong with your ', ... - 'plot, but we know they''re not equal. Please go ', ... - 'to a TA at helpdesk OR email your TA']; + if ~isequal(studPlot.ZLabel, solnPlot.ZLabel) + studentAxes.ZLabel.Color = ERROR_COLOR; + studentAxes.ZLabel.FontSize = ... + studentAxes.Label.FontSize * BAD_FONT_FACTOR; + solutionAxes.ZLabel.FontSize = ... + solutionAxes.Label.FontSize * BAD_FONT_FACTOR; + msg{n}{end+1} = sprintf('Plot %d: Incorrect ZLabel', n); end - % show the two plots - f = figure('Name', 'Student''s Plot', 'NumberTitle', 'off'); - ax = axes(f); - hold(ax, 'on'); - % recreate plot - title(ax, studPlot.Title); - xlabel(ax, studPlot.XLabel); - ylabel(ax, studPlot.YLabel); - zlabel(ax, studPlot.ZLabel); - % for each set of data, plot. - % for each point, plot - for pt = 1:numel(studPlot.Points) - point = studPlot.Points(pt); - x = point.X; - y = point.Y; - z = point.Z; - if isempty(z) - p = plot(ax, x, y, ... - [point.Marker point.LineStyle]); + % We can first plot lines on both that are same. Use + % ismember to determine! + segs = solnPlot.Segments; + segs = segs(ismember(segs, studPlot.Segments)); + for s = numel(segs):-1:1 + seg = segs(s); + pts = [seg.Start seg.Stop]; + if any([pts.Z] ~= 0) + solnLine = plot3(solutionAxes, [pts.X], [pts.Y], [pts.Z]); + studLine = plot3(studentAxes, [pts.X], [pts.Y], [pts.Z]); else - p = plot3(ax, x, y, z, ... - [point.Marker point.LineStyle]); + solnLine = plot(solutionAxes, [pts.X], [pts.Y]); + studLine = plot3(studentAxes, [pts.X], [pts.Y]); end - p.Color = point.Color; + solnLine.Color = seg.Color; + studLine.Color = seg.Color; + solnLine.LineStyle = seg.Style; + studLine.LineStyle = seg.Style; end - % for each segment, plot - for s = 1:numel(studPlot.Segments) - seg = studPlot.Segments(s); - xx = seg.Segment{1}; - yy = seg.Segment{2}; - zz = seg.Segment{3}; - if isempty(zz) - p = plot(ax, xx, yy, ... - [seg.Marker seg.LineStyle]); + pts = solnPlot.Points; + pts = pts(ismember(pts, studPlot.Points)); + for p = numel(pts):-1:1 + pt = pts(p); + if pt.Z ~= 0 + solnPt = plot3(solutionAxes, pt.X, pt.Y, pt.Z); + studPt = plot3(studentAxes, pt.X, pt.Y, pt.Z); else - p = plot(ax, xx, yy, zz, ... - [seg.Marker seg.LineStyle]); + solnPt = plot(solutionAxes, pt.X, pt.Y); + studPt = plot(studentAxes, pt.X, pt.Y); end - p.Color = seg.Color; + solnPt.Marker = pt.Marker; + studPt.Marker = pt.Marker; + solnPt.Color = pt.Color; + studPt.Color = pt.Color; end + % now, it differs. First we'll plot everything on SOLUTION + % that isn't found in STUDENTS + segs = solnPlot.Segments; + segs = segs(~ismember(segs, studPlot.Segments)); + for s = numel(segs):-1:1 + seg = segs(s); + pts = [seg.Start seg.Stop]; + if any([pts.Z] ~= 0) + solnLine = plot3(solutionAxes, [pts.X], [pts.Y], [pts.Z]); + else + solnLine = plot(solutionAxes, [pts.X], [pts.Y]); + end + solnLine.Color = seg.Color; + solnLine.LineStyle = seg.Style; + solnLine.LineWidth = BAD_LINE_WIDTH; + end - ax.XLim = studPlot.Limits(1:2); - ax.YLim = studPlot.Limits(3:4); - ax.ZLim = studPlot.Limits(5:6); - ax.Position = studPlot.Position; - ax.PlotBoxAspectRatio = studPlot.PlotBox; + segs = studPlot.Segments; + segs = segs(~ismember(segs, solnPlot.Segments)); + for s = numel(segs):-1:1 + seg = segs(s); + pts = [seg.Start seg.Stop]; + if any([pts.Z] ~= 0) + studLine = plot3(studentAxes, [pts.X], [pts.Y], [pts.Z]); + else + studLine = plot(studentAxes, [pts.X], [pts.Y]); + end + studLine.Color = seg.Color; + studLine.LineStyle = seg.Style; + studLine.LineWidth = BAD_LINE_WIDTH; + end - f = figure('Name', 'Solution''s Plot', 'NumberTitle', 'off'); - ax = axes(f); - hold(ax, 'on'); - % recreate plot - title(ax, solnPlot.Title); - xlabel(ax, solnPlot.XLabel); - ylabel(ax, solnPlot.YLabel); - zlabel(ax, solnPlot.ZLabel); - % for each point, plot - for pt = 1:numel(solnPlot.Points) - point = solnPlot.Points(pt); - x = point.X; - y = point.Y; - z = point.Z; - if isempty(z) - p = plot(ax, x, y, ... - [point.Marker point.LineStyle]); + pts = solnPlot.Points; + pts = pts(~ismember(pts, studPlot.Points)); + for p = numel(pts):-1:1 + pt = pts(p); + if pt.Z ~= 0 + solnPt = plot3(solutionAxes, pt.X, pt.Y, pt.Z); else - p = plot3(ax, x, y, z, ... - [point.Marker point.LineStyle]); + solnPt = plot(solutionAxes, pt.X, pt.Y); end - p.Color = point.Color; + solnPt.Marker = pt.Marker; + solnPt.Color = pt.Color; + solnPt.MarkerSize = BAD_MARKER_SIZE; end - % for each segment, plot - for s = 1:numel(solnPlot.Segments) - seg = solnPlot.Segments(s); - xx = seg.Segment{1}; - yy = seg.Segment{2}; - zz = seg.Segment{3}; - if isempty(zz) - p = plot(ax, xx, yy, ... - [seg.Marker seg.LineStyle]); + pts = studPlot.Points; + pts = pts(~ismember(pts, solnPlot.Points)); + for p = numel(pts):-1:1 + pt = pts(p); + if pt.Z ~= 0 + studPt = plot3(studentAxes, pt.X, pt.Y, pt.Z); else - p = plot(ax, xx, yy, zz, ... - [seg.Marker seg.LineStyle]); + studPt = plot(studentAxes, pt.X, pt.Y); end - p.Color = seg.Color; + studPt.Marker = pt.Marker; + studPt.Color = pt.Color; + studPt.MarkerSize = BAD_MARKER_SIZE; end - ax.XLim = solnPlot.Limits(1:2); - ax.YLim = solnPlot.Limits(3:4); - ax.ZLim = solnPlot.Limits(5:6); - ax.Position = solnPlot.Position; - ax.PlotBoxAspectRatio = solnPlot.PlotBox; - return; + if ~isequal(studPlot.Limits(1:2), solnPlot.Limits(1:2)) + studentAxes.XColor = ERROR_COLOR; + msg{n}{end+1} = sprintf('Plot %d: Incorrect XLimits', n); + end + if ~isequal(studPlot.Limits(3:4), solnPlot.Limits(3:4)) + studentAxes.YColor = ERROR_COLOR; + msg{n}{end+1} = sprintf('Plot %d: Incorrect YLimits', n); + end + if ~isequal(studPlot.Limits(5:6), solnPlot.Limits(5:6)) + studentAxes.ZColor = ERROR_COLOR; + msg{n}{end+1} = sprintf('Plot %d: Incorrect ZLimits', n); + end + txtHelper = cell(3, 1); + if any(solnPlot.Position < (studPlot.Position - Plot.POSITION_MARGIN)) ... + || any(solnPlot.Position > (studPlot.Position + Plot.POSITION_MARGIN)) + txtHelper{1} = ... + 'Subplot Incorrect'; + msg{n}{end+1} = sprintf('Plot %d: Incorrect Position (did you remember to call subplot?)', n); + end + if any(solnPlot.PlotBox < (studPlot.PlotBox - Plot.POSITION_MARGIN)) ... + || any(solnPlot.PlotBox > (studPlot.PlotBox + Plot.POSITION_MARGIN)) + txtHelper{2} = ... + 'Axis Incorrect'; + msg{n}{end+1} = sprintf('Plot %d: Incorrect Aspect Ratio (did you forget to call axis square, or axis equal?)', n); + end + if studPlot.isAlien + txtHelper{3} = 'Invalid Data'; + msg{n}{end+1} = sprintf('Plot %d: You have invalid data - make sure you stick to plot and plot3 for plotting data!', n); + end + txtHelper(cellfun(@isempty, txtHelper)) = []; + if ~isempty(txtHelper) + txtHelper = text(mean(studPlot.Limits(1:2)), ... + mean(studPlot.Limits(3:4)), ... + txtHelper); + txtHelper.HorizontalAlignment = 'center'; + txtHelper.VerticalAlignment = 'middle'; + txtHelper.Color = ERROR_COLOR; + txtHelper.BackgroundColor = [1 1 1]; + txtHelper.FontUnits = 'normalized'; + drawnow; + txtHelper.FontSize = 2 * txtHelper.FontSize; + end end end + msg = [msg{:}]; + if ~isempty(msg) + msg = strjoin(msg, newline); + solutionFigure.Visible = 'on'; + studentFigure.Visible = 'on'; + end end - + end function plots = populatePlots() From eb8f18a7e4079ba5807c7d577c5f6101321a5402 Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Tue, 13 Nov 2018 15:37:37 +0530 Subject: [PATCH 03/15] Refactor Plot with correct generateFeedback method --- Plot.m | 101 ++++++++++++++---------------------------------------- Point.m | 4 +-- Segment.m | 49 +------------------------- 3 files changed, 28 insertions(+), 126 deletions(-) diff --git a/Plot.m b/Plot.m index 683e888..22ae178 100644 --- a/Plot.m +++ b/Plot.m @@ -618,8 +618,9 @@ % segs % pts % limits - msg = ''; - if ~strcmp(this.Title, that.Title) + if this.isAlien + msg = 'You plotted something other than lines or points, so we could not grade your submission'; + elseif ~strcmp(this.Title, that.Title) msg = sprintf('You gave title "%s", but we expected "%s"', ... this.Title, that.Title); elseif ~strcmp(this.XLabel, that.XLabel) @@ -633,86 +634,36 @@ this.ZLabel, that.ZLabel); elseif any(this.Position < (that.Position - Plot.POSITION_MARGIN)) ... || any(this.Position > (that.Position + Plot.POSITION_MARGIN)) - msg = sprintf(['Your plot has a position of [%0.2f, %0.2f, %0.2f, %0.2f], ', ... - 'but we expected [%0.2f, %0.2f, %0.2f, %0.2f] (Did you call subplot correctly?)'], ... - this.Position(1), this.Position(2), this.Position(3), this.Position(4), ... - that.Position(1), that.Position(2), that.Position(3), that.Position(4)); + msg = 'Your plot has the wrong position (Did you call subplot correctly?)'; elseif any(this.PlotBox < (that.PlotBox - Plot.POSITION_MARGIN)) ... || any(this.PlotBox > (that.PlotBox + Plot.POSITION_MARGIN)) - msg = sprintf(['Your plot has a Plot Box of [%0.2f, %0.2f, %0.2f], ', ... - 'but we expected [%0.2f, %0.2f, %0.2f] (Did you call subplot correctly?)'], ... - this.PlotBox(1), this.PlotBox(2), this.PlotBox(3), ... - that.PlotBox(1), that.PlotBox(2), that.PlotBox(3)); - else - % we need to check Segs and Points - % do roll call - solnSegs = that.Segments; - studSegs = this.Segments; - isFound = false; - for i = numel(solnSegs):-1:1 - solnSeg = solnSegs(i); - isFound = false; - for j = numel(studSegs):-1:1 - if solnSeg.equals(studSegs(j)) - solnSegs(i) = []; - studSegs(j) = []; - isFound = true; - break; - end - end - if ~isFound - msg = sprintf('You didn''t plot segment (%0.2f, %0.2f)(%0.2f, %0.2f)', ... - solnSeg.Start.X, ... - solnSeg.Start.Y, ... - solnSeg.Stop.X, ... - solnSeg.Stop.Y); - break; - end - end - if isFound && ~isempty(studSegs) - seg = studSegs(1); - msg = sprintf('You plotted segment (%0.2f, %0.2f)(%0.2f, %0.2f) when you shouldn''t have', ... - seg.Start.X, ... - seg.Start.Y, ... - seg.Stop.X, ... - seg.Stop.Y); - end - if isempty(msg) - % look at points - solnPoints = that.Points; - studPoints = this.Points; - isFound = false; - for i = numel(solnPoints):-1:1 - solnPoint = solnPoints(i); - isFound = false; - for j = numel(studPoints):-1:1 - if solnPoint.equals(studPoints(j)) - solnPoints(i) = []; - studPoints(j) = []; - isFound = true; - break; - end - end - if ~isFound - msg = sprintf('You didn''t plot point (%0.2f, %0.2f)', ... - solnPoint.X, solnPoint.Y); - break; - end - end - if isFound && ~isempty(studPoints) - pt = studPoints(1); - msg = sprintf('You plotted point (%0.2f, %0.2f) when you shouldn''t have', ... - pt.X, pt.Y); - end - end - end - - if isempty(msg) && ~isequal(this.Limits(1:4), that.Limits(1:4)) + msg = 'Your axes aren''t lined up (did you call axis correctly?'; + elseif ~isequal(this.Limits(1:4), that.Limits(1:4)) msg = sprintf(['Your plot has Limits of [%0.2f, %0.2f, %0.2f, %0.2f], ', ... 'but we expected [%0.2f, %0.2f, %0.2f, %0.2f] (Did you call axis or xlim/ylim correctly?)'], ... this.Limits(1), this.Limits(2), this.Limits(3), this.Limits(4), ... that.Limits(1), that.Limits(2), that.Limits(3), that.Limits(4)); + elseif this.dataEquals(that) + % if data equals, we've alreay checked everything else. it has + % to be styles (points or lines) + msg = 'Your point or line styles are incorrect'; + elseif this.pointEquals(that) + % if not even data equals, some bad data there + msg = 'Your plot data differs from the solution'; + else + % At this point, we've checked: + % Alien + % Title, XYZ labels + % Position (Subplot) + % PlotBox (Axis) + % Limits + % Data + % + % If we reach here, we don't really know what's up. Tell the + % user we don't know - see a TA + msg = 'Your plot is different, but we can''t tell why. Please reach out to a TA, and include this feedback'; end + msg = sprintf('%s\nFor more information, please use checkPlots', msg); studPlot = img2base64(this.Image); solnPlot = img2base64(that.Image); diff --git a/Point.m b/Point.m index 420b452..ca83b6f 100644 --- a/Point.m +++ b/Point.m @@ -141,6 +141,4 @@ methods (Static) end -end - - +end \ No newline at end of file diff --git a/Segment.m b/Segment.m index 09c4289..92b6c36 100644 --- a/Segment.m +++ b/Segment.m @@ -143,51 +143,4 @@ sorted = reshape(sorted, size(segments)); end end -end -%% Plot: Class Containing Data for a Plot -% -% Holds data needed for each plot in fields. -% -% Has methods to check if a student's plot matches the solution, and to -% give feedback for the student plot. -% -%%% Fields -% -% * Segments: A structure array of all the segments in the plot -% -% * Points: A structure array of all the points in the plot -% -% * Title: A String of the title used for the plot -% -% * XLabel: A String of the xLabel used for the plot -% -% * YLabel: A String of the yLabel used for the plot -% -% * ZLabel: A String of the zLabel used for the plot -% -% * Position: A 1X4 double vector of the position of the axes in the figure -% window -% -% * PlotBox: A 1X3 vector representing the relative axis scale factors -% -% * Image: An image taken of the plot, as an MxNx3 uint8 array. -% -% * Legend: A string array of all the names in the legend -% -% * Limits: A 1x6 double vector representing the axes limits -% -%%% Methods -% -% * Plot -% -% * equals -% -% * generateFeedback -% -%%% Remarks -% -% The Plot class keeps all relevant data about a specific plot; note that -% a subplot is considered a single plot. Like the File class, the Plot -% class copies over any data necessary to recreate the plot entirely; as -% such, the plot can be deleted once a Plot object is created! -% \ No newline at end of file +end \ No newline at end of file From 2d42f03fd93a5c21d34d0342146f91aee7711b78 Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Tue, 13 Nov 2018 15:41:09 +0530 Subject: [PATCH 04/15] Fix bug where correct plots error checkPlots --- checkPlots.m | 1 - 1 file changed, 1 deletion(-) diff --git a/checkPlots.m b/checkPlots.m index d410c0a..fc9fa20 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -118,7 +118,6 @@ studs(s) = []; isFound = true; eq = true; - msg = ''; data.student = []; data.solution = []; end From f734a3759901f18c218f4a941716c7fc0c1678f2 Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Wed, 21 Nov 2018 23:11:28 -0500 Subject: [PATCH 05/15] Add significant performance improvements --- Plot.m | 63 +++++++------------------------------------------------ Point.m | 3 --- Segment.m | 4 ++-- 3 files changed, 10 insertions(+), 60 deletions(-) diff --git a/Plot.m b/Plot.m index 22ae178..695effd 100644 --- a/Plot.m +++ b/Plot.m @@ -370,6 +370,8 @@ % for all pts, if any point is identical, kill it points = unique(points); this.Points = points; + this.Segments = unique(this.Segments); + this.Points = unique(this.Points); end end methods (Access=public) @@ -505,62 +507,13 @@ % dataEquals is the same as equals, except it strictly checks point % and segment data - raw coordinates. function areEqual = dataEquals(this, that) - % Point Call - % for each point set, see if found in this - thatPoints = that.Points; - thisPoints = this.Points; - - for i = numel(thatPoints):-1:1 - thatPoint = thatPoints(i); - % look through thisSegs; once found, delete from both - isFound = false; - for j = numel(thisPoints):-1:1 - if thatPoint.dataEquals(thisPoints(j)) - isFound = true; - thisPoints(j) = []; - thatPoints(i) = []; - break; - end - end - if ~isFound - areEqual = false; - return; - end - end - if ~isempty(thisPoints) || ~isempty(thatPoints) + % already unique & sorted; just do dataEquals of points and + % segs + if numel(this.Points) ~= numel(that.Points) ... + || numel(this.Segments) ~= numel(that.Segments) areEqual = false; - return; - end - % Nothing should be left in either set; if both sets are - % non-empty, then false - - % Roll Call - % for each line segment in that, see if found in this - % Since they are unique, remove from both sets when found. - % Then, at end, if both are empty, equal; otherwise, unequal. - thatSegs = that.Segments; - thisSegs = this.Segments; - - for i = numel(thatSegs):-1:1 - thatSeg = thatSegs(i); - % look through thisSegs; once found, delete from both - isFound = false; - for j = numel(thisSegs):-1:1 - if thatSeg.dataEquals(thisSegs(j)) - isFound = true; - thisSegs(j) = []; - thatSegs(i) = []; - break; - end - end - if ~isFound - areEqual = false; - return; - end - end - % Nothing should be left in either set; if both sets are - % non-empty, then false - if ~isempty(thisSegs) || ~isempty(thatSegs) + elseif ~all(dataEquals(this.Points, that.Points)) ... + || ~all(dataEquals(this.Segments, that.Segments)) areEqual = false; else areEqual = true; diff --git a/Point.m b/Point.m index ca83b6f..e3063ea 100644 --- a/Point.m +++ b/Point.m @@ -138,7 +138,4 @@ end - - methods (Static) - end end \ No newline at end of file diff --git a/Segment.m b/Segment.m index 92b6c36..cbec33a 100644 --- a/Segment.m +++ b/Segment.m @@ -111,8 +111,8 @@ end function tf = dataEquals(this, that) - tf = [this.Start] == [that.Start] ... - & [this.Stop] == [that.Stop]; + tf = dataEquals([this.Start], [that.Start]) ... + & dataEquals([this.Stop], [that.Stop]); tf = reshape(tf, size(this)); end From 6977818858b03f9ba6123166f86a04df9c902967 Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Wed, 21 Nov 2018 23:12:20 -0500 Subject: [PATCH 06/15] Make minor fixes for showing of feedback --- checkPlots.m | 59 ++++++++++------------------------------------------ 1 file changed, 11 insertions(+), 48 deletions(-) diff --git a/checkPlots.m b/checkPlots.m index fc9fa20..568d54a 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -29,9 +29,9 @@ % warnings. % function [eq, msg, data] = checkPlots(fun, varargin) -ERROR_COLOR = [.45 0 0]; +ERROR_COLOR = [.85 0 0]; BAD_MARKER_SIZE = 12; -BAD_LINE_WIDTH = 2; +BAD_LINE_WIDTH = 10; BAD_FONT_FACTOR = 3; %#ok<*LAXES> % try to convert to function handle @@ -120,6 +120,7 @@ eq = true; data.student = []; data.solution = []; + break; end end if ~isFound @@ -248,72 +249,34 @@ if ~isequal(studPlot.Title, solnPlot.Title) studentAxes.Title.Color = ERROR_COLOR; studentAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; - solutionAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; + % solutionAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; msg{n}{end+1} = sprintf('Plot %d: Incorrect Title', n); end if ~isequal(studPlot.XLabel, solnPlot.XLabel) studentAxes.XLabel.Color = ERROR_COLOR; studentAxes.XLabel.FontSize = ... studentAxes.XLabel.FontSize * BAD_FONT_FACTOR; - solutionAxes.XLabel.FontSize = ... - solutionAxes.XLabel.FontSize * BAD_FONT_FACTOR; + % solutionAxes.XLabel.FontSize = ... + % solutionAxes.XLabel.FontSize * BAD_FONT_FACTOR; msg{n}{end+1} = sprintf('Plot %d: Incorrect XLabel', n); end if ~isequal(studPlot.YLabel, solnPlot.YLabel) studentAxes.YLabel.Color = ERROR_COLOR; studentAxes.YLabel.FontSize = ... studentAxes.YLabel.FontSize * BAD_FONT_FACTOR; - solutionAxes.YLabel.FontSize = ... - solutionAxes.YLabel.FontSize * BAD_FONT_FACTOR; + % solutionAxes.YLabel.FontSize = ... + % solutionAxes.YLabel.FontSize * BAD_FONT_FACTOR; msg{n}{end+1} = sprintf('Plot %d: Incorrect YLabel', n); end if ~isequal(studPlot.ZLabel, solnPlot.ZLabel) studentAxes.ZLabel.Color = ERROR_COLOR; studentAxes.ZLabel.FontSize = ... studentAxes.Label.FontSize * BAD_FONT_FACTOR; - solutionAxes.ZLabel.FontSize = ... - solutionAxes.Label.FontSize * BAD_FONT_FACTOR; + % solutionAxes.ZLabel.FontSize = ... + % solutionAxes.Label.FontSize * BAD_FONT_FACTOR; msg{n}{end+1} = sprintf('Plot %d: Incorrect ZLabel', n); end - - % We can first plot lines on both that are same. Use - % ismember to determine! - segs = solnPlot.Segments; - segs = segs(ismember(segs, studPlot.Segments)); - for s = numel(segs):-1:1 - seg = segs(s); - pts = [seg.Start seg.Stop]; - if any([pts.Z] ~= 0) - solnLine = plot3(solutionAxes, [pts.X], [pts.Y], [pts.Z]); - studLine = plot3(studentAxes, [pts.X], [pts.Y], [pts.Z]); - else - solnLine = plot(solutionAxes, [pts.X], [pts.Y]); - studLine = plot3(studentAxes, [pts.X], [pts.Y]); - end - solnLine.Color = seg.Color; - studLine.Color = seg.Color; - solnLine.LineStyle = seg.Style; - studLine.LineStyle = seg.Style; - end - - pts = solnPlot.Points; - pts = pts(ismember(pts, studPlot.Points)); - for p = numel(pts):-1:1 - pt = pts(p); - if pt.Z ~= 0 - solnPt = plot3(solutionAxes, pt.X, pt.Y, pt.Z); - studPt = plot3(studentAxes, pt.X, pt.Y, pt.Z); - else - solnPt = plot(solutionAxes, pt.X, pt.Y); - studPt = plot(studentAxes, pt.X, pt.Y); - end - solnPt.Marker = pt.Marker; - studPt.Marker = pt.Marker; - solnPt.Color = pt.Color; - studPt.Color = pt.Color; - end - - % now, it differs. First we'll plot everything on SOLUTION + % It differs. First we'll plot everything on SOLUTION % that isn't found in STUDENTS segs = solnPlot.Segments; segs = segs(~ismember(segs, studPlot.Segments)); From 1902668762b03ff49def64c435d1e80e15dd148b Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Wed, 21 Nov 2018 23:21:55 -0500 Subject: [PATCH 07/15] Move view creation to separate function --- checkPlots.m | 392 ++++++++++++++++++++++++++------------------------- 1 file changed, 198 insertions(+), 194 deletions(-) diff --git a/checkPlots.m b/checkPlots.m index 568d54a..daae39c 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -29,10 +29,6 @@ % warnings. % function [eq, msg, data] = checkPlots(fun, varargin) -ERROR_COLOR = [.85 0 0]; -BAD_MARKER_SIZE = 12; -BAD_LINE_WIDTH = 10; -BAD_FONT_FACTOR = 3; %#ok<*LAXES> % try to convert to function handle if ischar(fun) @@ -139,6 +135,7 @@ % * If no, then look at position (subplot) % % * If all else fails, pick one at random + isFound = false; for s = numel(studs):-1:1 studPlot = studs(s); @@ -189,195 +186,8 @@ warning('We couldn''t find a good match for this plot, so take any feedback with a grain of salt'); end eq = false; - % - % Feedback is given as bolding and/or changing colors. The - % following should be considered: - % * Title - % * Labels - % * Line Colors - % * Point Colors - % * Point Markers - % * Line Styles - % * Points that exist in one, but not other - % * Segments that exist in one, but not other - % - % Do the following: - % - % 1. If the title is messed up, color with ERROR_COLOR; - % 2. If the labels are messed up, color with ERROR_COLOR; - % 3. If a line is otherwise equal except for style or - % color, expand line width to BAD_SEGMENT_WIDTH - % 4. If a point is otherwise equal except for marker or - % color, expand marker size to BAD_MARKER_SIZE - % 5. If a point exists in only one, plot with - % BAD_MARKER_SIZE. - % 6. If a segment exists in only one, plot with - % BAD_LINE_WIDTH. - % 7. If the position is wrong, add text in middle of axes - % 8. If the axis style is wrong, add text in middle of axes - % 9. If the axis limits are wrong, color the Axis itself - % 10. If alien, add text in middle of axes - % - % Start by creating two plots. - %%% Title - solutionAxes = axes(solutionFigure, ... - 'Position', solnPlot.Position, ... - 'XLim', solnPlot.Limits(1:2), ... - 'YLim', solnPlot.Limits(3:4), ... - 'ZLim', solnPlot.Limits(5:6), ... - 'PlotBoxAspectRatio', solnPlot.PlotBox); - solutionAxes.XLabel.String = solnPlot.XLabel; - solutionAxes.YLabel.String = solnPlot.YLabel; - solutionAxes.ZLabel.String = solnPlot.ZLabel; - solutionAxes.Title.String = sprintf('[%d] %s', n, solnPlot.Title); - hold(solutionAxes, 'on'); - studentAxes = axes(studentFigure, ... - 'Position', solnPlot.Position, ... - 'XLim', studPlot.Limits(1:2), ... - 'YLim', studPlot.Limits(3:4), ... - 'ZLim', studPlot.Limits(5:6), ... - 'PlotBoxAspectRatio', studPlot.PlotBox); - studentAxes.XLabel.String = studPlot.XLabel; - studentAxes.YLabel.String = studPlot.YLabel; - studentAxes.ZLabel.String = studPlot.ZLabel; - studentAxes.Title.String = studPlot.Title; - hold(studentAxes, 'on'); - if ~studPlot.dataEquals(solnPlot) - msg{n}{end+1} = sprintf('Plot %d: Incorrect Data', n); - end - %%% Title - if ~isequal(studPlot.Title, solnPlot.Title) - studentAxes.Title.Color = ERROR_COLOR; - studentAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; - % solutionAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; - msg{n}{end+1} = sprintf('Plot %d: Incorrect Title', n); - end - if ~isequal(studPlot.XLabel, solnPlot.XLabel) - studentAxes.XLabel.Color = ERROR_COLOR; - studentAxes.XLabel.FontSize = ... - studentAxes.XLabel.FontSize * BAD_FONT_FACTOR; - % solutionAxes.XLabel.FontSize = ... - % solutionAxes.XLabel.FontSize * BAD_FONT_FACTOR; - msg{n}{end+1} = sprintf('Plot %d: Incorrect XLabel', n); - end - if ~isequal(studPlot.YLabel, solnPlot.YLabel) - studentAxes.YLabel.Color = ERROR_COLOR; - studentAxes.YLabel.FontSize = ... - studentAxes.YLabel.FontSize * BAD_FONT_FACTOR; - % solutionAxes.YLabel.FontSize = ... - % solutionAxes.YLabel.FontSize * BAD_FONT_FACTOR; - msg{n}{end+1} = sprintf('Plot %d: Incorrect YLabel', n); - end - if ~isequal(studPlot.ZLabel, solnPlot.ZLabel) - studentAxes.ZLabel.Color = ERROR_COLOR; - studentAxes.ZLabel.FontSize = ... - studentAxes.Label.FontSize * BAD_FONT_FACTOR; - % solutionAxes.ZLabel.FontSize = ... - % solutionAxes.Label.FontSize * BAD_FONT_FACTOR; - msg{n}{end+1} = sprintf('Plot %d: Incorrect ZLabel', n); - end - % It differs. First we'll plot everything on SOLUTION - % that isn't found in STUDENTS - segs = solnPlot.Segments; - segs = segs(~ismember(segs, studPlot.Segments)); - for s = numel(segs):-1:1 - seg = segs(s); - pts = [seg.Start seg.Stop]; - if any([pts.Z] ~= 0) - solnLine = plot3(solutionAxes, [pts.X], [pts.Y], [pts.Z]); - else - solnLine = plot(solutionAxes, [pts.X], [pts.Y]); - end - solnLine.Color = seg.Color; - solnLine.LineStyle = seg.Style; - solnLine.LineWidth = BAD_LINE_WIDTH; - end + msg{n} = createView(solnPlot, studPlot, solutionFigure, studentFigure, n); - segs = studPlot.Segments; - segs = segs(~ismember(segs, solnPlot.Segments)); - for s = numel(segs):-1:1 - seg = segs(s); - pts = [seg.Start seg.Stop]; - if any([pts.Z] ~= 0) - studLine = plot3(studentAxes, [pts.X], [pts.Y], [pts.Z]); - else - studLine = plot(studentAxes, [pts.X], [pts.Y]); - end - studLine.Color = seg.Color; - studLine.LineStyle = seg.Style; - studLine.LineWidth = BAD_LINE_WIDTH; - end - - pts = solnPlot.Points; - pts = pts(~ismember(pts, studPlot.Points)); - for p = numel(pts):-1:1 - pt = pts(p); - if pt.Z ~= 0 - solnPt = plot3(solutionAxes, pt.X, pt.Y, pt.Z); - else - solnPt = plot(solutionAxes, pt.X, pt.Y); - end - solnPt.Marker = pt.Marker; - solnPt.Color = pt.Color; - solnPt.MarkerSize = BAD_MARKER_SIZE; - end - - pts = studPlot.Points; - pts = pts(~ismember(pts, solnPlot.Points)); - for p = numel(pts):-1:1 - pt = pts(p); - if pt.Z ~= 0 - studPt = plot3(studentAxes, pt.X, pt.Y, pt.Z); - else - studPt = plot(studentAxes, pt.X, pt.Y); - end - studPt.Marker = pt.Marker; - studPt.Color = pt.Color; - studPt.MarkerSize = BAD_MARKER_SIZE; - end - - if ~isequal(studPlot.Limits(1:2), solnPlot.Limits(1:2)) - studentAxes.XColor = ERROR_COLOR; - msg{n}{end+1} = sprintf('Plot %d: Incorrect XLimits', n); - end - if ~isequal(studPlot.Limits(3:4), solnPlot.Limits(3:4)) - studentAxes.YColor = ERROR_COLOR; - msg{n}{end+1} = sprintf('Plot %d: Incorrect YLimits', n); - end - if ~isequal(studPlot.Limits(5:6), solnPlot.Limits(5:6)) - studentAxes.ZColor = ERROR_COLOR; - msg{n}{end+1} = sprintf('Plot %d: Incorrect ZLimits', n); - end - txtHelper = cell(3, 1); - if any(solnPlot.Position < (studPlot.Position - Plot.POSITION_MARGIN)) ... - || any(solnPlot.Position > (studPlot.Position + Plot.POSITION_MARGIN)) - txtHelper{1} = ... - 'Subplot Incorrect'; - msg{n}{end+1} = sprintf('Plot %d: Incorrect Position (did you remember to call subplot?)', n); - end - if any(solnPlot.PlotBox < (studPlot.PlotBox - Plot.POSITION_MARGIN)) ... - || any(solnPlot.PlotBox > (studPlot.PlotBox + Plot.POSITION_MARGIN)) - txtHelper{2} = ... - 'Axis Incorrect'; - msg{n}{end+1} = sprintf('Plot %d: Incorrect Aspect Ratio (did you forget to call axis square, or axis equal?)', n); - end - if studPlot.isAlien - txtHelper{3} = 'Invalid Data'; - msg{n}{end+1} = sprintf('Plot %d: You have invalid data - make sure you stick to plot and plot3 for plotting data!', n); - end - txtHelper(cellfun(@isempty, txtHelper)) = []; - if ~isempty(txtHelper) - txtHelper = text(mean(studPlot.Limits(1:2)), ... - mean(studPlot.Limits(3:4)), ... - txtHelper); - txtHelper.HorizontalAlignment = 'center'; - txtHelper.VerticalAlignment = 'middle'; - txtHelper.Color = ERROR_COLOR; - txtHelper.BackgroundColor = [1 1 1]; - txtHelper.FontUnits = 'normalized'; - drawnow; - txtHelper.FontSize = 2 * txtHelper.FontSize; - end end end msg = [msg{:}]; @@ -405,5 +215,199 @@ plots = []; end end - - \ No newline at end of file + +function message = createView(solnPlot, studPlot, solutionFigure, studentFigure, n) +ERROR_COLOR = [.85 0 0]; +BAD_MARKER_SIZE = 12; +BAD_LINE_WIDTH = 10; +BAD_FONT_FACTOR = 3; + % Feedback is given as bolding and/or changing colors. The + % following should be considered: + % * Title + % * Labels + % * Line Colors + % * Point Colors + % * Point Markers + % * Line Styles + % * Points that exist in one, but not other + % * Segments that exist in one, but not other + % + % Do the following: + % + % 1. If the title is messed up, color with ERROR_COLOR; + % 2. If the labels are messed up, color with ERROR_COLOR; + % 3. If a line is otherwise equal except for style or + % color, expand line width to BAD_SEGMENT_WIDTH + % 4. If a point is otherwise equal except for marker or + % color, expand marker size to BAD_MARKER_SIZE + % 5. If a point exists in only one, plot with + % BAD_MARKER_SIZE. + % 6. If a segment exists in only one, plot with + % BAD_LINE_WIDTH. + % 7. If the position is wrong, add text in middle of axes + % 8. If the axis style is wrong, add text in middle of axes + % 9. If the axis limits are wrong, color the Axis itself + % 10. If alien, add text in middle of axes + % + % Start by creating two plots. + %%% Title + message = cell(0); + solutionAxes = axes(solutionFigure, ... + 'Position', solnPlot.Position, ... + 'XLim', solnPlot.Limits(1:2), ... + 'YLim', solnPlot.Limits(3:4), ... + 'ZLim', solnPlot.Limits(5:6), ... + 'PlotBoxAspectRatio', solnPlot.PlotBox); + solutionAxes.XLabel.String = solnPlot.XLabel; + solutionAxes.YLabel.String = solnPlot.YLabel; + solutionAxes.ZLabel.String = solnPlot.ZLabel; + solutionAxes.Title.String = sprintf('[%d] %s', n, solnPlot.Title); + hold(solutionAxes, 'on'); + studentAxes = axes(studentFigure, ... + 'Position', solnPlot.Position, ... + 'XLim', studPlot.Limits(1:2), ... + 'YLim', studPlot.Limits(3:4), ... + 'ZLim', studPlot.Limits(5:6), ... + 'PlotBoxAspectRatio', studPlot.PlotBox); + studentAxes.XLabel.String = studPlot.XLabel; + studentAxes.YLabel.String = studPlot.YLabel; + studentAxes.ZLabel.String = studPlot.ZLabel; + studentAxes.Title.String = studPlot.Title; + hold(studentAxes, 'on'); + if ~studPlot.dataEquals(solnPlot) + message{end+1} = sprintf('Plot %d: Incorrect Data', n); + end + %%% Title + if ~isequal(studPlot.Title, solnPlot.Title) + studentAxes.Title.Color = ERROR_COLOR; + studentAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; + % solutionAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; + message{end+1} = sprintf('Plot %d: Incorrect Title', n); + end + if ~isequal(studPlot.XLabel, solnPlot.XLabel) + studentAxes.XLabel.Color = ERROR_COLOR; + studentAxes.XLabel.FontSize = ... + studentAxes.XLabel.FontSize * BAD_FONT_FACTOR; + % solutionAxes.XLabel.FontSize = ... + % solutionAxes.XLabel.FontSize * BAD_FONT_FACTOR; + message{end+1} = sprintf('Plot %d: Incorrect XLabel', n); + end + if ~isequal(studPlot.YLabel, solnPlot.YLabel) + studentAxes.YLabel.Color = ERROR_COLOR; + studentAxes.YLabel.FontSize = ... + studentAxes.YLabel.FontSize * BAD_FONT_FACTOR; + % solutionAxes.YLabel.FontSize = ... + % solutionAxes.YLabel.FontSize * BAD_FONT_FACTOR; + message{end+1} = sprintf('Plot %d: Incorrect YLabel', n); + end + if ~isequal(studPlot.ZLabel, solnPlot.ZLabel) + studentAxes.ZLabel.Color = ERROR_COLOR; + studentAxes.ZLabel.FontSize = ... + studentAxes.Label.FontSize * BAD_FONT_FACTOR; + % solutionAxes.ZLabel.FontSize = ... + % solutionAxes.Label.FontSize * BAD_FONT_FACTOR; + message{end+1} = sprintf('Plot %d: Incorrect ZLabel', n); + end + % It differs. First we'll plot everything on SOLUTION + % that isn't found in STUDENTS + segs = solnPlot.Segments; + segs = segs(~ismember(segs, studPlot.Segments)); + for s = numel(segs):-1:1 + seg = segs(s); + pts = [seg.Start seg.Stop]; + if any([pts.Z] ~= 0) + solnLine = plot3(solutionAxes, [pts.X], [pts.Y], [pts.Z]); + else + solnLine = plot(solutionAxes, [pts.X], [pts.Y]); + end + solnLine.Color = seg.Color; + solnLine.LineStyle = seg.Style; + solnLine.LineWidth = BAD_LINE_WIDTH; + end + + segs = studPlot.Segments; + segs = segs(~ismember(segs, solnPlot.Segments)); + for s = numel(segs):-1:1 + seg = segs(s); + pts = [seg.Start seg.Stop]; + if any([pts.Z] ~= 0) + studLine = plot3(studentAxes, [pts.X], [pts.Y], [pts.Z]); + else + studLine = plot(studentAxes, [pts.X], [pts.Y]); + end + studLine.Color = seg.Color; + studLine.LineStyle = seg.Style; + studLine.LineWidth = BAD_LINE_WIDTH; + end + + pts = solnPlot.Points; + pts = pts(~ismember(pts, studPlot.Points)); + for p = numel(pts):-1:1 + pt = pts(p); + if pt.Z ~= 0 + solnPt = plot3(solutionAxes, pt.X, pt.Y, pt.Z); + else + solnPt = plot(solutionAxes, pt.X, pt.Y); + end + solnPt.Marker = pt.Marker; + solnPt.Color = pt.Color; + solnPt.MarkerSize = BAD_MARKER_SIZE; + end + + pts = studPlot.Points; + pts = pts(~ismember(pts, solnPlot.Points)); + for p = numel(pts):-1:1 + pt = pts(p); + if pt.Z ~= 0 + studPt = plot3(studentAxes, pt.X, pt.Y, pt.Z); + else + studPt = plot(studentAxes, pt.X, pt.Y); + end + studPt.Marker = pt.Marker; + studPt.Color = pt.Color; + studPt.MarkerSize = BAD_MARKER_SIZE; + end + + if ~isequal(studPlot.Limits(1:2), solnPlot.Limits(1:2)) + studentAxes.XColor = ERROR_COLOR; + message{end+1} = sprintf('Plot %d: Incorrect XLimits', n); + end + if ~isequal(studPlot.Limits(3:4), solnPlot.Limits(3:4)) + studentAxes.YColor = ERROR_COLOR; + message{end+1} = sprintf('Plot %d: Incorrect YLimits', n); + end + if ~isequal(studPlot.Limits(5:6), solnPlot.Limits(5:6)) + studentAxes.ZColor = ERROR_COLOR; + message{end+1} = sprintf('Plot %d: Incorrect ZLimits', n); + end + txtHelper = cell(3, 1); + if any(solnPlot.Position < (studPlot.Position - Plot.POSITION_MARGIN)) ... + || any(solnPlot.Position > (studPlot.Position + Plot.POSITION_MARGIN)) + txtHelper{1} = ... + 'Subplot Incorrect'; + message{end+1} = sprintf('Plot %d: Incorrect Position (did you remember to call subplot?)', n); + end + if any(solnPlot.PlotBox < (studPlot.PlotBox - Plot.POSITION_MARGIN)) ... + || any(solnPlot.PlotBox > (studPlot.PlotBox + Plot.POSITION_MARGIN)) + txtHelper{2} = ... + 'Axis Incorrect'; + message{end+1} = sprintf('Plot %d: Incorrect Aspect Ratio (did you forget to call axis square, or axis equal?)', n); + end + if studPlot.isAlien + txtHelper{3} = 'Invalid Data'; + message{end+1} = sprintf('Plot %d: You have invalid data - make sure you stick to plot and plot3 for plotting data!', n); + end + txtHelper(cellfun(@isempty, txtHelper)) = []; + if ~isempty(txtHelper) + txtHelper = text(mean(studPlot.Limits(1:2)), ... + mean(studPlot.Limits(3:4)), ... + txtHelper); + txtHelper.HorizontalAlignment = 'center'; + txtHelper.VerticalAlignment = 'middle'; + txtHelper.Color = ERROR_COLOR; + txtHelper.BackgroundColor = [1 1 1]; + txtHelper.FontUnits = 'normalized'; + drawnow; + txtHelper.FontSize = 2 * txtHelper.FontSize; + end +end \ No newline at end of file From e3c86260929921c2e2570e2730e1261ab9c80c67 Mon Sep 17 00:00:00 2001 From: Hannah White Date: Sat, 24 Nov 2018 10:48:32 -0500 Subject: [PATCH 08/15] Restructures order of plot matching --- checkPlots.m | 165 +++++++++++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 70 deletions(-) diff --git a/checkPlots.m b/checkPlots.m index fc9fa20..6337d64 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -105,14 +105,104 @@ 'NumberTitle', 'off', ... 'Name', 'Student Plot(s)'); msg = cell(1, numel(solns)); - % same number of plots; now loop through + % same number of plots; now loop through layers + + i = numel(solns); + % First, find any plots that are exactly equal: + for n = numel(solns):-1:1 + solnPlot = solns(n); + for s = numel(studs):-1:1 + studPlot = studs(s); + if solnPlot.equals(studPlot) + solnsOrdered(i) = solns(n); + studsOrdered(i) = studs(s); + solns(n) = []; + studs(s) = []; + i = i - 1; + end + end + + end + + % Layer 2: for any unmatched plots, find where the data is equal, + % ignoring other elements + for n = numel(solns):-1:1 + solnPlot = solns(n); + for s = numel(studs):-1:1 + studPlot = studs(s); + if solnPlot.dataEquals(studPlot) + solnsOrdered(i) = solns(n); + studsOrdered(i) = studs(s); + solns(n) = []; + studs(s) = []; + i = i - 1; + end + end + end + + % Layer 3: for any unmatched plots, find where the points are + % equal, ignoring other elements + for n = numel(solns):-1:1 + solnPlot = solns(n); + for s = numel(studs):-1:1 + studPlot = studs(s); + if solnPlot.pointEquals(studPlot) + solnsOrdered(i) = solns(n); + studsOrdered(i) = studs(s); + solns(n) = []; + studs(s) = []; + i = i - 1; + end + end + end + + + % Layer 4: or any unmatched plots, find where the data is close, + % still ignoring other elements + for n = numel(solns):-1:1 + solnPlot = solns(n); + for s = numel(studs):-1:1 + studPlot = studs(s); + + pMatch = 0; + for p = solnPlot.Points + if ismember(p,studPlot.Points) + pMatch = pMatch + 1; + end + end + if pMatch > (numel(solnPlot.Points) - 0.1 * numel(solnPlot.Points)) + solnsOrdered(i) = solns(n); + studsOrdered(i) = studs(s); + solns(n) = []; + studs(s) = []; + i = i - 1; + end + end + end + + % Layer 5: or any unmatched plots, find if any plot is remaining in + % the same position + for n = numel(solns):-1:1 + solnPlot = solns(n); + for s = numel(studs):-1:1 + studPlot = studs(s); + if isequal(solnPlot.Position,studPlot.Position) + solnsOrdered(i) = solns(n); + studsOrdered(i) = studs(s); + solns(n) = []; + studs(s) = []; + i = i - 1; + end + end + end + + for n = numel(solns):-1:1 msg{n} = cell(0); solnPlot = solns(n); - isFound = false; for s = numel(studs):-1:1 studPlot = studs(s); - % if we find an equal one, remove from both + % if we find an equal one, add to remove from both if studPlot.equals(solnPlot) solns(n) = []; studs(s) = []; @@ -122,72 +212,8 @@ data.solution = []; end end - if ~isFound - % we couldn't find an equal one. Loop through and find - % "closest" one, compare and report - - % * First, plots with the exact same segments and points - % should match up - % - % * If no, then recheck - this time, just look at raw X, Y, - % and Z data. If it matches, then that's our plot - % - % * If no, then we can't really be sure what they meant. - % Look at the Title - % - % * If no, then look at position (subplot) - % - % * If all else fails, pick one at random - isFound = false; - for s = numel(studs):-1:1 - studPlot = studs(s); - % Check all segments and points, separately. - if studPlot.dataEquals(solnPlot) - % exact match! - isFound = true; - break; - end - end - - if ~isFound - % Data Match failed; check raw X, Y, Z - for s = numel(studs):-1:1 - studPlot = studs(s); - % if ismember is all true, match! - if studPlot.pointEquals(solnPlot) - isFound = true; - break; - end - end - end - - if ~isFound - % Data never matched; try next (Title) - for s = numel(studs):-1:1 - studPlot = studs(s); - if strcmpi(studPlot.Title, solnPlot.Title) - isFound = true; - break; - end - end - end - if ~isFound - % Title never matched; look for plot in same position - for s = numel(studs):-1:1 - studPlot = studs(s); - if isequal(studPlot.Position, solnPlot.Position) - isFound = true; - break; - end - end - end - - if ~isFound - % we can't find anything. Just get first and engage - studPlot = studs(1); - warning('We couldn''t find a good match for this plot, so take any feedback with a grain of salt'); - end - eq = false; + + end % % Feedback is given as bolding and/or changing colors. The % following should be considered: @@ -425,7 +451,6 @@ end end -end function plots = populatePlots() % Get all handles; since the Position is captured, that can be used From 36e94cc9c2a681988f72aa32f4d49205dc41ac76 Mon Sep 17 00:00:00 2001 From: Hannah White Date: Sat, 24 Nov 2018 10:54:45 -0500 Subject: [PATCH 09/15] Add matching by title --- checkPlots.m | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/checkPlots.m b/checkPlots.m index 6337d64..4f89bb6 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -110,6 +110,7 @@ i = numel(solns); % First, find any plots that are exactly equal: for n = numel(solns):-1:1 + msg{n} = cell(0); solnPlot = solns(n); for s = numel(studs):-1:1 studPlot = studs(s); @@ -196,24 +197,23 @@ end end - + % Layer 5: or any unmatched plots, find if any plot remaining has + % the same title for n = numel(solns):-1:1 - msg{n} = cell(0); solnPlot = solns(n); for s = numel(studs):-1:1 studPlot = studs(s); - % if we find an equal one, add to remove from both - if studPlot.equals(solnPlot) - solns(n) = []; - studs(s) = []; - isFound = true; - eq = true; - data.student = []; - data.solution = []; + if isequal(solnPlot.Title,studPlot.Title) + solnsOrdered(i) = solns(n); + studsOrdered(i) = studs(s); + solns(n) = []; + studs(s) = []; + i = i - 1; end - end - - end + end + end + + % % Feedback is given as bolding and/or changing colors. The % following should be considered: From b4f52d7c615cf84acf73e0cb41651733b02bc01f Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Fri, 30 Nov 2018 17:48:58 -0500 Subject: [PATCH 10/15] Fix minor bugs --- checkPlots.m | 111 ++++++++++++++++++++++++++++------ unitTests/fiveLinePass.m | 5 +- unitTests/fiveLinePass_soln.m | 1 + 3 files changed, 95 insertions(+), 22 deletions(-) diff --git a/checkPlots.m b/checkPlots.m index daae39c..17c0518 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -186,7 +186,10 @@ warning('We couldn''t find a good match for this plot, so take any feedback with a grain of salt'); end eq = false; - msg{n} = createView(solnPlot, studPlot, solutionFigure, studentFigure, n); + [msg{n}, data] = createView(solnPlot, studPlot, n, ... + 'solutionFigure', solutionFigure, ... + 'studentFigure', studentFigure); + end end @@ -195,6 +198,9 @@ msg = strjoin(msg, newline); solutionFigure.Visible = 'on'; studentFigure.Visible = 'on'; + else + close(studentFigure); + close(solutionFigure); end end @@ -216,10 +222,26 @@ end end -function message = createView(solnPlot, studPlot, solutionFigure, studentFigure, n) +%% createView: Create Feedback View for two plots +% +% createView will plot two plots together, and will show the feedback via +% the plot itself, as well as an output message. +% +% M = createView( +function [message, data] = createView(solnPlot, studPlot, n, varargin) +p = inputParser; +p.FunctionName = 'checkPlots'; +p.addRequired('studentFigure'); +p.addRequired('solutionFigure'); + +p.parse(varargin{:}); + +solutionFigure = p.Results.solutionFigure; +studentFigure = p.Results.studentFigure; + ERROR_COLOR = [.85 0 0]; BAD_MARKER_SIZE = 12; -BAD_LINE_WIDTH = 10; +BAD_LINE_WIDTH = 5; BAD_FONT_FACTOR = 3; % Feedback is given as bolding and/or changing colors. The % following should be considered: @@ -261,7 +283,12 @@ solutionAxes.XLabel.String = solnPlot.XLabel; solutionAxes.YLabel.String = solnPlot.YLabel; solutionAxes.ZLabel.String = solnPlot.ZLabel; - solutionAxes.Title.String = sprintf('[%d] %s', n, solnPlot.Title); + if ~isempty(solnPlot.Title) + n = solnPlot.Title; + elseif isnumeric(n) + n = num2str(n); + end + solutionAxes.Title.String = solnPlot.Title; hold(solutionAxes, 'on'); studentAxes = axes(studentFigure, ... 'Position', solnPlot.Position, ... @@ -275,14 +302,14 @@ studentAxes.Title.String = studPlot.Title; hold(studentAxes, 'on'); if ~studPlot.dataEquals(solnPlot) - message{end+1} = sprintf('Plot %d: Incorrect Data', n); + message{end+1} = sprintf('Plot %s: Incorrect Data', n); end %%% Title if ~isequal(studPlot.Title, solnPlot.Title) studentAxes.Title.Color = ERROR_COLOR; studentAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; % solutionAxes.TitleFontSizeMultiplier = BAD_FONT_FACTOR; - message{end+1} = sprintf('Plot %d: Incorrect Title', n); + message{end+1} = sprintf('Plot %s: Incorrect Title', n); end if ~isequal(studPlot.XLabel, solnPlot.XLabel) studentAxes.XLabel.Color = ERROR_COLOR; @@ -290,7 +317,7 @@ studentAxes.XLabel.FontSize * BAD_FONT_FACTOR; % solutionAxes.XLabel.FontSize = ... % solutionAxes.XLabel.FontSize * BAD_FONT_FACTOR; - message{end+1} = sprintf('Plot %d: Incorrect XLabel', n); + message{end+1} = sprintf('Plot %s: Incorrect XLabel', n); end if ~isequal(studPlot.YLabel, solnPlot.YLabel) studentAxes.YLabel.Color = ERROR_COLOR; @@ -298,7 +325,7 @@ studentAxes.YLabel.FontSize * BAD_FONT_FACTOR; % solutionAxes.YLabel.FontSize = ... % solutionAxes.YLabel.FontSize * BAD_FONT_FACTOR; - message{end+1} = sprintf('Plot %d: Incorrect YLabel', n); + message{end+1} = sprintf('Plot %s: Incorrect YLabel', n); end if ~isequal(studPlot.ZLabel, solnPlot.ZLabel) studentAxes.ZLabel.Color = ERROR_COLOR; @@ -306,8 +333,29 @@ studentAxes.Label.FontSize * BAD_FONT_FACTOR; % solutionAxes.ZLabel.FontSize = ... % solutionAxes.Label.FontSize * BAD_FONT_FACTOR; - message{end+1} = sprintf('Plot %d: Incorrect ZLabel', n); + message{end+1} = sprintf('Plot %s: Incorrect ZLabel', n); + end + % plot everything that is the same + segs = solnPlot.Segments; + segs = segs(ismember(segs, studPlot.Segments)); + for s = numel(segs):-1:1 + seg = segs(s); + pts = [seg.Start seg.Stop]; + if any([pts.Z] ~= 0) + solnLine = plot3(solutionAxes, [pts.X], [pts.Y], [pts.Z]); + studLine = plot3(studentAxes, [pts.X], [pts.Y], [pts.Z]); + else + solnLine = plot(solutionAxes, [pts.X], [pts.Y]); + studLine = plot(studentAxes, [pts.X], [pts.Y]); + end + studLine.Color = seg.Color; + studLine.LineStyle = seg.Style; + solnLine.Color = seg.Color; + solnLine.LineStyle = seg.Style; + data.studSegments(s) = studLine; + data.solnSegments(s) = solnLine; end + % It differs. First we'll plot everything on SOLUTION % that isn't found in STUDENTS segs = solnPlot.Segments; @@ -320,7 +368,7 @@ else solnLine = plot(solutionAxes, [pts.X], [pts.Y]); end - solnLine.Color = seg.Color; + solnLine.Color = ERROR_COLOR; solnLine.LineStyle = seg.Style; solnLine.LineWidth = BAD_LINE_WIDTH; end @@ -335,22 +383,41 @@ else studLine = plot(studentAxes, [pts.X], [pts.Y]); end - studLine.Color = seg.Color; + studLine.Color = ERROR_COLOR; studLine.LineStyle = seg.Style; studLine.LineWidth = BAD_LINE_WIDTH; end + % plot all points pts = solnPlot.Points; - pts = pts(~ismember(pts, studPlot.Points)); + pts = pts(ismember(pts, studPlot.Points)); for p = numel(pts):-1:1 pt = pts(p); if pt.Z ~= 0 solnPt = plot3(solutionAxes, pt.X, pt.Y, pt.Z); + studPt = plot3(studentAxes, pt.X, pt.Y, pt.Z); else solnPt = plot(solutionAxes, pt.X, pt.Y); + studPt = plot(studentAxes, pt.X, pt.Y); end solnPt.Marker = pt.Marker; solnPt.Color = pt.Color; + studPt.Marker = pt.Marker; + studPt.Color = pt.Color; + data.solnPoints(p) = solnPt; + data.studPoints(p) = studPt; + end + pts = solnPlot.Points; + pts = pts(~ismember(pts, studPlot.Points)); + for p = numel(pts):-1:1 + pt = pts(p); + if pt.Z ~= 0 + solnPt = plot3(solutionAxes, pt.X, pt.Y, pt.Z); + else + solnPt = plot(solutionAxes, pt.X, pt.Y); + end + solnPt.Marker = pt.Marker; + solnPt.Color = ERROR_COLOR; solnPt.MarkerSize = BAD_MARKER_SIZE; end @@ -364,38 +431,38 @@ studPt = plot(studentAxes, pt.X, pt.Y); end studPt.Marker = pt.Marker; - studPt.Color = pt.Color; + studPt.Color = ERROR_COLOR; studPt.MarkerSize = BAD_MARKER_SIZE; end if ~isequal(studPlot.Limits(1:2), solnPlot.Limits(1:2)) studentAxes.XColor = ERROR_COLOR; - message{end+1} = sprintf('Plot %d: Incorrect XLimits', n); + message{end+1} = sprintf('Plot %s: Incorrect XLimits', n); end if ~isequal(studPlot.Limits(3:4), solnPlot.Limits(3:4)) studentAxes.YColor = ERROR_COLOR; - message{end+1} = sprintf('Plot %d: Incorrect YLimits', n); + message{end+1} = sprintf('Plot %s: Incorrect YLimits', n); end if ~isequal(studPlot.Limits(5:6), solnPlot.Limits(5:6)) studentAxes.ZColor = ERROR_COLOR; - message{end+1} = sprintf('Plot %d: Incorrect ZLimits', n); + message{end+1} = sprintf('Plot %s: Incorrect ZLimits', n); end txtHelper = cell(3, 1); if any(solnPlot.Position < (studPlot.Position - Plot.POSITION_MARGIN)) ... || any(solnPlot.Position > (studPlot.Position + Plot.POSITION_MARGIN)) txtHelper{1} = ... 'Subplot Incorrect'; - message{end+1} = sprintf('Plot %d: Incorrect Position (did you remember to call subplot?)', n); + message{end+1} = sprintf('Plot %s: Incorrect Position (did you remember to call subplot?)', n); end if any(solnPlot.PlotBox < (studPlot.PlotBox - Plot.POSITION_MARGIN)) ... || any(solnPlot.PlotBox > (studPlot.PlotBox + Plot.POSITION_MARGIN)) txtHelper{2} = ... 'Axis Incorrect'; - message{end+1} = sprintf('Plot %d: Incorrect Aspect Ratio (did you forget to call axis square, or axis equal?)', n); + message{end+1} = sprintf('Plot %s: Incorrect Aspect Ratio (did you forget to call axis square, or axis equal?)', n); end if studPlot.isAlien txtHelper{3} = 'Invalid Data'; - message{end+1} = sprintf('Plot %d: You have invalid data - make sure you stick to plot and plot3 for plotting data!', n); + message{end+1} = sprintf('Plot %s: You have invalid data - make sure you stick to plot and plot3 for plotting data!', n); end txtHelper(cellfun(@isempty, txtHelper)) = []; if ~isempty(txtHelper) @@ -410,4 +477,8 @@ drawnow; txtHelper.FontSize = 2 * txtHelper.FontSize; end -end \ No newline at end of file +end + +function toggleVisibility(segs) + +end \ No newline at end of file diff --git a/unitTests/fiveLinePass.m b/unitTests/fiveLinePass.m index 4d74b71..740a4a9 100644 --- a/unitTests/fiveLinePass.m +++ b/unitTests/fiveLinePass.m @@ -3,9 +3,10 @@ % One line, all passing % function fiveLinePass - plot(1:100, 1:100, 'b--'); + hold on; + plot(1:120, 1:120, 'b--'); plot(2:200, 200:-1:2, 'k-'); plot(5:-1:1, 10:-1:6, 'c*-.'); - plot(1:2:100, 1:2:100, 'kd'); + plot(1:2:104, 1:2:104, 'kd'); plot(1, 3, 'v'); end \ No newline at end of file diff --git a/unitTests/fiveLinePass_soln.m b/unitTests/fiveLinePass_soln.m index 0062515..74eeb44 100644 --- a/unitTests/fiveLinePass_soln.m +++ b/unitTests/fiveLinePass_soln.m @@ -3,6 +3,7 @@ % One line, all passing % function fiveLinePass_soln + hold on; plot(1:100, 1:100, 'b--'); plot(2:200, 200:-1:2, 'k-'); plot(5:-1:1, 10:-1:6, 'c*-.'); From fc655a14eff2a08c0ce8f9e2b831247095e31559 Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Sat, 1 Dec 2018 13:52:55 -0500 Subject: [PATCH 11/15] Add button for toggle of visibility --- checkPlots.m | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/checkPlots.m b/checkPlots.m index e30539e..11bf866 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -199,14 +199,33 @@ studs(1) = []; end msg = cell(1, numel(solnsOrdered)); - data = cell(1, numel(solnsOrdered)); - for n = 1:numel(solnsOrdered) - [msg{n}, data] = createView(solnPlot, studPlot, n, ... + for n = numel(solnsOrdered):-1:1 + [msg{n}, data(n)] = createView(solnPlot, studPlot, n, ... 'solutionFigure', solutionFigure, ... 'studentFigure', studentFigure); end + msg = [msg{:}]; if ~isempty(msg) + % create button + pts = [data.studPoints, ... + data.studSegments, ... + data.solnPoints, ... + data.solnSegments]; + BTN_WIDTH = 200; + BTN_HEIGHT = 20; + tmpPosn = solutionFigure.Position; + + posn = [(tmpPosn(3) - BTN_WIDTH) / 2, ... + tmpPosn(4) - BTN_HEIGHT - 5, ... + BTN_WIDTH, BTN_HEIGHT]; + uicontrol(solutionFigure, ... + 'style', 'pushbutton', ... + 'String', 'Hide correct data', ... + 'Callback', {@toggleVisibility, pts}, ... + 'HorizontalAlignment', 'center', ... + 'Position', posn, ... + 'FontSize', 20); msg = strjoin(msg, newline); solutionFigure.Visible = 'on'; studentFigure.Visible = 'on'; @@ -285,6 +304,10 @@ % % Start by creating two plots. %%% Title + data.solnSegments = reshape(plot([]), 1, 0); + data.studSegments = reshape(plot([]), 1, 0); + data.solnPoints = reshape(plot([]), 1, 0); + data.studPoints = reshape(plot([]), 1, 0); message = cell(0); solutionAxes = axes(solutionFigure, ... 'Position', solnPlot.Position, ... @@ -491,6 +514,12 @@ end end -function toggleVisibility(segs) - +function toggleVisibility(button, ~, segs) + if ~isempty(segs) && strcmpi(segs(1).Visible, 'on') + [segs.Visible] = deal('off'); + button.String = 'Show correct data'; + elseif ~isempty(segs) + [segs.Visible] = deal('on'); + button.String = 'Hide correct data'; + end end \ No newline at end of file From db2806403835807ff5c743f85ad0dd279ce408b6 Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Mon, 3 Dec 2018 14:50:04 -0500 Subject: [PATCH 12/15] Fix minor bugs --- Segment.m | 10 +++++++--- build.m | 2 ++ checkPlots.m | 4 +++- unitTests/fiveLinePass_soln.m | 4 ++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Segment.m b/Segment.m index cbec33a..c355471 100644 --- a/Segment.m +++ b/Segment.m @@ -111,9 +111,13 @@ end function tf = dataEquals(this, that) - tf = dataEquals([this.Start], [that.Start]) ... - & dataEquals([this.Stop], [that.Stop]); - tf = reshape(tf, size(this)); + if isempty(this) || isempty(that) + tf = []; + else + tf = dataEquals([this.Start], [that.Start]) ... + & dataEquals([this.Stop], [that.Stop]); + tf = reshape(tf, size(this)); + end end function [sorted, inds] = sort(segments, varargin) diff --git a/build.m b/build.m index 0db9709..8b12b82 100644 --- a/build.m +++ b/build.m @@ -17,6 +17,8 @@ delete(['.' filesep 'release' filesep '*']); % pcode Plot pcode('Plot.m'); + pcode('Segment.m'); + pcode('Point.m'); pcode('checkPlots.m'); movefile('*.p', ['.' filesep 'release']); diff --git a/checkPlots.m b/checkPlots.m index 11bf866..6f0c704 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -28,7 +28,7 @@ % Any exceptions thrown by the student are caught and re-issued as % warnings. % -function [eq, msg, data] = checkPlots(fun, varargin) +function [eq, msg] = checkPlots(fun, varargin) %#ok<*LAXES> % try to convert to function handle if ischar(fun) @@ -207,6 +207,7 @@ msg = [msg{:}]; if ~isempty(msg) + eq = false; % create button pts = [data.studPoints, ... data.studSegments, ... @@ -230,6 +231,7 @@ solutionFigure.Visible = 'on'; studentFigure.Visible = 'on'; else + eq = true; close(studentFigure); close(solutionFigure); end diff --git a/unitTests/fiveLinePass_soln.m b/unitTests/fiveLinePass_soln.m index 74eeb44..18d34b7 100644 --- a/unitTests/fiveLinePass_soln.m +++ b/unitTests/fiveLinePass_soln.m @@ -4,9 +4,9 @@ % function fiveLinePass_soln hold on; - plot(1:100, 1:100, 'b--'); + plot(1:120, 1:120, 'b--'); plot(2:200, 200:-1:2, 'k-'); plot(5:-1:1, 10:-1:6, 'c*-.'); - plot(1:2:100, 1:2:100, 'kd'); + plot(1:2:104, 1:2:104, 'kd'); plot(1, 3, 'v'); end \ No newline at end of file From 00270e1d05bd7f9a7ee8c30d68b927088592596b Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Sat, 8 Dec 2018 19:23:25 -0500 Subject: [PATCH 13/15] Fix #4 --- checkPlots.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checkPlots.m b/checkPlots.m index 6f0c704..869d177 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -328,7 +328,7 @@ solutionAxes.Title.String = solnPlot.Title; hold(solutionAxes, 'on'); studentAxes = axes(studentFigure, ... - 'Position', solnPlot.Position, ... + 'Position', studPlot.Position, ... 'XLim', studPlot.Limits(1:2), ... 'YLim', studPlot.Limits(3:4), ... 'ZLim', studPlot.Limits(5:6), ... From 5d4a700b3d50a8bf83bbc4ffd74175032d1d65e9 Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Sat, 8 Dec 2018 19:44:16 -0500 Subject: [PATCH 14/15] Fix #4, Fix #5 --- checkPlots.m | 10 +++++----- unitTests/subplots.m | 10 ++++++++++ unitTests/subplots_soln.m | 10 ++++++++++ unitTests/subplots_test.m | 17 +++++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 unitTests/subplots.m create mode 100644 unitTests/subplots_soln.m create mode 100644 unitTests/subplots_test.m diff --git a/checkPlots.m b/checkPlots.m index 869d177..0b73bea 100644 --- a/checkPlots.m +++ b/checkPlots.m @@ -200,9 +200,11 @@ end msg = cell(1, numel(solnsOrdered)); for n = numel(solnsOrdered):-1:1 - [msg{n}, data(n)] = createView(solnPlot, studPlot, n, ... - 'solutionFigure', solutionFigure, ... - 'studentFigure', studentFigure); + [msg{n}, data(n)] = createView(solnsOrdered(n), ... + studsOrdered(n), ... + n, ... + 'solutionFigure', solutionFigure, ... + 'studentFigure', studentFigure); end msg = [msg{:}]; @@ -244,10 +246,8 @@ % for the subplot checking pHandles = findobj(0, 'type', 'axes'); if numel(pHandles) ~= 0 - figure('Visible','off') plots(numel(pHandles)) = Plot(pHandles(end)); for i = 1:(numel(pHandles) - 1) - figure('Visible','off') plots(i) = Plot(pHandles(i)); end else diff --git a/unitTests/subplots.m b/unitTests/subplots.m new file mode 100644 index 0000000..a5f1601 --- /dev/null +++ b/unitTests/subplots.m @@ -0,0 +1,10 @@ +function subplots + subplot(2, 2, 2) + plot(1:100, 1:100, 'b--'); + subplot(2, 2, 1) + plot(1:5:50, 1:5:50, 'g*'); + subplot(2, 2, 3) + plot(80:-9:1, 1:9:80, 'r-*'); + subplot(2, 2, 4) + plot(5:20, 5:20, 'k'); +end \ No newline at end of file diff --git a/unitTests/subplots_soln.m b/unitTests/subplots_soln.m new file mode 100644 index 0000000..f3cf4bc --- /dev/null +++ b/unitTests/subplots_soln.m @@ -0,0 +1,10 @@ +function subplots_soln + subplot(2, 2, 1) + plot(1:100, 1:100, 'b--'); + subplot(2, 2, 2) + plot(1:5:50, 1:5:50, 'g*'); + subplot(2, 2, 3) + plot(80:-9:1, 1:9:80, 'r-*'); + subplot(2, 2, 4) + plot(5:20, 5:20, 'k'); +end \ No newline at end of file diff --git a/unitTests/subplots_test.m b/unitTests/subplots_test.m new file mode 100644 index 0000000..c0984ec --- /dev/null +++ b/unitTests/subplots_test.m @@ -0,0 +1,17 @@ +function [passed, msg] = subplots_test() + +try + [isSame, ~] = checkPlots(@subplots); +catch e + passed = false; + msg = sprintf('Expected false; got exception %s', e.identifier); + return; +end +if isSame + passed = false; + msg = 'Expected false; got true'; + return; +end +passed = true; +msg = ''; +end \ No newline at end of file From 067ae46c2990b5445ddc14a9e32b10c382c43c00 Mon Sep 17 00:00:00 2001 From: Alexander Rao Date: Sun, 9 Dec 2018 17:33:13 -0500 Subject: [PATCH 15/15] Copy from new Autograder Plot.m --- Plot.m | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Plot.m b/Plot.m index 695effd..8716384 100644 --- a/Plot.m +++ b/Plot.m @@ -600,23 +600,11 @@ % if data equals, we've alreay checked everything else. it has % to be styles (points or lines) msg = 'Your point or line styles are incorrect'; - elseif this.pointEquals(that) + else % if not even data equals, some bad data there msg = 'Your plot data differs from the solution'; - else - % At this point, we've checked: - % Alien - % Title, XYZ labels - % Position (Subplot) - % PlotBox (Axis) - % Limits - % Data - % - % If we reach here, we don't really know what's up. Tell the - % user we don't know - see a TA - msg = 'Your plot is different, but we can''t tell why. Please reach out to a TA, and include this feedback'; end - msg = sprintf('%s\nFor more information, please use checkPlots', msg); + msg = sprintf('%s. For more information, please use checkPlots', msg); studPlot = img2base64(this.Image); solnPlot = img2base64(that.Image);