Commit Description:
Fix tick spacing so it covers the entire range.
Commit Description:
Fix tick spacing so it covers the entire range.
Show/Diff file:
Action:
isometric-park-fna/UI/Graph.cs
311 lines | 12.7 KiB | text/x-csharp | CSharpLexer
using System;
using Num = System.Numerics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using ImGuiNET;
using ImPlotNET;
using JM.LinqFaster;
using isometricparkfna.Messages;
using isometricparkfna.Components;
using isometricparkfna.Engines;
namespace isometricparkfna.UI
{
public static class GraphWindow
{
public static bool hadFocus = false;
public static int year = 1;
public static bool show_totals = false;
public static bool show_subsidies = false;
public static bool show_upkeep = false;
public static bool always_show_zero = false;
private static string[] money_series = { "Total Funds",
"Subsidies", "Upkeep", "Contracts", "Cashflow", "Misc"
};
private static string[] tree_series = { "Total trees", "Dead trees", "Crowded trees"};
public static Dictionary<string, IEnumerable<double>> data_sets = new Dictionary<string, IEnumerable<double>>();
public static Dictionary<string, bool> data_sets_show = new List<string>()
.Concat(money_series)
.Concat(tree_series)
.Select(e => (e, false))
.ToDictionary(t => t.Item1, t=> t.Item2);
public static ImGuiColor[] colors = { ImGuiColor.BLACK, ImGuiColor.RED, ImGuiColor.GREEN,
ImGuiColor.BLUE, ImGuiColor.DARKGREY, ImGuiColor.LIGHTRED, ImGuiColor.LIGHTGREEN,
ImGuiColor.LIGHTBLUE, ImGuiColor.BLACK };
public static Dictionary<string, ImGuiColor> data_sets_color = new List<string>()
.Concat(money_series)
.Concat(tree_series)
.Zip(colors, (first, second) => (first, second))
.ToDictionary(t => t.Item1, t => t.Item2);
public static void DrawLinearAxis(ImDrawListPtr draw_list, Num.Vector2 range, bool vertical, int points, Num.Vector2 position) {
//See DrawLinearLabels for explanation
var tick_spacing = (int)Math.Abs((range.Y - range.X) / (points - 1));
var tick_length = 5;
var tick_adjust = vertical ? new Num.Vector2(tick_length, 0) : new Num.Vector2(0, tick_length);
var tick_position = position;
for(int i = 0; i < points; i++) {
draw_list.AddLine(tick_position, Num.Vector2.Add(tick_position, tick_adjust), (uint)ImGuiColor.LIGHTGREY, 1.0f);
if (vertical) {
tick_position = new Num.Vector2(position.X, position.Y + ((i + 1) * tick_spacing));
}
else {
//We increment one, otherwise the first tick is
tick_position = new Num.Vector2(position.X + ((i + 1) * tick_spacing), position.Y);
}
}
}
public static async void DrawLinearLabels(ImFontPtr font, ImDrawListPtr draw_list, Num.Vector2 domain, Num.Vector2 range, bool vertical, int labels, Num.Vector2 starting_position) {
//We need to increment by one in order to cover the entire range.
//For example, if our range is 0 - 100, and we want 11 ticks, 100 / 10 gives us a spacing o 10
//So our 11 labels become 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100.
var tick_spacing = (int)Math.Abs((range.Y - range.X) / (labels - 1));
var tick_length = 5;
var tick_adjust = vertical ? new Num.Vector2(tick_length, 0) : new Num.Vector2(0, tick_length);
var tick_position = new Num.Vector2(0, range.Y);
var tick_absolute_position = Num.Vector2.Add(starting_position, tick_position);
for(int i = 0; i < labels; i++) {
var value = Scale(range, domain, (int)(vertical ? tick_position.Y : tick_position.X));
var label = String.Format("{0}", value);
draw_list.AddText(font, 11.0f, tick_absolute_position, (uint)ImGuiColor.BLACK, label);
//Logging.Info(String.Format("Drawing {0:} at {1} ({2})", label, tick_absolute_position, tick_position));
if (vertical) {
tick_position = new Num.Vector2(0, range.Y + ((i + 1) * tick_spacing));
}
else {
tick_position = new Num.Vector2(range.X + ((i + 1) * tick_spacing), 0);
}
tick_absolute_position = Num.Vector2.Add(starting_position, tick_position);
}
}
public static void DrawLine(ImDrawListPtr draw_list, Num.Vector2 c, Num.Vector2[] points, ImGuiColor col) {
var p = Num.Vector2.Zero;
for (int i = 0; i < points.Length; i++)
{
points[i] = Num.Vector2.Add(points[i], c);
}
draw_list.AddPolyline(ref points[0], points.Length, (uint)col, 0 /*ImDrawFlags.RoundCornersDefault*/, 1.0f);
}
public static int Scale(Num.Vector2 domain, Num.Vector2 range, int num) {
//https://stats.stackexchange.com/a/281164
var domain_span = Math.Sign(domain.Y - domain.X) * Math.Max(1, Math.Abs(domain.Y - domain.X));
var range_span = range.Y - range.X;
var start = range.X - domain.X;
return (int) (((num - domain.X) / domain_span) * range_span + range.X);
}
public static void Render(ImFontPtr font, Simulation sim, ImGuiWindowBridgeEngine engine)
{
bool newShow = true;
ImGui.PushFont(font);
ImGui.GetStyle().WindowMenuButtonPosition = ImGuiDir.None;
StyleSets.defaultSet.push();
if(GraphWindow.hadFocus)
{
ImGui.PushStyleColor(ImGuiCol.Text, StyleSets.white);
}
ImGui.SetNextWindowSize(new Num.Vector2(400, 400));
ImGui.Begin("Graph", ref newShow, ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings);
if (GraphWindow.hadFocus)
{
ImGui.PopStyleColor();
}
GraphWindow.hadFocus = ImGui.IsWindowFocused();
data_sets["Total Funds"] = sim.allBudgets().Select(b => (double)b.money);
data_sets["Subsidies"] = sim.allBudgets().Select(b => (double)b.subsidy);
data_sets["Upkeep"] = sim.allBudgets().Select(b => (double)b.upkeep);
data_sets["Contracts"] = sim.allBudgets().Select(b => (double)b.contracts);
data_sets["Misc"] = sim.allBudgets().Select(b => (double)b.misc);
data_sets["Cashflow"] = sim.allBudgets().Select(b => (double)b.cashflow);
data_sets["Total trees"] = sim.allBudgets().Select(b => (double)b.trees);
data_sets["Dead trees"] = sim.allBudgets().Select(b => (double)b.dead_trees);
data_sets["Crowded trees"] = sim.allBudgets().Select(b => (double)b.crowded_trees);
var periods = 12.0d * GraphWindow.year;
var keys = data_sets_show.Keys.ToList();
var totals = data_sets["Total Funds"];
var max = 0.0d;
var min = 0.0d;
foreach (var key in keys) {
if (data_sets_show[key] && totals.Count() > 0) {
var series_max = data_sets[key].Max() * 1.10f;
max = Math.Max(series_max, max);
}
}
foreach (var key in keys) {
if (data_sets_show[key] && totals.Count() > 0) {
var series_min = data_sets[key].Min();
series_min = series_min >= 0? series_min * 0.90f : series_min *1.10f;
min = Math.Min(series_min, min);
}
}
ImPlot.PushStyleVar(ImPlotStyleVar.LineWeight, 2.0f);
ImPlot.PushStyleVar(ImPlotStyleVar.MinorAlpha, 0.0f);
ImPlot.SetNextPlotLimits(totals.Count()-periods, totals.Count(), min, max, ImGuiCond.Always);
if (ImPlot.BeginPlot("My Plot", null, null, new Num.Vector2(-1,0), ImPlotFlags.NoLegend | ImPlotFlags.NoMousePos )) {
foreach (var key in keys) {
var show = data_sets_show[key];
var data = data_sets[key];
var data_array = data_sets[key].ToArray();
if (data_array.Length > 0 && show)
{
ImPlot.PlotLine(key, ref data_array[0], data_array.Length);
ImPlot.AnnotateClamped(data_array.Length-1, data_array[data_array.Length-1],
new Num.Vector2(5, -10), StyleSets.grey, key);
}
}
ImPlot.EndPlot();
}
ImPlot.PopStyleVar(2);
ImGui.RadioButton("1 year", ref GraphWindow.year, 1);
ImGui.SameLine();
ImGui.RadioButton("5 years", ref GraphWindow.year, 5);
ImGui.SameLine();
ImGui.RadioButton("25 years", ref GraphWindow.year, 25);
ImGui.SameLine();
ImGui.RadioButton("100 years", ref GraphWindow.year, 100);
ImGui.Text("Trees:");
for (int i = 0; i < tree_series.Length; i++)
{
var key = tree_series[i];
if (Menu.activeButton(key, data_sets_show[key], StyleSets.selected, StyleSets.white))
{
data_sets_show[key] = !data_sets_show[key];
}
if (i != tree_series.Length-1)
{
ImGui.SameLine();
}
}
ImGui.Text("Money:");
for (int i = 0; i < money_series.Length; i++)
{
var key = money_series[i];
if (Menu.activeButton(key, data_sets_show[key], StyleSets.selected, StyleSets.white))
{
data_sets_show[key] = !data_sets_show[key];
}
if ((i % 4 != 3) && (i != money_series.Length-1)) {
ImGui.SameLine();
}
}
ImGui.Text("Always show zero:");
ImGui.SameLine();
ImGui.Checkbox("##AlwaysShowZero", ref always_show_zero);
var draw_list = ImGui.GetWindowDrawList();
var padding = 30;
var domain_min = always_show_zero ? 0 : int.MaxValue;
var domain_max = int.MinValue;
var c = ImGui.GetCursorScreenPos();
//Y Axis
draw_list.AddLine(Num.Vector2.Add(new Num.Vector2(padding, padding), c),
Num.Vector2.Add(new Num.Vector2(padding, 200), c),
(uint)ImGuiColor.LIGHTGREY, 0.0f);
//X Axis
draw_list.AddLine(Num.Vector2.Add(new Num.Vector2(padding, 200 -padding), c),
Num.Vector2.Add(new Num.Vector2(350, 200 - padding), c),
(uint)ImGuiColor.LIGHTGREY, 1.0f);
foreach (var key in keys)
{
if (data_sets_show[key] && data_sets[key].Count() > 0)
{
domain_min = Math.Min(domain_min, (int)data_sets[key].Min());
domain_max = Math.Max(domain_max, (int)data_sets[key].Max());
}
}
var domain = new Num.Vector2(domain_min, domain_max);
var range = new Num.Vector2(200 - padding, 0 + padding);
var x_domain = new Num.Vector2((data_sets["Misc"].Count() - (int)periods),
data_sets["Misc"].Count());
var x_range = new Num.Vector2(0 + padding, 350 - padding);
foreach (var key in keys)
{
var show = data_sets_show[key];
var data = data_sets[key];
var color = data_sets_color[key];
if (data.Count() > 0 && show)
{
IEnumerable<int> data_array = data_sets[key].Select((p) => Scale(domain, range, (int)p));
var data_array2 = data_array.Select((p, i) => new Num.Vector2(Scale(x_domain, x_range, i), p)).ToArray();
DrawLine(draw_list, c, data_array2, color);
draw_list.AddText(font, 12, data_array2.Last(), (uint)color, key);
}
}
DrawLinearAxis(draw_list, domain /*new Num.Vector2(0, 200)*/, true, 11, Num.Vector2.Add(c, new Num.Vector2(padding, padding)));
DrawLinearLabels(font, draw_list, domain, range /*new Num.Vector2(0, 200)*/, true, 11, c);
DrawLinearAxis(draw_list, new Num.Vector2(0, 350), false, 12, Num.Vector2.Add(c, new Num.Vector2(padding, 200 - padding)));
ImGui.Dummy(new Num.Vector2(350, 200));
ImGui.End();
ImGui.GetStyle().WindowMenuButtonPosition = ImGuiDir.Left;
StyleSets.defaultSet.pop();
ImGui.PopFont();
}
}
}