Feel free to share demo-related thoughts here.
If you have technical questions, please create a support ticket in the DevExpress Support Center.
Thank you for the feedback!
If you have technical questions, please create a support ticket in the DevExpress Support Center.
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.