jQuery each going outside of function - javascript

I have a jqPlot chart that I want to add links on and I believe I figured out a way to do it using an array such as [[[1,2,"http://google.com"]],[[2,3,"http://yahoo.com]]] however, when I try to load this via XML, jQuery, and Ajax it doesn't quite work.
I believe that the problem lies within the .each clauses found in this code:
function getBars(xml)
{
var categoryid = 1;
var bars = [];
$(xml).find("category").each(
function()
{
bars.push(loadBars(categoryid,$(this)));
categoryid++;
});
return bars;
}
function loadBars(categoryid,xml)
{
var bar = [];
var bars = [];
$(xml).find("bar").each(function()
{
bar.push(parseInt(categoryid));
bar.push(parseInt($(this).attr("size")));
bar.push($(this).attr("link"));
bars.push(bar);
});
$("#debug").append("\nBAR:")
debug2dArray(bars);
return bars;
}
The XML looks like:
<?xml version="1.0"?>
<chart>
<category>
<bar size="20" link="http://google.com"/>
</category>
<category>
<bar size="70" link="http://yahoo.com" />
</category>
</chart>
Here is a jsFiddle
Update
After updating the variables to be non-global, the chart now displays right, but two of the same values are still being added to the array. Code has been updated to reflect changes.

I haven't digested your whole code yet, but one really fatal pitfall you're doing is using variables in your functions that haven't been declared with var (I'm particularly looking at how you've used your bar variable on both functions).
When you use a variable without declaring it with var like you're doing here, you're bringing the variable to a global visibility. That means that that variable is the same variable used (most) everywhere in your code. The same bar in the first function is the same bar in the second.
When your two functions start, the first thing it does is clear the bar variable (i.e. bar = [];). Since they're sharing bar references, calling one function effectively nullifies what the other did.
Is this your intention? If not (or even so), you should declare your variable with var:
var categoryId = 1,
bar = [];

In addition to the lack of var, you are returning variables at the end of the each iterators, instead of the end of the function. Here's a working fiddle: http://jsfiddle.net/fwRSH/1/
function loadBars(categoryid, xml) {
var bar = [];
var bars = [];
$(xml).find("bar").each(function() {
bar.push(parseInt(categoryid, 10));
bar.push(parseInt($(this).attr("size"), 10));
bar.push($(this).attr("link"));
bars.push(bar);
//$("#debug").append("\nBAR:"); //not defined in fiddle, commented out
//debug2dArray(bars); //not defined in fiddle, commented out
});
return bars; //moved from end of "each" iterator to here.
}
function getBars(xml) {
var categoryid = 1;
var bars = [];
$(xml).find("category").each(function() {
bars.push(loadBars(categoryid, $(this)));
categoryid++;
});
return bars;
}
$(document).ready(function() {
var bars = [];
$("div#barchart").css("background-color", "#F00");
$("div#barchart").css("height", "200px");
$("div#barhcart").css("width", "400px");
//moved for debugging
bars = getBars($('div#xmlDI'));
/* returns:
* [
* [
* [1, 20, "http://google.com"]
* ],
* [
* [2, 70, "http://yahoo.com"]
* ]
* ]
*/
$.jqplot("barchart", bars, {
seriesDefaults: {
renderer: $.jqplot.BarRenderer,
rendererOptions: {
fillToZero: true
}
},
axes: {
// Use a category axis on the x axis and use our custom ticks.
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
ticks: ['one', 'two'],
autoscale: true
},
yaxis: {
autoscale: true
}
}
});
});​

None of your variables are declared using var, particularly the bars array. This causes them to be implicitly global, and you overwrite the variable every time you call loadBars.

I am not sure how you want your graph to look like. Because the data you provide to the graph, in general terms, is 'correctly' displayed. If you would write it in the following way:
[
[[1, 30, "http://google.com"], [2,0,""]],
[[1,0,""],[2, 40, "http://yahoo.com"]]
]
...it would give exactly the same results, the library just assumes that the data which is not provided for a particular series is 0 and this is how it is treated as visible here.
Since you do not like it this way my guess is that you made a formatting error in your data variable, as we can see here the 'gap' is gone.
Therefore, I think that the below is the format you are after:
[[
[1, 30, "http://google.com"],
[2, 40, "http://yahoo.com"]
]]
Additionally, as it goes to clicking on a bar of a bar chart you could find useful the answer to the problem. There you could see how to capture the click and how to open a URL. You would just need to slightly adopt it to your need as I used a global array of URLs.

Code to parse the XML:
var bars = [], cat = 0;
$.ajax({
type: 'GET',
url: 'plotlinks.xml',
dataType: "xml",
cache: true,
success: function(data, textStatus, jqXHR) {
$(data).find("category").each( function() {
var barSet = [cat];
$(this).find("bar").each(function() {
var $elt = $(this);
barSet.push([$elt.attr('size'),$elt.attr('link')]);
});
cat++;
bars.push(barSet);
});
// bars is an array; each element is an array.
// The first element in the inner array is the
// category "index" (0,1,2,...). All other
// elements represent a link for that category.
// Those elements are arrays of [size,url].
alert($.stringifyJSON(bars));
}
});
Resulting json:
[[0,
["20","http://google.com"]
],
[1,
["70","http://yahoo.com"]
]
]

Related

Highcharts JS- add third variable to tooltip for two series

I've already figured out how to make a chart using highcharts where there are three variables- one on the X axis, one on the Y axis, and one on the tooltip. The way to do this is to add the following to the tooltip:
tooltip: {
formatter () {
// this.point.x is the timestamp in my original chartData array
const pointData = chartData.find(row => row.timestamp === this.point.x)
return pointData.somethingElse
}
}
See this fiddle for the full code:
https://jsfiddle.net/m9e6thwn/
I would simply like to do the same, but with two series instead of one. I can't get it to work. I tried this:
tooltip: {
formatter () {
// this.point.x is the timestamp in my original chartData array
const pointData = chartData1.find(row => row.timestamp === this.point.x)
return pointData.somethingElse
const pointData2 = chartData2.find(row => row.timestamp === this.point.x)
return pointData2.somethingElse
}
}
Here is the fiddle of the above: https://jsfiddle.net/hdeg9x02/ As you can see, the third variable only appears on one of the two series. What am I getting wrong?
There are some issues with the way you are using the formatter now. For one, you cannot have two returns in the same function without any if clauses. That will mean that only the first return will be used.
Anyway, here are some improvements I suggest you do for your code.
Add the extra information for each point to highcharts, that makes it a lot easier to access this information through highcharts. E.g. in a tooltip. You can set the data like this:
chartData1.map(function(row) {
return {
x: row.timestamp,
y: row.value,
somethingElse: row.somethingElse
}
})
If you do that, then returning the correct tooltip for each series is a simple matter of doing this:
tooltip: {
formatter () {
// this.point.x is the timestamp in my original chartData array
return this.point.somethingElse
}
}
Working JSFiddle example: https://jsfiddle.net/ewolden/dq7L64jg/6/
If you wanted more info in the tooltip you could then do:
tooltip: {
formatter () {
// this.point.x is the timestamp in my original chartData array
return this.point.somethingElse + ", time: " + str(this.x) + ", value: " + str(this.y)
}
}
Addtionally, you need to ensure that xAxis elements, i.e. your timestamps are sorted. This is a requirement for highcharts to function properly. As it is, your example is reporting
Highcharts error #15: www.highcharts.com/errors/15
in console, because chartData2 is in reverse order. It looks okay for this example, but more complicated examples can lead to the chart not looking as you expect it to.
For this example using reverse is easy enough: data: chartData2.reverse().map(function(row) {return {x: row.timestamp, y: row.value, somethingElse: row.somethingElse}})
Working JSFiddle example: https://jsfiddle.net/ewolden/dq7L64jg/7/

chartjs push array to label not working

Dynamically updating a chartjs chart and creating the labels in an array format (["A","B","C"]). However chartjs doesn't accept a push of the label array unless it is in the format "A","B","C" (without brackets). Anyone else experience this or have I misunderstood? Se code below. Produces this
Instead of this (ok when adding labels as chart.data.labels.push("A","B","C","D")
var chart = new Chart(document.getElementById("element"), {
type: 'bar',
options: {
legend: {
display: false
}
}
});
//PUSH DATA TO GRAPH.
var verserier = [];
var veromslperserie = [];
var stat = seriestat(); //function to retrieve data for labels
$.each(stat, function(i, item) {
verserier.push(i);
veromslperserie.push(item["omsl"]);
});
chart.data.labels.push(verserier); //error occurs here
chart.data.datasets.push({
label: "Omsl",
data: veromslperserie,
backgroundColor: colorarray,
});
chart.update();
When you push outside of the loop you are actually pushing verserier into position [n], which in this case is 0.
If you do not add values again you can do
chart.data.labels = verserier
I don't have the explanation of the "why", but here's a workaround :
var data_array = new Array();
// This doesn't work (but with no error on my side, appart from visually wrong labels)
myChart.data.labels.push(data_array);
// But this works
for(i=0;i<data_array.length;i++)
{
myChart.data.labels.push(data_array[i]);
}

dc.js: include all bars when count is zero

I need to include all bars in the graph. Example: I have five bars (1,2,3,4,5) with reduceCount from score. When all bars have values, the graph is fine.
But, when bars don't have values (in this case, one):
I tried adding .y(d3.scale.ordinal().domain([1, 2, 3, 4, 5])) but this method doesn't exist in a rowchart graph.
You can use a "fake group" to ensure that bins exist even when there aren't any values to fill them.
From the FAQ:
function ensure_group_bins(source_group) { // (source_group, bins...}
var bins = Array.prototype.slice.call(arguments, 1);
return {
all:function () {
var result = source_group.all().slice(0), // copy original results (we mustn't modify them)
found = {};
result.forEach(function(d) {
found[d.key] = true;
});
bins.forEach(function(d) {
if(!found[d])
result.push({key: d, value: 0});
});
return result;
}
};
};
Use it like this:
var mod_group = ensure_group_bins(your_group, 1, 2, 3, 4, 5);
chart.group(mod_group)
I wouldn't use .data() here because it can interfere with capped charts, of which the row chart is one. (Although it's entirely reasonable to expect it to work.)

[possible bug]Values in array as attribute of object set to undefined

I have encountered one of the most bizzare and frustrating behaviours yet. I have sample data:
var nodes = [ //Sample data
{
ID: 1,
Chart: 1,
x: 50,
y: 50,
width: 100,
height: 80,
color: "#167ee5",
text: "Start",
label: "Start",
targets: [2]
},
{
ID: 2,
Chart: 1,
x: 500,
y: 170,
width: 100,
height: 80,
color: "#167ee5",
text: "End",
label: "End",
targets: [3]
},
{
ID: 3,
Chart: 1,
x: 270,
y: 350,
width: 100,
height: 80,
color: "#167ee5",
text: "Mid",
label: "Mid",
targets: []
}
];
for my web application. The issue is with the targets attribute. As you can see it is array. However when I do
console.log(nodes[0]);
and inspect the result in the browser it shows that the value for targets at index 0 is undefined. Same for every other targets that has some values in them (whether 1 or more).
However if I do
console.log(nodes.[0].targets);
it prints out [2]. If I do Array.isArray(nodes[0].targets) it returns false, yet if I do console.log(nodes[0]) and inspect the result in the browser console, it shows that the object prototype is in fact Array and simply the value at index 0 is undefined.
It worked the day before and now it doesn't. The only thing I did was I restructured the object that uses this variable later. But the console log is being called before the object is even instantiated for the first time (and it doesn't change the nodes var anyway, only reads it).
Does anyone have any clues as to what might be causing this behaviour. If it helps I am using Paperscript and this code runs in the paperscript scope (as it did before when everything worked fine).
UPDATE
Ok after more blind debugging I have determined the block of code that causes the issue, how or why is completely beyond me.
Basically I define an object constructor beflow. The constructor loops through the nodes, makes Paperscript shapes and adds the targets to the arbitrary data attribute of the paperJS path object:
function Flowchart(nodes, chartdata) {
//Member vars. They are only used internally
var connections = [];
var shapes = [];
var connectors = [];
//Constructor operations
createShapes(nodes); //If I uncomment this, the problem goes away
//...
function createShapes(nodes) {
nodes.forEach(function (node) { //for each node data entry
console.log(node); //At this point, the targets are screwed up already
var point = new Point(node.x, node.y); //make a PaperJS point for placement
var size = new Size(node.width, node.height); //make a PaperJS size object
var shape = makeRectangle(point, size, 8, node.color); //Pass to the object instantiating function
shape.data = { //Store arbitrary data for programming reference.
ID: node.ID,
label: node.label,
text: node.text,
'connectors': {
to: [],
from: []
},
targets: node.targets //this is undefined
};
console.log(node.targets) //this logs [2] or [3] but not Array[1]...
shapes.push(shape); //Store reference for later
});
shapes.forEach(function (shape) { //loop though all drawn objects
if (shape.data.targets.length > 0) { //if shape has targets
var targets = _.filter(this.shapes, function (target) {
return _.contains(shape.data.targets, target.data.ID);
});
for (var i = 0; i < shape.data.targets.length; i++) {
shape.data.targets[i] = targets[i]; //Replace the ID-type reference with drawn object reference
}
}
});
}
//... The rest of the object
}
console.log(nodes);
//It doesnt seem to matter whether i put this before or after instantiating.
//It doesnt even matter IF I instantiate in the first place.
var chart = new Flowchart(nodes, chartdata);
This behaviour has been caused by the changes to how Chrome treats enumerable properties of objects. Because Chrome updates silently, it's impossible to notice.
It must have been causing me a lot of headache if I remembered the cause after all this time... (Also it's embarrassing how bad I was at writing questions, but I guess that I realise it means I have progressed since then somewhat).

Highcharts: passing additional information to a tooltip

I have an array of data points that I am passing to a Highcharts chart that looks like
mydata = [{
x: 1,
y: 3,
nameList: ["name1", "name2"]
}, {
x: 2,
y: 4,
nameList: ["name3", "name4"]
}]
I build the chart like this:
$("#chart").highcharts("StockChart", {
series: [{
data: mydata
}, {
data: yourdata
}]
});
Now, I would like to be able to access the nameList array from the shared tooltip, which I'm trying to do as follows:
tooltip: {
formatter: function() {
var s = "";
$.each(this.points, function(i, point) {
s += point.point.nameList;
});
return s;
},
shared: true
}
but when examining the point objects in Firebug using console.log(point), I can't seem to find the nameList entry anywhere in them. How could I access this auxiliary information in a shared series tooltip? All help is appreciated.
Eureka!
By default, Highcharts will accept several different types of input for the data of a series, including
An array of numerical values. In this case, the numberical values will be interpreted
and y values, and x values will be automatically calculated, either starting at 0 and
incrementing by 1, or from pointStart and pointInterval given in the plotOptions.
An array of arrays with two values. In this case, the first value is the x value and the
second is the y value. If the first value is a string, it is applied as the name of the
point, and the x value is incremented following the above rules.
An array of objects with named values. In this case the objects are point configuration
objects as seen below.
However, the treatment of type 3 is different from types 1 and 2: if the array is greater than the turboThreshold setting, then arrays of type 3 won't be rendered. Hence, to fix my problem, I just needed to raise the turboThreshold setting like so:
...
plotOptions: {
line: {
turboThreshold: longestArray.length + 1
}
},
...
and the chart renders the longestArray data properly. Hurray! The only drawback is that there is a considerable time spent rendering the data for much longer arrays due to "expensive data checking and indexing in long series." If any of you know how I might be able to bypass this checking or otherwise be able to speed up the processing of this data, I'd be extremely thankful if you'd let me know how.
I can see it here:
tooltip: {
formatter: function() {
var s = "";
console.log(this.points[0].point.nameList); // ["name1", "name2"]
$.each(this.points, function(i, point) {
s += point.point.nameList;
});
return s;
},
shared: true
}

Categories

Resources