Monitoring datas with HighChart using .net6 and javaScript - javascript

I have a BackgroundService class that is load 60 point of data at first load then add a point every minute to chart.I have used SignalR with HighChart in .Net6,the problem is that adding point in javascript class does not work.I want to add a point with it's data's at the end of the chart then shift it .
this is backgroundService Class:
public class TxnRespHistoryBackgroundCaller : BackgroundService
{
public ITxnRespHistoryRepository Repository { get; }
public IHubContext<MonitoringHub> HubContext { get; }
private bool isFirstCall = true;
public TxnRespHistoryBackgroundCaller(ITxnRespHistoryRepository repository, IHubContext<MonitoringHub> hubContext)
{
Repository = repository;
HubContext = hubContext;
}
private static List<string> FailResps
{
get
{
try
{
return new List<string>() { "3", "92", "91", "96", "80", "84" };
}
catch
{
return new List<string>();
}
}
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Run(async () =>
{
while (!stoppingToken.IsCancellationRequested)
{
if (isFirstCall == true)
{
try
{
var datasets = new List<dynamic>();
DateTime FinishTime = DateTime.Now;
try
{
FinishTime = Repository.GetMax(TxnRespHistory.Columns.EndTime);
}
catch
{
try
{
FinishTime = Repository.GetServerTime();
}
catch
{
FinishTime = DateTime.Now;
}
}
var yesterdayalltxn = await Repository.GetTxnHisByResp(59, TransactionType.Yesterday, FinishTime, FailResps);
var failtxn = await Repository.GetTxnHisByResp(59, TransactionType.fail, FinishTime, FailResps);
var alltxn = await Repository.GetTxnHisByResp(59, TransactionType.All, FinishTime, FailResps);
var suctxn = await Repository.GetTxnHisByResp(59, TransactionType.successful, FinishTime, FailResps);
datasets.Add(new
{
data = suctxn.Value,
label = "Success",
Key = "Success",
color = "#308014"
});
datasets.Add(new
{
data = failtxn.Value,
label = "UnSuccess",
Key = "Fail",
color = "#ff0000"
});
datasets.Add(new
{
data = alltxn.Value,
label = "Total",
Key = "Total",
color = "#7094db"
});
datasets.Add(new
{
data = yesterdayalltxn.Value,
label = "Yesterday",
Key = "Yesterday",
color = "#b3b3b3"
});
var result = new
{
success = true,
labels = alltxn.Key,
datasets = datasets
};
await HubContext.Clients.All.SendAsync("populatetxns", JsonConvert.SerializeObject(result));
isFirstCall = false;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
else
{
try
{
var newtxn = Repository.AddtxnHisByResp(FailResps);
var result = new
{
labels = newtxn.Key,
datasets = newtxn.Value
};
await HubContext.Clients.All.SendAsync("populatetxns", JsonConvert.SerializeObject(result));
Thread.Sleep(10000);
}
catch (Exception)
{
throw;
}
}
}
});
}
}
and this is javaScript Code :
let txnresphisbycha = {};
$(document).ready(function () {
let isFirstCallSuccess = false;
var connection = new signalR
.HubConnectionBuilder()
.withUrl("/monitoringHub").build();
connection.start().then(() => console.log("hubconnected")).catch(() => console.log(error));//establish connection
connection.on("populatetxns", (result) => {
if (isFirstCallSuccess == false)
getChartData(JSON.parse(result));
else
addChartData(JSON.parse(result));
});
function getChartData(result) {
var dataSets = new Array();
for (i = 0; i < result.datasets.length; i++) {
var entity = {};
entity.name = result.datasets[i].label;
entity.data = result.datasets[i].data;
entity.color = result.datasets[i].color;
entity.type = 'area';
dataSets.push(entity);
}
txnresphisbycha = Highcharts.chart('txnhisbyrespcha', {
chart: {
zoomType: 'x',
alignTicks: false,
height: (10 / 22 * 100) + '%',// set ratio
},
title: {
text: 'Total Transaction Chart'
},
credits: {
text: ''
},
legend: {
enabled: true,
rtl: true,
y: 25,
margin: 0,
shadow: true,
verticalAlign: 'top',
itemStyle: {
cursor: "pointer",
}
},
tooltip: {
enabled: true,
rtl: true,
split: false,
shared: true,
xDateFormat: '%H:%M',
useHTML: true,
style: {
fontSize: '15px',
rtl: true,
},
distance: 30,
padding: 5,
headerFormat: '<span>{point.key}</span><br/>'
},
xAxis: {
zoomEnabled: true,
categories: result.labels,
type: 'datetime',
tickInterval: 1,
labels: {
type: 'datetime',
step: 1,
rotation: 55,
style: {
fontSize: '11px'
},
}
},
yAxis: {
title: {
text: "Quantity",
style: {
fontSize: '15px',
},
},
opposite: false,
endOnTick: true,
showLastLabel: true,
labels: {
style: {
fontSize: '15px',
},
align: 'right',
x: -7,
y: 5,
formatter: function () {
return this.value;
}
}
},
plotOptions: {
series: {
pointRange: 1 * 60 * 1000,
fillOpacity: 0.2,
marker: {
symbol: 'circle'
}
}
},
series: dataSets,
navigator: {
enabled: true
}
});
isFirstCallSuccess = true;
}
function addChartData(result) {
if (!isFirstCallSuccess) return;
debugger;
if (txnresphisbycha.series[0].data[txnresphisbycha.series[0].data.length - 1].category != result.labels[0]) {
txnresphisbycha.series[0].addPoint({
x: result.labels[0],
function() {
for (var i in result.datasets) {
y: result.datasets[i]
}
},
dataLables: { enabled: true }
},
true
,true)
txnresphisbycha.update();
}
}
})

Related

Does the Chart.js tooltip not work with logarithmic or the Cartesian Time axis?

with my current configs, I can't get tooltips to appear. I am using the latest version of Chart.js, with the latest luxon, and the luxon-chartjs plugin via cdn.
i am using a logarithmic Y axis and cartesian timeline x-axis
i can only get a tooltip to appear when I include a blank callback for label. However, that tooltip that appears only has the unformatted date of the datapoint and no other info. when i ask the label callback to return something, nothing is displayed... when i include a blank callback for title or return anything in the title callback, this works and is displayed. i have hacked together a tooltip but in previous versions I've had no issue with the chart automagically creating these for me.
here is my current code and the working fiddle
let maxArray, correctArray, incorrectArray, objectArray
let yourImage = new Image()
yourImage.src = `https://res.cloudinary.com/behaviorreportcard/image/upload/w_10,h_18,c_scale/v1623785517/Untitled_10_qbwmx4.png`;
let ctx = document.getElementById('myChart').getContext('2d');
Chart.defaults.font.size = '10px';
Chart.defaults.plugins.tooltip.enabled = true
const Interval = luxon.Interval,
DateTime = luxon.DateTime,
Duration = luxon.Duration;
let minTime = "2017-01-19",
maxTime = "2017-04-01",
start = DateTime.fromISO(minTime),
end = DateTime.fromISO(maxTime),
duration = Duration.fromObject({
days: 1
});
function getStart() {
let newStart
if (start.weekday != 7) {
return start.minus({
days: start.weekday
}).toFormat("yyyy-MM-dd")
} else {
return start.toFormat("yyyy-MM-dd")
}
}
let emptyArray = Interval.fromDateTimes(start, end).splitBy(duration).map(t => ({
date: t.s.toFormat("yyyy-MM-dd"),
correct: null,
incorrect: null
}));
let incomingData = [{
date: '2017-01-01',
correct: 10,
incorrect: 20
},
{
date: '2017-01-02',
correct: 10,
incorrect: 0.09
},
{
date: '2017-01-03',
correct: 0.9,
incorrect: null
}, {
date: '2017-01-04',
correct: 3,
incorrect: 0.09
}, {
date: '2017-01-27',
correct: 5,
incorrect: 0.09
}, {
date: '2017-01-28',
correct: 10,
incorrect: null
}, {
date: '2017-02-19',
correct: null,
incorrect: 50
}, {
date: '2017-02-20',
correct: 75,
incorrect: 50
}, {
date: '2017-02-28',
correct: 60,
incorrect: 0.085
}, {
date: '2017-03-01',
correct: 75,
incorrect: null
}, {
date: '2017-03-02',
correct: 10,
incorrect: null
}
]
let returns = []
emptyArray.forEach(x => {
let objToReplace = incomingData.find(o => o.date === x.date)
let objIndex = incomingData.indexOf(objToReplace);
if (objIndex != -1) {
returns.push(incomingData[objIndex])
} else {
returns.push(x)
}
})
let dates = []
let corrects = []
let incorrects = []
let nulls = []
let nullVal = 0.025
returns.forEach(x => {
dates.push(x.date)
if (x.correct != null && x.incorrect != null) {
nulls.push(null)
} else if (x.correct == null && x.incorrect != null) {
nulls.push(nullVal)
}
if (x.incorrect == null) {
incorrects.push(null)
} else {
incorrects.push(x.incorrect)
}
if (x.correct == null) {
corrects.push(null)
} else {
corrects.push(x.correct)
}
})
function returnList(key) {
return returns.map(x => {
return x[key]
})
}
function returnNulls() {
let list = []
returns.forEach(x => {
if ((x.correct != null && x.incorrect == null) || (x.correct == null && x.incorrect != null)) {
list.push(nullVal)
} else {
list.push(null)
}
})
return list
}
console.log(returnNulls())
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: returnList('date'),
datasets: [{
label: 'none',
data: returnNulls(),
spanGaps: false,
pointStyle: yourImage,
},
{
label: 'Corrects',
data: returnList('correct'),
borderWidth: 1,
pointRadius: 5,
spanGaps: false,
fill: false,
borderColor: 'green',
pointStyle: 'circle'
},
{
label: 'Incorrects',
data: returnList('incorrect'),
fill: false,
spanGaps: false,
borderColor: 'red',
pointStyle: 'triangle',
pointRadius: 5,
borderWidth: 1
}
]
},
options: {
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
interaction: {
mode: 'nearest'
},
plugins: {
tooltip: {
enabled: true,
callbacks: {
label: function(item) {},
title: function(item) {
let date = item[0].label.split(',').slice(0, 2).join(',')
let dataSet = item[0].dataset.label
if (dataSet == 'none') {
return
}
return date + '\n' + dataSet
}
}
},
legend: {
display: true,
position: 'bottom',
labels: {
filter: (x, y) => {
if (!x.text.includes('none')) {
return x
}
},
usePointStyle: 'true',
fontColor: 'black',
fontSize: 12
}
},
title: {
display: true,
text: 'Your daily average results for chapter __',
fontColor: 'black',
fontSize: 18,
}
},
scales: {
x: {
ticks: {
font: {
size: 12
}
},
min: getStart(),
max: '2017-03-31',
type: 'time',
time: {
unit: 'day',
stepSize: 7
}
},
y: {
type: 'logarithmic',
max: 100,
min: 0.01,
ticks: {
autoSkip: false,
callback: function(value,
index,
values) {
switch (value) {
case 1:
return '1 per minute';
case 0.1:
return '.1/min';
case 0.01:
return '.01/min';
case 0.001:
return '.001/min';
case 10:
return '10 per min';
case 100:
return '100 per min';
case 1000:
return '1000 per min';
}
},
}
},
},
},
}
);
the HTML
<div id='chart' class='chart' width='400px' height='100%'>
<canvas id="myChart" class='chart' ></canvas>
</div>
and the scripts I'm calling out to
https://cdn.jsdelivr.net/npm/chart.js#3.3.2/dist/chart.min.js
https://cdnjs.cloudflare.com/ajax/libs/luxon/1.27.0/luxon.min.js
https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon#0.2.1

