I have a mostly working code that can load one record, but I'm not sure how to expand it. The closest that I've been able to get is to pull 1 record column names from a csv based on the logic from this bl.ocks code snippet.
How can I modify the function below to pull an entire dataset rather than just one record?
By modifying code from the bl.ocks code, below is what I've been able to achieve. But I'm still having trouble looping the second function into pulling every row. In R, I could do something like rowtoHTML(0:length(data))
var rowToHtml = function( row ) {
var result = "";
for (key in row) {
result += key + ": " + row[key] + "<br/>"
}
return result;
}
var previewCsvUrl = function( csvUrl ) {
d3.csv( csvUrl, function( rows ) {
d3.select("div#preview").html(
"<b>First row:</b><br/>" + rowToHtml( rows[0] ));
})
}
d3.select("body")
.append("div")
.attr("id", "preview")
.style("margin", "5px")
previewCsvUrl("Test.csv")
This question has been asked before. I've tried to do my due diligence by reading through similar questions, but was not able to use the recommended solution based off of my technology constraints or due to inexperience with Javascript. The sample code I've used comes from a trimmed version of this bl.ocks code: http://bl.ocks.org/hlvoorhees/9d58e173825aed1e0218
Edit - I understand that hosting a file locally is the right move, but its not an option for me (hence this approach). Python has been recommended for local hosting, but I'm unable to install it on my machine at this time. I'm working with Edge and IE as my two browser options. I really appreciate the help!
Related
I've made a timeline tool based off of Mike Bostock's old Brush & Zoom example. It works great when the date range is fairly simple but becomes unworkable when there are clusters of events (e.g. hourly) within a longer time range (e.g. days or weeks). The brush becomes too thin to be usable and the user is left trying to fiddle with zooming and panning in order to see the data (as in the example below).
As a first attempt at a solution I created a context menu for the brush and use the brush extent to redefine/filter the data based on the brush range (I may be using the wrong terms here). It 'sort of' works though it is a clunky and imprecise "one shot" method. Results and code below.
I am thinking that if I could "zoom" the brush (or "brush the brush") that would be a more interactive and user friendly way of working with this type of data situation. I've searched around for d3 examples and haven't found any. I am also concerned that my "subtimeline" approach won't be performative interactively since it redefines the date set and rebuilds the timeline.
I am interested in any ideas about how to handle this sort of data situation and/or if this "brushing the brush" is a dead end. Is there a better d3 way to handle this?
(edit: the display date for the last event above reads 10:50 – that is wrong, it should be 11:50 which is what is in the data)
// code edited for clarity
function createSubtimeline() {
subtimelineDates.push(moment(x.domain()[0], "L LT"));
subtimelineDates.push(moment(x.domain()[1], "L LT"));
updateData()
}
function updateData() {
var activeData
if (subtimelineDates.length != 0) {
var firstDate = subtimelineDates[0];
var lastDate = subtimelineDates[1];
activeData = timelineJson.events.filter(function (e) {
var startDate = moment(e.startDate, "L LT");
if (startDate.isAfter(firstDate) && startDate.isBefore(lastDate)) {
if (e.eventHidden == false) {
return true
} else {
return false
}
} else {
return false
}
});
} else {
activeData = timelineJson.events.filter(event => event.eventHidden == false);
}
var tStart = moment(activeData[0].startDate, "MM/D/YYYY h:mm:ss a");
var tEnd = moment(activeData[activeData.length - 1].startDate, "MM/D/YYYY h:mm:ss a");
// update timeline range
x.domain([tStart, tEnd]);
x2.domain([tStart, tEnd]);
}
I am using D3's pack(root) method. That method will at some point call the packEnclose method which is not working. Can someone explain how that method works.
The function seems to be working most of the time but when I give it some different data it will sometimes not work.
Below is a code sample of how I call D3 pack
var stratify = d3.stratify()
.parentId(function(d) {
return d.id.substring(0, d.id.lastIndexOf("#")); });
var root = stratify(data)
.sum(function(d) { return d.value; })
.sort(function(a, b) { return (a.value - b.value) })
// .eachAfter(function(node) { node.value = node.data.value;})
var pack = d3.pack()
.size([width - margin, height - margin])
.padding(5);
pack(root);
I have tried enough and I am sure the only difference between a working example and a failing example are the values themselves. If you want to test out I could send you the data arrays, because I cannot attach them to the question.
I never figured this out but found a weird way of fixing the issue. After investigating the difference between the data that worked and the data that didn't I found there was a difference. The data that did not work looked like it was full of integers after writing it to another file, while the data that worked looked like a bunch of floats. Even though I explicitly put parseFloat in my code so both should be floats.
Anyway I just added 0.01 (not 0.000000001 as that would not work for some reason) to every single data point. For some reason this is working and has not cause any problems yet.
Was able to get a click event working on a pie/bar chart working per this question, which returned the key/value of the path selected. I am simply wondering if there is a way to get at all the selected path/rect of a given chart within say a click like this?
I have basically tried:
d3.selectAll('g').forEach(function(d) {
for (var prop in d) {
if (d[prop].classList !=== undefined && d[prop].classList.contains('selected')) {
var ( elem in d[prop].children ) {
// I can see the path element there but have been unable to get at it
console.log(d[prop].children[elem]);
}
}
}
});
what I am trying to do is get at the path in order to get the d.data.key for all selected chart paths on the page.
You should probably look at the data rather than trying to reverse-engineer it from the svg elements.
It sounds like chart.filters() will give you what you want.
http://dc-js.github.io/dc.js/docs/html/dc.baseMixin.html#filters__anchor
I am puzzling over why the following simple update pattern doesn't work. This follows the recommended General Update Pattern , as far as I can see.
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
...
var dat = ["One","Two","Buckle my shoe"];
var sel = d3.selectAll("p.test").data(dat);
sel.enter().append("p").classed("test", true);
sel.exit().remove();
//update 1 ... doesn't work
sel.text(function(d) { return d;})
The paragraphs get created fine, but the text isn't set. However, if I do this:
//update 2 ... this works as expected
d3.selectAll("p.test").text(function(d) { return d;});
...everything works fine. The first version has always worked in the past.
Update: I tried using the full d3 library ...
<script src="https://d3js.org/d3.v4.min.js"></script>
... and the first version works again. Do I need more than d3.selection?
To clarify, my past practice has been to define a separate update function that takes the selection as a parameter. Eg, function doUpdate(sel) { sel.text(...);}This is for cases where I expect the data elements to have few changes in size, but many changes in content. Storing the selection as a variable and repeatedly running updates on it has worked well before.
So after studying the release notes, it seems this is not going to be backwardly compatible, for some good reasons. First, the short answer:
Replace this:
sel.enter().append("p").classed("test", true);
...
sel.text(function(d) { return d;}) //update block
with this:
var update = sel.enter().append("p").classed("test", true).merge(sel);
...
update.text(function(d) { return d;}) //update block
The reason for this is described in this article (thanks #mbostock) and is a fix for empty selector problems with v3. The point I missed at first was that the enter() block needs to run first so that the merge() block has a populated selection to work on. Which means that the merge() call must come off the end of the enter() block chain.
The format of the change documents sort of hid that, because many examples use chains of function calls. I'm used to splitting the enter/update blocks into separate variables. This aids readability (usually) and means I can farm out the enter/update actions to separate functions - more reusable code that way.
So with that in mind, this doesn't work:
var enter = sel.enter();
var update = enter.merge(sel); //Nope! Not populated at this point.
enter.append(...); //too late! Update block uses an empty selection.
But this works okay
var enter = sel.enter();
enter.append(...);
var update = enter.merge(sel); //defined after block is populated
This is similar to dc.js - how to create a row chart from multiple columns but I want to take it a step further and enable filtering when the rows are clicked.
To answer the question "What is it supposed to filter?" - Only show records with value > 0. For example when Row 'a' is clicked it will only show records with value for a > 0. Hence, the Type pie chart will change to foo:1, bar:2
I guess I have to overwrite onClick method? But I am not sure how.
chart.onClick = function(d) {}
jsfiddle from the answer to the above question - http://jsfiddle.net/gordonwoodhull/37uET/6/
Any suggestions?
Thanks!
Okay, here's a solution where if a record has values > 0 for any of the selected rows, that record is included. As #Ethan said, it's a matter of defining a filter handler:
sidewaysRow.filterHandler(function(dim, filters) {
if(filters && filters.length)
dim.filterFunction(function(r) {
return filters.some(function(c) {
return r[c] > 0;
});
})
else dim.filterAll();
return filters;
});
Also, since the filterFunction only has access to the key, we pass the entire record through as the key. This doesn't make a whole lot of sense in the "real world" but since we're already using crossfilter sideways, it is probably fine:
var dim = ndx.dimension(function(r) { return r; });
New version of the fiddle: https://jsfiddle.net/gordonwoodhull/b7cak6xj/
BTW it sounds like you want to only select one row at a time. Here's how to do that:
sidewaysRow.addFilterHandler(function(filters, filter) {
filters.length = 0;
filters[0] = filter;
return filters;
})
(This will be simpler in dc 2.1 on the develop branch, where the charts use the result of the filter handlers instead of requiring you to modify the filters in place; the body becomes just return [filter];)