Render bands / bandData in jqplot when series contains null values - jqplot

I am using jqplot to render a line graph.
My series data looks like:
results =[
['1/1/2014', 1000],
['2/1/2014', 2000],
['3/1/2014', 3000],
['4/1/2014', 4000],
['5/1/2014', null]
];
my call to jqplot looks something like
$.jqplot('myChart', results,
{
series: [
{
rendererOptions: {
bands: {
show: true,
interval: '10%'
},
}
}
]
});
The chart will render, but it will be missing the 10% bands above and below.
If i change the null value
['5/1/2014', null]
to be
['5/1/2014', 5000]
then the bands will render correctly.
My does data have some missing values. Is there any way to make the bands render for non-null data points on the line, even if the line does have some null data points?

Instead of sending null for those values, omit them entirely and depend on the dateAxisRenderer to correctly space the values on the axis.
results = [
['1/1/2014', 1000],
['2/1/2014', 2000],
['3/1/2014', 3000],
['4/1/2014', 4000],
['6/1/2014', 3500]
];
$.jqplot ('myChart', [results],
{
series: [{
rendererOptions: {
bands: {
show: true,
interval: '10%'
}
}
}],
axes: {
xaxis: {
renderer: $.jqplot.DateAxisRenderer
}
}
});
JSFiddle version here (don't forget the extra script reference for dateAxisRenderer)
http://jsfiddle.net/4vmNf/1/
Alternatively, you can pass separate arrays for upper & lower band data, and this does not have to follow the same intervals as the underlying data array.
lowerBand = [];
upperBand = [];
for(var i = 0; i < results.length; i++) {
if (results[i][1]) {
lowerBand.push([results[i][0], results[i][1] * 0.9]);
upperBand.push([results[i][0], results[i][1] * 1.1]);
} else {
// not clear to me how you want to calculate band for missing values
lowerBand.push([results[i][0], 3500]);
upperBand.push([results[i][0], 6500]);
}
}
And then use bandData option:
series: [{
rendererOptions: {
bandData: [lowerBand, upperBand]
}
}]

Related

c3.js - hide tooltip for specific data sets

I have a c3.js chart which has 4 datasets. Is it possible to set the tooltop only to display for 1 set of data?
From the code below I only want the tooltip to display for data4.
var chart = c3.generate({
bindto: '#chart3',
data: {
//x: 'x1',
xFormat: '%d/%m/%Y %H:%M', // how the date is parsed
xs: {
'data1': 'x1',
'data2': 'x2',
'data3': 'x3',
'data4': 'x4'
},
columns: [
x1data,
y1data,
x2data,
y2data,
x3data,
y3data,
x4data,
y4data,
],
types: {
data1: 'area',
},
},
legend: {
show: false
}
});
There is the tooltip option for show:false but that disables them all.
Can it display for just 1 dataset?
The tooltip.position() function can be used to control the position of the tooltip, and we can set the tooltip position way off the canvas as a quick hack to hide it when we do not want to see it. However, I do not know how to return the default which is not documented - maybe someone else can elaborate on that.
tooltip: {
grouped: false,
position: (data, width, height, element) => {
if (data[0].id === 'data2'){ // <- change this value to suit your needs
return { top: 40, left: 0 };
}
return { top: -1000, left: 0 };
}
}
EDIT: After digging around for a solution I found that Billboard.js (a fork of C3.js on github) provides a tooltip.onshow() function that the API docs say is 'a callback that will be invoked before the tooltip is shown'. So it would appear that Billboard.js already has the a potential solution where you could intercept the data and hide the tooltip.

C3.js combination chart with time series - tooltip not functional

I've been trying for 3 days to get this chart to display the way I want it to. Everything was working 100% until I realized the grouped bar chart numbers were off.
Example: When the bottom bar value equals 10 and the top bar value equals 20, the top of the grouped bar read 30. This is the default behavior, but not how I want to represent my data. I want the top of the grouped bar to read whatever the highest number is, which lead me to this fiddle representing the data exactly how I wanted to.
After refactoring my logic, this is what I have so far. As you can see the timeseries line is broken up and the tooltip is not rendering the group of data being hovered over.
My questions:
1) How to get the tooltip to render all three data points (qty, price, searches)
2) How to solidify the timeseries line so it's not disconnected
Any help would be greatly appreciated so I can move on from this 3 day headache!
Below is most of my code - excluding the JSON array for brevity, which is obtainable at my jsfiddle link above. Thank you in advance for your time.
var chart = c3.generate({
bindto: '#chart',
data: {
x: 'x-axis',
type: 'bar',
json: json,
xFormat: '%Y-%m-%d',
keys: {
x: 'x-axis',
y: 'searches',
value: ['qty', 'searches', 'price']
},
types: {
searches: 'line'
},
groups: [
['qty', 'price']
],
axes: {
qty: 'y',
searches: 'y2'
},
names: {
qty: 'Quantity',
searches: 'Searches',
price: 'Price ($)'
},
colors: {
price: 'rgb(153, 153, 153)',
qty: 'rgb(217, 217, 217)',
searches: 'rgb(255, 127, 14)'
}
},
bar: {
width: {
ratio: 0.60
}
},
axis: {
x: {
type: 'timeseries',
label: { text: 'Timeline', position: 'outer-right' },
tick: {
format: '%Y-%m-%d'
}
},
y: {
type: 'bar',
label: {
text: 'Quantity / Price',
position: 'outer-middle'
}
},
y2: {
show: true,
label: {
text: 'Searches',
position: 'outer-middle'
}
}
},
tooltip: {
grouped: true,
contents: function(d, defaultTitleFormat, defaultValueFormat, color) {
var data = this.api.data.shown().map(function(series) {
var matchArr = series.values.filter(function(datum) {
return datum.value != undefined && datum.x === d[0].x;
});
if (matchArr.length > 0) {
matchArr[0].name = series.id;
return matchArr[0];
}
});
return this.getTooltipContent(data, defaultTitleFormat, defaultValueFormat, color);
}
}
});
1) If I got it right, you want tooltip to show all values, even if some of them are null.
Null values are hidden by default. You can replace them with zero (if it is suitable for your task) and thus make them visible.
Also, it seems to me that there is a shorter way to get grouped values:
var data = chart.internal.api.data().map(function(item) {
var row = item.values[d[0].index]; // get data for selected index
if (row.value === null) row.value = 0; // make null visible
return row;
});
2) I think you are talking about line.connectNull option:
line: {
connectNull: true
}
UPDATE
Looks like having duplicate keys breaks work of api.data() method.
You need to change json structure to make keys unique:
Before:
var json = [
{"x-axis":"2017-07-17","qty":100},
{"x-axis":"2017-07-17","price":111},
{"x-axis":"2017-07-17","searches":1},
{"x-axis":"2017-07-18","qty":200},
{"x-axis":"2017-07-18","price":222},
{"x-axis":"2017-07-18","searches":2}
];
After:
var json = [
{"x-axis":"2017-07-17","qty":100,"price":111,"searches":1},
{"x-axis":"2017-07-18","qty":200,"price":222,"searches":2}
];
See fiddle.

