✅作者简介:热爱科研的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)
使用一个或多个名称-值对参数指定地平线图的其他选项。在所有其他输入参数之后指定选项。
名称-值对参数/属性
-
XData
XData 是一个二维矩阵,其中每列指定要在图表的各个切片中显示的数据的 x 坐标。XData
也可以是一维矩阵,它可以表示每个子图/单独切片的公共 x 值。 -
YData
YData 是一个二维矩阵,其中每列指定要在图表的各个切片中显示的数据的 y 坐标。 -
Labels
图表中每个切片的标签列表 -
NumBands
用于划分地平线图表的波段/段数
风格名称-值对参数/属性
-
ColorAboveBaseline
它用于确定基线上方的条带着色的颜色梯度。 -
ColorBelowBaseline
它用于确定基线以下的带被着色的颜色梯度 -
XLabel
X 轴标签 -
YLabel
Y 轴标签 -
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 雷达方面
卡尔曼滤波跟踪、航迹关联、航迹融合