React & Chartjs: Chart re-rendering on mouseover

My state is changing based on incoming props, which then triggers a rebuild of the chart. However, what seems to be happening is that when I mouseover the chart it reveals old data, or data that's disappeared then reappears.
Here's a gif showing the problem: https://imgur.com/a/SQbhi9p
And here's my chart code:
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.pricesData !== prevState.pricesData) {
return { pricesData: nextProps.pricesData };
} else {
return null;
}
}
componentDidMount() {
this.buildChart();
}
componentDidUpdate(prevProps) {
if (!_.isEqual(this.props.pricesData, prevProps.pricesData)) {
this.buildChart();
}
}
buildChart fn:
buildChart = () => {
let datasets = [];
if (this.state.pricesData) {
this.state.pricesData.forEach((set) => {
if (set.titel === "Competiors price") {
let obj = {};
obj.label = set.titel;
obj.backgroundColor = set.color;
obj.data = [0, set.price];
obj.tooltip = set.tooltip;
datasets.push(obj);
} else {
let obj = {};
obj.label = set.titel;
obj.backgroundColor = set.color;
obj.data = [set.price, 0];
obj.tooltip = set.tooltip;
datasets.push(obj);
}
});
}
const myChart = new Chart(this.chartRef.current, {
type: "bar",
data: {
labels: ["Change=", "Competitor"],
datasets: datasets,
},
options: {
legend: {
display: false,
},
title: {
display: true,
fontSize: 16,
text: "Your estimated monthly costs",
},
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [
{
stacked: true,
},
],
yAxes: [
{
stacked: true,
ticks: {
callback: function (value) {
return "€" + value;
},
},
},
],
},
},
});
this.setState({ chart: myChart });
};