How to integrate KendoUI chart with SignalR

i want to create a real time chart, using Kendo ui chart and signalr. I see this example, but has no code. So i try alone.
A little demonstration of my code:
At first I created a kendo chart
function queueActivityChart() {
$("#queueActivityChart").kendoChart({
legend: {
visible: true
},
seriesDefaults: {
labels: {
visible: true,
format: "{0}",
background: "transparent"
}
},
series: [{
type: "line",
field: "Incoming",
categoryField: "DateTime",
}],
valueAxis: {
labels: {
format: "{0}"
},
line: {
visible: false
}
},
categoryAxis: {
labels:
{
rotation: -90,
dateFormats:
{
seconds: "ss",
minutes: "HH:mm:ss",
hours: "HH:mm",
days: "dd/MM",
months: "MMM 'yy",
years: "yyyy"
}
}, type: "Date", field: "DateTime", baseUnit: "seconds"
}
});
var chart = $("#queueActivityChart").data("kendoChart");
chart.options.transitions = false;
}
$(document).ready(queueActivityChart);
$(document).bind("kendo:skinChange", queueActivityChart);
Then I have this part of code, that get from server data
$scope.signalRData = [];
$scope.signalR.on('receiveCounters', function (data) {
$scope.queueData = data;
for (var i = 0; i < data.length; i++) {
$scope.signalRData.push(data[i]);
}
while ($scope.signalRData.length > 12) {
$scope.signalRData.splice(0, 1);
}
$("#queueActivityChart").data("kendoChart").setDataSource(
new kendo.data.DataSource({
group: {
field: "Name"
},
data: $scope.signalRData
}));
});
This works! And I get a picture of the latest updated items.
But the problem is that this chart is like to put one picture in front of other. I mean that this is the first time that load Data Source; that creates a chart of my data, the second time my data has changed, some values are still in my array some others has move out, the third too.
It seems like it puts a picture of my current data in front of the
previous data. It's not smoothie and cannot use chart's legend
property because I initialize my Data Source everytime.
Can someone help me how can create a smoothie kendo chart with real time data like the kendo official example? Also can somehow to add scroller to bottom?
I looked at the code for the benchmark and I think you may be missing in your chart which is renderAs: "canvas"
Also, in the example, the data is kept locally (saved) and then moved so it creates that "smooth" effect you may be talking about.
Here is the code that you can be of interest:
function step() {
addPoint();
$("#chart").data("kendoChart").refresh();
frames++;
if (playing) {
kendo.animationFrame(step);
}
}
function addPoint() {
var stockData,
change,
lastValue;
// Shift existing categories to the left and add the next date at the end
lastDate = new Date(lastDate.getTime() + TICKS_PER_DAY);
categoryList.push((lastDate.getMonth() + 1) + "/" + (lastDate.getDay() + 1));
if (categoryList.length > POINTS) {
categoryList.shift();
}
for (var i = 0; i < stocks.length; i++) {
stockData = stocks[i];
change = (Math.random() > 0.5 ? 1 : - 1) * Math.random() * 10;
lastValue = stockData[stockData.length - 1] || Math.random() * 10;
// Add a new pseudo-random data point
stockData.push(Math.min((i + 1) * 20, Math.max((i + 1) * 10, lastValue + change)));
// Shift the data points of each series to the left
if (stockData.length > POINTS) {
stockData.shift();
}
}
}
Check out the source code of your example for the full source code and use the dojo to test our their code and play around with it easily

