diff --git a/plot/plot.pdf b/plot/plot.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..437213fa5cf317de17c41e278caf11d6cb3ff965
Binary files /dev/null and b/plot/plot.pdf differ
diff --git a/plot/plot_statistic.bat b/plot/plot_statistic.bat
index 4b47c4346745aa28a2c442271db02a5c6d0527ce..63bd5039e4eb17fa272793e9a6a380989a6ada65 100644
--- a/plot/plot_statistic.bat
+++ b/plot/plot_statistic.bat
@@ -1 +1 @@
-matlab -batch "plot; exit"
\ No newline at end of file
+matlab -batch "plot_statistic('statistic.csv', {'headset_total_latency_0'}, 'plot.pdf'); exit"
\ No newline at end of file
diff --git a/plot/plot_statistic.m b/plot/plot_statistic.m
index 10cf06c01e5dffdd823a6032e2002230ea3c8466..8e057248b25209bda35a14fa82ab3212dc467a26 100644
--- a/plot/plot_statistic.m
+++ b/plot/plot_statistic.m
@@ -1,13 +1,249 @@
-function plot_statistic(columns, time_start, time_end)
-    statistic = load_statistic("statistic.csv");
+function plot_statistic(src_file, columns, dst_file, time_start, time_end)
+    if ~exist("src_file", "var")
+        disp("No file was specified for plotting!");
+        return;
+    end
 
-    test = statistic(:,14);
-    a = table2array(test);
-    a(isnan(a))=0;
+    if ~exist("columns", "var")
+        disp("No columns were specified for plotting!");
+        return;
+    end
 
-    plot(a);
+    for index=1:size(columns)
+        if columns{index} == "frame_number"
+            disp("The frame number should not be plotted and should!")
+            return;
+        end
+        if columns{index} == "transform_id"
+            disp("The transform id should not be plotted and should!")
+            return;
+        end
+    end
+
+    statistic = load_statistic(src_file);
+    select = select_columns(statistic, columns);
+
+    if size(select.labels) <= 1
+        disp("No columns found!");
+        return;
+    end
+
+    time_end_max = find_time_end(select);
+
+    if ~exist("time_start", "var")
+        disp("Time start was set to one!");
+        time_start = 1;
+    end
+
+    if ~exist("time_end", "var")
+        disp("Time end was set to max time end!");
+        time_end = time_end_max;
+    end
+
+    if time_start < 1
+        disp("Time start was clamped to one!");
+        time_start = 1;
+    end
+
+    if time_end > time_end_max
+        disp("Time end was clamped to max end time!");
+        time_end = time_end_max;
+    end
+
+    if time_start > time_end
+        disp("Time start and time end was reversed!");
+        time_temp = time_end;
+        time_end = time_start;
+        time_start = time_temp;
+    end
+
+    figure_handle = plot_columns(select, time_start, time_end);
+
+    if figure_handle == "error"
+        return;
+    end
+
+    if ~exist("dst_file", "var")
+        disp("Showing figure in window mode");
+        figure_handle.Visible = "on";
+    else
+        disp("Saving plot to file '" + dst_file + "'");
+        save_plot(figure_handle, dst_file);
+        close(figure_handle);
+    end
 end
 
 function statistic = load_statistic(file_name)