echarts: How can I add a normal distribution curve on bar chart?

I have plotted bar chart using echarts:
How can I show a normal distribution curve on bar chart line shown in the below image:
export class MainComponent implements OnInit, AfterViewInit, OnDestroy {
binsCount: number = 20;
histogramDetails: any = [];
//#endregion
constructor(private router: Router,
private store: Store<any>,
private projectService: ProjectService,
private taskService: TaskService,
private messageService: MessageService, ) { }
async createNewPlot(task: Task) {
if(this.selectedPlotType.name === 'Histogram') {
plotOption = await this.loadHistogramPlotData(task) ;
}
}
loadHistogramPlotData(task) {
if (!task || !this.selectedVariableX) {
return
}
return new Promise((resolve, reject) => {
this.taskService.getOutputVariablesHistogramPlot(task.id, this.selectedVariableX.id).subscribe(
response => {
//reset data
log.debug(`response = ${JSON.stringify(response)}`);
const plotData = this.setHistogramDetails(response.hist_plot_data);
resolve(plotData);
},
error => {
log.error(error);
reject(error)
}
);
})
}
setHistogramDetails(histogramDetails: any) {
// histogramDetails ? this.histogramDetails.push(histogramDetails) : null ;
const nums = histogramDetails.realization
let min = Math.floor(Math.min(...nums));
let max = Math.ceil(Math.max(...nums));
const binSize = (max - min) / this.binsCount;
let xaxisData: number[] = [];
let yseries = [];
let previousNumber = min;
for (let i = 0; i <= this.binsCount; i++) {
xaxisData.push(parseFloat(previousNumber.toFixed(1)));
yseries.push(0);
previousNumber = previousNumber + binSize;
}
for (const num of nums) {
for (let i = 1; i < xaxisData.length; i++) {
if (num < xaxisData[i]) {
yseries[i]++;
break;
}
}
}
const plotData: number[] = yseries;
const options = {
grid: {
left: 30,
top: 10,
bottom: 100
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#eee'
}
}
},
legend: {
orient: 'vertical',
left: '5%',
bottom: 30,
itemHeight: 3,
itemGap: 14,
textStyle: {
fontSize: 10,
color: '#333333'
},
data: ['Specified Distribution', 'Simulated Distribution']
},
xAxis: [
{
type: 'category',
data: [xaxisData[0] * 10, ...xaxisData, xaxisData[xaxisData.length - 1] * 10],
boundaryGap: ['40%', '40%'],
axisTick: {
alignWithLabel: true
},
axisPointer: {
type: 'shadow'
}
}
],
yAxis: [
{
type: 'value',
splitNumber: 5,
axisLabel: {
formatter: '{value}',
fontSize: 10
}
}
],
dataZoom: [{
type: 'inside',
throttle: 50
}],
series: [
{
name: 'Simulated Distribution',
type: 'bar',
color: '#2DA8D8',
large: true,
data: plotData,
}
],
histogramDetails: histogramDetails
};
return options;
};
}
Echarts has no built-in function normal distribution. You need to calculate it based on your data and add like usual line series or MarkLine for bar.
Adding normal distribution is an open Github issue on the echarts-stat library:
https://github.com/ecomfe/echarts-stat/issues/4
Also you can use this:
function normalDist(theta, x) {
return 1 / (theta * Math.sqrt(2 * Math.PI)) * Math.exp(- x * x / 2 / theta / theta);
}