ZoomRange Highstock works not correct?

I made a Highstock diagramm and got aproblem with zooming on the yAxis.
I have a Button and 2 textfield to get the wanted min/max values for the axis. With min:0, max: 100 it works well. With min:0, max:80 it doesn't (max will still be 100 in the Diagramm).
If I use the mouse for zooming it works well (even a min of: 3.7 and a max of 3.894 is possible). But using the mouse is not an Option, because in the later Diagramm there will be 3 yAxes with individual zoom.
$(function () {
var seriesOptions = [],
seriesCounter = 0,
names = ['MSFT', 'AAPL', 'GOOG'];
/**
* Create the chart when all data is loaded
* #returns {undefined}
*/
function createChart() {
$('#container').highcharts('StockChart', {
rangeSelector: {
selected: 4
},
chart:{
zoomType: 'xy'
},
yAxis: [
{
labels: {
format: '{value}',
},
height: '100%',
opposite: false,
plotLines: [{
value: 0,
width: 2,
color: 'silver'
}]
},
],
plotOptions: {
series: {
compare: 'percent'
}
},
tooltip: {
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
valueDecimals: 2
},
series: seriesOptions
},
function(chart){
$('#btn').click(function(){
var min = temp_min.value,
max = temp_max.value;
chart.yAxis[0].setExtremes((min),(max));
});
});
}
$.each(names, function (i, name) {
$.getJSON('https://www.highcharts.com/samples/data/jsonp.php?filename=' + name.toLowerCase() + '-c.json&callback=?', function (data) {
if(seriesCounter==0){
seriesOptions[i] = {
name: name,
data: data,
yAxis: 0
};
} else {
seriesOptions[i] = {
name: name,
data: data,
yAxis: 0
};
}
// As we're loading the data asynchronously, we don't know what order it will arrive. So
// we keep a counter and create the chart when all the data is loaded.
seriesCounter += 1;
if (seriesCounter === names.length) {
createChart();
}
});
});
});
JSFiddle
Another Question: Is it possible to set up a scrollbar for the yAxis as well?
Thanks for your help, Patrick
This is related with fact that tickInterval is not regular, so is rounded to value (like 100). The solution is using tickPositioner which calculates ticks, based on extremes which you define.
tickPositioner: function (min,max) {
var positions = [],
tick = Math.floor(min),
increment = Math.ceil((max - min) / 5);
for (tick; tick - increment <= max; tick += increment) {
positions.push(tick);
}
return positions;
},
http://jsfiddle.net/6s11kcwd/
The scrollbar is supported only for xAxis.

