Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Yongjian Feng committed Jul 28, 2021
0 parents commit 44ed850
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 0 deletions.
Binary file added BarChartRace2D.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added BarChartRace3D.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
288 changes: 288 additions & 0 deletions BarChartRace3D.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
classdef BarChartRace3D < handle
% Copyright 2021 The MathWorks, Inc.

properties (Access=public)
colors; %colors for bars
labels; %labels for bars
title; %title
ticks; %horizontal ticks
positiveOnly; %all values are positive. Use whole canvas
animationFrames; %number of frames for animation. 3d always whole canvas
show3D; %3D barChartRace
axisColor; %axisColor
outfile; %animation gif output
end

% private properties for internal use
properties (Access=private)
xlimit;
ylimit;
barWidth;
maxColors;
numBars;
dataCount;
gap;

WidthLeft;
EdgeLeft;
end

methods (Access=public)
function bcr = BarChartRace3D()
bcr.xlimit = [-400 400];
bcr.ylimit = [-300 300];
bcr.barWidth = 30;
bcr.gap = 10;
% animate 24 frames between each step
bcr.animationFrames = 24;
bcr.positiveOnly = false; % default to showing both + and -
% initialize default colors to be used
% user can overwrite later
bcr.colors = [
1 0 0; ...
0 1 0; ...
0 0 1; ...
1 1 0; ...
1 0 1; ...
0 1 1; ...
1 0.64 0; ...
0 0 0.55; ...
0.5 0 0; ...
0.7 0.8 1; ...
1 0.71 0.76; ...
0.9 0.9 0.98];
bcr.WidthLeft = 30;
bcr.EdgeLeft = 5;
bcr.axisColor = [0.8 0.8 0.8];
bcr.outfile = 'BarChartRace3D.gif';
end

function preDraw(obj, data)
[obj.maxColors, ~] = size(obj.colors);
[obj.dataCount, obj.numBars] = size(data);
if obj.numBars > 8
obj.barWidth = 30;
else
obj.barWidth = 50;
end
end

function xScale = getXscale(obj, data, i)
if i > obj.dataCount
xScale = 1;
return;
end
data1 = data(i, :);
max1 = max(abs(data1));

lengthMax = 350 - 110;
if obj.positiveOnly || obj.show3D
lengthMax = lengthMax * 2;
end
xScale = lengthMax/max1;
end

function [xScale1, xScale2] = computeXscales(obj, data, i)
xScale1 = obj.getXscale(data, i);
if i >= obj.dataCount
% last sample
xScale2 = xScale1;
else
xScale2 = obj.getXscale(data, i + 1);
end
end

function yPos = getYpos(obj, yIndex)
if obj.show3D
if yIndex >= 5
yleft = -(obj.WidthLeft/2 + (yIndex - 5)*(obj.WidthLeft + obj.EdgeLeft));
else
yleft = (obj.EdgeLeft + obj.WidthLeft/2) + (4 - yIndex)*(obj.WidthLeft + obj.EdgeLeft);
end
yPos = yleft;
else
yPos = 250 - (obj.barWidth + obj.gap)*(yIndex - 1);
end
end

function drawX(obj)
if obj.show3D
plot([-290 260], [-138 -273], 'Color', obj.axisColor);
plot([-290+obj.WidthLeft 260+60], [-138 -273], 'Color', obj.axisColor);
plot([260 260+60], [-273 -273], 'Color', obj.axisColor);
else
plot([-290 400], [-250 -250], 'Color', obj.axisColor);
end
end

function drawY(obj)
if obj.show3D
plot([-290 -290], [-138 140], 'Color', obj.axisColor);
plot([-290+obj.WidthLeft -290+obj.WidthLeft], [-138 140], 'Color', [0.95 0.95 0.95]);
plot([-290 -290+obj.WidthLeft], [-138 -138], 'Color', [0.90 0.90 0.90]);
else
if obj.positiveOnly
plot([-290 -290], [295 -250], 'Color', obj.axisColor);
else
plot([0 0], [295 -250], 'Color', obj.axisColor);
end
end
end

function drawTitle(obj)
if ~isempty(obj.title)
text(0, -295, obj.title, 'FontSize', 18, 'HorizontalAlignment','center');
end
end

