Search code examples
matlabplotmatlab-figure

How to plot very low negative values in Matlab


I want to plot the following - see below.

The reason that I want to use semilogy - or something else, maybe you have suggestions? - is that the data goes so low that the scale is such that all the positive data appears to be zero.

Of course semilogy doesn't work with negative data. But what can I do? The goal is that positive and negative data are somehow visible in the plot as different from zero.

I saw this question (Positive & Negitive Log10 Scale Y axis in Matlab), but is there a simpler way?

Another issue I have with the semilogy command is that the data are plotted as if they go from November to April, whereas they really go from January to June!

%% Date vector
Y = [];
for year = 2008:2016
    Y = vertcat(Y,[year;year]);
end
M = repmat([01;07],9,1);
D = [01];
vector = datetime(Y,M,D);

%% Data
operatingValue=...
   1.0e+05 *...
   [0.020080000000000,   0.000010000000000,   0.000430446606112,   0.000286376498540,   0.000013493575572,   0.000008797774209;...
    0.020080000000000,   0.000020000000000,   0.000586846360023,   0.000445575962649,   0.000118642085670,   0.000105982759202;...
    0.020090000000000,   0.000010000000000,   0.000304503221392,   0.000168068072591,  -0.000004277640797,   0.000006977580173;...
    0.020090000000000,   0.000020000000000,   0.000471819542315,   0.000318827321824,   0.000165018495621,   0.000188500216550;...
    0.020100000000000,   0.000010000000000,   0.000366527395452,   0.000218539902929,   0.000032265798656,   0.000038839492621;...
    0.020100000000000,   0.000020000000000,   0.000318807172600,   0.000170892065948,  -0.000093830970932,  -0.000096575559444;...
    0.020110000000000,   0.000010000000000,   0.000341114962826,   0.000187311222835,  -0.000118595282218,  -0.000135188693035;...
    0.020110000000000,   0.000020000000000,   0.000266317725166,   0.000128625220303,  -0.000314547081599,  -0.000392868178754;...
    0.020120000000000,   0.000010000000000,   0.000104302824558,  -0.000000079359646,  -0.001817533087893,  -0.002027417507676;...
    0.020120000000000,   0.000020000000000,   0.000093484465168,  -0.000019260661622,  -0.002180826237198,  -0.001955577709102;...
    0.020130000000000,   0.000010000000000,   0.000052921606827,  -0.000175185193313,  -4.034665389612666,  -4.573270848282296;...
    0.020130000000000,   0.000020000000000,   0.000027218083520,  -0.000167098897097,                   0,                   0;...
    0.020140000000000,   0.000010000000000,   0.000044907412504,  -0.000106127286095,  -0.012248660549809,  -0.010693498138601;...
    0.020140000000000,   0.000020000000000,   0.000061663936450,  -0.000070280400096,  -0.015180683545658,  -0.008942771925367;...
    0.020150000000000,   0.000010000000000,   0.000029214681162,  -0.000190870890021,                   0,                   0;...
    0.020150000000000,   0.000020000000000,   0.000082672707169,  -0.000031566292849,  -0.003226048850797,  -0.003527284081616;...
    0.020160000000000,   0.000010000000000,   0.000084562787728,  -0.000024916156477,  -0.001438488940835,  -0.000954872893879;...
    0.020160000000000,   0.000020000000000,   0.000178181932848,   0.000054988621755,  -0.000172520970578,  -0.000139835312255]

figure;
semilogy( datenum(vector), operatingValue(:,3), '-+', datenum(vector), operatingValue(:,4), '-o',...
    datenum(vector), operatingValue(:,5), '-*', datenum(vector), operatingValue(:,6), '-x',...
    'LineWidth',1.2 ), grid on;
dateaxis('x', 12);

