Your search did not match any results.

Charts - SignalR Service

This example demonstrates a real-time data update in a financial candlestick chart bound to a SignalR server. Note that data used in this demo is for demonstration purposes only.

To integrate the Chart with a SignalR server, specify a CustomStore. Use the CustomStore's push(changes) method to insert, update, and remove data objects. This method accepts an array and allows you to update data in batches.

Backend API
@(Html.DevExtreme().Chart() .ID("chart") .Title("Stock Price") .Series(s => { s.Add() .Type(SeriesType.Candlestick) .ArgumentField("date") .Pane("Price") .Aggregation(a => a.Enabled(true) .Calculate((@<text> function(e) { var prices = e.data.map(function(i) { return i.price; }); if(prices.length) { return { date: new Date((e.intervalStart.valueOf() + e.intervalEnd.valueOf()) / 2), open: prices[0], high: Math.max.apply(null, prices), low: Math.min.apply(null, prices), close: prices[prices.length - 1] }; } } </text>)) .Method(ChartSeriesAggregationMethod.Custom)); s.Add() .Type(SeriesType.Bar) .ArgumentField("date") .ValueField("volume") .Color("red") .Pane("Volume") .Name("Volume") .Aggregation(a => a.Enabled(true) .Method(ChartSeriesAggregationMethod.Sum)); }) .CustomizePoint((@<text> function(arg) { if(arg.seriesName === "Volume") { var ohlc = $("#chart").dxChart("getAllSeries")[0].getPointsByArg(arg.argument)[0].data; if(ohlc.close >= ohlc.open) { return { color: "#1db2f5" }; } } } </text>)) .Panes(p => { p.Add().Name("Price"); p.Add().Name("Volume").Height(80); }) .ArgumentAxis(a => a.ArgumentType(ChartDataType.DateTime) .MinVisualRangeLength(l => l.Minutes(10)) .VisualRange(v => v.Length(VizTimeInterval.Hour)) ) .Legend(l => l.Visible(false)) .ValueAxis(v => v.Add().PlaceholderSize(50)) .Margin(m => m.Right(30)) .ScrollBar(s => s.Visible(true)) .LoadingIndicator(l => l.Enabled(true)) .ZoomAndPan(z => z.ArgumentAxis(ChartZoomAndPanMode.Both)) .Tooltip(t => t.Enabled(true) .Shared(true) .ArgumentFormat("shortDateShortTime") .ContentTemplate(@<text> <% var volume = points.filter(point => point.seriesName === 'Volume')[0]; %> <% var prices = points.filter(point => point.seriesName !== 'Volume')[0]; %> <div class='tooltip-template'> <div><%- argumentText %></div> <div> <span>Open: </span> <%- formatCurrency(prices.openValue) %> </div> <div> <span>High: </span> <%- formatCurrency(prices.highValue) %> </div> <div> <span>Low: </span> <%- formatCurrency(prices.lowValue) %> </div> <div> <span>Close: </span> <%- formatCurrency(prices.closeValue) %> </div> <div> <span>Volume: </span> <%- formatNumber(volume.value) %> </div> </div> </text>) ) .Crosshair(c => c.Enabled(true).HorizontalLine(hl => hl.Visible(false))) ) <script src="~/signalr/signalr-client.js"></script> <script> var formatCurrency = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2 }).format; var formatNumber = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format; var connection = new signalR.HubConnectionBuilder() .withUrl("@Url.Content("~/stockTickDataHub")") .configureLogging(signalR.LogLevel.Information) .build(); $(function () { connection.start() .then(function () { var store = new DevExpress.data.CustomStore({ load: function () { return connection.invoke("getAllData"); }, key: "date" }); $("#chart").dxChart({ dataSource: store }); connection.on("updateStockPrice", function (data) { store.push([{ type: "insert", key: data.date, data: data }]); }); }); }); </script>
using DevExtreme.AspNet.Data; using DevExtreme.AspNet.Mvc; using DevExtreme.NETCore.Demos.Models; using DevExtreme.NETCore.Demos.Models.SampleData; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; namespace DevExtreme.NETCore.Demos.Controllers { public class ChartsController : Controller { public ActionResult SignalRService() { return View(); } } }
using System.Collections.Generic; using DevExtreme.NETCore.Demos.Models.SignalRTickData; using Microsoft.AspNetCore.SignalR; namespace DevExtreme.NETCore.Demos.Hubs { public class StockTickDataHub : Hub { private readonly TickDataService _service; public StockTickDataHub(TickDataService service) { _service = service; } public IEnumerable<TickItem> GetAllData() { return _service.GetAllData(); } } }
using DevExtreme.NETCore.Demos.Hubs; using Microsoft.AspNetCore.SignalR; using System; using System.Collections.Generic; using System.Threading; using DevExpress.Data.Utils; namespace DevExtreme.NETCore.Demos.Models.SignalRTickData { public class TickDataService { private IHubContext<StockTickDataHub> hubContext { get; set; } private readonly object updateStockPricesLock = new object(); private TickItem[] tickData; private int lastTickIndex; private Timer timer; public TickDataService(IHubContext<StockTickDataHub> hubCtx) { hubContext = hubCtx; tickData = GenerateTestData(); lastTickIndex = tickData.Length - 1; timer = new Timer(Update, null, 1000, 1000); } public IEnumerable<TickItem> GetAllData() { var data = new List<TickItem>(); lock(updateStockPricesLock) { for(var i = lastTickIndex; data.Count < 10000; i--) { data.Add(tickData[i]); if(i == 0) { i = tickData.Length - 1; } } } return data; } private void Update(object state) { lock(updateStockPricesLock) { lastTickIndex = (lastTickIndex + 1) % tickData.Length; var tick = tickData[lastTickIndex]; tick.Date = DateTime.Now; BroadcastStockPrice(tick); } } private void BroadcastStockPrice(TickItem item) { hubContext.Clients.All.SendAsync("updateStockPrice", item); } private TickItem[] GenerateTestData() { var lastPrice = 140m; var random = NonCryptographicRandom.System; var slowRandomValue = random.NextDouble(); var data = new TickItem[60 * 60 * 20]; var curTime = DateTime.Now; for(var i = 0; i < data.Length / 2; i++) { lastPrice = Math.Round(lastPrice * (decimal)(1 + 0.002 * (-1 + 2 * random.NextDouble())), 2); if(i % 50 == 0) { slowRandomValue = random.NextDouble(); if(slowRandomValue > 0.3 && slowRandomValue <= 0.5) slowRandomValue -= 0.2; if(slowRandomValue > 0.5 && slowRandomValue <= 0.7) slowRandomValue += 0.2; } var volume = (int)(100 + 1900 * random.NextDouble() * slowRandomValue); data[data.Length - 1 - i] = new TickItem(lastPrice, volume, curTime.AddSeconds(-1 * i)); data[i] = new TickItem(lastPrice, volume, curTime.AddSeconds(-1 * (data.Length - 1 - i))); } return data; } } }
using System; namespace DevExtreme.NETCore.Demos.Models.SignalRTickData { public class TickItem { public DateTime Date { get; set; } public decimal Price { get; set; } public int Volume { get; set; } public TickItem(decimal price, int volume, DateTime date) { Date = date; Price = price; Volume = volume; } } }
#chart { height: 440px; } .tooltip-template span { font-weight: 500; }

To display updated data in real time, use the aggregation configuration object. In this object, set the enabled property to true, the method property to custom, and then implement the calculate function to process the incoming data. In this demo, the calculate function aggregates data into a one point for each interval.

You can also use the contentTemplate function to update the tooltip content with incoming data.