Adding a functionality in the chart.js file

This is a chart.js file with functionality of of clicking and adding of data to the graph but I want to add some data beforehand so that opening it shows some of the point.
Please, help me telling me how to do this so that some of data as points in shown on the graph when the user opens it.
Here is my code :
let globalChartRef;
window.addEventListener('DOMContentLoaded', function () {
createGraph();
document.getElementById('ChartCnvs').onmousedown = function (result) {
ourClickHandler(result);
};
}, false);
function randomColor(alpha) {
return String('rgba(' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + Math.round(Math.random() * 255) + ',' + alpha + ')');
}
function ourClickHandler(element) {
let scaleRef,
valueX,
valueY;
for (var scaleKey in globalChartRef.scales) {
scaleRef = globalChartRef.scales[scaleKey];
if (scaleRef.isHorizontal() && scaleKey == 'x-axis-1') {
valueX = scaleRef.getValueForPixel(element.offsetX);
} else if (scaleKey == 'y-axis-1') {
valueY = scaleRef.getValueForPixel(element.offsetY);
}
}
if (valueX > globalChartRef.scales['x-axis-1'].min && valueX < globalChartRef.scales['x-axis-1'].max
&& valueY > globalChartRef.scales['y-axis-1'].min && valueY < globalChartRef.scales['y-axis-1'].max) {
globalChartRef.data.datasets.forEach((dataset) => {
dataset.data.push({
x: valueX,
y: valueY,
extraInfo: 'info'
});
});
globalChartRef.update();
}
}
function createGraph() {
var config = {
type: 'scatter',
data: {
datasets: [{
label: "Dataset Made of Clicked Points",
data: [],
fill: false,
showLine: true
}]
},
options: {
title: {
display: true,
text: "Chart.js Interactive Points"
},
scales: {
xAxes: [{
type: "linear",
display: true,
scaleLabel: {
display: true,
labelString: 'X-Axis'
},
ticks: {
min: -10,
suggestedMax: 5
}
},],
yAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Y-Axis'
},
ticks: {
beginAtZero: true,
suggestedMax: 15
}
}]
},
responsive: true,
maintainAspectRatio: true
}
};
config.data.datasets.forEach(function (dataset) {
dataset.borderColor = randomColor(0.8);
dataset.backgroundColor = randomColor(0.7);
dataset.pointBorderColor = randomColor(1);
dataset.pointBackgroundColor = randomColor(1);
dataset.pointRadius = 7;
dataset.pointBorderWidth = 2;
dataset.pointHoverRadius = 8;
});
var ctx = document.getElementById('ChartCnvs').getContext('2d');
globalChartRef = new Chart(ctx, config);
}

