I'm trying to update a d3.js bar chart whenever a user clicks a button.
I've been tying to add in a transition somewhere as show here but it doesn't update my data on the fly.
I'm working with this jsfiddle.
I've tried something like the following code but it doesn't update my data:
btn.onclick = function(){
data.push({date: new Date(2013, 3, 10), total: 78});
updatePlot();
}
function updatePlot(){
d3.select('#graphTest').transition();
}
How can I change my updatePlot method so that it updates the data in my bar graph?
It looks like you're skipping a few steps. D3 doesn't actually do a ton of magic for you behind the scenes. You still need to push the data around and update the DOM yourself. D3 just provides a convenient programming model for managing this process.
So, with any visualization you've got the following interesting pieces:
Data array
DOM nodes
Data array elements bound to DOM nodes.
Attributes and styles on DOM nodes.
Your code is updating the data array but it's not managing any of the other parts. You need to drive your updated data back in to the DOM (and specifically the attributes/styles that make the nodes visible).
So update plot will need to do the following:
Call selection.data to bind the new data to the existing DOM. You can defined a key function to control how data maps to DOM nodes.
Handle the enter, exit and update selections however you like.
Transition the attributes and styles based on the new data.
For #3, this isn't just a matter of calling selection.transition, but instead you need to think about which attributes and styles you wish to transition. Calling attr and style on a transition is exactly like calling them on a selection, except the DOM gets updated many times with interpolated values instead of just once with the final value.
To understand this process better you should read Thinking with Joins and Working with Transitions.
Related
On the line chart there is the select event where you can select points & legend items.Line chart has activeElements input property which takes an array of the active elements.
Line chart will not react to changes to the activeElements array unless you enter it in the markup using .slice() either as a local variable OR redux state object:
[activeEntries]="array.slice()"
[activeEntries]="(array | async).slice()"
This works but then the (select) event is not triggered when you click on points anymore.
If you however remove .slice() -- you will be able to select both -- but the chart will no longer react to activeEntries changes no matter what.
This includes having the activeEntries a redux state object where the whole state is a new object on each change -- calling changeDetection everywhere does not affect it at all.
Here's a stackblitz:
https://stackblitz.com/edit/angular-ngx-charts-testing-stuff?file=src/ -- stackblitz.
I have commented the code & markup everywhere so you can understand what I'm trying to do in the code.
I'd like to be able to click on points + legend when using .slice() or have the chart react to changes to activeEntries array.
It actually takes a whole lot more to highlight the active entries and it was purely a coincidence that calling .slice worked at all.
What I needed wasn't actually a feature yet.
I cannot wrap my head around D3's select statement.
When creating a graph, you would typically do something like this:
svg.selectAll("rect")
.data(my_graph_data)
.enter()
.append("rect")
..//etc.
What I don't understand is the selectAll.
At this point in time, there is NO element to select!
The elements are only appended later based on the data.
Or should I read these kind of D3 statements from right to left?
In that case, why would I need to select the elements?
You need to select the (non-existant) elements because of the subsequent call to .data(). This call instructs D3 to match the data given in the argument to the elements you've selected. If, as in your case, the selection is empty, D3 will create placeholder elements for all of them -- the enter selection. This selection you can then use, as you do, to append new elements.
This .selectAll(...).data(...) pattern is consistent regardless of what you have in your SVG already. As long as you handle all three (enter, update, exit) selections, it will always do the expected thing. In the initial selection, there may be no elements, elements for all of the data, or somewhere in between.
Technically, you don't have to use this pattern when adding new elements initially, it just makes the code more consistent. In particular, you can use this pattern in a function that will update the SVG with new data, regardless of its state (i.e. works the same way initially and when getting and displaying new and changed data).
I have a button in each row, when pressed that row is removed from one table, and added to another table:
var row = $(this).closest("tr").remove().clone()
$('.my-other-table-class').append(row);
and at first it appears to work perfectly, the row is removed from one table and added to another, but when I force a re-draw (by changing the sorting of one of the columns, for example) all the rows are back as they were, and the buttons no longer work. This is the case for both the removed rows, and the rows added to the other table.
Is this because I'm using a .jsp table as a data-source? Would this work correctly if I dynamically added all the rows to the table using JavaScript at load-time, or if I used a modelMap collection as a data-source?
Thanks a lot for any advice.
Solution for future googlers - I have no idea how my google-Fu did not find the answer, I had all the right keywords!
In short:
I was doing this to add:
$('.my-other-table-class').append(row);
And this to remove:
var row = $(this).closest("tr").remove().clone()
but I should've been doing this add:
$('.my-other-table-class').dataTable().fnAddData([$(this).attr("data-val1"), "var2"]);
And this to remove:
$('.my-table').dataTable().fnDeleteRow($(this).closest('tr')[0]);
With more detail:
What I am really doing here is modifying the DOM with JQuery (well duh, but I'm really new at this, remember...) - I figured the DOM was the data-source for my table, so that made sense? The table is redrawn, it rereads the DOM and updates? Well not really.
In order to be dynamic, if you use DOM (in other words HTML, or in my case .jsp rendered as HTML) as your data-source, upon initialization, datatables will copy all that information into a JavaScript array.... so rather then my original thought:
"The DOM is not updating correctly, and that issues is propogating up in to my table... because HTML is static...or something?"
it turns out the actual problem was:
"I was updating the DOM, but the real data source was a JavaScript array I wasn't seeing. So upon redraw, this array was overwriting the DOM and my changes were being lost."
TL;DR: Use the Data-tables API and don't modify the data-source directly, unless you need to.
If the event handlers are one of the issues, it may help to set up your event handler like this:
$("#myTable").on("click", "button.moveMe", function(e) {
e.preventDefault();
var row = $(this).closest("tr").remove().clone()
$('.my-other-table-class').append(row);
});
This will set a handler on the table that has id="myTable". The handler will look for click events on buttons with the "moveMe" class. It will catch the event on rows that are added later, as well as the rows that exist when this hook is created.
Reference: http://api.jquery.com/on/
I am trying to understand some slightly odd behavior I am seeing in a page I am making using KnockoutJS. An observable array seems to get duplicate items every time I clear and reapply bindings. The quickest way to understand the problem is to look at this JSFiddle demo. Just click any edit button several times, and watch this list grow!
The heart of the code for this demo is in the following method:
var _bindItemDetail = function (jsonData) {
//clear existing bindings
ko.cleanNode($("#itemdetails").get(0));
// observables in selected item.
_viewModel.SelectedItem(ko.mapping.fromJS(jsonData));
// Apply Bindings
ko.applyBindings(_viewModel.SelectedItem, $("#itemdetails").get(0));
};
The essence of what I am trying to achieve is to create a list and details page in one. The list JSON is fetch on initial page load, and the detail JSON is fetched and bound to the "detail" html whenever an edit link is clicked.
In addition to solving the problem, I am trying to understand the behavior, and learn some lessons about how to clean up stale resources properly when using knockout.
Thanks for any help
The problem is that in your _bindItemDetail function, you are reapplying the bindings on your modified view where you already had replicated the elements.
var _bindItemDetail = function (jsonData) {
//clear existing bindings
ko.cleanNode($("#itemdetails").get(0));
// observables in selected item.
_viewModel.SelectedItem(ko.mapping.fromJS(jsonData));
// Apply Bindings
ko.applyBindings(_viewModel.SelectedItem, $("#itemdetails").get(0));
};
ko.cleanNode() merely removes bindings from the elements, it doesn't revert the view back to its initial state. In general, you should only ever call ko.applyBindings on a set of nodes once. Doing it more than once, is just asking for problems.
Frankly you're not really making good use of knockout. The majority of your code is using jquery to handle all the low-level details. The point of using knockout is to not have to worry about those lower level details.
I've adjusted your fiddle a bit to make better use of knockout with less emphasis on using jquery.
In the view:
Used the click binding to handle your Edit click events.
Used the with binding to conditionally show the editor fields. The stopBindings handler is not needed.
In the view model:
Added the click handler editClicked to the view model.
Removed jquery event bindings.
Removed the ko.cleanNode/ko.applyBindings combo you had when binding items. You shouldn't do that and you just don't need it, knockout will handle all that for you.
Updated fiddle
I have a page with a large table. At the top of the table I've added several filters for eliminating rows in the table. When someone changes a filter, jQuery processes the changes and shows/hides rows in the table that match the filters.
The processing code starts by showing all the rows in the table. Then, it steps through each filter, and wherever necessary, it hides rows in the table. So, each time the user changes a filter, they will see the entire table momentarily and then watch as the rows disappear until the filtering is complete.
Is there a JavaScript or jQuery function for delaying output to the browser until all processing is complete?
You can clone the table, perform whatever operations you need to on the clone, and replace the original table once you're done:
var newTable = $("#yourTable").clone();
//Do whatever you need to do to newTable
$("#yourTable").replaceWith(newTable);
Here's a working example.
Here is a crude example: http://jsfiddle.net/YNZJf/
Basically use jquery to change the html, then once finished set it back to you table.
$tmp = $( $('#table').html()));
$tmp.filter(); // do work;
$('#table').html($tmp.html());
I know you've already accepted an answer, but cloning the table is not how I would choose to implement this. I'd prefer to change the code that modifies the table to not modify it directly. Instead, create an output array that contains the list of modifications you will make to the actual visible table. Then, run your code and have it create this output array (which rows are shown and hidden or any other modifications). Then, when the lengthy part of the code is all done and you have this output array, you simply loop through the final output array and apply the changes. If this last part is all done in one synchronous piece of JS, the viewer will not see the intermediate steps, just the final result.
You should note that there are some characteristics of cloning and operating on it before it's inserted into the document to be aware of:
There could be issues cloning and operating on something with ids assigned to elements in it (since you can only have one object with a given id).
Non-jQuery event handlers may not get copied onto new objects.
You should specify the appropriate parameters to the .clone([withDataAndEvents], [deepWithDataAndEvents]) function
If your table is large, cloning might be slow (lots of duplicate objects and properties to create).
If any of your operations to modify the table assume it's in the document, they might not work on the cloned table until it's inserted.