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.

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.

Backend API
Copy to CodePen
Apply
Reset
const DemoApp = angular.module('DemoApp', ['dx']); DemoApp.controller('DemoController', ($scope) => { const store = new DevExpress.data.CustomStore({ load() { return connection.invoke('getAllData'); }, key: 'date', }); $scope.chartOptions = { dataSource: { store, }, margin: { right: 30, }, loadingIndicator: { enabled: true, }, title: 'Stock Price', series: [{ pane: 'Price', argumentField: 'date', type: 'candlestick', aggregation: { enabled: true, method: 'custom', calculate(e) { const prices = e.data.map((i) => 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], }; } return null; }, }, }, { pane: 'Volume', name: 'Volume', type: 'bar', argumentField: 'date', valueField: 'volume', color: 'red', aggregation: { enabled: true, method: 'sum', }, }], panes: [{ name: 'Price', }, { name: 'Volume', height: 80, }], customizePoint(arg) { if (arg.seriesName === 'Volume') { const point = $('#chart').dxChart('getAllSeries')[0].getPointsByArg(arg.argument)[0].data; if (point.close >= point.open) { return { color: '#1db2f5' }; } } return null; }, tooltip: { enabled: true, shared: true, argumentFormat: 'shortDateShortTime', contentTemplate(pointInfo) { const volume = pointInfo.points.filter((point) => point.seriesName === 'Volume')[0]; const prices = pointInfo.points.filter((point) => point.seriesName !== 'Volume')[0]; return `<div class='tooltip-template'><div>${pointInfo.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>`; }, }, crosshair: { enabled: true, horizontalLine: { visible: false }, }, zoomAndPan: { argumentAxis: 'both', }, scrollBar: { visible: true, }, legend: { visible: false, }, argumentAxis: { argumentType: 'datetime', minVisualRangeLength: { minutes: 10 }, visualRange: { length: 'hour', }, }, valueAxis: { placeholderSize: 50, }, }; $scope.connectionStarted = false; const connection = new signalR.HubConnectionBuilder() .withUrl('https://js.devexpress.com/Demos/NetCore/stockTickDataHub') .configureLogging(signalR.LogLevel.Information) .build(); connection.start() .then(() => { connection.on('updateStockPrice', (data) => { store.push([{ type: 'insert', key: data.date, data }]); }); $scope.connectionStarted = true; $scope.$apply(); }); const formatCurrency = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2 }).format; const formatNumber = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format; });
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/22.1.4/css/dx.common.css" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/22.1.4/css/dx.light.css" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script>window.jQuery || document.write(decodeURIComponent('%3Cscript src="js/jquery.min.js"%3E%3C/script%3E'))</script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script> <script>window.angular || document.write(decodeURIComponent('%3Cscript src="js/angular.min.js"%3E%3C/script%3E'))</script> <script src="https://cdn3.devexpress.com/jslib/22.1.4/js/dx.all.js"></script> <script src="js/signalr.js"></script> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="index.js"></script> </head> <body class="dx-viewport"> <div class="demo-container" ng-app="DemoApp" ng-controller="DemoController"> <div ng-if="connectionStarted"> <div id="chart" dx-chart="chartOptions"></div> </div> </div> </body> </html>
#chart { height: 440px; } .tooltip-template span { font-weight: 500; }
using System.Collections.Generic; using DevExtreme.MVC.Demos.Models.SignalRTickData; using Microsoft.AspNet.SignalR; namespace DevExtreme.MVC.Demos.Hubs { public class StockTickDataHub : Hub { private readonly TickDataService _service; public StockTickDataHub() { _service = TickDataService.Instance; } public IEnumerable<TickItem> GetAllData() { return _service.GetAllData(); } } }
using DevExtreme.MVC.Demos.Hubs; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using System; using System.Collections.Generic; using System.Threading; namespace DevExtreme.MVC.Demos.Models.SignalRTickData { public class TickDataService { public readonly static TickDataService Instance = new TickDataService(GlobalHost.ConnectionManager.GetHubContext<StockTickDataHub>().Clients); private IHubConnectionContext<dynamic> Clients { get; set; } private readonly object updateStockPricesLock = new object(); private TickItem[] tickData; private int lastTickIndex; private Timer timer; private TickDataService(IHubConnectionContext<dynamic> clients) { Clients = clients; 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) { Clients.All.updateStockPrice(item); } private TickItem[] GenerateTestData() { var lastPrice = 140m; var random = new Random(); 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; } } }