0
点赞
收藏
分享

微信扫一扫

基于matlab实现地平线图

✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。

🍎个人主页:Matlab科研工作室

🍊个人信条:格物致知。

⛄ 内容介绍

描述

Horizon Chart 是一种数据可视化技术,它以紧凑且分层的格式显示时间序列数据,提高可读性并能够随时间比较多个数据集。

依赖关系

为了获得最佳性能和准确的图表,请安装Mapping Toolbox

句法

  • HorizonChart 为每个切片创建 Horizon Chart x 轴数据和 y 轴数据
  • horizonChart(x, y)创建一个包含 x 和 y 数据且 NumBands = 2 的水平图表,即在每个切片中,数据分为 2 个区域 - 一个位于基线上方,一个位于基线下方
  • horizonChart(x, y, numBands)使用 x 和 y 数据创建一个水平图表,其中波段数 = numBands。Number of Bands 是数据被划分为的部分的数量
  • horizonChart(__ , Name, Value)使用一个或多个名称-值对参数指定地平线图的其他选项。在所有其他输入参数之后指定选项。

名称-值对参数/属性

  • XDataXData 是一个二维矩阵,其中每列指定要在图表的各个切片中显示的数据的 x 坐标。
    XData也可以是一维矩阵,它可以表示每个子图/单独切片的公共 x 值。
  • YDataYData 是一个二维矩阵,其中每列指定要在图表的各个切片中显示的数据的 y 坐标。
  • Labels图表中每个切片的标签列表
  • NumBands用于划分地平线图表的波段/段数

风格名称-值对参数/属性

  • ColorAboveBaseline它用于确定基线上方的条带着色的颜色梯度。
  • ColorBelowBaseline它用于确定基线以下的带被着色的颜色梯度
  • XLabelX 轴标签
  • YLabelY 轴标签
  • Title地平线图的标题

⛄ 部分代码

