How to find the highest attribute in a dataset, then display the "name" associated with that - d3.js

I'm still relatively new to programming and I have a project I am working on. I am making a staff efficiency dashboard for a fictional pizza company. I want to find the quickest pizza making time and display the time and the staff members name to the user.
With the data charts it has been easy. Create a function, then use dc, e.g dc.barChart("#idOfDivInHtmlPage")
I suspect I might be trying to be too complicated, and that I've completely forgotten how to display any outputs of a js function to a html page.
I've been using d3.js, dc.js and crossfilter to represent most of the data visually in an interactive way.
Snippet of the .csv
Name,Rank,YearsService,Course,PizzaTime
Scott,Instore,3,BMC,96
Mark,Instore,4,Intro,94
Wendy,Instore,3,Intro,76
This is what I've tried so far:
var timeDim = ndx.dimension(function(d) {
return [d.PizzaTime, d.Name]
});
var minStaffPizzaTimeName = timeDim.bottom(1)[0].PizzaTime;
var maxStaffPizzaTimeName = timeDim.top(1)[0].PizzaTime;
}
then in the html
<p id="minStaffPizzaTimeName"></p>
<script type="text/javascript" src="static/js/graph.js">
document.write("minStaffPizzaTimeName");
</script>

You are surely on the right track, but in javascript you often have to consider the timing of when things will happen.
document.write() (or rather, anything at the top level of a script) will get executed while the page is getting loaded.
But I bet your data is loaded asynchronously (probably with d3.csv), so you won't have a crossfilter object until a bit later. You haven't shown these parts but that's the usual way to use crossfilter and dc.js.
So you will need to modify the page after it's loaded. D3 is great for this! (The straight javascript way to do this particular thing isn't much harder.)
You should be able to leave the <p> tag where it is, remove the extra <script> tag, and then, in the function which creates timeDim:
d3.select('#minStaffPizzaTimeName').text(minStaffPizzaTimeName);
This looks for the element with that ID and replaces its content with the value you have computed.
General problem solving tools
You can use the dev tools dom inspector to make sure that the p tag exists with id minStaffPizzaTimeName.
You can also use
console.log(minStaffPizzaTimeName)
to see if you are fetching the data correctly.
It's hard to tell without a running example but I think you will want to define your dimension using the PizzaTime only, and convert it from a string to a number:
var timeDim = ndx.dimension(function(d) {
return +d.PizzaTime;
});
Then timeDim.bottom(1)[0] should give you the row of your original data with the lowest value of PizzaTime. Adding .Name to that expression should retrieve the name field from the row object.
But you might have to poke around using console.log or the interactive debugger to find the exact expression that works. It's pretty much impossible to use dc.js or D3 without these tools, so a little investment in learning them will pay off big time.

Boom, finally figured it out.
function show_fastest_and_slowest_pizza_maker(ndx) {
var timeDim = ndx.dimension(dc.pluck("PizzaTime"));
var minPizzaTimeName = timeDim.bottom(1)[0].Name;
var maxPizzaTimeName = timeDim.top(1)[0].Name;
d3.select('#minPizzaTimeName')
.text(minPizzaTimeName);
d3.select('#maxPizzaTimeName')
.text(maxPizzaTimeName);
}
Thanks very much Gordon, you sent me down the right path!

Related

Using svelte actions to manipulate an html element with d3

this is somewhat related to my previous post where I learned a bit more about actions.
I have been trying to figure out how to work with this nifty feature but I seem to be a bit stuck in the past few hours.
In my Component I create an SVG viewbox like so:
<svg id="pitch" viewBox={`0 0 ${width} ${height}`} use:foo>
</svg>
then drawPitch is this function:
function foo(node) {
// the node has been mounted in the DOM
let g = node.append('h1');
g.text("This is the text I'd like to render to check that it works");
return {
destroy() {
// the node has been removed from the DOM
}
};
}
From what I've understood in the docs, the use:foo will pass the calling node to foo, so I thought directly appending svg elements to it should work.
Do I need to update it somehow?
Here is a repl with reproducible code.
I get the following error:
Missing "./types/runtime/internal/keyed_each.js" export in "svelte" package
Thank you!
I would expect the code in foo to start with d3.select(node), and everything to work based off that. Otherwise the DOM tree generated by d3 will not be connected to your document at all. Alternatively the resulting element (selection.node()) has to be appended to node at some point.
The error sounds highly unrelated and probably would require more context.
Note: You cannot add HTML directly to SVGs, SVGs are for canvas-like vector graphics, not document layouts. If you want to insert text, use the <text> element.

Reloading JSON fails to render correctly in canvas with Fabric.js