Solution

  • Save the function symlog in your directory.

     function symlog(varargin)
        % SYMLOG bi-symmetric logarithmic axes scaling
        %   SYMLOG applies a modified logarithm scale to the specified or current
        %   axes that handles negative values while maintaining continuity across
        %   zero. The transformation is defined in an article from the journal
        %   Measurement Science and Technology (Webber, 2012):
        %
        %     y = sign(x)*(log10(1+abs(x)/(10^C)))
        %
        %   where the scaling constant C determines the resolution of the data
        %   around zero. The smallest order of magnitude shown on either side of
        %   zero will be 10^ceil(C).
        %
        %   SYMLOG(ax=gca, var='xyz', C=0) applies this scaling to the axes named
        %   by letter in the specified axes using the default C of zero. Any of the
        %   inputs can be ommitted in which case the default values will be used.
        %
        %   SYMLOG uses the UserData attribute of the specified axes to record the
        %   current transformation applied so that subsequent calls to symlog
        %   operate on the original data rather than the newly transformed data.
        %
        % Example:
        %   x = linspace(-50,50,1e4+1);
        %   y1 = x;
        %   y2 = sin(x);
        %
        %   subplot(2,4,1)
        %   plot(x,y1,x,y2)
        %
        %   subplot(2,4,2)
        %   plot(x,y1,x,y2)
        %   set(gca,'XScale','log') % throws warning
        %
        %   subplot(2,4,3)
        %   plot(x,y1,x,y2)
        %   set(gca,'YScale','log') % throws warning
        %
        %   subplot(2,4,4)
        %   plot(x,y1,x,y2)
        %   set(gca,'XScale','log','YScale','log') % throws warning
        %
        %   subplot(2,4,6)
        %   plot(x,y1,x,y2)
        %   symlog('x')
        %
        %   s = subplot(2,4,7);
        %   plot(x,y1,x,y2)
        %   symlog(s,'y') % can but don't have to provide s.
        %
        %   subplot(2,4,8)
        %   plot(x,y1,x,y2)
        %   symlog() % no harm in letting symlog operate in z axis, too.
        %
        % Created by:
        %   Robert Perrotta
        %
        % Referencing:
        %   Webber, J. Beau W. "A Bi-Symmetric Log Transformation for Wide-Range
        %   Data." Measurement Science and Technology 24.2 (2012): 027001.
        %   Retrieved 6/28/2016 from
        %   https://kar.kent.ac.uk/32810/2/2012_Bi-symmetric-log-transformation_v5.pdf
    
        % default values
        ax = []; % don't call gca unless needed
        var = 'xyz';
        C = 0;
    
        % user-specified values
        for ii = 1:length(varargin)
            switch class(varargin{ii})
                case 'matlab.graphics.axis.Axes'
                    ax = varargin{ii};
                case 'char'
                    var = varargin{ii};
                case {'double','single'}
                    C = varargin{ii};
                otherwise
                    error('Don''t know what to do with input %d (type %s)!',ii,class(varargin{ii}))
            end
        end
    
        if isempty(ax) % user did not specify a value
            ax = gca;
        end
    
        % execute once per axis
        if length(var) > 1
            for ii = 1:length(var)
                symlog(ax,var(ii),C);
            end
            return
        end
    
        % From here on we redefine C to be 10^C
        C = 10^C;
    
        % Axes must be in linear scaling
        set(ax,[var,'Scale'],'linear')
    
        % Check for existing transformation
        userdata = get(ax,'UserData');
        if isfield(userdata,'symlog') && isfield(userdata.symlog,lower(var))
            lastC = userdata.symlog.(lower(var));
        else
            lastC = [];
        end
        userdata.symlog.(lower(var)) = C; % update with new value
        set(ax,'UserData',userdata)
    
    
        if strcmpi(get(ax,[var,'LimMode']),'manual')
            lim = get(ax,[var,'Lim']);
            lim = sign(lim).*log10(1+abs(lim)/C);
            set(ax,[var,'Lim'],lim)
        end
    
        % transform all objects in this plot into logarithmic coordiates
        transform_graph_objects(ax, var, C, lastC);
    
        % transform axes labels to match
        t0 = max(abs(get(ax,[var,'Lim']))); % MATLAB's automatically-chosen limits
        t0 = sign(t0)*C*(10.^(abs(t0))-1);
        t0 = sign(t0).*log10(abs(t0));
        t0 = ceil(log10(C)):ceil(t0); % use C to determine lowest resolution
        t1 = 10.^t0;
    
        mt1 = nan(1,8*(length(t1))); % 8 minor ticks between each tick
        for ii = 1:length(t0)
            scale = t1(ii)/10;
            mt1(8*(ii-1)+(1:8)) = t1(ii) - (8:-1:1)*scale;
        end
    
        % mirror over zero to get the negative ticks
        t0 = [fliplr(t0),-inf,t0];
        t1 = [-fliplr(t1),0,t1];
        mt1 = [-fliplr(mt1),mt1];
    
        % the location of our ticks in the transformed space
        t1 = sign(t1).*log10(1+abs(t1)/C);
        mt1 = sign(mt1).*log10(1+abs(mt1)/C);
        lbl = cell(size(t0));
        for ii = 1:length(t0)
            if t1(ii) == 0
                lbl{ii} = '0';
        % uncomment to display +/- 10^0 as +/- 1
        %     elseif t0(ii) == 0
        %         if t1(ii) < 0
        %             lbl{ii} = '-1';
        %         else
        %             lbl{ii} = '1';
        %         end
            elseif t1(ii) < 0
                lbl{ii} = ['-10^{',num2str(t0(ii)),'}'];
            elseif t1(ii) > 0
                lbl{ii} = ['10^{',num2str(t0(ii)),'}'];
            else
                lbl{ii} = '0';
            end
        end
        set(ax,[var,'Tick'],t1,[var,'TickLabel'],lbl)
        set(ax,[var,'MinorTick'],'on',[var,'MinorGrid'],'on')
        rl = get(ax,[var,'Ruler']);
        try
            set(rl,'MinorTick',mt1)
        catch err
            if strcmp(err.identifier,'MATLAB:datatypes:onoffboolean:IncorrectValue')
                set(rl,'MinorTickValues',mt1)
            else
                rethrow(err)
            end
        end
    
    
    
        function transform_graph_objects(ax, var, C, lastC)
        % transform all lines in this plot
        lines = findobj(ax,'Type','line');
        for ii = 1:length(lines)
            x = get(lines(ii),[var,'Data']);
            if ~isempty(lastC) % undo previous transformation
                x = sign(x).*lastC.*(10.^abs(x)-1);
            end
            x = sign(x).*log10(1+abs(x)/C);
            set(lines(ii),[var,'Data'],x)
        end
    
        % transform all Patches in this plot
        patches = findobj(ax,'Type','Patch');
        for ii = 1:length(patches)
            x = get(patches(ii),[var,'Data']);
            if ~isempty(lastC) % undo previous transformation
                x = sign(x).*lastC.*(10.^abs(x)-1);
            end
            x = sign(x).*log10(1+abs(x)/C);
            set(patches(ii),[var,'Data'],x)
        end
    
        % transform all Retangles in this plot
        rectangles = findobj(ax,'Type','Rectangle');
        for ii = 1:length(rectangles)
            q = get(rectangles(ii),'Position'); % [x y w h]
            switch var
                case 'x'
                    x = [q(1) q(1)+q(3)]; % [x x+w]
                case 'y'
                    x = [q(2) q(2)+q(4)]; % [y y+h]
            end
            if ~isempty(lastC) % undo previous transformation
                x = sign(x).*lastC.*(10.^abs(x)-1);
            end
            x = sign(x).*log10(1+abs(x)/C);
    
            switch var
                case 'x'
                    q(1) = x(1);
                    q(3) = x(2)-x(1);
                case 'y'
                    q(2) = x(1);
                    q(4) = x(2)-x(1);
            end
    
            set(rectangles(ii),'Position',q)
        end
    

    Plot your functions including symlog(gca,'y',-1.7) in the end:

    plot( datenum(vector), operatingValue(:,3), '-+', datenum(vector), operatingValue(:,4), '-o',...
        datenum(vector), operatingValue(:,5), '-*', datenum(vector), operatingValue(:,6), '-x',...
        'LineWidth',1.2 ), grid on;
    symlog(gca,'y',-1.7)
    

    Here is your plot with positive and negative values: Final plot

    Hope this solves your problem.