classdef horizonChart < matlab.graphics.chartcontainer.ChartContainer  & ...
        matlab.graphics.chartcontainer.mixin.Legend

    % horizonChart creates a Horizon Chart x-axis data and
    % y-axis data for each slice
    % 
    % horizonChart(x, y) creates a horizon chart with x an y data and
    % NumBands = 2 i.e. in each of the slices, the data is divided into
    % 2 regions - one above the baseline and one below the baseline
    % 
    % horizonChart(x, y, numBands) creates a horizon chart with x and y data
    % with number of bands = numBands. Number of Bands is the number of
    % sections that the data is divided into.
    % 
    % horizonChart(__ , Name, Value) specifies additional options for the horizon chart
    % using one or more name-value pair arguments. Specify the options after all other
    % input arguments.
        
    properties
        XData (:, :) double = [];
        YData (:, :) double = [];
        Labels (1, :) string = [];
        NumBands double {mustBeGreaterThanOrEqual(NumBands, 1)} = 2;

        XLabel string = "";
        YLabel string = "";
        Title string = "";
    end

    properties(Dependent)
        Baseline double = 0;
        ColorAboveBaseline {validatecolor} = [0, 0, 1];
        ColorBelowBaseline {validatecolor} = [1, 0, 0];
    end
    
    properties(Access = private,Transient,NonCopyable)
        PatchObjectListPerSlice (:, :)  = [];
        AxisHandlePerSlice (1, :) = [];
        SegRanges (1, :) double = [];
        NumSlices;
        ColorMap (:, 3) double; 
        
        
    end

    properties(Access = private)
        Baseline_I = NaN ;
        BaselineSetMethod string = "auto";
        
        ColorAboveBaseline_I = NaN;
        ColorAboveBaselineSetMethod = "auto";

        ColorBelowBaseline_I = NaN;
        ColorBelowBaselineSetMethod = "auto";
        
        ContainsMappingToolbox matlab.lang.OnOffSwitchState = 'on';
    end


    methods 
        function obj = horizonChart(varargin)
           % Intialize list of arguments
           args = varargin;
           leadingArgs = cell(0);

            if numel(args) >= 2 && isnumeric(args{1}) ...
                && isnumeric(args{2}) 
                   
                x = args{1};   
                y = args{2};

                if mod(numel(args), 2) == 1 && isnumeric(args{3})
                   % horizonChart(x, y, numBands)
                   numBands = args{3};
                   leadingArgs = [leadingArgs {'XData', x, 'YData', y , 'NumBands', numBands}];
                   args = args(4:end);
                else
                   % horizonChart(x, y)
                   leadingArgs = [leadingArgs {'XData', x, 'YData', y }];
                   args = args(3:end);
                end
            else
                if numel(args) < 2
                    error('Invalid Input Arguments. Too few inputs were provided. ')
                else
                    error('The first two arguments are not numeric and do not conform to XData and YData definition')
                end
            end
            
            % Combine positional arguments with name/value pairs
            args = [leadingArgs args];

            % Call superclass constructor method
            obj@matlab.graphics.chartcontainer.ChartContainer(args{:});

        end
    end
    methods(Access=protected)
        function setup(obj)
            if ~any(strcmp('Mapping Toolbox', {ver().Name})) 
                obj.ContainsMappingToolbox = 'off';
                warning("Mapping Toolbox is not installed. " + ...
                    "This may lead to degraded performance of the horizon chart. " + ...
                    "Install Mapping Toolbox for better performance")
            end
        end

        function update(obj)
            
            [obj.XData, obj.YData] = transformInputData(obj.XData, obj.YData) ;
            % Validate Inputs 
            validateInputs(obj.XData, obj.YData, obj.Labels);
            
            % If Grid Layout is already defined then clear the layout
            % during the update step
            children = obj.getLayout().Children;
            set(children, "Parent" , []);
            
            % Clear all patch objects 
            obj.PatchObjectListPerSlice = [];
            obj.AxisHandlePerSlice = [];            

            % Set GridLayout to be vertical layout
            obj.NumSlices = size(obj.YData, 2);
            obj.getLayout().GridSize = [obj.NumSlices 1];
            title(obj.getLayout(), obj.Title);
            xlabel(obj.getLayout(), obj.XLabel);
            ylabel(obj.getLayout(), obj.YLabel);
            
            % If user doesn't specify baseline we setup the baseline as the
            % median of the data. If the user specifies baseline, we adjust
            % the segment lengths to match the new baseline.
            if obj.BaselineSetMethod == "auto"
                obj.calculateSegmentsWithoutUserSetBaseline();
            else
                obj.calculateSegmentsWithUserSetBaseline();
            end 
            
            for slice = 1:obj.NumSlices
                % We obtain XData/ YData for each slice and calculate 
                % which band each data point belongs to  
                sliceXData = obj.XData(:, min(slice,size(obj.XData,2)))';
                sliceYData = obj.YData(:, slice)';

                binsPoint = binData(sliceYData, obj.SegRanges);

                PatchObjectList = [];
                order = [];
               
                % Get axis for the current tile and set all the properties
                ax = nexttile(obj.getLayout());
               
                % Disable data tips
                disableDefaultInteractivity(ax)

                % Specify labels only if the user has specified labels for
                % each slice
                if slice <= numel(obj.Labels) 
                    title(ax, obj.Labels(slice));
                end 

                y_min_slice = max(obj.YData(:));
                
                ax.XLim = [min(sliceXData), max(sliceXData)];
                ax.YTickLabel = [];
                ax.YTick = [];

                polygonOrder = gobjects(0);
                hold(ax, 'all')
                
                % Calculate color map for all the bands
                bgColor = get(ax, 'Color');
                order = ax.ColorOrder;
                
                if obj.ColorAboveBaselineSetMethod == "auto"
                    obj.ColorAboveBaseline_I = order(1, :); 
                end
                
                if obj.ColorBelowBaselineSetMethod == "auto"
                    obj.ColorBelowBaseline_I = order(2, :);
                end
               
                
                obj.CalculateColorMap(bgColor);


                % For each band we create a polygon that makes up the area
                % of the band. 
                for band = 1:obj.NumBands  
                    lower = obj.SegRanges(band);
                    upper = obj.SegRanges(band + 1);
                    color = obj.ColorMap(band, :);

                    % Calculate the vertices of the polygon depeding on
                    % whether it lies above the baseline/ or below the
                    % baseline 
                    [x_vertices, y_vertices] = generatePolygonPoints(sliceXData, sliceYData, lower, upper, lower >= obj.Baseline_I, obj.ContainsMappingToolbox);
                    
                    % Transform polygon and reflect it over the baseline
                    y_vertices = transformPolygon(y_vertices, lower, upper, obj.Baseline_I);
                                        
                    % Create the PatchObject for the band
                    PatchObject = patch(ax, x_vertices, y_vertices, color, 'DisplayName', num2str(lower) + " - " + num2str(upper));

                    
                    % If x_vertices/ y_vertices are empty then we need to
                    % create an empty patch object
                    if numel(x_vertices) == 0
                         PatchObject = patch(ax, NaN, NaN, color, 'DisplayName', num2str(lower) + " - " + num2str(upper));
                    else
                         % Find minimum all transformed y data in a particular slice
                         y_min_slice = min(y_min_slice, min(y_vertices(:)));
                    end  

                    % The bands that lie furthest from the baseline are
                    % displayed in the front. While the bands that are the
                    % closest to the baseline are displayed in the back
                    if lower >= obj.Baseline_I
                        polygonOrder = [PatchObject, polygonOrder];
                    else
                        polygonOrder = [polygonOrder, PatchObject];
                    end
                    
                    PatchObjectList = [PatchObjectList, PatchObject];
                end
                
                if y_min_slice ~= obj.Baseline_I
                    ax.YLim(1) = y_min_slice;
                end

                ax.Children = polygonOrder;
                hold(ax, 'off')

                obj.PatchObjectListPerSlice = [obj.PatchObjectListPerSlice; PatchObjectList];
                obj.AxisHandlePerSlice = [obj.AxisHandlePerSlice, ax];
            end  

            cbh = obj.buildColorBar(obj.AxisHandlePerSlice(end));
            cbh.Layout.Tile = 'east';
        end

        function CalculateColorMap(obj, backgroundColor)

            % The color of a band is decided by whether it lies above or
            % below the baseline. In case of bands that lie below the
            % baseline, the lower bands have a darker shade of
            % obj.colorsBelowBaseline. In case of bands that lie above the
            % baseline, the upper bands have a darker shade of
            % obj.colorsAboveBaseline
            nBandsBelowBaseline = sum(obj.SegRanges(2:end)<=obj.Baseline_I);
            nBandsAboveBaseline = obj.NumBands - nBandsBelowBaseline;
            
            % Calculate color gradient for the bands below the baseline
            alphas = fliplr(linspace(0.5, 1, nBandsBelowBaseline))';
            colorsBelowBaseline = alphas .* obj.ColorBelowBaseline_I + (1 - alphas) .* backgroundColor;

            % Calculate color gradient for the bands above the baseline
            alphas = linspace(0.5, 1, nBandsAboveBaseline)';
            colorsAboveBaseline = alphas .* obj.ColorAboveBaseline_I + (1 - alphas) .* backgroundColor;

            obj.ColorMap = [colorsBelowBaseline; colorsAboveBaseline];

        end
        
    end

    methods(Access = private)
        function calculateSegmentsWithoutUserSetBaseline(obj)
            % We divide the data into segments which contain equal amount of
            % data. For eg: If NumBands = 5, the first segment contains 20%
            % of the data. The second segment represents 20% - 40% of data
            % and so on
            obj.SegRanges = quantile(obj.YData(:), linspace(0, 1, obj.NumBands + 1));
            if mod(obj.NumBands, 2) == 0
                obj.Baseline_I = obj.SegRanges(obj.NumBands / 2 + 1);
            else
                obj.Baseline_I = obj.SegRanges((obj.NumBands + 1) / 2);
            end
        end

        function calculateSegmentsWithUserSetBaseline(obj)
            % In the first step, we divide data into segments using the
            % method proposed above.
            % Then we calculate segments below the baseline and above the
            % baseline. We accordingly divide the data below/above the
            % baseline using the newly found segments. 

            all_data = obj.YData(:);
            max_data = max(all_data);
            min_data = min(all_data);

            if obj.Baseline_I >= max_data
                nSegmentsAboveBaseline = 0;
                nSegmentsBelowBaseline = obj.NumBands;
            elseif obj.Baseline_I <= min_data
                nSegmentsAboveBaseline = obj.NumBands;
                nSegmentsBelowBaseline = 0;
            else
                segRanges = quantile(all_data, linspace(0, 1, obj.NumBands + 1));
                nSegmentsBelowBaseline = find(obj.Baseline_I >= segRanges, 1, 'last');

                if nSegmentsBelowBaseline == 0
                    nSegmentsBelowBaseline = nSegmentsBelowBaseline + 1;
                elseif nSegmentsBelowBaseline == obj.NumBands
                    nSegmentsBelowBaseline = nSegmentsBelowBaseline - 1;
                end

                nSegmentsAboveBaseline = obj.NumBands- nSegmentsBelowBaseline;
            end

            dataBelowBaseline = all_data(all_data < obj.Baseline_I);
            dataAboveBaseline = all_data(all_data > obj.Baseline_I);

            segRangesBelowBaseline = [];
            segRangesAboveBaseline = [];

            if nSegmentsBelowBaseline ~= 0 
                segRangesBelowBaseline = quantile(dataBelowBaseline, linspace(0, 1, nSegmentsBelowBaseline + 1));
            end 

            if nSegmentsAboveBaseline ~= 0
                segRangesAboveBaseline = quantile(dataAboveBaseline, linspace(0, 1, nSegmentsAboveBaseline + 1));
            end 

            obj.SegRanges = [segRangesBelowBaseline(1:nSegmentsBelowBaseline), obj.Baseline_I, segRangesAboveBaseline(2: nSegmentsAboveBaseline + 1)];
        end

        function cbh = buildColorBar(obj, ax)
            % Build a colorbar where the length of each color is proportional 
            % to the ratio of the length of each segment
            segLengths = obj.SegRanges(2:end) - obj.SegRanges(1: end - 1);
            lengthRatios = segLengths / sum(segLengths);
            lengthRatios = round(lengthRatios * 100);
            tLength = sum(lengthRatios);
            modifiedColorMap = colormap(repelem(obj.ColorMap, lengthRatios, 1));
            cbh = colorbar(ax);
            cumulLengthRatios = cumsum(lengthRatios) / tLength;
            cbh.Ticks = [0, cumulLengthRatios];
            cbh.TickLabels = num2cell(obj.SegRanges);
        end
    end

    methods
        function set.Baseline(obj, newBaseline)
            obj.BaselineSetMethod = "manual";
            obj.Baseline_I = newBaseline;
        end

        function baseline = get.Baseline(obj)
             baseline = obj.Baseline_I;
        end

        function set.ColorAboveBaseline(obj, color)
            obj.ColorAboveBaselineSetMethod = "manual";
            obj.ColorAboveBaseline_I = validatecolor(color);
        end

        function colorAboveBaseline = get.ColorAboveBaseline(obj)
            colorAboveBaseline = obj.ColorAboveBaseline_I;
        end

        function set.ColorBelowBaseline(obj, color)
            obj.ColorBelowBaselineSetMethod = "manual";
            obj.ColorBelowBaseline_I = validatecolor(color);
        end

        function colorBelowBaseline = get.ColorBelowBaseline(obj)
            colorBelowBaseline = obj.ColorBelowBaseline_I;
        end

    end