function drawBar(obj, yPos, xTop, i, value)

color = obj.colors(mod(i-1, obj.maxColors)+1, :);
edgeColor = arrayfun(@(x) (x - 0.5)*(x>0.5), color);

if obj.positiveOnly
x = [-290 xTop-290 xTop-290 -290];
else
x = [0 xTop xTop 0];
end
y = [yPos - obj.barWidth/2 yPos - obj.barWidth/2 yPos + obj.barWidth/2 yPos + obj.barWidth/2];
h = fill(x, y, color, 'EdgeColor', edgeColor);
set(h,'facealpha',.5);
if i < length(obj.labels)
lbl = obj.labels{i};
text(-295, yPos, lbl, 'HorizontalAlignment', 'right', 'Color', edgeColor);
text(x(2) + 5, yPos, num2str(value), 'Color', edgeColor);
end
end

function animateBar(obj, xInit, xEnd, yInit, yEnd, fStep, i, vInit, vEnd)
if fStep > obj.animationFrames
return;
end

xPos = xInit + (xEnd - xInit)*fStep/obj.animationFrames;
yPos = yInit + (yEnd - yInit)*fStep/obj.animationFrames;
value = vInit + (vEnd - vInit)*fStep/obj.animationFrames;

if obj.show3D
obj.drawBar3D(yPos, xPos, i, value);
else
obj.drawBar(yPos, xPos, i, value);
end
end


function race(obj, data)
[p, f, ~] = fileparts(obj.outfile);
obj.outfile = fullfile(p, [f '.gif']);

firstFrame = true;

obj.preDraw(data);
for step = 1:obj.dataCount - 1
data_cur = data(step, :);
[~, yCurs] = sort(data_cur, 'descend');

data_next = data(step + 1, :);
[~, yNexts] = sort(data_next, 'descend');

[xScale1, xScale2] = obj.computeXscales(data, step);
for fStep = 1:obj.animationFrames
clf
axis off
hold on
xlim(obj.xlimit);
ylim(obj.ylimit);

obj.drawX();
obj.drawY();
obj.drawTitle();
for barOrder = 1:obj.numBars
%
yInit = obj.getYpos(barOrder);
barIndex = yCurs(barOrder);
endIndex = find(yNexts == barIndex);
yEnd = obj.getYpos(endIndex);

xInit = data_cur(barIndex)*xScale1;
xEnd = data_next(barIndex)*xScale2;

vInit = data_cur(barIndex);
vEnd = data_next(barIndex);

obj.animateBar(xInit, xEnd, yInit, yEnd, fStep, barIndex, vInit, vEnd);
end

[img, map] = rgb2ind(frame2im( getframe(gcf)),256);
if firstFrame
imwrite(img,map,obj.outfile,'gif','DelayTime',0.5);
firstFrame = false;
else
imwrite(img,map,obj.outfile,'gif','writemode', 'append','delaytime',1/obj.animationFrames);
end
hold off
end

end

end


function drawBar3D(obj, yPos, xTop, i, value)
yleft = yPos;
xLen = xTop;

yright = BarChartRace3D.getRightY(yleft, xLen);
widthRight = BarChartRace3D.getRightWidth(obj.WidthLeft, xLen);

colrs = BarChartRace3D.getColors(obj.colors(mod(i-1, obj.maxColors)+1, :));

%left polygon
h = fill([-290 xLen-290 xLen-290 -290], [yleft-obj.WidthLeft/2 (yright-widthRight/2) (yright+widthRight/2) (yleft+obj.WidthLeft/2)], colrs(1, :), 'EdgeColor', colrs(5, :));
set(h,'facealpha',.5);

%front polygon
h=fill([xLen-290 xLen-290+widthRight, xLen-290+widthRight, xLen-290], [yright-widthRight/2 yright-widthRight/2 yright+widthRight/2 yright+widthRight/2], colrs(2, :), 'EdgeColor', colrs(5, :));
set(h,'facealpha',.5);

