Related
On the first enter I bind data to a svg like this:
var dateContainer = svg.selectAll("g.dateContainerClass")
.data(dateData, function(d) {
return d.id
});
var dateContainerEnter = dateContainer.enter()
.append("g")
.attr("class", "dateContainerClass");
At this point the bound data consists of an array with 5 entries. So i is 0 to 4, first to last. Now the array is updated: two new entries are spliced in between 0 and 1.
dateData.splice((i + 1), 0, rawData[(rawIndex + 1)], rawData[(rawIndex + 2)])
After that the new dateData is bound to the selection again, as shown above. What I was expecting to happen is this:
i=0 (old)
i=1 (new)
i=2 (new)
i=3 (old)
i=4 (old)
etc.
What actually happens is this:
i=0 (old)
i=1 (new)
i=2 (new)
i=1 (old)
i=2 (old)
etc.
Am I doing something wrong or is this expected behavior? Any help would be really appreciated!
EDIT:
Added snippet:
Please run snippet full screen and click on Day1. When you inspect the element, the id of class "dateflag" should go up one each, which it does not.
var windowWidth = window.innerWidth;
var storyline = d3.select(".Storyline").append("svg");
function update(dateData, rawData) {
console.log(dateData)
var x = d3.scaleLinear()
.domain([0, (dateData.length - 1)])
.range(["10%", "80%"]);
storyline
.transition()
.duration(500)
.delay(50)
.attr("width", (dateData.length * 20) + "%")
.attr("height", "100%")
var dateFlags = storyline.selectAll("g.dateflag")
.data(dateData, function(d, i) {
return d
});
//ENTER
var dateFlagsEnter = dateFlags.enter()
.append("g")
.attr("class", "dateflag")
.attr("id", function(d, i) {
console.log("ContainerID: " + i)
return i
});
dateFlagsEnter.append("foreignObject")
.attr("class", "timePoints")
.attr("width", "130px")
.attr("height", "100%")
.attr("x", function(d, i) {
return x(i);
})
.attr("y", function(d, i) {
var topBottom;
if ((i % 2) == 1) {
topBottom = "35%";
} else {
topBottom = "40%";
}
return topBottom;
})
.attr("opacity", "0%");
dateFlagsEnter.select(".timePoints").append("xhtml:div")
.attr("class", "tpGroup")
dateFlagsEnter.select(".tpGroup").append("xhtml:div")
.attr("id", "dateLabel")
.append("xhtml:div")
.html(function(d, i) {
return d + " index= " + i;
})
.attr("id", "dateLabelText")
.on("click", function(d, i) {
var clickIndex = i
console.log("clickedID: " + clickIndex)
expand(dateData, clickIndex);
});
//UPDATE
dateFlags.merge(dateFlagsEnter).select(".timePoints")
.transition()
.duration(500)
.delay(50)
.attr("x", function(d, i) {
return x(i);
})
.attr("opacity", "100%");
dateFlags.merge(dateFlagsEnter).select("#dateLabelText")
.html(function(d) {
return d;
});
//EXIT
dateFlags.exit().remove();
};
function expand(dateData, clickIndex) {
var lineIndex = clickIndex
dateData.splice((lineIndex + 1), 0, "Day1.1", "Day1.2")
update(dateData);
};
function getDateData() {
var dateData = ["Day1", "Day2", "Day3", "Day4", "Day5", ]
update(dateData);
};
getDateData();
<div class="Storyline"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
When you set .attr("id" to a function, the function is executed immediately, and only once. That means that when you insert new values into the data array, you also need to re-calculate that attribute. If you move .attr("id" to after the .merge, everything works as expected:
var windowWidth = window.innerWidth;
var storyline = d3.select(".Storyline").append("svg");
function update(dateData, rawData) {
console.log(dateData)
var x = d3.scaleLinear()
.domain([0, (dateData.length - 1)])
.range(["10%", "80%"]);
storyline
.transition()
.duration(500)
.delay(50)
.attr("width", (dateData.length * 20) + "%")
.attr("height", "100%")
var dateFlags = storyline.selectAll("g.dateflag")
.data(dateData, function(d, i) {
return d
});
//ENTER
var dateFlagsEnter = dateFlags.enter()
.append("g")
.attr("class", "dateflag");
dateFlagsEnter.append("foreignObject")
.attr("class", "timePoints")
.attr("width", "130px")
.attr("height", "100%")
.attr("x", function(d, i) {
return x(i);
})
.attr("y", function(d, i) {
var topBottom;
if ((i % 2) == 1) {
topBottom = "35%";
} else {
topBottom = "40%";
}
return topBottom;
})
.attr("opacity", "0%");
dateFlagsEnter.select(".timePoints").append("xhtml:div")
.attr("class", "tpGroup")
dateFlagsEnter.select(".tpGroup").append("xhtml:div")
.attr("id", "dateLabel")
.append("xhtml:div")
.html(function(d, i) {
return d + " index= " + i;
})
.attr("id", "dateLabelText")
.on("click", function(d, i) {
var clickIndex = i
console.log("clickedID: " + clickIndex)
expand(dateData, clickIndex);
});
//UPDATE
dateFlags.merge(dateFlagsEnter)
.attr("id", function(d, i) {
console.log("ContainerID: " + i)
return i
})
.select(".timePoints")
.transition()
.duration(500)
.delay(50)
.attr("x", function(d, i) {
return x(i);
})
.attr("opacity", "100%");
dateFlags.merge(dateFlagsEnter).select("#dateLabelText")
.html(function(d) {
return d;
});
//EXIT
dateFlags.exit().remove();
};
function expand(dateData, clickIndex) {
var lineIndex = clickIndex
dateData.splice((lineIndex + 1), 0, "Day1.1", "Day1.2")
update(dateData);
};
function getDateData() {
var dateData = ["Day1", "Day2", "Day3", "Day4", "Day5", ]
update(dateData);
};
getDateData();
<div class="Storyline"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Here is my issue: I have a map SVG and a bar Graph SVG. I want to create a coordinated selection. For instance, the user highlights a bar in the graph and the country on the map corresponding to that data is also highlighted and Vice Versa.
Right now I can highlight any country on the map and the proper corresponding bar will also highlight. However, the same does not work for the bars. When I highlight a bar, a random country is highlighted and after that the country names, as displayed in a tooltip, are all jumbled and wrong.
Here is the map -> bar graph highlight:
...
map.selectAll("countries")
.data(b.features)
.enter()
.append("path")
.attr("d", path)
//.style("stroke", "black")
.on("mouseover", function(d) {
activeDistrict = d.properties.ADMIN,
chart.selectAll("rect")
.each(function(d) {
if(d){
if (d.Country == activeDistrict){
console.log("confirmed" + d.Country)
d3.select(this).style("stroke", "blue").style("stroke-width", "3");
}
}
})
...
Here is the bar graph -> map highlight. This is the function I cannot get to behave properly.
var bars = chart.selectAll(".bars")
.data(data)
.enter()
.append("rect")
.on("mouseover", function(d) {
activeDistrict = d.Country,
//console.log(activeDistrict),
map.selectAll("path")
.data(b.features)
.each(function(d) {
if (d){
//console.log("activeDistrict = " + activeDistrict)
if (d.properties.ADMIN == activeDistrict){
d3.select(this).style("stroke", "blue").style("stroke-width", "3");
console.log(d.properties.ADMIN + "=" + activeDistrict)
}
}
});
And here is my entire JS:
<script>
window.onload = setMap();
function setMap(){
d3.csv("/data/blah.csv").then(function(data) {
//console.log(data);
d3.json("/data/blah.topojson").then(function(data2) {
//console.log(data2);
//Code with data here
var width = window.innerWidth * 0.5, // 960
height = 460;
var activeDistrict;
//chart vars
var chartWidth = window.innerWidth * 0.425,
chartHeight = 473,
leftPadding = 25,
rightPadding = 2,
topBottomPadding = 5,
chartInnerWidth = chartWidth - leftPadding - rightPadding,
chartInnerHeight = chartHeight - topBottomPadding * 2,
translate = "translate(" + leftPadding + "," + topBottomPadding + ")";
var yScale = d3.scaleLinear()
.range([0, chartHeight])
.domain([0, 2000]);
//create new svg container for the map
var map = d3.select("body")
.append("svg")
.attr("class", "map")
.attr("width", width)
.attr("height", height);
//create new svg container for the chart
var chart = d3.select("body")
.append("svg")
.attr("width", chartWidth)
.attr("height", chartHeight)
.attr("class", "chart");
//create Albers equal area conic projection centered on France
var projection = d3.geoNaturalEarth1()
.center([0, 0])
.rotate([-2, 0, 0])
//.parallels([43, 62])
.scale(175)
.translate([width / 2, height / 2]);
var path = d3.geoPath()
.projection(projection);
//translate TopoJSON
d3.selectAll(".boundary")
.style("stroke-width", 1 / 1);
var b = topojson.feature(data2, data2.objects.ne_10m_admin_0_countries);
//console.log(b)
//console.log(b.features[1].properties.ADMIN) //country name
var graticule = d3.geoGraticule();
var attrArray = ["blah blah blah"];
function joinData(b, data){
//loop through csv to assign each set of csv attribute values to geojson region
for (var i=0; i<data.length; i++){
var csvRegion = data[i]; //the current region
var csvKey = data[i].Country; //the CSV primary key
//console.log(data[i].Country)
//loop through geojson regions to find correct region
for (var a=0; a<b.features.length; a++){
var geojsonProps = b.features[a].properties; //gj props
var geojsonKey = geojsonProps.ADMIN; //the geojson primary key
//where primary keys match, transfer csv data to geojson properties object
if (geojsonKey == csvKey){
//assign all attributes and values
attrArray.forEach(function(attr){
var val = parseFloat(csvRegion[attr]); //get csv attribute value
geojsonProps[attr] = val; //assign attribute and value to geojson properties
});
};
};
};
return b;
};
joinData(b,data);
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
//Dynamically Call the current food variable to change the map
var currentFood = "Beef2";
var valArray = [];
data.forEach(function(element) {
valArray.push(parseInt(element[currentFood]));
});
var currentMax = Math.max.apply(null, valArray.filter(function(n) { return !isNaN(n); }));
console.log("Current Max Value is " + currentMax + " for " + currentFood)
var color = d3.scaleQuantile()
.domain(d3.range(0, (currentMax + 10)))
.range(d3.schemeReds[7]);
function drawMap(currentMax){
d3.selectAll("path").remove();
// Going to need to do this dynamically
// Set to ckmeans
var color = d3.scaleQuantile()
.domain(d3.range(0, currentMax))
.range(d3.schemeReds[7]);
//console.log(b[1].Beef1)
map.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
map.append("path")
.datum(graticule.outline)
.attr("class", "graticule outline")
.attr("d", path);
console.log(map.selectAll("path").size())
map.selectAll("countries")
.data(b.features)
.enter()
.append("path")
.attr("d", path)
//.style("stroke", "black")
.on("mouseover", function(d) {
activeDistrict = d.properties.ADMIN,
chart.selectAll("rect")
.each(function(d) {
if(d){
//console.log("activeDistrict = " + activeDistrict)
if (d.Country == activeDistrict){
console.log("confirmed" + d.Country)
d3.select(this).style("stroke", "blue").style("stroke-width", "3");
}
}
})
tooltip.transition() //(this.parentNode.appendChild(this))
.duration(200)
.style("opacity", .9)
.style("stroke-opacity", 1.0);
tooltip.html(d.properties.ADMIN + "<br/>" + d.properties[currentFood] + "(kg/CO2/Person/Year)")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
activeDistrict = d.properties.ADMIN,
chart.selectAll("rect")
.each(function(d) {
if (d){
//console.log("activeDistrict = " + activeDistrict)
if (d.Country == activeDistrict){
d3.select(this).style("stroke", "none").style("stroke-width", "0");
}
}
})
tooltip.transition()
.duration(500)
.style("opacity", 0)
.style("stroke-opacity", 0);
})
.style("fill", function(d) { return color(d.properties[currentFood]) });
};
drawMap(currentMax);
console.log("sum", d3.sum(valArray))
//console.log(map.selectAll("path")._groups[0][200].__data__.properties.ADMIN)
function setChart(data, data2, currentMax, valArray){
d3.selectAll("rect").remove();
d3.selectAll("text").remove();
var color = d3.scaleQuantile()
.domain(d3.range(0, (currentMax + 10)))
.range(d3.schemeReds[7]);
var chartBackground = chart.append("rect2")
.attr("class", "chartBackground")
.attr("width", chartInnerWidth)
.attr("height", chartInnerHeight)
.attr("transform", translate);
var yScale = d3.scaleLinear()
.range([0, chartHeight])
.domain([0, (currentMax+10)]);
var chartTitle = chart.append("text")
.attr("x", 20)
.attr("y", 40)
.attr("class", "chartTitle")
.text(currentFood.slice(0, -1));
var chartSub = chart.append("text")
.attr("x", 20)
.attr("y", 90)
.attr("class", "chartSub")
.text((d3.sum(valArray)*76) + " Billion World Total");
// Place Axis at some point
var bars = chart.selectAll(".bars")
.data(data)
.enter()
.append("rect")
.on("mouseover", function(d) {
activeDistrict = d.Country,
//console.log(activeDistrict),
map.selectAll("path")
.data(b.features)
.each(function(d) {
if (d){
//console.log("activeDistrict = " + activeDistrict)
if (d.properties.ADMIN == activeDistrict){
d3.select(this).style("stroke", "blue").style("stroke-width", "3");
console.log(d.properties.ADMIN + "=" + activeDistrict)
}
}
});
tooltip.transition() //(this.parentNode.appendChild(this))
.duration(200)
.style("opacity", .9)
.style("stroke-opacity", 1.0);
tooltip.html(d.Country + "<br/>" + d[currentFood] + "(kg/CO2/Person/Year)")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
map.selectAll("path")
.data(b.features)
.each(function(d) {
if (d){
//console.log("activeDistrict = " + activeDistrict)
if (d.properties.ADMIN == activeDistrict){
d3.select(this).style("stroke", "none").style("stroke-width", "0");
console.log(d.properties.ADMIN + "=" + activeDistrict)
}
}
});
tooltip.transition()
.duration(500)
.style("opacity", 0)
.style("stroke-opacity", 0);
})
.sort(function(a, b){
return a[currentFood]-b[currentFood]
})
.transition() //add animation
.delay(function(d, i){
return i * 5
})
.duration(1)
.attr("class", function(d){
return "bars" + d.Country;
})
.attr("width", chartWidth / data.length - 1)
.attr("x", function(d, i){
return i * (chartWidth / data.length);
})
.attr("height", function(d){
return yScale(parseFloat(d[currentFood]));
})
.attr("y", function(d){
return chartHeight - yScale(parseFloat(d[currentFood]));
})
.style("fill", function(d){ return color(d[currentFood]); });
};
setChart(data, data2, currentMax, valArray);
function createDropdown(data){
//add select element
var dropdown = d3.select("body")
.append("select")
.attr("class", "dropdown")
.on("change", function(){
changeAttribute(this.value, data)
});
//add initial option
var titleOption = dropdown.append("option")
.attr("class", "titleOption")
.attr("disabled", "true")
.text("Select Attribute");
//add attribute name options
var attrOptions = dropdown.selectAll("attrOptions")
.data(attrArray)
.enter()
.append("option")
.attr("value", function(d){ return d })
.text(function(d){ return d });
};
createDropdown(data);
function changeAttribute(attribute, data){
//change the expressed attribute
currentFood = attribute;
var valArray = [];
data.forEach(function(element) {
valArray.push(parseInt(element[currentFood]));
});
var currentMax = Math.max.apply(null, valArray.filter(function(n) { return !isNaN(n); }));
console.log("Current Max Value is " + currentMax + " for " + currentFood)
// Set a dynamic color range
var color = d3.scaleQuantile()
.domain(d3.range(0, currentMax))
.range(d3.schemeReds[7]);
//recolor enumeration units
drawMap(currentMax);
//reset chart bars
setChart(data, data2, currentMax, valArray);
};
}); //csv
}); //json
}; // end of setmap
When drawing countries initially you use:
map.selectAll("countries")
.data(b.features)
.enter()
.append("path")
As there are no elements with the tag countries on your page, the initial selection is empty, and .enter().append("path") creates a path for each item in your data array.
But when you do a mouseover on the bars you re-assign the data with a selectAll().data() sequence, but you do it a bit differently:
map.selectAll("path")
.data(b.features)
...
There are paths in your map that aren't countries: the graticule and the outline. Now we've selected all the paths and assigned new data to them. Since the first two items in the selection are the graticule and the outline they now have the data of the first two items in the data array. All the countries will have bound data of a country that is two away from them in the data array. This is why the wrong data will be highlighted when mouseovering the bars and afterwards why the country tooltips are wrong.
It is not clear why you update the data (I don't see it changing), you could append the countries as so:
var countries = map.selectAll("countries")
.data(b.features)
.enter()
.append("path")
... continue as before
or
map.selectAll("countries")
.data(b.features)
.enter()
.append("path")
.attr("class","country")
... continue as before
And then in the mouseover function of the bars use:
countries.each(....
or
map.selectAll(".country").each(...
Either way still lets you update the data with .data() if needed.
I'll note that the each method isn't necessary, but may be preferable in some situations, by the looks of it you could use:
var bars = chart.selectAll(".bars")
.data(data)
.enter()
.append("rect")
.on("mouseover", function(d) {
activeDistrict = d.Country,
map.selectAll(".country")
.data(b.features)
.style("stroke", function(d) {
if (d.properties.ADMIN == activeDistrict) return "blue"; else return color(d.properties[currentFood])
})
.style("stroke-width", function(d) {
if (d.properties.ADMIN == activeDistrict) return "3" else return 0;
});
})
...
You might try the following to be a bit more consistent in what you create and what you select.
The map:
select by class because you have more path in the svg
add a class to the bar to highlight instead of setting a style
map.selectAll(".countries")
.data(b.features)
.enter()
.append("path")
.attr("class", "countries")
.attr("d", path)
//.style("stroke", "black")
.on("mouseover", function(d) {
activeDistrict = d.properties.ADMIN,
chart.selectAll(".bars")
.classed("highlight", function(d) {
return d && d.Country === activeDistrict;
});
tooltip.transition() //(this.parentNode.appendChild(this))
.duration(200)
.style("opacity", .9)
.style("stroke-opacity", 1.0);
tooltip.html(d.properties.ADMIN + "<br/>" + d.properties[currentFood] + "(kg/CO2/Person/Year)")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
activeDistrict = d.properties.ADMIN,
chart.selectAll(".bars")
.classed("highlight", false);
tooltip.transition()
.duration(500)
.style("opacity", 0)
.style("stroke-opacity", 0);
})
.style("fill", function(d) { return color(d.properties[currentFood]) });
};
Drawing of the bars
fix the class, add a space between the bars and country
do not bind new data to the map paths on mouse over and mouse out
select map paths by class not by type
add a class to the map path to highlight instead of setting a style
var bars = chart.selectAll(".bars")
.data(data)
.enter()
.append("rect")
.on("mouseover", function(d) {
activeDistrict = d.Country;
//console.log(activeDistrict),
map.selectAll(".countries")
.classed("highlight", function(d) {
return d && d.properties.ADMIN === activeDistrict;
});
tooltip.transition() //(this.parentNode.appendChild(this))
.duration(200)
.style("opacity", .9)
.style("stroke-opacity", 1.0);
tooltip.html(d.Country + "<br/>" + d[currentFood] + "(kg/CO2/Person/Year)")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
map.selectAll(".countries")
.classed("highlight", false);
tooltip.transition()
.duration(500)
.style("opacity", 0)
.style("stroke-opacity", 0);
})
.sort(function(a, b){
return a[currentFood]-b[currentFood]
})
.transition() //add animation
.delay(function(d, i){
return i * 5
})
.duration(1)
.attr("class", function(d){
return "bars " + d.Country;
})
.attr("width", chartWidth / data.length - 1)
.attr("x", function(d, i){
return i * (chartWidth / data.length);
})
.attr("height", function(d){
return yScale(parseFloat(d[currentFood]));
})
.attr("y", function(d){
return chartHeight - yScale(parseFloat(d[currentFood]));
})
.style("fill", function(d){ return color(d[currentFood]); });
i already asked this here Map + Legend on same svg
Back then my legend was working fine ^^
I didn't touch my code and when i executed it 2 days agi, the legend color went all wrong (color remains the same in some cases while value is not the same)
I really can't explain this behaviour
i tried many posts/answers that i found on internet but couldn't fix it
would someone please help me ?
Here's my code :
<script type="text/javascript">
function myFunction() {
$("#container").children().remove();
var s = document.getElementById("combobox");
var combobox = s.options[s.selectedIndex].text;
if (combobox=="Ventes Nationales") {
var fichier = "http://localhost/project-folder/province.geojson";
}
var h=$("#container").height();
var w=$("#container").width();
var svg = d3.select("#container")
.append("svg")
.attr("width", w)
.attr("height", h);
var div = d3.select("#container").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var color_domain = [100000,250000,500000,1000000,3000000];
var extent_color_domain = [0,100000,250000,500000,1000000,3000000];
var color = d3.scale.threshold()
.domain(color_domain)
.range(["#ffffcc","#d9f0a3","#addd8e","#78c679","#31a354","#006837"]);
var legend_labels = ["< 100 000", "100 000 +", "250 000 +", "500 000 +", "1 000 000 +", " > 3 000 000"];
d3.csv("http://localhost/project-folder/marocdislog.csv", function(data) {
d3.json(fichier, function(json) {
for (var i = 0; i < data.length ; i++) {
//Grab state name
var dataState = data[i].nom;
//Grab data value, and convert from string to float
var dataValue = data[i].population;
//Find the corresponding state inside the GeoJSON
for (var j = 0; j < json.features.length; j++) {
var jsonState = json.features[j].properties.nom;
if (dataState == jsonState) {
//Copy the data value into the JSON
json.features[j].properties.CA = dataValue;
//Stop looking through the JSON
break;
}
}
}
var projection = d3.geo.mercator().scale(1).translate([0, 0]);
var path = d3.geo.path().projection(projection);
var b = path.bounds( json ),
s = .95 / Math.max((b[1][0] - b[0][0]) / w, (b[1][1] - b[0][1]) / h),
t = [(w - s * (b[1][0] + b[0][0])) / 2, (w - s * (b[1][1] + b[0][1])) / 2];
projection
.scale(s)
.translate(t);
svg.selectAll("path")
.data(json.features)
.enter()
.append('path')
.attr('d', path)
.attr("stroke","white")
.attr("stroke-width",1)
.style("fill", function(d) {
//Get data value
var value = d.properties.CA;
if (value) {
//If value exists…
return color(value);
} else {
//If value is undefined…
return "#ccc";
}
})
.on("mouseover", function(d) {
div.transition()
.duration(50)
.style("opacity", .9);
div.html(d.properties.nom+"<br/>"+d.properties.CA)
.style("left", (d3.event.pageX + 30) + "px")
.style("top", (d3.event.pageY - 30) + "px")})
.on("mouseout", function() {
div.transition()
.duration(50)
.style("opacity", 0);
div.html("")
.style("left", "0px")
.style("top", "0px");})
;
var ls_w = 20, ls_h = 20;
var legend = svg.selectAll("g.legend")
.data(extent_color_domain)
.enter().append("g")
.attr("class", "legend")
.attr( 'transform', function(d,i) {
return 'translate( ' + (w - 200) + ' ' + (h - (i*ls_h) - ls_h) + ' )'
});
legend.append("rect")
.attr("x", 20)
.attr("y", -20 )
.attr("width", ls_w)
.attr("height", ls_h)
.style("fill", function(d, i) { return color(d); })
.style("opacity", 0.8);
legend.append("text")
.attr("x", 50)
.attr("y", -4 )
.text(function(d, i){ return legend_labels[i]; });
var nom = svg.append("text")
.attr("class", "legendTitle")
.attr("text-anchor","middle")
.attr( 'transform','translate( ' + (w/2) + ' ' + (h-5) + ' )')
.text("Mon titre");
});
});
}
</script>
Here's the plunker : https://plnkr.co/edit/veQCOBFdBZx1yIf2kM6C
Many thanks in advance for your help
Shame on me. Had to update chrome. Works fine
Alright so I've been working on trying to get my stacked area chart to show the value on mouseover, and I got it to work (miracle) but it only shows one value, no matter where you move the mouse. So when I go into any of the 5 different colors, it only shows one value for the whole color, no matter where the mouse is. Could I get some help fixing this??
Here's my code:
var t = 0;
var n = 40;
var dnsData = getDNS();
var connectData = getConnect();
var SSLData = getSSL();
var sendData = getSend();
var serverBusyData = getServerBusy();
var receiveData = getReceive();
function getDNS() {
var time = 0;
var arr = [];
for(var i=0; i<bardata.length; i++){
var obj = {
time: i,
value: bardata[i].aggs.dns.avg
};
arr.push(obj);
}
t=time;
return arr;
}
function getConnect() {
var time = 0;
var arr = [];
for (var i = 0; i < bardata.length; i++) {
var obj = {
time: i,
value: bardata[i].aggs.con.avg + bardata[i].aggs.dns.avg
};
arr.push(obj);
}
t = time;
return arr;
}
function getSSL() {
var time = 0;
var arr = [];
for (var i = 0; i < bardata.length; i++) {
var obj = {
time: i,
value: bardata[i].aggs.ssl.avg + bardata[i].aggs.con.avg + bardata[i].aggs.dns.avg
};
arr.push(obj);
}
t = time;
return arr;
}
function getSend() {
var time = 0;
var arr = [];
for (var i = 0; i < bardata.length; i++) {
var obj = {
time: i,
value: bardata[i].aggs.snd.avg + bardata[i].aggs.ssl.avg + bardata[i].aggs.con.avg + bardata[i].aggs.dns.avg
};
arr.push(obj);
}
t = time;
return arr;
}
function getServerBusy() {
var time = 0;
var arr = [];
for (var i = 0; i < bardata.length; i++) {
var obj = {
time: i,
value: bardata[i].aggs.srvbsy.avg + bardata[i].aggs.snd.avg + bardata[i].aggs.ssl.avg + bardata[i].aggs.con.avg + bardata[i].aggs.dns.avg
};
arr.push(obj);
}
t = time;
return arr;
}
function getReceive() {
var time = 0;
var arr = [];
for (var i = 0; i < bardata.length; i++) {
var obj = {
time: i,
value: bardata[i].aggs.rcv.avg + bardata[i].aggs.srvbsy.avg + bardata[i].aggs.snd.avg + bardata[i].aggs.ssl.avg + bardata[i].aggs.con.avg + bardata[i].aggs.dns.avg
};
arr.push(obj);
}
t = time;
return arr;
}
var margin = {
top: 10,
right: 10,
bottom: 20,
left: 40
},
width = 760 - margin.left - margin.right,
height = 425 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([t, n + 1])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 2500])
.range([height, 0]);
var line = d3.svg.area()
.interpolate("basis")
.x(function (d, i) {
return x(d.time);
})
.y0(function (d, i) {
return y(d.value);
})
.y1(function(d, i){
return height;
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// extra svg to clip the graph and x axis as they transition in and out
var graph = g.append("svg")
.attr("width", width)
.attr("height", height + margin.top + margin.bottom);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var axis = graph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = xAxis);
/****************ADDED THESE TWO CHUNKS************************/
var focus = graph.append("svg")
.style("display", "none");
focus.append("circle")
.attr("class", "y")
.style("fill", "none")
.style("stroke", "black")
.attr("r", 4);
/****************ADDED THESE TWO CHUNKS************************/
g.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path5 = graph.append("g")
.append("path")
.data([receiveData])
.attr("class", "receiveLine")
.style({'fill':'#005266'})
.append("title")
.text(function(d, i){return d[i].value});
var path4 = graph.append("g")
.append("path")
.data([serverBusyData])
.attr("class", "serverBusyLine")
.style({'fill':'#008FB2'})
.on("mouseover", function(){ focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
var path3 = graph.append("g")
.append("path")
.data([sendData])
.attr("class", "sendLine")
.style({'fill':'#00CCFF'})
.on("mouseover", function(){ focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
var path2 = graph.append("g")
.append("path")
.data([SSLData])
.attr("class", "SSLLine")
.style({'fill':'#4DDBFF'})
.on("mouseover", function(){ focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
var path1 = graph.append("g")
.append("path")
.data([connectData])
.attr("class", "connectLine")
.style({'fill':'#99EBFF'})
.on("mouseover", function(){ focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
var path0 = graph.append("g")
.append("path")
.data([dnsData])
.attr("class", "connectLine")
.style({'fill':'#E6FAFF'})
.on("mouseover", function(){ focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
tick();
function tick() {
graph.select(".connectLine")
.attr("d", line);
graph.select(".SSLLine")
.attr("d", line);
graph.select(".sendLine")
.attr("d", line);
graph.select(".serverBusyLine")
.attr("d", line);
graph.select(".receiveLine")
.attr("d", line);
}
/****************ADDED THESE TWO CHUNKS************************/
var bisectDate = d3.bisector(function(d){return d.value;}).left;
function mousemove(){
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(line, x0, 1),
d0 = line[i - 1],
d1 = line[i];
}
/****************ADDED THESE TWO CHUNKS************************/
Here's a fiddle: https://jsfiddle.net/5kkv0ct4/
I appreciate any and all help!!
As a quick (but not so elegant) solution, you can listen for a mouse move on the graph SVG and then update the tool tips' text using the inverse (domain value) of the mouse coordinate in relation to the y-scale.
So, call the following code on your graph variable:
graph
.on("mousemove", function() {
d3.selectAll("path").select("title").text(y.invert(d3.mouse(this)[1]));
});
I have an issue and I really need your help.
I have a realtime graph with a vertical bar that moves with cursor and i want it to show the value of the graph (d.time and d.value) when the cursor points to. http://jsfiddle.net/QBDGB/54/ i have two series of data (data1s and data2s) that is generated randomly and I put the time in which the data is generated in "time" variable as you can see:
now = new Date(Date.now() - duration);
var data1 = initialise();
var data2 = initialise();
//Make stacked data
var data1s = data1;
var data2s = [];
for(var i = 0; i < data1s.length; i++){
data2s.push({
value: data1s[i].value + data2[i].value,
time: data2[i].time
}
)};
function initialise() {
var arr = [];
for (var i = 0; i < n; i++) {
var obj = {
time: Date.now(),
value: Math.floor(Math.random() * 100)
};
arr.push(obj);
}
return arr;
}
When I hover around the graph I want the tooltip show the time and value but it does not recognize it and show "undefined" since I do not know how to pass my datasets (data1s and data2s) so "mouseover function can recognize which data to show! This is how the tooltip functions are made and call from "path1" and "path2".
function mouseover() {
div.transition()
.duration(500)
.style("opacity", 1);
}
function mousemove(d) {
div
.text( d.time+ ", " + d.value)
.style("left", (d3.event.pageX ) + "px")
.style("top", (d3.event.pageY ) + "px");
}
function mouseout() {
div.transition()
.duration(500)
.style("opacity", 1e-6);
}
var path1 = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data1s])
.attr("class", "line1")
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseout", mouseout);
var path2 =svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data2s])
.attr("class", "line2")
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseout", mouseout);
Do you have any idea of what to do? i think i need to add
svg.selectAll("path1")
.attr("opacity", 1)
or svg.selectAll("datas1")
.attr("opacity", 1)
Somewhere! but i do not know how..
Thank you,
Update your mouseover function as:
function mousemove(d) {
div
.text( d[0].time+ ", " + d[0].value)
.style("left", (d3.event.pageX ) + "px")
.style("top", (d3.event.pageY ) + "px");
}
Include the index to the object 'd'.
Hope that helps.