end

function [x, y] = validateInputs(x, y, labels)
    x_size = size(x);
    y_size = size(y);

    if x_size(1) == 0 || x_size(2) == 0 || y_size(1) == 0 || y_size(2) == 0
        error("Horizon chart cannot be constructed with empty data");
    end

    if ~isreal(x) || ~isreal(y)
        error("Chart does not work for complex data")
    end 

    if x_size(1) ~= y_size(1)
        error("Number of datapoints for each slice does not match between X and Y");
    end

    if ~validateIsIncreasing(x)
        error("X values should be strictly monotonically increasing")
    end

    if x_size(2) > 1 && x_size(2) < y_size(2)
        error("Number of slices for X-Data can either be 1 or need to match the Y-Data");
    end

    if numel(labels) ~= 0 && numel(labels) ~= y_size(2)
        error("Size of Labels is incorrect. It should either be empty or equal to the number of slices/ 1st dimension of Y data");
    end

end


function isIncreasing = validateIsIncreasing(x)
    for slice = 1:size(x, 2)
        if ~issorted(x(:, slice), 'strictascend')
            isIncreasing = false;
            return;
        end
    end

    isIncreasing = true;
end

function resultAr = binData(data, bins)
    resultAr = histcounts(data, bins);
end

function [x_data, y_data] = transformInputData(x_data, y_data)
    if size(x_data, 1) == 1
        x_data = x_data';
    end

    if size(y_data, 1) == 1
        y_data = y_data';
    end