if yPos < 0
%Lower half section draw top
h = fill([-290 xLen-290 xLen-290+widthRight -290+obj.WidthLeft], [yleft+obj.WidthLeft/2 yright+widthRight/2 yright+widthRight/2 yleft+obj.WidthLeft/2], colrs(3, :), 'EdgeColor', colrs(5, :));
else
h = fill([-290 xLen-290 xLen-290+widthRight -290+obj.WidthLeft], [yleft-obj.WidthLeft/2 yright-widthRight/2 yright-widthRight/2 yleft-obj.WidthLeft/2], colrs(4, :), 'EdgeColor', colrs(5, :));
end
set(h,'facealpha',.5);

if i < length(obj.labels)
lbl = obj.labels{i};
text(-295, yPos, lbl, 'HorizontalAlignment', 'right', 'Color', colrs(5, :));
text(xLen-290+widthRight + 5, yright, num2str(value), 'Color', colrs(5, :));
end
end

end

methods(Static)
function rwidth = getRightWidth(lwidth, xLen)
scale = (580/(580+xLen));
rwidth = lwidth/scale;
end
function yright = getRightY(yleft, xLen)
scale = (580/(580+xLen));
yright = yleft/scale;
end
function colors = getColors(color)
colors = zeros(5, 3);
% left is the same
colors(1, :) = color;
% front shall be lighter
colors(2, :) = arrayfun(@(x) (x+0.4)*(x<0.6) + (x>=0.6), color);
% up shall be little lighter
colors(3, :) = arrayfun(@(x) (x+0.1)*(x<0.9) + (x>=0.9), color);
% buttom shall be darker
colors(4, :) = arrayfun(@(x) (x-0.2)*(x>0.2), color);
% edge
colors(5, :) = arrayfun(@(x) (x - 0.5)*(x>0.5), color);
end
end
end
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# BarChartRace3D
It is all about data and it is all about presentation. BarChartRace is a popular way to visualize change of data over time. To further impress your audiences, a 3D BarchartRace can help you to present your points with great power. See the following sample animation Gif's generated by BarChartRace3D.

Refresh your browser to see the animation effect of the
sample gifs below.
## 3D sample output
![3D](./BarChartRace3D.gif)
## 2D sample output
![2D](./BarChartRace2D.gif)

## Sample and Demo
Run testBCR3D for a demo. From this folder

`testBCR3D`

View testBCR3D for sample code.

## Usage

### Construct a BarChartRace3D
Call the constructor to create a BarChartRace3D object

`bcr3 = BarchartRace3D();`

### Optional configurations
These are optional configurations. If not set, the default will be used.

#### labels
Set the labels for the bars.

`bcr3.labels = {'Bar1', 'Bar2', 'Bar3'};`

#### 2D/3D
Default to be 2D. Show 3D by setting this

`bcr3.show3D=true;`

#### output file name
Default to be BarChartRace3D.gif. Note only animation GIF out put is supported.
Change to another filename by setting

`bcr3.outfile = tmp.gif`

#### title
Default to be empty.

`bcr3.title = Title`

#### color for each bar
It has builtin (default) colors. If you want to set the color, you can do this to set 3 colors (R, G, B)
for example. If colors defined below is less than the bars, these colors will be
reused. For this example, the forth bar will use R again.

`bcr3.colors = [1 0 0; 0 1 0; 0 0 1];`

#### positive only
BarChartRace3D handles positive and negative data by default for 2D mode.
If you know all the data is positive, you can set the following, and then
BarChartRace3D will use the whole canvas for showing positive data.
Note 3D mode only handles positive data.

`bcr3.positiveOnly=true;`


### set data and race
The data shall be an nxm array, where n is the number of iterations, and m is the number of bars.

`data = randi(10, 8); bcr3.race(data);`





11 changes: 11 additions & 0 deletions license.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Copyright (c) 2021, The MathWorks, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. In all cases, the software is, and all modifications and derivatives of the software shall be, licensed to you solely for use in conjunction with MathWorks products and service offerings.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.




6 changes: 6 additions & 0 deletions security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Reporting Security Vulnerabilities

If you believe you have discovered a security vulnerability, please report it to
[[email protected]](mailto:[email protected]). Please see
[MathWorks Vulnerability Disclosure Policy for Security Researchers](https://www.mathworks.com/company/aboutus/policies_statements/vulnerability-disclosure-policy.html)
for additional information.
Loading

0 comments on commit 44ed850

Please sign in to comment.