I have highstock in react application. I've added historical data upload functionality. My problem is that I need to add both historical data and new data which could happen simultaneously. I need to understand where user on the chart currently. If chart was scrolled and user sees historical data I need to do nothing in case of new data was added. But if user sees latest data and chart got new data (not historical one) I need to autoscroll graph to show last data. Of course user can scroll graph back and forward.
I was trying to change extremes with next condition:
if ( extremes.userMax === oldMax && extremes.userMax < dataMax ) {
e.target.xAxis[ 0 ].setExtremes(
dataMax - ( userMax - userMin ),
dataMax
);
}
but it doesn't work.
Is it possible to determine current user position on the chart?
Code above was added as event callback for redraw.
Thank you for response.
Here is full code I use to automatically scroll chart:
if ( e.target.xAxis ) {
const extremes = e.target.xAxis[ 0 ].getExtremes();
const { userMax, userMin, oldMax, dataMax } = e.target.xAxis[ 0 ];
if ( extremes.userMax === oldMax && extremes.userMax < dataMax ) {
e.target.xAxis[ 0 ].setExtremes(
dataMax - ( userMax - userMin ),
dataMax
);
}
May be I need to clarify how I store and change data in redux. All chart data placed in array:
[{
open(pin):1
high(pin):3
low(pin):1
close(pin):2
volume(pin):10
x(pin):1581850800000
}, ...
]
So if I need to upload historical data from server I just add data to the beginning of array. In case I need to add new candle to chart I just push new element in the end of array. Chart component gets new series array through redux update.
UPD:
I finally solved my problem with next logic in redraw event:
if ( dataMax - userMax < pointRange * 2 ) {
e.target.xAxis[ 0 ].setExtremes(
dataMax - ( userMax - userMin ),
dataMax,
false
);
}
The idea is to check how many candles out of user view and if there are more than 2 just do nothing otherwise move chart to show latest candles.
false in setExtremes needs to avoid endless redraw.
Related
Have an issue while exporting data from Highcharts in react v2.1.3 ( highcharts-react-official )
My use case:
PDF export should have all data points in it ( e.g if current view shows 5 data points and on scroll there are 5 more the exported pdf should have all 10 data points )
Images ( JPEG and PNG ) should only have current view export ( Whatever is visible should be exported )
Problem:
Initial exporting options:
chartOptions: { xAxis: [{max: undefined, min: undefined}] // to export all }
However this has a problem with scroll
If I export graph without scrolling the graph export has all data points but if I export after scrolling a bit the exported graph has only current visible chart
Expected: Export all event after scrolling
Current Behaviour: After scroll exports only current visible
You can wrap exportChart method and adapt axis extremes depending on the exporting type. For example:
(function(H) {
H.wrap(H.Chart.prototype, 'exportChart', function(proceed, options) {
const xAxis = this.xAxis[0];
const extremes = xAxis.getExtremes();
if (options && options.type === 'application/pdf') {
xAxis.setExtremes(null, null);
}
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
xAxis.setExtremes(extremes.min, extremes.max);
});
}(Highcharts));
Live demo: http://jsfiddle.net/BlackLabel/cbmus1xg/
Docs: https://www.highcharts.com/docs/extending-highcharts/extending-highcharts
I have a Highchart Stock chart that shows EMA's using Stock chart indicator.
I do not want to use the rangefinder, as the chart should always show 100 candlesticks.
The EMA's work, but need to have extra candlesticks so that the first ones have EMA's. The API returns 100 candlesticks, but the first candlesticks do not have EMA's.
So, what is a good way to either:
Hide the first 50 candlesticks or
Only show the last 100 candlesticks?
I gather this might be done with the range selector, but not sure how to do it programatically.
You need to use setExtremes method. You can call it for example in load event:
chart: {
events: {
load: function() {
const points = this.series[0].points;
const min = points[points.length - 101].x;
this.xAxis[0].setExtremes(min, null, true, false);
}
}
}
Live demo: https://jsfiddle.net/BlackLabel/62djn43u/
API Reference: https://api.highcharts.com/class-reference/Highcharts.Axis#setExtremes
I have seen similar questions just like these (even with similar title) but none of them provide the answer I need.
I have a bar-negative-stack chart with drilldown.
In order to keep the Y-axis centered, I am calculating the maximum value among the series and setting it as the extremes of the chart.
The thing is I haven't find a good place to put this logic. I have tried putting it on redraw or render events but, since I am changing the yAxis, this causes a stackoverflow.
What I have so far that is more or less working is having the drilldown event set up with a setTimeout to delay the calculation, otherwise it would get the values before the drilldown. I would need some event like drilldownFinish.
Here's a working fiddle with this drilldown logic (not on negative-bar-stack though): http://jsfiddle.net/6qpq78do/
Any ideas on how to do this without a setTimeout?
Thanks
Maximum callstack exceeded message is caused by setExtremes function. Its third argument is redraw - it's true by default: https://api.highcharts.com/class-reference/Highcharts.Axis#setExtremes
So setExtremes will call the redraw event again and the redraw event will call setExtremes and so on...
The solution here is to create a flag (redrawEnbaled) that is going to control the access to the logic placed in redrawEvent and prevent this infinite recursive loop:
var redrawEnabled = true;
(...)
events: {
redraw: function() {
if (redrawEnabled) {
redrawEnabled = false;
let values = [];
this.series.forEach(s => {
s.yData.forEach(v => values.push(Math.abs(v)));
});
let max = values.reduce((acc, val) => (acc > val) ? acc : val, 0);
this.yAxis[0].setExtremes(-max, max);
redrawEnabled = true;
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/zjuy21k5/
I have a chart that looks like this:
Data comes from a web service and I want all the data to remain intact as it comes through to my model. The bars on the chart represent timespans (just a number column that displays the timespan.TotalHours).
Occasionally, as shown in the image, a time span will be negative - meaning it was ahead of schedule. When this is the case, I do not want to display that segment at all and in fact would like to subtract that time from the green segment. From what I've looked up, something that could help me with the actual subtraction of the negative number could be post processing. But how would I be able to modify my chart creation after that's done?
In case it's relavent, the code for the chart's creation is below:
<script type="text/javascript">
google.charts.load('current', { 'packages': ['corechart'] });
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.DataTable();
// Since every row will alwyas have this as the first column, we do not need to iterate. The first column will always be this title.
data.addColumn("string", "Category");
// This iterates through each stage (timespan) and creates a column for it.
// And display only the relavent time spans on the chart.
#foreach (var stage in Model.selectedForm.rows[0].stages)
{
#Html.Raw("data.addColumn('number', '" + Regex.Replace(stage.label, "(\\B[A-Z])", " $1") + "');") // Adding a numeric column for each timespan (stage) in that row. Using Regex to make the labels more friendly to the user.
}
#foreach (var row in Model.selectedForm.rows)
{
#Html.Raw("data.addRow(['" + row.label + "'") //The first part of our row is the label - each subsequent stage will be filled in at each iteration below
foreach (var stage in row.stages) //For each stage in the row being created ...
{
if (stage.timespan.TotalHours <= 0)
{
#Html.Raw(", " + "null") // replace the column with a null value
}
else
{
#Html.Raw(", " + stage.timespan.TotalHours) //...Continue the HTML builder adding additional timespans to eventually...
}
}
#Html.Raw("]);\n\n") //...close it here
}
var options = {
titlePosition: 'none',
width: 1200,
height: 400,
legend: { position: 'top', maxLines: 3 },
bar: { groupWidth: '75%' },
isStacked: true
}
var chart = new google.visualization.BarChart(document.getElementById('SOPChart'));
chart.draw(data, options);
}
</script>
Edit: I've added a check in my code that generates the table to account for the negative value and not drawing the purple segment. However there is still the matter of subtracting that time from the previous segment which I am unclear on how to accomplish.
Edit: Added the updated code with comments. Also added an updated image of what this produces. Now that the actual chart is being handled, I still need a way to subtract the value off of the previous (green) segment. After this edit, here is what the chart looks like now:
if you want to try javascript on the client to correct the issue...
remove the last edit and allow the negative value to come thru
then use following snippet to correct, add just above --> var options = {
for (var i = 0; i < data.getNumberOfColumns(); i++) {
if (data.getValue(i, data.getNumberOfColumns() - 1) < 0) {
// subtract from next to last column
data.setValue(i, data.getNumberOfColumns() - 2, data.getValue(i, data.getNumberOfColumns() - 2) + data.getValue(i, data.getNumberOfColumns() - 1));
// remove negative from last column
data.setValue(i, data.getNumberOfColumns() - 1, null);
}
}
In my HighCharts implementation I'm dynamically changing the data of all series of the chart. This all works as expected, but the legend gets refreshed each time aswell causing the filtered legend to become unfiltered.
Let me explain with the following example.
If you look at this sample by HighCharts themselves you will see the legend showing 'John', 'Jane' and 'Joe' : http://www.highcharts.com/demo/column-stacked
If you click on John you'll only see the series of Jane and Joe. If you'd now refresh the underlieing series the legend will refresh and John his data will be visible again.
I need to prevent this from happening. Anybody got an idea on how I could do this?
FYI; I'm updating the data like this:
function updateSeries(data, chart, vm) {
for (var i = 0, len = chart.series.length; i < len; i++) {
chart.series[0].remove();
}
var series = calculateSeries(data, vm);
_.each(series, function (serie) {
chart.addSeries(serie, false);
});
chart.redraw();
}
I'm aware that I'm actually removing and re-adding the series, but that's because I'm unsure of how to do this otherwise.