end 

function [x_vertices, y_vertices] = generatePolygonPoints(dataX, dataY, lower, upper, isSegmentOverBaseline, containsMappingToolbox)

    if isSegmentOverBaseline
        keep = dataY >= lower;
    else
        keep = dataY < upper;
    end
    
    xi = dataX(keep);
    yi = dataY(keep);
    
    yi(yi >= upper) = upper;
    yi(yi < lower) = lower;

    if containsMappingToolbox
        [x_u, y_u] = polyxpoly(dataX, dataY, dataX, upper * ones(1, numel(dataX)));
        [x_l, y_l] = polyxpoly(dataX, dataY, dataX, lower * ones(1, numel(dataX)));
        xi = [xi, x_u', x_l'];
        yi = [yi, y_u', y_l'];
    end


    [xi, idx] = sort(xi);
    yi = yi(idx);

    x_vertices = [xi fliplr(xi)];
    if isSegmentOverBaseline
        y_vertices = [yi ones(1, numel(xi)) * lower];
    else
        y_vertices = [yi ones(1, numel(xi)) * upper];
    end
end

function yi = transformPolygon(yi, lower, upper, baseline)
        
    if lower >= baseline
        yi = yi - (lower - baseline);
    else
        yi = yi + (baseline - upper);
        yi = baseline + abs(baseline - yi);
    end