I have a bit of an issue with Fabric.js. I'm loading a JSON string in to the canvas and then at some time in the future I want to load more JSON in again (possibly the same JSON). The first load seems to work fine but the second load has an issue: the foreground path object doesn't appear, even though its bounding box is clickable. I'm not sure if this is a problem relating to the backgroundImage, or the way I'm clearing the canvas before I load the JSON again, or the actual way I'm loading JSON (or all three).
I've put a simple fiddle here.
var j = { ... }; //json object (see fiddle)
var c = new fabric.Canvas( document.getElementById("c") );
c.loadFromJSON(j, function(objects, options) {
c.renderAll();
setTimeout(function(){
c.backgroundImage=0;
c.clear();
c.renderAll();
setTimeout(function(){
c.loadFromJSON(j, function(objects, options) {
c.renderAll();
c.setActiveObject(c.item(0));
});
},3000);
},3000);
});
I create the initial canvas view from the JSON object, then clear it out after 3 seconds, then load it in again after another 3 seconds (this is just to simulate clicking a button or whatever in a real app).
Would appreciate any help on this and apologies if this has been dealt with before.
Cheers
[EDIT] I've noticed that if I copy the JSON object in to a second variable so that i'm initialising two objects with the same content, and load that in the second loop, it will load again properly. Could the original JSON variable be getting modified or cached and that's causing the issue? (fiddle for this here
i checked the objects that you product and i see that when you load the svg for the second time, all the 'paths' object is missing.
you can see it too, by adding a console.log before the first load and another one just before the 2nd load.
like this:
console.log(JSON.stringify(j));
anyway, to the point,
you just need to stringify your object before you try to load it, in that way you keep all the object as one ,
i added a line that converts you svg object to json string and pass it on a temp variable
tmp = JSON.stringify( j );
take a look ,i update your code,on the fiddle: https://jsfiddle.net/5j7ejLmk/2/
if you find my answer correct please mark it as so.
good luck

How to code in LimeSurvey so that a new row appears only when the previous row has been filled

I am using LimeSurvey and I want to include a question where the respondent can include up to 30 names as answer. However, I don't want to initially present the respondent with 30 boxes as it is overwhelming and requires a ton of scrolling to proceed if you only have a few names to enter. Is is possible to code the question so that a new box appears only after the previous box has been filled? Thanks.
Here is another approach that uses buttons to add/remove the array rows - http://manual.limesurvey.org/Workarounds:_Manipulating_a_survey_at_runtime_using_Javascript#Variable_Length_Array_.28Multi_Flexible_Text.29_question
Cheers
EDIT: This answer was written because I could not find the answer presented by tpartner beforehand. The main difference is that mine is based on filling out the previous row and tpartner's on buttons to add or remove rows.
The following code should work for all single-choice arrays (e.g. 5-point scale array) and is adaptable to other types if you know some Javascript/jQuery. I want to do more like that - just not today. So feel free to ask for implementations for other question types.
The code can be added in the beginning of the template.js file using the template editor. The variables "quest" and "first" have to be adapted based on your survey.
//Function to only display a new row if there is an answer in the previous row
//NOTICE: Rows which are reset to "No answer" will not be hidden
//NOTICE: This scipt was written based on LimeSurvey 2.00+ build 131107
//NOTICE: It only works for single-choice arrays (e.g. 5-point scale array) or multiple short texts
//BEGIN
$(document).ready( function() {
//SGQ code of the question to apply this to
var quest = "12345X1234X12345";
//A(nswer) code of the first row
var first = "1";
//hide all rows except the first
$("tr[id^='javatbd" + quest + "']").css("display","none");
$("tr[id='javatbd" + quest + first + "']").css("display","table-row");
//display rows if previous is answered
$("[name^='" + quest + "']").change(function() {
if(this.value.trim().length >= 1)
$("tr[id='javatbd" + this.name + "']").next().css("display","table-row");
});
});
//END
Best regards
Which question type are you planning to use? Your best bet, according to me, should be to use Multiple Short text type question and create 30 text boxes. You can then use javascript to hide these text boxes and show them as soon as the previous text box gets some value as input.
cheers!
With version 2.x (not sure which version starts allowing this, I am running 2.7 and it works there), this functionality is inbuilt via the relevance equation and doesn't require coding. Just enter !is_empty(questioncode_Code) in the relevance equation for the text box you want to let appear. Code is the code of the field one above which triggers the appearance.

HighChart / HighStock chart.refresh seems to fail to reload the chart

