How can I make highlighted points stay after regraphing using flot? - javascript

Currently, I have a plot that supports "lockable" points. I register a plotclick event, highlight the point, and display a tooltip that I give the id of "lockedPoint" + item.dataindex. I also have a zoom feature that uses jquery.flot.selection.js. By using this, I modify my x and y axis maximums and minimums, and I replot my data (essentially throwing away the old data). I am trying to preserve the "locked points" when zooming.
One solution I have thought of is when gathering my data, I can specifically push the point into the correct place in the series that it needs to be if it is within range of the zoom and then highlight the point. However, this does not seem to be working correctly.
I store the data about "locked points" in an associative array initialized like this.
for (var i = 1; i < 5; i++){
lockedPoints["Series " + i] = [];
}
Then I allow a maximum of three items to be pushed onto each array (max of three lockable points per series). I would replace the point in the array with the new highlighted point (I believe the only thing that would change would be the dataindex). Is it possible that I could make points survive zooming by pushing them into the data series when gathering data?
function gatherData(kElement, a0Element){
//PRE: kElement is the id of the element containing the rate constant, and a0Element is the id of the element
// containing the molarity
//POST: FCTVAL is a data series in the format of {data: data, lines: {show: true}, color: "color", label: "label"}
var xData = []; //x-coordinates
var yData = []; //y-coordinates
var data = []; //array of coordinate pairs
var startingPoint; //least x-value to graph
var finishingPoint; //greatest x-value to graph
var range; //range of x-values
var interval; //value to evenly space x-values for calculations
var current; //current value of x-coordinate for which we are
// calculating a y-value
var labelParent; //parent node of x-axis labels
var k; //rate constant value
var molarValue; //molarity value
var result; //result of the rate equation
numXPoints = 1001;
if (document.getElementById("logCheck").value == "On"){ //graph logarithmically
logarithmic = true;
}
else{ //graph decay
logarithmic = false;
}
if (document.getElementById("blackWhite").value == "On"){
blackWhite = true;
}
else{
blackWhite = false;
}
k = document.getElementById(kElement).value;
molarValue = document.getElementById(a0Element).value;
startingPoint = minX; //we will say time starts at 0 always for this plot
finishingPoint = maxX;
range = finishingPoint - startingPoint; //calculated range for determining points to plot
interval = range / numXPoints; //we will graph numXPoints points
current = startingPoint;
result = molarValue * Math.pow(Math.E, (-k * current));
if (logarithmic){ //for logarithmic calculations
result = Math.log(result);
}
if (result > maxValue){ //store largest y-value
maxValue = result;
}
for (var i = 0; i < numXPoints; i++){ //store x-values and calculated y-values
// and find max y-value
xData[i] = current;
yData[i] = result;
current += interval;
result = molarValue * Math.pow(Math.E, (-k * current));
if (logarithmic){ //for logarithmic calculations
result = Math.log(result);
}
if (yData[i] > maxValue){ //store largest y-value
maxValue = yData[i];
}
if (yData[i] < minValue && minValue > -400){ //store smallest y-value
minValue = yData[i];
}
}
for (var i = 0; i < numXPoints; i++){ //combine coordinates into one series
data[i] = [xData[i], yData[i]];
}
//modified jquery.flot.js to support dashed line hover.
//values modified were pointRadius and radius in the drawPointHighlight method.
if (blackWhite == true){
switch(kElement){
case "kValue1":
return {points:{show: true, radius: 0}, data: data, lines:{show: true}, color: "black", label: "Series 1", shadowSize: 0};
break;
case "kValue2":
return {points:{show: true, radius: 0}, data: data, dashes:{show: true, dashLength: 2}, color: "black", label: "Series 2", shadowSize: 0};
break;
case "kValue3":
return {points:{show: true, radius: 0}, data: data, dashes:{show: true, dashLength: 10}, color: "black", label: "Series 3", shadowSize: 0};
break;
case "kValue4":
return {points:{show: true, radius: 0}, data: data, dashes:{show: true, dashLength: 20}, color: "black", label: "Series 4", shadowSize: 0};
break;
}
}
else{
switch (kElement){ //return proper object to match flot graph description
case "kValue1":
return {points:{show: false, radius: 0}, data: data, lines:{show: true}, color: "red", label: "Series 1"};
break;
case "kValue2":
return {points:{show: false, radius: 0}, data: data, lines:{show: true}, color: "blue", label: "Series 2"};
break;
case "kValue3":
return {points:{show: false, radius: 0}, data: data, lines:{show: true}, color: "green", label: "Series 3"};
break;
case "kValue4":
return {points:{show: false, radius: 0}, data: data, lines:{show: true}, color: "gold", label: "Series 4"};
break;
}
}
}
I can find a way to deal with the tooltip removal, but highlighting the same point is the most trivial part.