end

function [ y_data, x_data, labels] = GenerateRandomData(n)

    x_data = [2000:2021];
    y_data_all = [[791, 829, 881, 950, 1010, 1080, 1190, 1260, 1300, 1420, 1530, 1620, 1700, 1820, 1940, 2110, 2280, 2430, 2600, 2680, 2520, 2740], 
                  [770, 830, 849, 931, 988, 1050, 1150, 1230, 1290, 1190, 1240, 1300, 1360, 1370, 1380, 1350, 1380, 1380, 1420, 1450, 1410, 1500],
                  [3980, 4010, 3990, 4080, 4150, 4220, 4300, 4360, 4310, 4040, 4210, 4230, 4270, 4370, 4370, 4430, 4480, 4550, 4590, 4580, 4370, 4450],
                  [2040, 2100, 2120, 2130, 2200, 2220, 2290, 2340, 2330, 2280, 2320, 2370, 2390, 2400, 2400, 2430, 2470, 2530, 2570, 2620, 2400, 259],
                  [1830, 1890, 1880, 1880, 1920, 1940, 1950, 2000, 1970, 1880, 1890, 1920, 1860, 1820, 1810, 1850, 1870, 1900, 1900, 1920, 1750, 1850]];
    labels_all = ["Apple(AAPL)", "Microsoft(MSFT)", "Amazon(AMZN)", "Google(GOOGL)", "Coca Cola(KO)"];
    
    y_data = y_data_all(1:n, :)';
    labels = labels_all(1:n);
end

