From 9817581d5a8da9485d2d2ba887fa18b0d36a3308 Mon Sep 17 00:00:00 2001 From: Gilberto Galvis Date: Fri, 22 Oct 2021 11:11:19 -0400 Subject: [PATCH] fix issue #430 and make sure quiver3 works for any case --- .../handlegraphics/updateQuiver.m | 530 +++++++++++++----- 1 file changed, 377 insertions(+), 153 deletions(-) diff --git a/plotly/plotlyfig_aux/handlegraphics/updateQuiver.m b/plotly/plotlyfig_aux/handlegraphics/updateQuiver.m index 57524ede..af2d0e94 100644 --- a/plotly/plotlyfig_aux/handlegraphics/updateQuiver.m +++ b/plotly/plotlyfig_aux/handlegraphics/updateQuiver.m @@ -1,210 +1,434 @@ -function obj = updateQuiver(obj, quiverIndex) +function obj = updateQuiver(obj, dataIndex) -%-------------------------------------------------------------------------% + %-------------------------------------------------------------------------% -%-AXIS INDEX-% -axIndex = obj.getAxisIndex(obj.State.Plot(quiverIndex).AssociatedAxis); + %-INITIALIZATIONS-% -%-QUIVER DATA STRUCTURE- % -quiver_data = get(obj.State.Plot(quiverIndex).Handle); + %-get structures-% + axIndex = obj.getAxisIndex(obj.State.Plot(dataIndex).AssociatedAxis); + plotData = get(obj.State.Plot(dataIndex).Handle); + [xSource, ySource] = findSourceAxis(obj,axIndex); -%-CHECK FOR MULTIPLE AXES-% -[xsource, ysource] = findSourceAxis(obj,axIndex); + %-get trace data-% + xData = plotData.XData; + yData = plotData.YData; + zData = plotData.ZData; -%-AXIS DATA-% -eval(['xaxis = obj.layout.xaxis' num2str(xsource) ';']); -eval(['yaxis = obj.layout.yaxis' num2str(ysource) ';']); + if isvector(xData), [xData, yData] = meshgrid(xData, yData); end -%-------------------------------------------------------------------------% + if strcmpi(plotData.AutoScale, 'on') + scaleFactor = getScaleFactor(xData, plotData.UData, 45); + else + scaleFactor = 1; + end + + uData = plotData.UData * scaleFactor; + vData = plotData.VData * scaleFactor; + wData = plotData.WData * scaleFactor; + + %-check if is 3D quiver-% + isQuiver3D = ~isempty(zData); -%-quiver xaxis-% -obj.data{quiverIndex}.xaxis = ['x' num2str(xsource)]; + %-update axis-% + if isQuiver3D, updateScene(obj, dataIndex); end -%-------------------------------------------------------------------------% + %-------------------------------------------------------------------------% -%-quiver yaxis-% -obj.data{quiverIndex}.yaxis = ['y' num2str(ysource)]; + %-set trace-% + if isQuiver3D + obj.data{dataIndex}.type = 'scatter3d'; + obj.data{dataIndex}.scene = sprintf('scene%d', xSource); + else + obj.data{dataIndex}.type = 'scatter'; + obj.data{dataIndex}.xaxis = sprintf('x%d', xSource); + obj.data{dataIndex}.yaxis = sprintf('y%d', xSource); + end -%------------------------------------------------------------------------% + obj.data{dataIndex}.mode = 'lines'; + obj.data{dataIndex}.visible = strcmp(plotData.Visible,'on'); + obj.data{dataIndex}.name = plotData.DisplayName; -%-quiver type-% -obj.data{quiverIndex}.type = 'scatter'; + %------------------------------------------------------------------------% -%-------------------------------------------------------------------------% + %-quiver line color-% + lineColor = 255 * plotData.Color; + obj.data{dataIndex}.line.color = getStringColor(lineColor); -%-quiver visible-% -obj.data{quiverIndex}.visible = strcmp(quiver_data.Visible,'on'); + %-quiver line width-% + obj.data{dataIndex}.line.width = 2.5 * plotData.LineWidth; -%------------------------------------------------------------------------% + %------------------------------------------------------------------------% + + %-set trace data for quiver line only-% + m = 1; -%-quiver mode-% -obj.data{quiverIndex}.mode = 'lines'; + for n = 1:numel(xData) -%-------------------------------------------------------------------------% + obj.data{dataIndex}.x(m) = xData(n); + obj.data{dataIndex}.x(m+1) = xData(n) + uData(n); + obj.data{dataIndex}.x(m+2) = nan; + + obj.data{dataIndex}.y(m) = yData(n); + obj.data{dataIndex}.y(m+1) = yData(n) + vData(n); + obj.data{dataIndex}.y(m+2) = nan; + + if isQuiver3D + obj.data{dataIndex}.z(m) = zData(n); + obj.data{dataIndex}.z(m+1) = zData(n) + wData(n); + obj.data{dataIndex}.z(m+2) = nan; + end + + m = m + 3; + end -%-scatter name-% -obj.data{quiverIndex}.name = quiver_data.DisplayName; + %-------------------------------------------------------------------------% -%------------------------------------------------------------------------% + %-set trace data for quiver barb-% + if isHG2() && strcmp(plotData.ShowArrowHead, 'on') + + maxHeadSize = plotData.MaxHeadSize * 1.5; + headWidth = 20; + + for n = 1:numel(xData) + + if isQuiver3D + quiverBarb = getQuiverBarb3D(... + xData(n), yData(n), zData(n), ... + uData(n), vData(n), wData(n), ... + maxHeadSize, headWidth, 'simple' ... + ); + else + quiverBarb = getQuiverBarb2D(... + xData(n), yData(n), ... + uData(n), vData(n), ... + maxHeadSize, headWidth ... + ); + end + + for m = 1:size(quiverBarb, 2) + obj.data{dataIndex}.x(end+1) = quiverBarb(1, m); + obj.data{dataIndex}.y(end+1) = quiverBarb(2, m); + + if isQuiver3D + obj.data{dataIndex}.z(end+1) = quiverBarb(3, m); + end + end + end + end -%-quiver line color-% -col = 255*quiver_data.Color; -obj.data{quiverIndex}.line.color = ['rgb(' num2str(col(1)) ',' num2str(col(2)) ',' num2str(col(3)) ')']; + %-------------------------------------------------------------------------% -%------------------------------------------------------------------------% + %-set trace legend-% + leg = get(plotData.Annotation); + legInfo = get(leg.LegendInformation); -%-quiver line width-% -obj.data{quiverIndex}.line.width = 2 * quiver_data.LineWidth; + switch legInfo.IconDisplayStyle + case 'on' + showLeg = true; + case 'off' + showLeg = false; + end -%------------------------------------------------------------------------% + obj.data{dataIndex}.showlegend = showLeg; -% check for x/y vectors -if isvector(quiver_data.XData) - [quiver_data.XData, quiver_data.YData] = meshgrid(quiver_data.XData,quiver_data.YData); + %-------------------------------------------------------------------------% end -%-get scale factor-% -if strcmpi(quiver_data.AutoScale, 'on') - xdata = quiver_data.XData; - udata = quiver_data.UData; +function updateScene(obj, dataIndex) - nsteps = 45; - steps = linspace(1e-3, 1, nsteps); - steps = steps(end:-1:1); + %-------------------------------------------------------------------------% - for n = 1:nsteps - scalefactor = steps(n); + %-INITIALIZATIONS-% + axIndex = obj.getAxisIndex(obj.State.Plot(dataIndex).AssociatedAxis); + plotData = get(obj.State.Plot(dataIndex).Handle); + axisData = get(plotData.Parent); + [xSource, ~] = findSourceAxis(obj, axIndex); + scene = eval( sprintf('obj.layout.scene%d', xSource) ); - x = xdata(:, 2:end, :); - u = xdata(:, 1:end-1,:) + scalefactor * udata(:, 1:end-1,:); - xflag = x>u; + aspectRatio = axisData.PlotBoxAspectRatio; + cameraPosition = axisData.CameraPosition; + dataAspectRatio = axisData.DataAspectRatio; + cameraUpVector = axisData.CameraUpVector; + cameraEye = cameraPosition./dataAspectRatio; - if all(xflag(:)) - break - end + try + normFac = 0.42 - 0.105 * (size(axisData.Layout.TileSpan, 2) - 1); + normFac = normFac * abs(min(cameraEye)); + catch + normFac = 0.42 * abs(min(cameraEye)); end -else - scalefactor = 1; + + %-------------------------------------------------------------------------% + + %-aspect ratio-% + scene.aspectratio.x = 1.0*aspectRatio(1); + scene.aspectratio.y = 1.0*aspectRatio(2); + scene.aspectratio.z = 1.0*aspectRatio(3); + + %-camera eye-% + scene.camera.eye.x = cameraEye(1) / normFac; + scene.camera.eye.y = cameraEye(2) / normFac; + scene.camera.eye.z = cameraEye(3) / normFac; + + %-camera up-% + scene.camera.up.x = cameraUpVector(1); + scene.camera.up.y = cameraUpVector(2); + scene.camera.up.z = cameraUpVector(3); + + %-camera projection-% + % scene.camera.projection.type = axisData.Projection; + + %-------------------------------------------------------------------------% + + %-scene axis configuration-% + rangeFac = 0.0; + + xRange = range(axisData.XLim); + scene.xaxis.range(1) = axisData.XLim(1) - rangeFac * xRange; + scene.xaxis.range(2) = axisData.XLim(2) + rangeFac * xRange; + + yRange = range(axisData.YLim); + scene.yaxis.range(1) = axisData.YLim(1) - rangeFac * yRange; + scene.yaxis.range(2) = axisData.YLim(2) + rangeFac * yRange; + + zRange = range(axisData.ZLim); + scene.zaxis.range(1) = axisData.ZLim(1) - rangeFac * zRange; + scene.zaxis.range(2) = axisData.ZLim(2) + rangeFac * zRange; + + scene.xaxis.zeroline = false; + scene.yaxis.zeroline = false; + scene.zaxis.zeroline = false; + + scene.xaxis.showline = true; + scene.yaxis.showline = true; + scene.zaxis.showline = true; + + scene.xaxis.ticklabelposition = 'outside'; + scene.yaxis.ticklabelposition = 'outside'; + scene.zaxis.ticklabelposition = 'outside'; + + scene.xaxis.title = axisData.XLabel.String; + scene.yaxis.title = axisData.YLabel.String; + scene.zaxis.title = axisData.ZLabel.String; + + %-tick labels-% + scene.xaxis.tickvals = axisData.XTick; + scene.xaxis.ticktext = axisData.XTickLabel; + scene.yaxis.tickvals = axisData.YTick; + scene.yaxis.ticktext = axisData.YTickLabel; + scene.zaxis.tickvals = axisData.ZTick; + scene.zaxis.ticktext = axisData.ZTickLabel; + + scene.xaxis.tickcolor = 'rgba(0,0,0,1)'; + scene.yaxis.tickcolor = 'rgba(0,0,0,1)'; + scene.zaxis.tickcolor = 'rgba(0,0,0,1)'; + scene.xaxis.tickfont.size = axisData.FontSize; + scene.yaxis.tickfont.size = axisData.FontSize; + scene.zaxis.tickfont.size = axisData.FontSize; + scene.xaxis.tickfont.family = matlab2plotlyfont(axisData.FontName); + scene.yaxis.tickfont.family = matlab2plotlyfont(axisData.FontName); + scene.zaxis.tickfont.family = matlab2plotlyfont(axisData.FontName); + + %-grid-% + if strcmp(axisData.XGrid, 'off'), scene.xaxis.showgrid = false; end + if strcmp(axisData.YGrid, 'off'), scene.yaxis.showgrid = false; end + if strcmp(axisData.ZGrid, 'off'), scene.zaxis.showgrid = false; end + + %-------------------------------------------------------------------------% + + %-SET SCENE TO LAYOUT-% + obj.layout = setfield(obj.layout, sprintf('scene%d', xSource), scene); + + %-------------------------------------------------------------------------% end -%------------------------------------------------------------------------% -%-format data-% -xdata = quiver_data.XData(:); -ydata = quiver_data.YData(:); -udata = quiver_data.UData(:)*scalefactor; -vdata = quiver_data.VData(:)*scalefactor; -%------------------------------------------------------------------------% +function quiverBarb = getQuiverBarb2D(... + xData, yData, ... + uData, vData, ... + maxHeadSize, headWidth ... + ) + + %-------------------------------------------------------------------------% + + %-initializations-% + + refVector = [uData; vData]; + refLen = norm(refVector); + invRefLen = 1/refLen; + + xRefAngle = acos( ([1,0]*refVector) * invRefLen ); + yRefAngle = acos( ([0,1]*refVector) * invRefLen ); + refAngle = [xRefAngle; yRefAngle]; -%-quiver x-% -m = 1; -for n = 1:length(xdata) -obj.data{quiverIndex}.x(m) = xdata(n); -obj.data{quiverIndex}.x(m+1) = xdata(n) + udata(n); -obj.data{quiverIndex}.x(m+2) = nan; -m = m + 3; + xHead = xData + uData; + yHead = yData + vData; + head = [xHead; yHead]; + + %-------------------------------------------------------------------------% + + quiverBarb = getBarb2D(head, refAngle, refLen, maxHeadSize, headWidth); + + %-------------------------------------------------------------------------% end -%------------------------------------------------------------------------% +function barb = getBarb2D(head, refAngle, refLen, maxHeadSize, headWidth) -%-quiver y-% -m = 1; -for n = 1:length(ydata) -obj.data{quiverIndex}.y(m) = ydata(n); -obj.data{quiverIndex}.y(m+1) = ydata(n) + vdata(n); -obj.data{quiverIndex}.y(m+2) = nan; -m = m + 3; + refPoint = -maxHeadSize * refLen * cos(refAngle'); + rotPoint1 = rotation2D(refPoint, deg2rad(headWidth)); + rotPoint2 = rotation2D(refPoint, deg2rad(-headWidth)); + + barbPoint1 = translation2D(rotPoint1, head); + barbPoint2 = translation2D(rotPoint2, head); + + barb = [barbPoint1', head, barbPoint2', NaN(2,1)]; end -%------------------------------------------------------------------------% +function outPoint = translation2D(inPoint, offsetPoint) + xt = offsetPoint(1); yt = offsetPoint(2); -%-quiver z-% + T = affine2d(... + [... + 1 , 0 , 0; ... + 0 , 1 , 0; ... + xt, yt, 1 ... + ]... + ); -% check for 3D plot -flag3d = ~isempty(quiver_data.ZData); + outPoint = transformPointsForward(T, inPoint); +end -if flag3d - - %-format data-% - zdata = quiver_data.ZData(:); - wdata = quiver_data.WData(:)*scalefactor; - - %-set 3d data-% - m = 1; - for n = 1:length(ydata) - obj.data{quiverIndex}.z(m) = zdata(n); - obj.data{quiverIndex}.z(m+1) = zdata(n) + wdata(n); - obj.data{quiverIndex}.z(m+2) = nan; - m = m + 3; - end - - %-scatter 3d type-% - obj.data{quiverIndex}.type = 'scatter3d'; +function outPoint = rotation2D(inPoint, phi) + T = affine2d(... + [... + cos(phi) , sin(phi), 0; ... + -sin(phi), cos(phi), 0; ... + 0 , 0 , 1; ... + ]... + ); + + outPoint = transformPointsForward(T, inPoint); end -%-------------------------------------------------------------------------% -%-quiver barbs-% -if isHG2() && strcmp(quiver_data.ShowArrowHead, 'on') - - % 'MaxHeadSize' scalar, matlab clips to 0.2 in r2014b - maxheadsize = quiver_data.MaxHeadSize; - % barb angular width, not supported by matlab - head_width = deg2rad(17.5); + +function quiverBarb = getQuiverBarb3D(... + xData, yData, zData, ... + uData, vData, wData, ... + maxHeadSize, headWidth, barbMode ... + ) - for n = 1:length(xdata) - % length of arrow - l = norm([udata(n), vdata(n)]); - - % angle of arrow - phi = atan2(vdata(n),udata(n)); - - % make barb with specified angular width, length is prop. to arrow - barb = [... - [-maxheadsize*l*cos(head_width), maxheadsize*l*sin(head_width)]; ... - [0, 0]; ... - [-maxheadsize*l*cos(head_width), -maxheadsize*l*sin(head_width)]; ... - [nan, nan]; ... - ]'; - - % affine matrix: rotate by arrow angle and translate to end of arrow - barb_transformation = affine2d([... - [cos(phi), sin(phi), 0]; ... - [-sin(phi), cos(phi), 0]; ... - [xdata(n) + udata(n), ydata(n) + vdata(n), 1]; - ]); - - % place barb at end of arrow - barb = transformPointsForward(barb_transformation, barb')'; - - % add barb to plot data, inserting is optimized in matlab >2010 - for col = 1:4 - obj.data{quiverIndex}.x(end+1) = barb(1,col); % point 1 - obj.data{quiverIndex}.y(end+1) = barb(2,col); - - if flag3d - obj.data{quiverIndex}.z(end+1) = zdata(n); - end - end + %-------------------------------------------------------------------------% + + %-initializations-% + + refVector = [uData; vData; wData]; + refLen = norm(refVector); + invRefLen = 1/refLen; + + xRefAngle = acos( ([1,0,0]*refVector) * invRefLen ); + yRefAngle = acos( ([0,1,0]*refVector) * invRefLen ); + zRefAngle = acos( ([0,0,1]*refVector) * invRefLen ); + refAngle = [xRefAngle; yRefAngle; zRefAngle]; + + xHead = xData + uData; + yHead = yData + vData; + zHead = zData + wData; + head = [xHead; yHead; zHead]; + + %-------------------------------------------------------------------------% + + xBarb = getBarb3D(head, refAngle, refLen, maxHeadSize, headWidth, 'x'); + yBarb = getBarb3D(head, refAngle, refLen, maxHeadSize, headWidth, 'y'); + zBarb = getBarb3D(head, refAngle, refLen, maxHeadSize, headWidth, 'z'); + + if strcmp(barbMode, 'extend') + quiverBarb = [xBarb, yBarb, zBarb]; + elseif strcmp(barbMode, 'simple') + quiverBarb1 = mean([xBarb(:,1), yBarb(:,1), zBarb(:,1)], 2); + quiverBarb2 = mean([xBarb(:,3), yBarb(:,3), zBarb(:,3)], 2); + quiverBarb = [quiverBarb1, xBarb(:,2), quiverBarb2, xBarb(:,4)]; end + + %-------------------------------------------------------------------------% end -%-------------------------------------------------------------------------% +function barb = getBarb3D(head, refAngle, refLen, maxHeadSize, headWidth, ... + refAxis) + + refPoint = -maxHeadSize * refLen * cos(refAngle'); + rotPoint1 = rotation3D(refPoint, deg2rad(headWidth), refAxis); + rotPoint2 = rotation3D(refPoint, deg2rad(-headWidth), refAxis); -%-scatter showlegend-% -leg = get(quiver_data.Annotation); -legInfo = get(leg.LegendInformation); + barbPoint1 = translation3D(rotPoint1, head); + barbPoint2 = translation3D(rotPoint2, head); -switch legInfo.IconDisplayStyle - case 'on' - showleg = true; - case 'off' - showleg = false; + barb = [barbPoint1', head, barbPoint2', NaN(3,1)]; end -obj.data{quiverIndex}.showlegend = showleg; +function outPoint = translation3D(inPoint, offsetPoint) + xt = offsetPoint(1); yt = offsetPoint(2); zt = offsetPoint(3); -%-------------------------------------------------------------------------% + T = affine3d(... + [... + 1 , 0 , 0 , 0; ... + 0 , 1 , 0 , 0; ... + 0 , 0 , 1 , 0; ... + xt, yt, zt, 1 ... + ]... + ); + + outPoint = transformPointsForward(T, inPoint); +end +function outPoint = rotation3D(inPoint, phi, refAxis) + switch refAxis + case 'x' + T = affine3d(... + [... + 1, 0 , 0 , 0; ... + 0, cos(phi) , sin(phi) , 0; ... + 0, -sin(phi), cos(phi) , 0; ... + 0, 0 , 0 , 1 ... + + ]... + ); + + case 'y' + T = affine3d(... + [... + cos(phi), 0, -sin(phi), 0; ... + 0 , 1, 0 , 0; ... + sin(phi), 0, cos(phi) , 0; ... + 0 , 0, 0 , 1 ... + ]... + ); + + case 'z' + T = affine3d(... + [... + cos(phi) , sin(phi), 0, 0; ... + -sin(phi), cos(phi), 0, 0; ... + 0 , 0 , 1, 0; ... + 0 , 0 , 0, 1 ... + ]... + ); + end + + outPoint = transformPointsForward(T, inPoint); +end + + + +function scaleFactor = getScaleFactor(xData, uData, nSteps) + xStep = max( abs(diff( mean(xData, 1) )) ); + uStep = max(abs(uData(:))); + + scaleFactor = 0.8 * xStep/uStep; +end + +function stringColor = getStringColor(numColor) + + stringColor = sprintf('rgb(%f,%f,%f)', numColor); end \ No newline at end of file