D3 reusable multi-line chart with JSON data - json

I'm trying to do some re-factoring on my charts to make them re-usable using this as a guide: http://bost.ocks.org/mike/chart/
I'm having problems drawing the lines in my multi-line graph though - specifically passing the data to the x and y values. If I hard code the element names it works, but if I try to use the xValue and yValue objects this does not work. I'm assuming that this is because I'm trying to call a function within the parameter of an other object, but I'm not sure how to get around this. In the exmaple Mike uses d[0] and d[1], but this won't work with JSON data (or I'm not sure how to make it work).
I've posted this JSFiddle so you can see the code. The problem lines are 125 to 131 which in turn is being called from line 165.
var main_line = d3.svg.line()
.interpolate("cardinal")
// Hard coding the elements works
//.x(function(d) { return main_x(d.date); })
//.y(function(d) { return main_y(d.buildFixTime); });
// Passing xValue and yValue does not work
.x(function(d) { return main_x(xValue); })
.y(function(d) { return main_y(yValue); });
http://jsfiddle.net/goodspeedj/fDyLY/
Thank you in advance.

You need to redefine your accessor method within .x() and .y(). The accessor method defines the way that a datum is pulled out of the data that is bound to the selection that you call the line generator on.
Suppose you have a relatively flat data structure such as the following.
data = [{x : 1, y : 2}, {x:1, y:3}, {x:4, y:5}];
You then bind the data to a selection with the following statement
d3.select("body").datum(data).append("path").attr("d",lineGenerator);
Quite a bit is going on underneath this statement. I'll give you a bit more of a walkthrough after showing you a commonly used example.
The important aspect to understand is that similarly to other calls in d3 such as
var exampleRectangles = d3.select("body")
.data(data).enter()
.append("rect")
.attr("width",2)
.attr("height", 3)
.attr("x",function(datum){return datum.x}) // pay attention to this line
.attr("y",0);
d3 is implicitly iterating over each element in your data. For each datum in your data array, in this case there is a total of three datum, you are going to add a rectangle to the dom.
In the line that I tell you to pay attention to you notice that you're defining an anonymous (unnamed) function. What is that datum parameter coming from? It's implicitly being passed to your anonymous function.
So each rectangle has it's own corresponding datum {x : 1, y : 2}, {x:1, y:3}, {x:4, y:5} respectively. Each rectangle's x coordinate is defined by the respective datum.x attribute. Under the sheets, d3 is implicitly looping over the data array that you've defined. A similar approach to the example d3 code could be written as above.
for (var i = 0; i < data.length; i++)
{
d3.select("body").append("rect")
.attr("width",2)
.attr("height", 3)
.attr("x",data[i].x)
.attr("y",0);
}
This follows from the notion of data driven documents (d3). For each item added (a rectangle in the above example a piece of data is tied to it. In the above example you see that there is something kind of similar to your .x() and .y() accessor functions :
.attr("x",function(datum){return datum.x})
This function is telling d3 how to filter over the total datum that's being passed to the .attr() accessor method.
So, you need to determine which data you need to get a hold of to make your .attr("d", lineGenerator)call make sense. The difference between your.datum(data)call and the typical.data(data)call is that instead of parceling the data that's being passed to.data(data)`, the whole array is given as a single piece of data to the line generator function (similar to main_line(data), wherein it will again implicitly loop over the points to construct your path.
So, what you need to do is determine what a single datum will be defined as for your function to operate on.
I'm not going to define that as I don't seem to know quite which information you are operating on, but I would hazard a guess at something like.
.x(xAccessor)
.y(yAccessor)
function xAccessor(datum)
{
return xScale(datum._id.month);
}
function yAccessor(datum)
{
return yScale(datum.buildFixTime);
}

The way you have it set up, xValue and yValue are functions; you have to actually execute them on something to get a value back.
.x(function(d) { return main_x( xValue(d) ); })
.y(function(d) { return main_y( yValue(d) ); });
If you weren't using a scale, you could use
.x(xValue)
.y(yValue);
but only because if you pass in a function d3 executes it for you with the data as a parameter. And that only works for d3 methods that expect functions as possible input -- the scale functions expect data values as input.
I wrote a long piece work for another user last week that you may find useful, explaining methods that accept functions as parameters.

Related

Is there a way of searching div element by class in GAS? [duplicate]

Is there a simple method to locate an XML node by its attribute in Google Apps Script? Here's an XML snippet:
<hd:components>
<hd:text name="ADM Custom admissions TE">
<hd:prompt>Admission</hd:prompt>
<hd:columnWidth widthType="minimum" minWidth="100"/>
</hd:text>
<hd:text name="ADM Insufficient heat end date TE">
<hd:prompt>To</hd:prompt>
</hd:text>
<hd:text name="ADM Insufficient heat start date TE">
<hd:prompt>From</hd:prompt>
</hd:text>
<hd:text name="ADM Third party payment period TE">
<hd:defMergeProps unansweredText="__________"/>
<hd:prompt>When (date or period)?</hd:prompt>
</hd:text>
For purposes of the XML file I'm trying to parse, the "name" attribute is a unique identifier, while what GAS thinks is the "name" for purposes of the XmlService.Element.getChild(name) method ("text" for each node shown in this snippet) is a non-unique classifier for the type of node. I'd like to be able to write a function to retrieve a specific node from this XML file with only the name attribute. XMLPath notation in other languages has this capability using the [# notation. Is there a way to do it in GAS, or do I need to write a function that walks through the XML until it finds a node with the right name attribute, or store it in some different type of data structure for fast searching if the XML file is sufficiently large?
Here's the snippet I started writing: it's fine if there's no built-in function, I just wondered if there was a better/faster way to do this. My function isn't so efficient, and I wondered if the XmlService had a more efficient internal data structure it's using to speed up searching. My approach is just to loop through all of the element's children until there's a match.
function getComponentFromXML(xml,name) {
for (var i = 0; i < xml.length; i++) {
var x = xml[i];
var xname = x.getAttribute('name').getValue();
if (xname == name) {
return getComponentAttributes(x);
}
}
}
There is no built-in search, so the only way is to read the list of elements looking for the one with the desired value of attribute 'name'. If elements is an array of elements to search through, you can do
var searchResults = elements.filter(function (e) {
return e.getAttribute('name') && e.getAttribute('name').getValue() == searchString;
});
(Both checks are needed to avoid an error when there is no 'name' attribute at all.)
How to obtain such an array elements may depend on XML document. If, as in your example, the elements to search are the immediate children of the root element, then
var doc = XmlService.parse(xmlString);
var elements = doc.getRootElement().getChildren();
would be a quick and easy way to do this.
In general, to get all elements without recursion, the getDescendants method can be used. It returns an array of Content object, which can be filtered down to Element objects:
var elements = doc.getDescendants().filter(function (c) {
return c.getType() == XmlService.ContentTypes.ELEMENT;
}).map(function (c) {
return c.asElement();
});

onClick is always returning 25

I'm using reactjs.
I have a series of divs that are created that represent a employee's availability. When clicked, they will display the number. However, they all alert the number 25 instead of their number.
for (
var i = state.availability[day][0];
i <= state.availability[day][1];
i++
) {
tempdaySlot.push(
<div
key={"ena:" + TimeConverter(i)}
style={LocalStyles.daySlot}
onClick={() => alert(i)}
>
{TimeConverter(i)}
</div>
);
}
state.availability is a two dimensional array that holds a number between 1-24 that represents someone's availability (ex [9-17])
TimeConverter is a function that converts a 24 hour type number to the usual 12 hour format (17 = "5 pm")
tempdaySlot is just a temporary array before I put it into a state variable
Use let i instead of var i. var hoists variable to the parent scope, let creates block scope.
this is a hoisting issue, because you defined the i using var it will be hoisted to the top of the function body so the last value which is 25 will be the only thing stored in that variable and since the variable isn't destroyed because it's hoisted it will only show the latest value.
just use let instead of var its a good practice to use let and const instead of var now so try getting used it, it will save you a lot of headaches.
Tl;dr declare i using let, not var
The var keyword creates variables with function scope. However, when you create a closure (like your onClick function), the lexical environment is captured by reference. When you call that onClick function later, it gets the current value of i (which is 25, since that's where the loop stopped), not the value of i when you created the function.
The let keyword creates i with traditional scoping, so in effect a new i is created on each iteration of the loop which solves this problem.
See What's the difference between using "let" and "var"? for more.

getting a random element from an array of movieclips(or labels in a timeline) in Flash CC . Actionscript 3

I am making a pretty neat quiz-game in flashCC right now and I definitely need your help.
My skills are more on the design then the programming side. So to many of you this might seem a baby question (and asked many times before) but from all the answers I saw so far, I couldn't get any results for my project.
So here is the thing :
I need the EXACT script for creating an array (with movieclips inside? or instance names of mcs? How does this even work?)
and a method, to pick a random element of this array without repeats until the "game is over".
Paul
The easiest way to pick a random element from an array without repeating is to first sort the array with a "random" function, then pop or shift items out of it until the array is empty.
Let's say you have an array of items which can be filled with either instances or instance names, you've chosen instance names: :
var FirstArray:Array = ["blau", "orange", "green"];
Now, you'll need a random sort function:
// you do not need to modify this function in any way.
// the Array.sort method accepts a function that takes in 2 objects and returns an int
// this function has been written to comply with that
function randomSort(a:Object, b:Object):int
{
return Math.random() > .5 ? -1 : 1;
}
The way a sort function normally works is it compares two objects and returns -1 if the first item precedes the second item, 1 if the opposite is true, and 0 if they are the same.
So what we're doing in the function above is returning -1 or 1 randomly. This should get the array all jumbled up when you call:
FirstArray.sort(randomSort);
Now that the array is randomly sorted, you can begin pulling items from it like so:
if(FirstArray.length) // make sure there's at least one item in there
{
// since you are using instance names, you'll need to use that to grab a reference to the actual instance:
var currentQuizItem:MovieClip = this[FirstArray.pop()];
// if you had filled your array with the actual instances instead, you would just be assigning FirstArray.pop() to currentQuizItem
// every time you call pop on an array, you're removing the last item
// this will ensure that you won't repeat any items
// do what you need to do with your MovieClip here
}
else
{
// if there aren't any items left, the game is over
}
When strung together, the above code should be enough to get you up and running.
You could try something like:
var array:Array = [1, 2, 3, 4, 5];
var shuffledArray:Array = [];
while (array.length > 0)
{
shuffledArray.push(array.splice(Math.round(Math.random() * (array.length - 1)), 1)[0]);
}
trace('shuffledArray: ', shuffledArray, '\nrandom item: ', shuffledArray[0]);

error in reading csv file to plot graph

i am just not able to read a csv file .I want to display a graph for it.
I am getting error:
TypeError: n is undefined
please help me out!!
d3.csv("example.csv", function(dataset){
var svg=d3.select("body").append("svg").attr("width",w).attr("height",h);
var xScale=d3.scale.ordinal().domain(d3.range(dataset.length)).rangeRoundBands([0,w],0.05);
var yScale=d3.scale.linear().domain([0,d3.max(dataset.value)]).range([0,h]);
svg.selectAll("rect").data(dataset).enter().append("rect").attr({x:function(d,i) {return xScale(i);}, y:function(d){
return h-yScale(d);}, width:xScale.rangeBand(),height:function(d){return yScale(d);},fill:function(d){return "rgb(0,0,"+(d.value*10)+")";}});
d3.select("svg").selectAll("text").data(dataset).enter().append("text").text(function(d) {return d.value;}).attr("x",function(d,i){
return xScale(i)+xScale.rangeBand()/2;}).attr("y",function(d){return h-yScale(d)+14;}).attr("font-family","sans-serif").attr("font-size","10px").attr
("fill","white").attr("text-anchor","middle");
d3.select("p").on("click",function(){
var numValues=dataset.length;
dataset=[];
for(var i=0;i<numValues;i++)
{var newNumber=Math.floor(Math.random()*25);
dataset.push(newNumber,newNumber);
}
yScale.domain([0,d3.max(dataset)]);
svg.selectAll("rect").data(dataset).transition().delay(function(d,i){return i/dataset.length*1000;})
.duration(500).attr("y",function(d){return h-yScale(d); }).attr("height",function(d) {return yScale(d);}).attr("fill",function(d){
return "rgb(0,0,"+(d.value*10)+")";});
svg.selectAll("text").data(dataset).transition().delay(function(d,i){return i/dataset.length*1000;}).duration(500).text(function(d){return d.value;})
.attr("x",function(d,i){return xScale(i)+xScale.rangeBand()/2;}).attr("y",function(d) {return h-yScale(d)+14;})
.attr("font-family","sans-serif").attr("font- size","10px").attr("fill","white").attr("text-anchor","middle")
;});
});
here is my csv file
names,value
john,78
brad,105
amber,103
james,2
dean,74
pat,45
matt,6
andrew,18
ashley,15
As mentioned by cuckovic there are quite a few errors in your code, not to mention that you seem to be using different styles to achieve similar things which is confusing. Anyway there are 3 fundamental things that are casuing you trouble, the first is your dataset. The value column of the csv is being read as a string. You need to convert it to a number by:
dataset.forEach(function (d,i) {
d.value = +d.value;
});
The next issue you have is the yScale where you have set the range to .range([0,h]);. This is the wrong way round for an svg viewport in which the y direction starts from top and increases towards the bottom. So you need to swap the 0 and h around in the range.
The next thing to address is the difference between d and d.value. When you bind data through the data() operator you are generally binding an array. In this case it is an array of objects. So after you have bound the data, d refers to each element of that array which in this case is an object containing a name and a value. This can be seen if you console.log your dataset. When d is passed to yScale it doesn't know what to do with it as it is not a number, what you really want to do is to pass d.value to yScale. So replace your d's with d.value.
Finally, the last part of your code starting at d3.select("p") does not seem to add anything.
I'd recommend reading Scott Murray's tutorials, particularly this one if you haven't already.

Randomly selecting an object property

I guess a step back is in order. My original question is at the bottom of this post for reference.
I am writing a word guessing game and wanted a way to:
1. Given a word length of 2 - 10 characters, randomly generate a valid english word to guess
2.given a 2 - 10 character guess, ensure that it is a valid english word.
I created a vector of 9 objects, one for each word length and dynamically created 172000
property/ value pairs using the words from a word list to name the properties and setting their value to true. The inner loop is:
for (i = 0; i < _WordCount[wordLength] - 2; i)
{
_WordsList[wordLength]["" + _WordsVector[wordLength][i++]] = true;
}
To validate a word , the following lookup returns true if valid:
function Validate(key:String):Boolean
{
return _WordsList[key.length - 2][key]
}
I transferred them from a vector to objects to take advantage of the hash take lookup of the properties. Haven't looked at how much memory this all takes but it's been a useful learning exercise.
I just wasn't sure how best to randomly choose a property from one of the objects. I was thinking of validating whatever method I chose by generating 1000 000 words and analyzing the statistics of the distribution.
So I suppose my question should really first be am I better off using some other approach such as keeping the lists in vectors and doing a search each time ?
Original question
Newbie first question:
I read a thread that said that traversal order in a for.. in is determined by a hash table and appears random.
I'm looking for a good way to randomly select a property in an object. Would the first element in a for .. in traversing the properties, or perhaps the random nth element in the iteration be truly random. I'd like to ensure that there is approximately an equal probability of accessing a given property. The Objects have between approximately 100 and 20000 properties. Other approaches ?
thanks.
Looking at the scenario you described in your edited question, I'd suggest using a Vector.<String> and your map object.
You can store all your keys in the vector and map them in the object, then you can select a random numeric key in the vector and use the result as a key in the map object.
To make it clear, take a look at this simple example:
var keys:Vector.<String> = new Vector.<String>();
var map:Object = { };
function add(key:String, value:*):void
{
keys.push(key);
map[key] = value;
}
function getRandom():*
{
var randomKey = keys[int(Math.random() * keys.length)];
return map[randomKey];
}
And you can use it like this:
add("a", "x");
add("b", "y");
add("c", "z");
var radomValue:* = getRandom();
Using Object instead of String
Instead of storing the strings you can store objects that have the string inside of them,
something like:
public class Word
{
public var value:String;
public var length:int;
public function Word(value:String)
{
this.value = value;
this.length = value.length;
}
}
Use this object as value instead of the string, but you need to change your map object to be a Dictionary:
var map:Dictionary = new Dictionary();
function add(key:Word, value:*):void
{
keys.push(key);
map[key] = value;
}
This way you won't duplicate every word (but will have a little class overhead).