dsuryd / dotNetify

Simple, lightweight, yet powerful way to build real-time web apps.
https://dotnetify.net
Other
1.17k stars 164 forks source link

req: example with explicit streaming? #172

Closed Spongman closed 5 years ago

Spongman commented 5 years ago

is there any chance you can add an example of using the API with explicit streaming?

i'm looking to integrate something like chartjs-plugin-streaming where the items in my IObservable are pushed one-at-a-time to the chart, like this:

https://nagix.github.io/chartjs-plugin-streaming/samples/push.html

just updating an array of values results in a graph whose x-axis stays stationary and the y-values change. but this is inappropriate for a graph over time (eg, a graph of network traffic, or stock prices) where you want the points to scroll to the left.

dsuryd commented 5 years ago

Isn't that very similar to the live chart demo in the website? Both x- and y-values change. You only need to replace the random data in that onReceive function with data sent by the back-end VM.

Spongman commented 5 years ago

no, not really. the problem with the live chart demo is that it's assumed that the cardinality of the x-values are always the same and that's only the y-values thatchange. it's also implemented in such a way as the whole data set changes each event. this has the effect that the points in the graph only move vertically, whereas in a real value vs. time graph, the data points move horizontally and appear from the right-hand side.

for example, contrast this example: http://dotnetify.net/core/examples/livechart

with the example i gave above: https://nagix.github.io/chartjs-plugin-streaming/samples/push.html

in the 1st (livechart) example, each time a new datum arrives, every point in the graph animates from one data point to another. the animation seems to indicate that the data values are changing, where in fact it's the x-axis that's changing. whereas, in the 2nd example, each point in the graph always corresponds to the same data point (until is scrolls off the left side).

also, and perhaps most importantly, the livechart example doesn't handle irregular data streams such that the number of points that appear on the graph may change over time.

dsuryd commented 5 years ago

I got it, you're focused on the animation of the chart. What I meant with 'similar' was, that in terms of data coming from the back-end, there's no difference. You could use the same dotNetify view model from the live demo and just replace the client-side chart rendering code to achieve this.

Spongman commented 5 years ago

i don't think it's just limited to the animation, i think that the VM has to handle a stream of data points, not just an array. as far as i can tell, the current example code just overwrites the entire state of the chart with a new array each time an event arrives, whereas what i want is just to stream each new data item in as it arrives.

dsuryd commented 5 years ago

If you open up your browser's console log on that live demo, you'd see that the line chart data (waveform) only sends the latest value, not the entire set.

Spongman commented 5 years ago

ah, great. so how do i get that value?

dsuryd commented 5 years ago

Which client-side library are you using? React, Vue?

dsuryd commented 5 years ago

The live chart demo has been updated to use the chart streaming plugin. Thank's for letting me know about the library; the demo looks much better now.

Spongman commented 5 years ago

Oh, that’s fantastic. Thanks so much!

Spongman commented 5 years ago

so i noticed that the example is using AddList and PushUpdates. is it possible to hook this up directly to an IObservable<T>?

i tried doing AddProperty<>().SubscribeTo() but nothing gets sent to the client when the observable's OnNext is called.

dsuryd commented 5 years ago

No, not supported at the moment, but you can create your own extension method.

I can help if you give me your code snippet and explain your intent. Or look at the examples in the website; there are plenty of them, especially in 'Elements' demo, that use AddProperty<>().SubscribeTo.

Spongman commented 5 years ago

yeah, i found this:

      AddProperty<string>("Clock")
         .SubscribeTo(rxTimer.Select(_ => DateTime.Now.ToString("hh:mm:ss tt")))
         .SubscribedBy(AddInternalProperty<bool>("Update"), _ => { PushUpdates(); return true; });

but i don't understand what's going on here.

why is it necessary to do the SubscribedBy stuff? surely if a VM property is subscribed to an IObservable, shouldn't it automatically push updates to the browser when the observable updates? i don't understand what the SubscribedBy is for. in what case does it make sense to be subscribed to an IObservable and not push updates to the browser?

dsuryd commented 5 years ago

PushUpdates is only required when the data update is not part of the synchronous flow initiated by a client's request. In the above case, the timer update originates from the server code, and thus requires explicit call to push the update to the client.

As to why that's even necessary, it's due to the fact that update is sent across the network with some degree of latency, and so there has to be a mechanism to allow the developer control over when the update should occur. PushUpdates does not just update a particular property, but every property in every view model on the same connection that has changed.

Spongman commented 5 years ago

ah, that makes sense, thanks.

Spongman commented 5 years ago

ok, i'm still having trouble with this.

firstly, i don't understand where the "Update" comes from in the example code above. What is that property name, who defines it, and who's causing that handler to get invoked?

Besides that, the Update handler does get invoked, and the value of _ is the value that has been pushed into the IObservable, but the value that makes it to the browser is an old value.

Also, is PushUpdates() going to do anything if i'm just subscribed to an IObservable? Do i need to explicitly tell it that a propoerty has changed? or is that handled automatically by the subscription?

dsuryd commented 5 years ago

The Update is a runtime property that gets created by AddInternalProperty. Its only purpose is to enable method chaining.

As I mentioned, you will need to explicitly call PushUpdates if it's either server-initiated or an async operation. Properties added through AddProperty won't need to be explicitly called out when their values changed.

Perhaps rewriting it in an imperative manner will make it easier to understand:

public class ClockVM : BaseVM
{
   private readonly Timer _timer;

   public string Clock { get; set; }

   public ClockVM()
   {
        _timer = new Timer(state =>
        {
           Clock = DateTime.Now.ToString("hh:mm:ss tt");

           Changed(nameof(Clock));  // Mark the 'Clock' property value as changed.
           PushUpdates();           // Push all changed property values to the client.

        }, null, 0, 1000); // every 1000 ms.      
   }

   public override void Dispose() 
   {
      _timer.Dispose();
   }
}