HighCharts. Angular. Redraw Chart with new Series on Event Click

kind of stuck in a hole here. I have a stacked Highchart that I'm trying to re-render when you click on a button. Here is what it looks like for now:
Clicking on any of the buttons will trigger a designated event handler that helps me generate a new series of data for that particular category. The data is organized in a way that bar-charts can consume.
For instance, clicking on the "Asset Class" button will return an output of:
(4) [{…}, {…}, {…}, {…}]
0: {name: "Cash", data: Array(1)}
1: {name: "Equity", data: Array(1)}
2: {name: "Fixed Income", data: Array(1)}
3: {name: "Fund", data: Array(1)}
length: 4
The problem I'm having is that the chart never seems to update even though I'm updating the series data. (this.chart.options.series = myNewSeries)
Some events will return more than 4 items (could be anywhere from 4 to 30 values) and I need them to stack as well.
Here is my code with the updating logic near the bottom:
export class ChartComponent{
constructor(){
|| block of script logic ||
this.options = {
chart: {
type: 'column',
height: 500,
width: 500,
style: {
fontFamily: "Arial"
},
events: {
redraw: function (){
alert("The chart is being redrawn")
}
}
},
title: {
text: ""
},
xAxis: {
categories: this.seriesData.category,
labels: {
style: {
fontSize: "14px"
}
}
},
yAxis: {
min: 0,
title: {
text: ""
},
labels: {
formatter: function () {
let valueString = (
this.value > 999.99 && this.value <= 999999.99 ?
"$" + (this.value / 1000).toFixed(0) + "K" : this.value > 999999.99 ?
"$" + (this.value / 1000000).toFixed(1) + "M" : this.value
)
return valueString
},
style: {
fontSize: "14px",
}
}
},
legend: {
x: 0,
y: 0,
verticalAlign: "top",
align: "right",
layout: "vertical",
itemStyle: {
fontSize: "16px",
color: "#6c6c6c",
},
symbolPadding: 8,
itemMarginTop: 10,
shadow: false,
labelFormatter: function () {
return `${this.name}`
}
},
tooltip: {
formatter: function () {
let name = this.series.name
let value = this.y
let valueString = `$${value.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`
let total = this.point.stackTotal
let percentage = ((value / total) * 100).toFixed(2)
let percentageString = `(${percentage})%`
return `<b>${name}</b> <br> ${valueString} ${percentageString}`
},
style: {
fontSize: "14px",
},
backgroundColor: "#ffffff"
},
plotOptions: {
column: {
stacking: 'normal',
dataLabels: {
enabled: false
}
},
series: {
pointWidth: 100,
borderColor: "rgba(0, 0, 0, 0)"
}
},
series: this.seriesData.series
}
}
options: Object
saveInstance(chartInstance): void {
this.chart = chartInstance;
}
updateSeriesData = (data: Array<any>, title): void => {
this.chart.options.series = data
this.chart.xAxis[0].update({categories: title})
}
// event handlers
getIndustryData = (e) => {
let newSeries = this.getSeriesTotals("Industry", "SecuritySectorLevel1", "SecuritySectorLevel2")
this.updateSeriesData([...newSeries.series], newSeries.category)
}
getSectorData = (e) => {
let newSeries = this.getSeriesTotals("Sector", "SecuritySectorLevel2", "SecuritySectorLevel1")
this.updateSeriesData([...newSeries.series], newSeries.category)
}
getAssetClassData = (e) =>{
let newSeries = this.getSeriesTotals("Asset Class", "AssetClassLevel1", "SecuritySectorLevel1")
this.updateSeriesData([...newSeries.series], newSeries.category)
}
getRegionData = (e) => {
let newSeries = this.getSeriesTotals("Region", "CountryOfRisk", "CountryOfIssuance")
this.updateSeriesData([...newSeries.series], newSeries.category)
}
getCurrencyData = (e) =>{
let newSeries = this.getSeriesTotals("Currency", "LocalCCY", "LocalCCYDescription")
this.updateSeriesData([...newSeries.series], newSeries.category)
}
}
Generally speaking, for the next person who surfs here:
In your HTML-Element you'll have something like:
<highcharts-chart
...
[(update)]="updateFlag">
</highcharts-chart>
And in the corresponding Typescript file you have a
updateFlag = false;
and after the section where you've changed something, you do:
this.updateFlag = true;

Categories