Horizon Chart
Data Intialization and Preprocessing
First, we need to read the data and do some inital preprocessing to ensure that y is a 2d matrix and that x is either a 2d matrix with same dimensions as y or that x is 1d matrix with same number of rows as y. Labels is the individual title for each slice in the tiled layout. 
Note: All data is random and is for representation purposes only. The data is not indicative of real stock market data. Further, each column indicates the price of each stock for the years 2000 - 2021. 
n_stocks = 5;
[y_data, x_data, labels] = GenerateRandomData(5);

Let us create a basic horizon chart
basicHorizonChart = horizonChart(x_data, y_data);
Let us add some labels to our chart. Here, each slice of the chart denotes the gdp of a country. So, we pass in country names as labels. 

basicHorizonChart = horizonChart(x_data, y_data, 6, 'Labels', labels);
Color Scheme
The horizon chart's color scheme is split into two zones: above the baseline and below the baseline. The user is required to specify a color for each zone. The segments in the chart are colored with a darker shade of the specified color based on their distance from the baseline. To set colors for the segments above and below the baseline, we can..

coloredHorizonChart = horizonChart(x_data, y_data, 6, 'Labels', labels, 'ColorAboveBaseline' , 'g' , 'ColorBelowBaseline' , 'm', 'Baseline', 1800);
Baseline
We can also set the baseline according to our preferences. In this case, depending on distribution of data we accordingly rearrange the number of bands above the baseline/ below the baseline. 

baselineHorizonChart = horizonChart(x_data, y_data , 6, 'Labels', labels, 'Baseline', 2000);
Labelling the chart
To decorate the chart, we can also add the X-Label, Y-Label and chart title. 
basicHorizonChart = horizonChart(x_data, y_data, 6, 'Labels', labels, 'XLabel', 'Time(in years)', 'YLabel', 'Stock Price(In USD)', 'Title', 'Stock Price over the years');

⛄ 运行结果


⛳️ 代码获取关注我

❤️部分理论引用网络文献,若有侵权联系博主删除
❤️ 关注我领取海量matlab电子书和数学建模资料

🍅 仿真咨询

1 各类智能优化算法改进及应用

生产调度、经济调度、装配线调度、充电优化、车间调度、发车优化、水库调度、三维装箱、物流选址、货位优化、公交排班优化、充电桩布局优化、车间布局优化、集装箱船配载优化、水泵组合优化、解医疗资源分配优化、设施布局优化、可视域基站和无人机选址优化

2 机器学习和深度学习方面

卷积神经网络(CNN)、LSTM、支持向量机(SVM)、最小二乘支持向量机(LSSVM)、极限学习机(ELM)、核极限学习机(KELM)、BP、RBF、宽度学习、DBN、RF、RBF、DELM、XGBOOST、TCN实现风电预测、光伏预测、电池寿命预测、辐射源识别、交通流预测、负荷预测、股价预测、PM2.5浓度预测、电池健康状态预测、水体光学参数反演、NLOS信号识别、地铁停车精准预测、变压器故障诊断

2.图像处理方面

图像识别、图像分割、图像检测、图像隐藏、图像配准、图像拼接、图像融合、图像增强、图像压缩感知

3 路径规划方面

旅行商问题(TSP)、车辆路径问题(VRP、MVRP、CVRP、VRPTW等)、无人机三维路径规划、无人机协同、无人机编队、机器人路径规划、栅格地图路径规划、多式联运运输问题、车辆协同无人机路径规划、天线线性阵列分布优化、车间布局优化

4 无人机应用方面

无人机路径规划、无人机控制、无人机编队、无人机协同、无人机任务分配

5 无线传感器定位及布局方面

传感器部署优化、通信协议优化、路由优化、目标定位优化、Dv-Hop定位优化、Leach协议优化、WSN覆盖优化、组播优化、RSSI定位优化

6 信号处理方面

信号识别、信号加密、信号去噪、信号增强、雷达信号处理、信号水印嵌入提取、肌电信号、脑电信号、信号配时优化

7 电力系统方面

微电网优化、无功优化、配电网重构、储能配置

8 元胞自动机方面

交通流 人群疏散 病毒扩散 晶体生长

9 雷达方面

卡尔曼滤波跟踪、航迹关联、航迹融合





举报

相关推荐

0 条评论