How to get a simple line for average on a bar chart in jqPlot

I am working with a bar chart in jqPlot where I need to show the average with a separate line on the chart. My question is how to do that? Would I need to use a Line chart for this? I have looked at few Line chart examples but they are shown as a trend in the bar chart which starts from the very first bar on the chart rather than showing the average.What I need is plotting a line using the average of all those bars displayed on the chart (screen shots as below)
My JSON string to plot the data is as follows:
var commonOption= {
title: ''
,stackSeries: true
,captureRightClick: true
,seriesDefaults:{
renderer:$.jqplot.BarRenderer
,rendererOptions: {
barMargin: 15
,highlightMouseDown: true
,fillToZero: true
},
pointLabels: {
show: true
,formatString: '%.1f'
,seriesLabelIndex:1
,hideZeros:false
}
}
,seriesColors: ['#A9CB5E']
,axes: {
xaxis: {
tickOptions:{angle:-45}
,tickRenderer: $.jqplot.CanvasAxisTickRenderer
,renderer: $.jqplot.CategoryAxisRenderer
,ticks: []
},
yaxis: {
labelRenderer: $.jqplot.CanvasAxisLabelRenderer
,padMin: 0
,pad: 1.1
, label: 'Percentage (%)'
,rendererOptions: { forceTickAt0: true}
//,min: 0
//,tickOptions:{formatString: '%.0f'},
}
}
,negativeSeriesColors:['#F08080']
/*,legend: {
show: true
,location: 'e'
,placement: 'outsideGrid'
}*/
,highlighter:{
show: true
,tooltipLocation: 's'
,yvalues: 2
,bringSeriesToFront:true
,showMarker:false
,tooltipAxes: 'y'
,formatString: "%n%s"
}
,cursor:{
show: true
,zoom:true
,showTooltip:false
,constrainZoomTo: 'y'
}
,grid:{
background: '#f8f8f8'
}
};
I believe what you are looking for is the jqplot CanvasOverlay functionality http://www.jqplot.com/deploy/dist/examples/canvas-overlay.html
After declaring all of you options and data in the function (in your example after "grid" option):
grid:{
background: '#f8f8f8'
},
canvasOverlay: {
show: true,
objects: [
{horizontalLine: {
name: 'avergae',
y: 20.8, //**AVERAGE_FLOAT_VALUE**
lineWidth: 2,
color: 'black',
shadow: false
}}
]
}
EDIT:
Yes, sorry about that. be sure not to forget to include "jqplot.canvasOverlay.min.js "
Hi I think it is better to implement a function that automatically calculates the average of the data points into the array. In fact the average of your bar char is about 18 and not 20!!
I suggest to implement a function for doing thi. See this jsFiddle example.
There is an article where it is shown how to draw and calculate the statistics for a bar chart: average, median, mode and standard deviation at this link:
http://www.meccanismocomplesso.org/en/mean-mode-median-barchart/ .
Array.prototype.average=function(){
var sum=0;
var j=0;
for(var i=0;i<this.length;i++){
if(isFinite(this[i])){
sum=sum+parseFloat(this[i]);
j++;
}
}
if(j===0){
return 0;
}else{
return sum/j;
}
}
...
canvasOverlay: {
show: true,
objects: [
{dashedHorizontalLine: {
name: 'average',
y: data.average(),
lineWidth: 3,
color: 'black',
shadow: false
}}
]
}

Resources