-    statistic = readtable(file_name);
-end
\ No newline at end of file
+    table = readtable(file_name, 'VariableNamingRule', 'preserve');
+   
+    statistic.table = {};
+    statistic.labels = {};
+    statistic.display_labels = {};
+    statistic.units = {};
+    statistic.ordered = {};
+
+    ordered = true;
+
+    for column_index=1:size(table.Properties.VariableNames, 2)
+        column = table(:,column_index);
+        name = table.Properties.VariableNames(column_index);
+
+        if ~all(ismissing(column))
+            splits = split(string(name{1}));
+
+            if size(splits, 1) >= 1
+                label = splits(1);
+                display_label = convertStringsToChars(label);
+
+                for index=1:strlength(display_label)
+                    if index == 1
+                        display_label(index) = upper(display_label(index));
+                    elseif display_label(index - 1) == '_'
+                        display_label(index - 1) = ' ';
+                        display_label(index) = upper(display_label(index));
+                    end
+                end
+
+                display_label = convertCharsToStrings(display_label);
+
+                statistic.labels{end + 1} = label;
+                statistic.display_labels{end + 1} = display_label;
+            else
+                disp("Can't parse label for column '" + name + "'!");
+                continue;
+            end
+
+            if size(splits, 1) >= 2
+                uint = extractBetween(splits(2), 2, strlength(splits(2)) - 1);
+                statistic.units{end + 1} = uint;
+            else
+                statistic.units{end + 1} = "undef";
+            end
+
+            statistic.table{end + 1} = table2array(column);
+            statistic.ordered{end + 1} = ordered;
+        else
+            ordered = false;
+        end
+    end
+end
+
+function select = select_columns(statistic, columns)
+    select.table = {};
+    select.labels = {};
+    select.display_labels = {};
+    select.units = {};
+    select.ordered = {};
+
+    for column_index=1:size(statistic.labels, 2)
+        label = statistic.labels{column_index}; 
+        found = false;
+
+        for index=1:size(columns, 2)
+            if label == columns{index}
+                found = true;
+                break;
+            end
+        end
+
+        if label == "frame_number"
+            found = true;
+        end
+        
+        if found
+            select.table{end + 1} = statistic.table{column_index};
+            select.labels{end + 1} = statistic.labels{column_index};
+            select.display_labels{end + 1} = statistic.display_labels{column_index};
+            select.units{end + 1} = statistic.units{column_index};
+            select.ordered{end + 1} = statistic.ordered{column_index};
+        end
+    end
+end
+
+function time_end = find_time_end(statistic)
+    time_end = 1;
+
+    for index=1:size(statistic.table, 2)
+        column = statistic.table{index};
+        column_end = find(~isnan(column), 1, "last");
+
+        if statistic.labels{index} == "frame_number"
+            time_end = max(time_end, column(column_end));
+        elseif ~statistic.ordered{index}
+            time_end = max(time_end, column_end);
+        end
+    end
+end
+
+function figure_handle = plot_columns(statistic, time_start, time_end)
+    ordered_found = false;
+    frame_numbers_found = false;
+    frame_numbers = [];
+
+    for index=1:size(statistic.labels, 2)
+        if statistic.labels{index} == "frame_number"
+            frame_numbers_found = true;
+            frame_numbers = statistic.table{index};
+        end
+
+        if statistic.ordered{index}
+            ordered_found = true;
+        end
+    end
+    
+    if ordered_found && ~frame_numbers_found
+        disp("Can't find frame numbers even though the statistic contains ordered columns!");
+        figure_handle = "error";
+        return;
+    end
+
+    time_line = 1:time_end;
+    column_samples = {};
+    column_labels = {};
+
+    for column_index=1:size(statistic.table, 2)
+        if statistic.labels{column_index} == "frame_number"
+            continue;
+        end
+
+        column = statistic.table{column_index};
+        samples = NaN(time_end, 1);
+
+        if statistic.ordered{column_index}
+            for row_index=1:size(frame_numbers)
+                time = frame_numbers(row_index);
+
+                if time_start <= time && time <= time_end
+                    samples(time) = column(row_index);
+                end
+            end
+        else
+            for row_index=1:size(frame_numbers)
+                if time_start <= row_index && row_index <= time_end
+                    samples(row_index) = column(row_index);
+                end
+            end
+        end
+
+        column_samples{end + 1} = samples;
+        column_labels{end + 1} = statistic.display_labels{column_index} + " [" + statistic.units{column_index} + "]";
+    end
+
+    figure_handle = figure("Visible", "off");
+    
+    for index=1:size(column_samples, 2)
+        samples = column_samples{index};
+        plot_indices = ~isnan(samples);
+
+        plot_handle = plot(time_line(plot_indices), samples(plot_indices));
+        plot_handle.DisplayName = convertCharsToStrings(column_labels{index});
+
+        hold on
+    end
+
+    legend_handle = legend();
+end
+
+function save_plot(figure_handle, dst_file)
+    saveas(figure_handle, dst_file)
+end