If you use a combination of setupGrid and draw, the highlights will persist between redraws and you won't need to manually reapply (like you would if you re-init).
Also, to manipulate the data between re-draws (say on the zoom selection), use a combination of getData and setData:
$("#flot_chart").bind("plotselected", function (event, ranges) {
var fr = ranges.xaxis.from;
var to = ranges.xaxis.to;
$.each(plot.getXAxes(), function(_, axis) {
var opts = axis.options;
opts.min = fr;
opts.max = to
});
//pad data points, so the hover effect doesn't look spotty
var series = plot.getData(); // get series
series[0].data = []; // clear it out
for (var i = fr; i < to; i+=(to-fr)/100){
series[0].data.push([i,Math.sin(i)]); // push in data with a suitable increment to i
}
plot.setData(series); // set the new data
plot.setupGrid(); // redo the grid
plot.draw(); // redraw the plot
plot.clearSelection();
});
Here's a fiddle example.

Related

Stacked bar chart with (computed) average line in Plotly.js

I've got a (stacked) bar chart and I want an average line plotted on my chart.
Let's take this example:
var trace1 = {
x: ['giraffes', 'orangutans', 'monkeys'],
y: [20, 14, 23],
name: 'SF Zoo',
type: 'bar'
};
var trace2 = {
x: ['giraffes', 'orangutans', 'monkeys'],
y: [12, 18, 29],
name: 'LA Zoo',
type: 'bar'
};
var data = [trace1, trace2];
var layout = {barmode: 'stack'};
Plotly.newPlot('myDiv', data, layout, {showSendToCloud:true});
Result:
Expected output:
I've found a similar question, but in that case it was pretty easy to add a line with a 'fixed' value. In this case I've got a stacked bar chart nicolaskruchten/pivottable, so the user can easily drag and drop columns. That makes computing the average harder.
I can loop through all results and compute the average value, but since Plotly is very powerful and has something like aggregate functions, I feel like there should be a better way.
How can I add a (computed) average line to my (stacked) bar chart?
Plotly.js not provided any direct options for drawing average line.
But you can do this simple way.
//Find average value for Y
function getAverageY() {
allYValues = trace1.y.map(function (num, idx) {
return num + trace2.y[idx];
});
if (allYValues.length) {
sum = allYValues.reduce(function (a, b) {
return a + b;
});
avg = sum / allYValues.length;
}
return avg;
}
//Create average line in shape
var layout = {
barmode: 'stack',
shapes: [{
type: 'line',
xref: 'paper',
x0: 0,
y0: getAverageY(),
x1: 1,
y1: getAverageY(),
line: {
color: 'green',
width: 2,
dash: 'dot'
}
}]
};
Updated:
You need to update your graph after loading this drawing a average
line for any numbers of trace.
//Check graph is loaded
if (document.getElementById('myDiv')) {
//draw average line
drawAvgLine(document.getElementById('myDiv'))
}
function drawAvgLine(graph) {
var graphData = graph.data; //Loaded traces
//making new layout
var newLayout = {
barmode: 'stack',
shapes: [{
type: 'line',
xref: 'paper',
x0: 0,
y0: getAverageY(graphData),
x1: 1,
y1: getAverageY(graphData),
line: {
color: 'green',
width: 2,
dash: 'dot'
}
}]
};
//Update plot pass existing data
Plotly.update('myDiv', graphData, newLayout)
}
//Calculate avg value
function getAverageY(graphData) {
var total = [],
undefined;
for (var i = 0, n = graphData.length; i < n; i++) {
var arg = graphData[i].y
for (var j = 0, n1 = arg.length; j < n1; j++) {
total[j] = (total[j] == undefined ? 0 : total[j]) + arg[j];
}
}
return total.reduce(function (a, b) {
return a + b;
}) / total.length;
}

Chart js sum of grouped bars

I have 3 different values for each grouped bar. I want to sum those 3 values and display the sum on top of each column - Σ59 and Σ89.
I'm testing code below which is not working correctly. Instead of column values, number 59 is displayed above every single column.
formatter: function(value, ctx) {
console.log(ctx.chart.data.datasets)
let sum = 0;
for (var i = 0; i < ctx.chart.data.datasets.length; i++) {
sum += parseInt(ctx.chart.data.datasets[i].data[0]);
}
return sum;
}
you can try something like this fiddle, but i did not format and position the labels as of now, you can do it as you wish.
"onComplete": function() {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
var sums = [0, 0, 0];
this.data.datasets.forEach(function(dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function(bar, index) {
var data = dataset.data[index];
ctx.fillText(data, bar._model.x, bar._model.y);
sums[index] += data;
});
});
sums.forEach(function(v, i) {
ctx.fillText(v, 150*(i+1)+(i*50), 40);
});
}
The formatter is called for each datalabel, meaning for every bar. That's the reason why you get the same value on top of every bar.
Possible solutions would be either to plot an "invisible" bar for the combined value or check inside the formatter function if it is called for the centered bar. Then you could use mulitline labels to create a label which has the value for the current bar and the combined value as well.
Edit:
After thinking about it the easiest way would be to use mixed charts. You can just calculate your combined values for each group and add a chart for these values. For example you add a line chart with showLines: false.
new Chart('myMixedCharts', {
type: 'bar',
data: {
labels: ['foo', 'bar'],
datasets: [
{
label: "Value 1",
data: [12, 5]
},
{
label: "Value 2",
data: [7, 8]
},
{
label: "Group value",
data: [19, 13],
type: "line",
showLines: false
}
]
}
});
Or as jsFiddle

