I created this simple example using data join in D3. It works: when user clicks on +1 a green is added to the list, when clicks on -1 the last color in the list is removed.
Here the code:
const colors = ["white", "yellow", "red", "brown", "orange"];
const root = d3.select("#root");
const addColor = d3.select("#add-color");
addColor.on("click", (d) => {
colors.push("green");
update();
});
const removeColor = d3.select("#remove-color");
removeColor.on("click", (d) => {
colors.pop();
update();
});
const ul = root.append("ul");
ul.selectAll("li")
.data(colors)
.join(
(enter) => enter.append("li").text((d) => d),
(update) => update,
(exit) => exit.remove()
);
function update(){
ul.selectAll("li")
.data(colors)
.join(
(enter) => enter.append("li").text((d) => d),
(update) => update,
(exit) => exit.remove()
);
}
#buttons-container {
display: flex;
margin-bottom: 30px;
}
#buttons-container div {
min-width: 30px;
text-align: center;
cursor: pointer;
border: 1px solid black;
margin-right: 50px;
}
<!DOCTYPE html>
<meta charset="utf-8" />
<html>
<body>
<div id="root">
<div id="buttons-container">
<div id="add-color">+1</div>
<div id="remove-color">-1</div>
</div>
</root>
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js"
></script>
<script type="text/javascript" src="./index.js"></script>
<script type="text/css" src="./styles.css"></script>
</body>
</html>
Then I commented the exit action:
function update() {
ul.selectAll("li")
.data(flavors, (d) => d)
.join(
(enter) =>
enter
.append("li")
.text((d) => d)
.style("color", "green"),
(update) => update.style("color", "steelblue"),
//(exit) => exit.style("color", "red").remove()
);
}
And it works in the same way. What am I missing? Is exit necessary?
By default, .join calls selection.remove() for the exiting selection, without having to explicitly pass a function. The code is equivalent.
The join method is a recent addition to d3 that manages the full update pattern (enter, update, exit).
It can be used in the most simple way, without specifying any part of the pattern, eg,
ul.selectAll("li")
.data(flavors, (d) => d)
.join("li")
.text((d) => d)
.style("color", "green");
In short, this code above will deal with the append part, will update existing elements and will remove any not needed. Its equivalent to:
var list = ul.selectAll("li")
.data(flavors, (d) => d);
list.enter()
.append("li")
.merge("list")
.text((d) => d)
.style("color", "green");
list.exit().remove();
if we want to add specific actions for a part of the pattern, than we can specify each one.
In your case, if you only want to differentiate the colors of the enter and update, then you don't need to specify the exit part.
Just to add to the already existing answers, your question's title is a bit misleading. Let's see your title and some variants:
"Is [the] exit selection necessary in D3?"
No, it's not. You can perfectly have a D3 code without an exit selection. Actually, you can even have an exit selection without calling selection.remove(), that is, you can do another stuff with the exit selection other than just removing the elements (for instance, setting a different colour). That brings us to another question:
"Is [the] exit selection synonymous with 'elements to be removed'?"
No, it's not.
"Is [the] exit selection necessary in a dynamic data visualisation?"
Generally yes, but again that's not necessary. So the adequate answer is no.
"Is [the] exit selection necessary in selection.join()?"
I believe that this is the question you're asking. Well, the exit selection is already there, as the other answers pointed. Here's a brief explanation:
If you look at the source code, which is quite small, you'll see:
export default function(onenter, onupdate, onexit) {
var enter = this.enter(), update = this, exit = this.exit();
enter = typeof onenter === "function" ? onenter(enter) : enter.append(onenter + "");
if (onupdate != null) update = onupdate(update);
if (onexit == null) exit.remove(); else onexit(exit);
return enter && update ? enter.merge(update).order() : update;
}
Thus, as you can see in this line...
if (onexit == null) exit.remove(); else onexit(exit);
...if you explicitly set an exit function (here the parameter named onexit) the code will call it, otherwise it will simply do exit.remove(), which is actually this.exit.remove(), where this is the selection.
Finally it's interesting to see that, as a convenient method, selection.join() assumes that the users want to remove the elements in the exit selection. However, as I already explained, that's not necessary.
I created a chrome extension which activates when I enter in an specified folder in Google Drive. This extension adds a diagram made with D3.js in that folder as you can see in the following picture:
Apart from the diagram, it also adds a dropdown list but the problem is that it doesn't work when I click on it (it doesn't show the options). The only thing it does is to create the rectangle to select elements as you can see in the next picture:
I tried to add other HTML elements in Google Drive such as buttons but they didn't work either. It seems that Google Drive deactivate the onclick event of the html elements I add.
The code of the dropdown list:
d3.select("#divSelect")
.append("select")
.attr("class","desplegable")
.attr("id","desplegable")
.on('change',function (d,i) {
var grupoSeleccionado = d3.select("#desplegable").node().value; //Examen seleccionado
if (grupoSeleccionado=="Total"){ //Vision general de los examenes
dibujarSpiderChart(data);
} else { //Vision de un examen en concreto
var notasAlumnoExamen=_.filter(data[0],{'group':grupoSeleccionado});
var notasMediasExamen=_.filter(data[1],{'group':grupoSeleccionado});
var dataGrupo=[];
dataGrupo.push(notasAlumnoExamen);
dataGrupo.push(notasMediasExamen);
dibujarSpiderChart(dataGrupo);
}
;})
.selectAll("option")
.data(gruposLista)
.enter()
.append("option")
.attr("value", function (d) { return d.id; })
.text(function (d) { return d.nombre; });
I'm a newbie using d3.js to create a multi-level filter plot based on user selections. All filters are working exceptenter link description here the categorical color selection. Colors are defaulting to fluid type (default should be material type) and using the radio button to switch doesn't work. Can anyone help?
https://plnkr.co/edit/yoFCbe?p=preview
function filter_color(){
colored = document.getElementById("color_Filter");
svg.append("circle")
.style("fill", function(d) {
if (colored == 0) {
return color(d["Material group"]);
} else {
return color(d["Fluid type"]);
}
})
}
You're not checking for the actual value of the radio box.
Get the checked/unchecked state and that if statement will run as it should:
colored = document.getElementById("color_Filter")[0].checked; // <= mind here
if (colored == 0) {
return color(d["Material group"]);
} else {
return color(d["Fluid type"]);
}
See https://plnkr.co/edit/gZOIbDgYj6OCs5aM1CV2?p=preview
I have a map with d3 circles showing the site locations, as well as a linechart showing the time trend for each of the site. I am trying to make a particular line highlight when a corresponding circle is clicked. Here is the code. I can't seem to connect the siteIDs with the following function:
function highlightLine(id) {
lineGroup.classed("g-highlight", function(d) {
return d.siteID == id.siteID;
});
};
Insert a console.log as shown below, and it should become clearer:
function highlightLine(id) {
lineGroup.classed("g-highlight", function(d) {
console.log(d);
return d.siteID == id.siteID;
});
};
Because you're binding to data that you've run through d3.nest, the id of d that you're interested in is actually d.key not d.siteID, which does not exist on that level. So the boolean inside classed should be
return d.key == id.siteID
That will cause the appropriate trendline's <g> to have a "g-highlight" class, however it still will not visibly color the line. I believe that's because your css rule .g-highlight { stroke:... } applies the stroke to the containing <g> instead of the <path> inside it. You can change that css rule to be .g-highlight path { ... } and that will color the path as you'd like.
To bind the click event in d3 you should select the object with that class and bind the click:
d3.selectAll(".g-highlight").on("click", function(d) {
return d.siteID == id.siteID;
});
I am building a small UI where the user has to select a point on each of the two SVGs shown.
These points coordinates are then shown under the SVGs. I would like to achieve this using D3's data-binding with the enter() and exit() methods. However it seems that D3 doesn't always update the part where I display the points coordinates, even if I call the enter() method on the bound elements. When removing data, the exit() methods works however.
Here is the main code :
function showPoints() {
var coordinatesElements = d3.select('#coordinates').selectAll('.point').data(points);
coordinatesElements.enter().append('div').classed('point', true)
.text(function (d) {
var textParts = [];
if (d.firstSvg) { textParts.push('first : '+JSON.stringify(d.firstSvg)); }
if (d.secondSvg) { textParts.push('second : '+JSON.stringify(d.secondSvg)); }
return textParts.join(' - ');
})
.append("span")
.classed('removeCalibrationPoint', true)
.html(" X")
.on('click', function(d, i) {
points.splice(i, 1);
showPoints();
});
coordinatesElements.exit().remove();
}
I have created a JSBin fiddle that demonstrates the problem.
The first problem is that you have an empty div of class point in your HTML. This will be selected by the .selectAll('.point') and cause the first element in your data not to show up.
The second problem is that you're not handling the update selection -- in some cases, you're not adding new data, but modifying existing data. The following code updates the text for the data in the update selection.
coordinatesElements.text(function (d) {
var textParts = [];
if (d.firstSvg) { textParts.push('first : '+JSON.stringify(d.firstSvg)); }
if (d.secondSvg) { textParts.push('second : '+JSON.stringify(d.secondSvg)); }
return textParts.join(' - ');
});
Complete demo here. Notice I've simplified the code slightly by setting the text only on the update selection -- elements added from the enter selection merge into the update selection, so there's no need to do it twice.