LoginSignup
30
28

More than 3 years have passed since last update.

「棒グラフがソートされながらぬるぬる動くやつ」=bar chart raceをMATLABで作成してみた.

Last updated at Posted at 2020-01-23

成果物:こんなgifアニメが作れます.

outFile.gif

こういう,「棒グラフで値がソートされながらぬるぬる動くやつ」,見たことないでしょうか?Bar chart raceとも呼ぶようです.順位変化がわかりやすいので作りたいなーとTwitterでつぶやいていたところ,反応をいただきました.以下の記事です.

ぬめぬめ動く棒グラフ Bar Chart Race を描いてみよう: 準備編

こちらを参考として(実はほぼ同じなのですが),時系列データを与えるとアニメーションgifファイルを作成するプログラムをMATLABで作成し,FileExchangeで公開しました.引用の作法など守っていただければばんばん使ってもらいたいです.
(QiitaもFileExchangeもこれが初投稿です)

barChartRace.m

基本のアイデア

基本アイデアは上記参考記事と同じなのですが,以下です.

  • h=barh(x,y) で棒グラフを描く.xが縦座標,yが横座標であること注意.
  • x, yをそれぞれ次の要素と補完(一次補完)して棒の位置と大きさを設定しなおし,再描画
    • ここで縦方向の幅の調整が必要.

ソースコード

main_barChartRace.m

データの生成と設定を行い,本体(barChartRace.m)を呼び出します.Optのメンバは全て設定しないと多分エラーになるので(本体内でエラー処理をサボっていますすいません),このコードで代入する値を変えて対処してください.縦方向の文字列,出力ファイル名を設定します.

データyは縦方向がデータ種ごと,横方向に時系列が並ぶようにしてください.1列目が0,全て正の値であることを想定しています.

main_barChartRace.m
clear
clc
close all

%% sample data
y=rand(10,6);y(:,1)=zeros(size(y(:,1)));
y=filter(1,[1 -1], y')';

% options
Opt.xTickLabelStrs=cell(size(y,1),1);
Opt.outFileName='outFile.gif';
Opt.xLabelStr='x';
Opt.yLabelStr='y';
Opt.titleStr='Title';
Opt.faceColor=[0,0,1];%'blue';


for n1=1:size(y,1)
    Opt.xTickLabelStrs(n1)={char('A'+(n1)-1)};
end

設定が終われば,以下の関数を呼ぶだけで指定した名前でgifファイルができ上がります.


barChartRace(y,Opt)

barChartRace.m

本体のMATLAB関数です.まずは縦方向のラベルと出力ファイル名を設定します.

barChartRace.m
xTickLabelStrs=Opt.xTickLabelStrs;  % tick labels (vertical)

if isequal(Opt.outFileName(end-3:end),'.gif')
    outFileName=Opt.outFileName; % output gif name
else
    outFileName=[Opt.outFileName '.gif']; % output gif name
end

データをソートして,縦方向の位置を算出します.

y=inData;
x=zeros(size(inData));
x(:,1)=(1:size(inData,1))';

for n1=2:size(inData,2)
    [val,ind]=sort(inData(:,n1),'ascend');
    for n2=1:size(ind,1)
        x(ind(n2),n1)=n2;
    end
end

新しいfigureを作成し,描画します.時刻間を適当な実秒数とフレーム数に割り当てて,1フレームずつ描画して出力ファイルに追加します.各フレームでの位置は隣接する時刻間の値を線形補完しています.


figure
hold on

grid on;
set(gca,'FontName','Arial','FontSize',10)
h=barh(x(:,1),inData(:,1),'stacked','FaceAlpha',0.5, 'EdgeColor','white');
h.FaceColor=Opt.faceColor;

xlim([0 max(max(h(1).YData)*1.2,1)])
ylim([0,size(inData,1)+1])
set(gca,'YTick',x(:,1),'YTickLabel',xTickLabelStrs(x(:,1)))
title(Opt.titleStr)
xlabel(Opt.xLabelStr)
ylabel(Opt.yLabelStr)

framesPerDataTick=24;
for k=2:size(y,2)

    for n1=0:framesPerDataTick
        tmp=((framesPerDataTick-n1)*x(:,k-1)+ (n1)*x(:,k))/framesPerDataTick;
        [val,ind]=sort(tmp);
        bWidth=min(diff(val));

        if bWidth>0.01
            h(1).XData= ((framesPerDataTick-n1)*x(:,k-1)+ (n1)*x(:,k))/framesPerDataTick;
            set(gca,'YTick',val,'YTickLabel',xTickLabelStrs(ind));

            h(1).YData=((framesPerDataTick-n1)*y(:,k-1)+ (n1)*y(:,k))/framesPerDataTick;
            set(h(1),'BarWidth',0.9/bWidth);
            % automatic scaling of horizontal axis
            xlim([0 max(max(h(1).YData)*1.2,1)])

            refresh(1)

        end
        [A,map] = rgb2ind(frame2im( getframe(gcf)),256);

        if k==2 && n1==0
            imwrite(A,map,outFileName,'gif','DelayTime',0.5);
        else
            imwrite(A,map,outFileName,'gif','writemode', 'append','delaytime',1/framesPerDataTick);
        end

    end
    for n1=1:4  %stop between time instant
        imwrite(A,map,outFileName,'gif','writemode', 'append','delaytime',1/framesPerDataTick);
    end
end
%padding final frame to display longer.
    imwrite(A,map,outFileName,'gif','writemode', 'append','delaytime',0.5);
end

1箇所ちょっとしたコツがあります.

        if bWidth>0.01

この部分ですが,縦方向の棒の幅を計算するとき,幅が小さすぎると描画がおかしくなってしまったので,ある程度幅があるときのみ描画しなおすこととしています.

アニメーションgifの作成方法についてはMATLAB公式をほぼそのまま利用しています.

imwrite

30
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
28