My elements are created from data in a CSV file
that updates every 1 minute.
I'm trying to update these elements as follows:
- Remove those whose data is no longer in the
CSV file
- Create new ones that appeared in the
CSV file
- Keep without edit those that still exist in the
CSV file
The CSV file looks like this:
label,value,market,numbergame
A,www.siteA.com,www.webA.com,1
B,www.siteB.com,www.webB.com,2
C,www.siteC.com,www.webC.com,3
D,www.siteD.com,www.webD.com,4
And an example of an update from it would be this (Disappearing B
and D
and appearing G
, Z
and Y
):
label,value,market,numbergame
A,www.siteA.com,www.webA.com,1
G,www.siteG.com,www.webG.com,2
C,www.siteC.com,www.webC.com,3
Z,www.siteZ.com,www.webZ.com,4
Y,www.siteY.com,www.webY.com,5
To call the function that updates every 1 minute the data I use this script:
<script id="auto-update-csv">
let interval_csv
window.addEventListener('DOMContentLoaded', () => {
interval_csv = setInterval(refresh_csv, 30000); // refresh every 60 secs
})
function refresh_csv() {
d3.csv("Lista_de_Jogos.csv", function(data){caixa_suspensa_5(data)});
}
</script>
The general script that contains the function called by autoupdate for this job (create the elements and update) is described like this:
<body style="background-color:black;">
<div style="color:white;font-weight:bold;overflow:hidden;overflow-y:scroll;" class="grid games" id="Lista-de-Jogos-Lateral">
<script id="script-da-caixa-de-selecao-suspensa-5">
var select_5 = d3.select("#Lista-de-Jogos-Lateral")
.append("div")
.attr("id","select-box-5")
.style("width","100%")
.style("max-height","574px");
function valorparaiframe(iframevalue) {
let link = iframevalue;
let newurl = link.split("OB_EV")[1];
newurl = newurl.split("/")[0];
return "https://sports.staticcache.org/scoreboards/scoreboards-football/index.html?eventId=" newurl;
}
function caixa_suspensa_5(data) {
let update_5 = select_5.selectAll("div")
.data(data);
update_5.exit().remove();
const div = update_5.enter().append("div").attr("class","matches")
div.append("iframe").merge(update_5)
.attr("src",d => valorparaiframe(d.value))
.style("width","100%")
.style("height","282");
div.append("form").merge(update_5)
.attr("action",d => d.market)
.attr("target","_blank")
.style("width","100%")
.append("input").merge(update_5)
.attr("type","submit")
.attr("target","_blank")
.style("width","100%")
.attr("value","Jogo Betfair")
div.append("form").merge(update_5)
.append("input").merge(update_5)
.attr("type","text")
.attr("id",d => "barra-de-texto-para-grafico-" d.numbergame)
.style("width","100%")
div.append("img").merge(update_5)
.attr("type","text")
.attr("src","https://sitedeapostas-com.imgix.net/assets/local/Company/logos/betfair_logo_transp.png?auto=compress,format&fit=clip&q=75&w=263&s=c1691b4034fd0c4526d27ffe8b1e839c")
.attr("name",d => "grafico-betfair-" d.numbergame)
}
d3.csv("Lista_de_Jogos.csv", function(data){caixa_suspensa_5(data)});
</script>
</div>
</body>
As seen, I tried to work with this part in the idea of trying to update the elements:
let update_5 = select_5.selectAll("div")
.data(data);
update_5.exit().remove();
const div = update_5.enter().append("div").attr("class","matches")
But when the trigger is activated, it becomes a huge mess with new elements being created and the old ones not being removed properly.
Which model should I use so that it works according to my need?
I use d3.js
version 4
:
<script src="https://d3js.org/d3.v4.js"></script>
The page template with multiple columns that are created and updated would look like this:
But when it updates every 1 minute, it turns this around, totally different from what I expected, the other elements disappear and keep only the <iframe>
:
Here is the <style>
used inside the <head>
for those who want more ease to recreate the page:
<style>
{
box-sizing: border-box;
}
.matches {
text-align:center;
float: left;
width: 355px;
border: 1px solid white;
border-collapse: collapse;
}
.column {
text-align:center;
float: left;
width: 355px;
border: 1px solid white;
border-collapse: collapse;
}
.grid {
float: left;
width: 1431px;
}
.row:after {
content: "";
display: table;
clear: both;
}
.button {
background-color: #33ccff;
color: black;
font-weight: bold;
}
input[type=submit] {
background-color: #33ccff;
color: black;
font-weight: bold;
}
html {
overflow: scroll;
overflow-x: hidden;
}
::-webkit-scrollbar {
width: 0px; /* remove scrollbar space /
background: transparent; / optional: just make scrollbar invisible /
}
/ optional: show position indicator in red */
::-webkit-scrollbar-thumb {
background: #FF0000;
}
</style>
CodePudding user response:
"it becomes a huge mess". Yes it will. Let's look at part of your code. Remember that when you use append you return a selection of the appended elements:
div // a selection of entered div elements
.append("form") // append forms to the divs, return a selection of forms
.merge(update_5) // merge a selection of forms with a the update selection of divs
... // style the selection of divs and forms
.append("input") // append inputs to the selection of divs and forms
.merge(update_5) // merge the update selection of divs with the inputs
... // style the inputs and the divs
The above is clearly going to cause elements to be appended all over the place where you don't want.
First, we're merging different types of elemenrts, and because of this, second, we're appending inputs to divs and forms when we probably don't want to. Essentially we're appending things to all sorts of things that shouldn't have things appended to them. The divs that remain each iteration get more and more contaminated with excess elements, and no where do you remove those elements (you shouldn't look to remove those excess elements, you should look to not add them in the first place).
Rather than fix the code you have as is, I'm going to suggest an approach suggested by Altocumulus in the comments: "Instead of binding data by index, which is the default, try using a key function to properly match data records": If the data for a single entry doesn't change, there is no need to update it once it is appended. Instead we only need to remove div elements for which the data has been removed, this means we only need to append and modify elements on enter:
let update_5 = select_5.selectAll(".matches")
.data(data,d=>d.label);
update_5.exit().remove();
// Enter new divs:
const enter = update_5.enter()
.append("div")
.attr("class","matches")
...
// Append the children to entered divs:
enter.append("form")
.attr("action",d => d.market)
.attr("target","_blank")
.style("width","100%")
.append("input")
.attr("type","submit")
.attr("target","_blank")
.style("width","100%")
.attr("value","Jogo Betfair");
...
There's no need to merge here as we aren't modifying existing elements if the data for those elements still exists in the data array (regardless of index).
Here's an example with everything except the iframe (as you haven't included example data with valid paths):
The above uses d3.csvParse() instead of d3.csv() to create csv data - which allows the snippet to be entirely self-contained, this requires the use of the pre elements to hold the csv data.