Plotting data
To be able to plot data in ImGui, ERS also includes a version of ImPlot. ImPlot is an immediate mode plotting library for ImGui. It allows plotting line plots, bar charts, pie charts, histograms, and much more. As examples, the event timeline, model progression, and speedup widgets are all built using ImPlot. Previews of ImPlot features and examples of creating plots can be found in their online demo.
Tip
If you're not familiar with ImGui or ImPlot, we recommend checking out the Dear ImGui Explorer. It contains live examples with source code of all features of ImGui and ImPlot.
- Click here for the ImGui examples.
- Click here for the ImPlot examples.
Example
The example below shows how to plot a line graph using data gathered from an ERS model. The model has two source entities that continuously create child entities (products). Each source keeps count how many products it has generated so far. A data gatherer script behavior keeps check at a regular interval how many products each source has generated and stores this data. It does so by using a view to iterate over all sources. The data from the data gatherer is shown in a line graph with one line for each source.
In this example, the data is shown indirectly: it is first gathered by the data gatherer and then shown using the gatherer's data. Of course, this is not the only way to plot data. It is also possible to read the data of entities and components directly and plot that data.

The result of the ImPlot example code.
internal struct SourceGenerateEvent : ILocalEvent<SourceGenerateEvent>
{
public Entity SourceEntity;
public void OnEvent()
{
// Randomly generate product entities in an infinite loop
SubModel subModel = SubModel.Get();
Source source = SourceEntity.GetComponent<Source>();
source.CreateProduct();
double delay = subModel.SampleRandomGenerator() * source.Randomness * (double)subModel.ModelPrecision;
EventScheduler.ScheduleLocalEvent(0, (ulong)delay, new SourceGenerateEvent() { SourceEntity = SourceEntity });
}
}
internal class Source : ScriptBehaviorComponent
{
public double Randomness = 10.0;
public ulong Generated = 0;
// Start the infinite event loop
public override void OnStart()
{
SubModel subModel = SubModel.Get();
double delay = subModel.SampleRandomGenerator() * Randomness * (double)subModel.ModelPrecision;
EventScheduler.ScheduleLocalEvent(0, (ulong)delay, new SourceGenerateEvent() { SourceEntity = ConnectedEntity });
}
// Create a product and keep track of the generation time
public void CreateProduct()
{
SubModel subModel = SubModel.Get();
subModel.CreateEntity(ConnectedEntity, "Product");
Generated++;
}
}
internal struct GatherDataEvent : ILocalEvent<GatherDataEvent>
{
public Entity DataGathererEntity;
public void OnEvent()
{
DataGatherer gatherer = DataGathererEntity.GetComponent<DataGatherer>();
gatherer.GatherData();
ulong delay = gatherer.Interval * SubModel.Get().ModelPrecision;
EventScheduler.ScheduleLocalEvent(0, delay, new GatherDataEvent() { DataGathererEntity = DataGathererEntity });
}
}
internal class DataGatherer : ScriptBehaviorComponent
{
public ulong Interval = 1;
public Dictionary<Entity, List<ulong>> Data = new();
public List<ulong> Times = new();
public override void OnStart()
{
// Initialize data lists
var sourceView = SubModel.Get().GetView<Source>([]);
while (sourceView.Next())
{
Entity entity = sourceView.GetEntity();
Data[entity] = new();
}
SubModel subModel = SubModel.Get();
ulong delay = Interval * subModel.ModelPrecision;
EventScheduler.ScheduleLocalEvent(0, delay, new GatherDataEvent() { DataGathererEntity = ConnectedEntity });
}
public void GatherData()
{
SubModel subModel = SubModel.Get();
var sourceView = subModel.GetView<Source>([]);
while (sourceView.Next())
{
Entity entity = sourceView.GetEntity();
Source source = sourceView.GetComponent<Source>();
Data[entity].Add(source.Generated);
ulong time = subModel.GetSimulator().CurrentTime / subModel.ModelPrecision;
Times.Add(time);
}
}
}
internal class Program
{
public static void RenderUI(Debugger debugger)
{
SubModel subModel = SubModel.Get();
ImGui.Begin("ImPlot example");
// Fit the plot each frame
ImPlot.SetNextAxesToFit();
if (ImPlot.BeginPlot("Generated products over time"))
{
DataGatherer gatherer = subModel.FindEntity("DataGatherer").GetComponent<DataGatherer>();
foreach (var pair in gatherer.Data)
{
// Check that Count > 0 because plotting an empty list is not possible
if (pair.Value.Count > 0)
ImPlot.PlotLine(pair.Key.GetName(), ref gatherer.Times.ToArray()[0], ref pair.Value.ToArray()[0], pair.Value.Count);
}
ImPlot.EndPlot();
}
ImGui.End();
}
static void Main(string[] args)
{
ERS.Initialize();
ComponentRegistry<DataGatherer>.Register();
ComponentRegistry<Source>.Register();
LocalEventRegistry<GatherDataEvent>.Register();
LocalEventRegistry<SourceGenerateEvent>.Register();
ModelContainer modelContainer = ModelContainer.Create();
modelContainer.Precision = 1_000_000;
Simulator sim = modelContainer.AddSimulator("Sim", SimulatorType.DiscreteEvent);
sim.EnterSubModel();
SubModel subModel = SubModel.Get();
Entity dataGathererEntity = subModel.CreateEntity("DataGatherer");
dataGathererEntity.AddComponent<DataGatherer>();
Entity sourceEntity1 = subModel.CreateEntity("Source 1");
sourceEntity1.AddComponent<Source>();
Entity sourceEntity2 = subModel.CreateEntity("Source 2");
Source source2 = sourceEntity2.AddComponent<Source>();
source2.Randomness = 5.0;
sim.ExitSubModel();
Ers.Debugger.Run(modelContainer, null, null, RenderUI);
ERS.Uninitialize();
}
}