Hia guys,
I'm currently learning to use HighCharts in order to graph some data.
After learning to set up on a local page, it was time to load the data dynamically / though ajax.
The chart loads fine, with series defined like:
var series1=[];
var series2=[];
var series3=[];
I then have a button, which fires an ajax request, and these arrays are filled with data, pulled from an sql database, formatted, and sent back to the webpage :D
(this has taken a while)
Now - with these arrays filled, I'm looking to get HighCharts to draw this data.
I've tried to use chart.refresh(); on the end of my ajax but this seems to give the error:
Uncaught TypeError: Object #<Object> has no method 'refresh'
I'm a little stumped and struggling to make progress - a nudge in the right direction would be very much appreciated!
Edit:
chart.series[0].setData(series1);
Is what needs firing after the arrays have been filled up.
Without seeing the rest of your code here is a wild guess:
You have set up your chart with these 3 series already "created" in the HighCharts options. You are loading the page, defining your HighCharts options (including the 3 series - as empty series), then firing the ajax call to populate the three series of data.
You are not actually pushing the new data in each series to the chart, however. You are just setting the vars to new values after they have been sent to HighCharts - refreshing (if even a valid HighCharts call) HighCharts does "nothing" because it is not re-reading the 3 var values.
Also, note that chart.refresh does not appear to be a valid HighCharts call. There is chart.redraw that you can try - but I do not think this will help in your case because of how you are defining your data series elements (again, no idea because the important part - telling HighCharts there is new data in code was not presented). Have a look at the API as well for pointers on dynamically assigning data.
How are you updating the series values? How are you telling the chart there is new data? It would be helpful to see the rest of the code because 3 lines of var x=[] is not really useful.
Here is a basic way to set the data of a series using setData:
$('#button').click(function() {
chart.series[0].setData([129.2, 144.0, 176.0, 135.6, 148.5, 216.4, 194.1, 95.6, 54.4, 29.9, 71.5, 106.4] );
});

Tying a data source from dojo.xhrPost to a text box for autocomplete

I inherited a web app that uses Dojo 1.5 and the template toolkit. I am enjoying learning dojo but it's at a slow pace.
Initially when bringing up our web form, we'll have a list of files on the right side of the page like so....
AAA_text
BBB_text_1
BBB_text_2
CCC_text
....
....
On the left side we have a search box that asks for the subset of file to use. Normally we would just type in "AAA" and then the div on the right side would find those files that match and display them after you press the "Search" key below the box.
What we are looking to do is to eliminate the "Search box" and have the list of files matching "AAA" to come up in the right side div as "AAA" is being typed, (or "BBB" or "CCC", etc).
I suppose in a nutshell it's the equivalent having the "Search" button pressed after every key is typed in the Search box.
Does this sound like a realistic goal or even possible? The code itself uses a ton of Template Tookit so I'm not looking to do any major rewrite.
If I am not making myself clear, let me know. I can elaborate for clarity. Many many thanks! Janie
EDIT: OK, I have solved a good deal of my problem so far and as it turns out, as so many of these things have a propensity to do, that what I am really needing is to get clear on how to make autocomplete work. Which is to say that I have a data source for my text box but not really sure how to tie it to the text box. I have a dojo.xhrPost routine that can handle grabbing the values.
It looks like this....
dijit.byId('files_matching').getValue(),
Googling dojo autocomplete examples gives me a zillion links and none of which are proving helpful. So I suppose my questions have transitioned to....
1. Can you even use autocomplete on a mere text box (I've seen links that say that you can only use it on combo boxes)
2. Is there a link out there somewhere that describes/shows in detail how to tie a dojo text box to a data source using dojo.xhrPost.
I am so close to solving this and I still seem to have a gaping chasm in front of me.
It's difficult to say for sure without seeing your code but if you don't have one already, I would recommend to create an ItemFileReadStore or something similar to start with. That way you can query that store locally on the client without having server requests after every key stroke.
It could look something like this:
var file_store = new dojo.data.ItemFileReadStore({ data: {
items: [{ name:"AAA_text" },
{ name:"AAA_text_1" },
{ name:"BBB_text_2" }]
}});
When you have that in place you can call a function from your text input's onChange event:
<input type="text" onchange="query_store(this.value);" />
And then you handle to actual query from the function called from the onchange event:
var query_store = function(search_for) {
var my_query = { name: search_for+"*" }; // * for wildcard match
completed = function(items, result){
for(var i = 0; i < items.length; i++){
var value = file_store.getValue(items[i], "name");
alert(value); // Instead of alert, you can save the values to your div.
}
};
file_store.fetch({ query: my_query, onComplete: completed });
}
A lot of good information about this can be found here
Hope this is at least a little helpful.

Resources