How to limit zooming of a vis.js network?

I've implemented a simple network using vis.js. Here's my code:
//create an array of nodes
var nodes = [
{
id: "1",
label: "item1"
},
{
id: "2",
label: "item2"
},
{
id: "3",
label: "item3"
},
];
// create an array with edges
var edges = [
{
from: "1",
to: "2",
label: "relation-1",
arrows: "from"
},
{
from: "1",
to: "3",
label: "relation-2",
arrows: "to"
},
];
// create a network
var container = document.getElementById('mynetwork');
// provide the data in the vis format
var data = {
nodes: nodes,
edges: edges
};
var options = {};
// initialize your network!
var network = new vis.Network(container, data, options);
On performing the zoom-out operation multiple times the network disappears. Are there any functions to limit the zooming level?
I wrote you some code to get this function working since there is no zoomMax function within the network of vis.js, I wrote some basic logic to help you out.
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var afterzoomlimit = { //here we are setting the zoom limit to move to
scale: 0.49,
}
var options = {};
var network = new vis.Network(container, data, options);
network.on("zoom",function(){ //while zooming
if(network.getScale() <= 0.49 )//the limit you want to stop at
{
network.moveTo(afterzoomlimit); //set this limit so it stops zooming out here
}
});
Here is a jsfiddle: https://jsfiddle.net/styb8u9o/
Hope this helps you.
You can use this code is better because you will never go to the middle of the network when you reach the zoom limit:
//NetWork on Zoom
network.on("zoom",function(){
pos = [];
pos = network.getViewPosition();
if(network.getScale() <= 0.49 )
{
network.moveTo({
position: {x:pos.x, y:pos.y},
scale: 0.49,
});
}
if(network.getScale() >= 2.00 ){
network.moveTo({
position: {x:pos.x, y:pos.y},
scale: 2.00,
});
}
});
Here is a version that preserves the last position, to prevent an annoying jump or slow pan when you get to the maximum extent.
let MIN_ZOOM = 0.5
let MAX_ZOOM = 2.0
let lastZoomPosition = {x:0, y:0}
network.on("zoom",function(params){
let scale = network.getScale()
if(scale <= MIN_ZOOM )
{
network.moveTo({
position: lastZoomPosition,
scale: MIN_ZOOM
});
}
else if(scale >= MAX_ZOOM ){
network.moveTo({
position: lastZoomPosition,
scale: MAX_ZOOM,
});
}
else{
lastZoomPosition = network.getViewPosition()
}
});
network.on("dragEnd",function(params){
lastZoomPosition = network.getViewPosition()
});
Still, it will be redundant once the following issue is resolved:
https://github.com/visjs/vis-network/issues/574

How to set enabled/disabled dataset in a NVD3 line chart?

I see in the legend on the top right here http://nvd3.org/examples/line.html radio buttons which I can click to turn on/off some datasets on the chart.
Is there an option to set this value for each dataset individually when I load data into chart?
You can set a disabled flag on the data to hide it on load. If you go to this example and add a disabled flag to the Data (JSON) tab you can see this.
function() {
var sin = [],
cos = [];
for (var i = 0; i < 100; i++) {
sin.push({x: i, y: Math.sin(i/10)});
cos.push({x: i, y: .5 * Math.cos(i/10)});
}
return [
{
values: sin,
key: 'Sine Wave',
color: '#ff7f0e',
disabled: true
},
{
values: cos,
key: 'Cosine Wave',
color: '#2ca02c'
}
];
}

How to display only last point on highcharts and that point should travel with chart line?

I have a area chart which is having a dynamic point that will be added to chart.I got this http://jsfiddle.net/rjpjwve0/
but it looks like the point gets displayed first and then after a delay the chart draws back. Now i want to display the last point which will be a animated point and it should travel with chart without delay in rendering.
Could any one help me to achieve this.
I put together a test, and it seems to work well.
I updated the load event to add a second series, using the same series.data[len -1] values; then in the setInterval portion, we update that new point at each iteration.
That way, by updating the existing marker rather than destroying one marker and creating another, the animation works as desired.
Code:
events: {
load: function () {
var series = this.series[0],
len = series.data.length;
//-------------------------------------
//added this part ->
this.addSeries({
id: 'end point',
type: 'scatter',
marker: {
enabled:true,
symbol:'circle',
radius:5,
fillColor:'white',
lineColor: 'black',
lineWidth:2
},
data: [[
series.data[len - 1].x,
series.data[len - 1].y
]]
});
var series2 = this.get('end point');
//-------------------------------------
setInterval(function () {
var x = (new Date()).getTime(),
y = Math.random();
len = series.data.length;
series.addPoint([x,y], true, true);
//and added this line -->
series2.data[0].update([x,y]);
}, 1000);
}
}
Fiddle:
http://jsfiddle.net/jlbriggs/a6pshutt/
You can try this :
series: [{
name: 'Random data',
marker : {
enabled : false,
lineWidth: 0,
radius: 0
},
data: (function () {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}]
Its works.
Greg.

Categories