Related
I basically copied the example https://bl.ocks.org/skokenes/a85800be6d89c76c1ca98493ae777572
Then I got the code to work with my data. So, I can now get the lasso to work.
But when I try to add back my old code for the circles to display a text-type tool tip, the lasso breaks. The code then puts the class variables such as not_possible or selected on the "text" elements rather than on the "circle" elements where they need to be.
I found that is the issue by using Chrome developer tools.
When the tool tips code is commented out, the lasso code works and the DOM looks like this:
<circle cx="854" cy="37" fill="red" r="7" class="selected"></circle>
When the tool tips code is live, the tool tips work but the lasso code doesn't work and the DOM looks like this:
<circle cx="854" cy="37" fill="red" r="4.9">
<title r="3.5" class> ==$0
"curr = 89.7, prev = 89.5, geo = Alaska, measure = Percent Citizen, Born in the US"
</title>
</circle>
I've tried changing the styles for the classes, for example, from ".possible" to "circle.possible" but that doesn't help. I've googled for suggestions but haven't found anything that I could make work. I've tried passing the circle selection thru lasso.items(circles) but that doesn't work.
This is the lasso code that does work: the troublesome ".append title" and "text" lines are commented out.
var margin = {top: 20, right: 15, bottom: 60, left: 60}
, width = 960 - margin.left - margin.right
, height = 960 - margin.top - margin.bottom;
var xScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d[0]; })])
.range([height, 0]);
var svgArea = d3.select('.content')
.append('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart');
var main = svgArea.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(d3.axisBottom(xScale));
main.append('g')
.append("text")
.attr("x", width / 2)
.attr("y", height + margin.bottom - 10)
.style("text-anchor", "middle")
.style("font", "14px times")
.text("Current X");
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(d3.axisLeft(yScale));
main.append('g')
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", 0 - (height / 2))
.attr("y", 0 - margin.left / 2)
.style("text-anchor", "middle")
.style("font", "14px times")
.text("Previous Y");
var rScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([ 4, 5 ]);
var lasso_start = function() {
lasso.items()
.attr("r",7)
.classed("not_possible",true)
.classed("selected",false)
;
};
var lasso_draw = function() {
lasso.possibleItems()
.classed("not_possible",false)
.classed("possible",true)
;
lasso.notPossibleItems()
.classed("not_possible",true)
.classed("possible",false)
;
};
var lasso_end = function() {
lasso.items()
.classed("not_possible",false)
.classed("possible",false)
;
lasso.selectedItems()
.classed("selected",true)
.attr("r", 7)
;
lasso.notSelectedItems()
.attr("r", 3.5)
;
};
var circles = main.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return xScale(d[1]); } )
.attr("cy", function (d) { return yScale(d[0]); } )
.attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
.attr("r", function (d) { return rScale(d[1]); })
//.append("title")
//.text(function(d) {
// return "curr = " + d[1] +
// ", prev = " + d[0] +
// ", geo = " + d[2] +
// ", measure = " + d[3];
// })
;
var lasso = d3.lasso()
.items(circles)
.closePathDistance(75) // max distance for the lasso loop to be closed
.closePathSelect(true) // can items be selected by closing the path?
.targetArea(svgArea) // area where the lasso can be started
.on("start",lasso_start) // lasso start function
.on("draw",lasso_draw) // lasso draw function
.on("end",lasso_end); // lasso end function
svgArea.call(lasso);
Why does including ".title" and ".text" cause a problem?
And how do I solve it?
I don't think the problem is with the CSS, but here it is:
<style>
// styling for D3 chart
.chart {
background: #fdfefe;
}
.main text {
font: 10px sans-serif;
}
// styling for D3-lasso
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
circle {
fill-opacity: 0.4;
}
.dot {
stroke: #000;
}
.lasso path {
stroke: rgb(80,80,80);
stroke-width: 2px;
}
.lasso .drawn {
fill-opacity: 0.05 ;
}
.lasso .loop_close {
fill: none;
stroke-dasharray: 4,4;
}
.lasso .origin {
fill: #3399FF;
fill-opacity: 0.5;
}
.not_possible {
fill: rgb(200,200,200);
}
.possible {
fill: #EC888C;
}
.selected {
fill: steelblue;
}
</style>
The problem appears to be that lasso is adding a radius attribute to the title elements here:
lasso.notSelectedItems()
.attr("r", 3.5)
;
resulting in all your not-selected elements, i.e., circles, and titles, having the attribute assigned, as your example suggests:
<title r="3.5" class>
Rather than calling lasso's selected and notSelected to change the radius and css class of the desired items, use a filter on the items array itself:
// Style the selected dots
lasso.items().filter(function(d) {return d.selected===true})
.classed(...)
.attr("r",7);
// Reset the style of the not selected dots
lasso.items().filter(function(d) {return d.selected===false})
.classed(...)
.attr("r",3.5);
You can get as specific as you want with the return value, i.e., omit any nodes (like title nodes) you don't want affected by the rules you apply to the selection.
The problem was that I couldn't get D3 lasso and my approach to tool tips to work together. I was appending a title element to each circle (point) on a scatter plot. This does NOT work:
var circles = main.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return xScale(d[1]); } )
.attr("cy", function (d) { return yScale(d[0]); } )
.attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
.attr("r", function (d) { return rScale(d[1]); })
.append("title")
.text(function(d) {
return "curr = " + d[1] +
", prev = " + d[0] +
", geo = " + d[2] +
", measure = " + d[3];
})
;
I found a coding example by Mikhail Shabrikov that solved the issue by avoiding .append("title") altogether. This works:
A new CSS element:
.tooltip {
position: absolute;
z-index: 10;
visibility: hidden;
background-color: lightblue;
text-align: center;
padding: 4px;
border-radius: 4px;
font-weight: bold;
color: black;
}
A new DIV element:
var tooltip = d3.select("body")
.append("div")
.attr('class', 'tooltip');
And mainly a modified circles element:
var circles = main.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return xScale(d[1]); } )
.attr("cy", function (d) { return yScale(d[0]); } )
.attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
.attr("r", 5)
.on("mouseover", function(d) {return tooltip.style("visibility", "visible")
.text(
"curr = " + d[1] +
", prev = " + d[0] +
", geo = " + d[2] +
", measure = " + d[3]
)
})
.on("mousemove", function() {
return tooltip.style("top", (event.pageY - 30) + "px")
.style("left", event.pageX + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
})
;
Shabrikov's code is near the very bottom of this item: circles, tool tips, mouse events
I have created an horizontal grouped stacked chart using D3. Everything seemed perfect until I narrowed down the values to two. In the snippet below, if you replace the json_data with this:
var json_data = {"headers":["Month","Country","Number"],"rows":[["2018-05-01 00:00:00.0","France",7],["2018-05-01 00:00:00.0","Germany",19],["2018-05-01 00:00:00.0","Italy",35],["2018-05-01 00:00:00.0","Spain",40],["2018-05-01 00:00:00.0","UK",23],["2018-04-01 00:00:00.0","France",14],["2018-04-01 00:00:00.0","Germany",21],["2018-04-01 00:00:00.0","Italy",37],["2018-04-01 00:00:00.0","Spain",32],["2018-04-01 00:00:00.0","UK",129]
]};
everything works fine and the chart looks responsive:
However, considering that in my snippet I have two values (UK, Germany), the bars overlap. I tried playing with this line:
console.log(d3.scale.ordinal().rangeBands([height, 0], 0.2) );
but I can't think of a way to make the bars responsive no matter what the number of values is.
Snippet:
/* ----- Data ----- */
var json_data = {"headers":["Month","Country","Number"],"rows":[["2018-05-01 00:00:00.0","Germany",19],["2018-05-01 00:00:00.0","United Kingdom",23],["2018-04-01 00:00:00.0","Germany",21],["2018-04-01 00:00:00.0","United Kingdom",129]
]};
var dataRows = json_data.rows;
/* ----- !Data ----- */
/* ----- Functions ----- */
//Create dictionary function (transformed JSON)
createDict = (data) => {
let groups = data.reduce((acc, arr) => {
if (acc.hasOwnProperty(arr[1])) {
acc[arr[1]].push(arr);
} else {
acc[arr[1]] = [arr];
}
return acc;
}, {});
let results = [];
for (let g in groups) {
let obj = {Value: g};
let a = groups[g][0];
let b = groups[g][1];
if (a[0] <= b[0]) {
obj.num = a[2];
obj.num2 = b[2];
} else {
obj.num = b[2];
obj.num2 = a[2];
}
results.push(obj);
}
return results;
}
//Returns unique values of a specific object of a JSON string
uniqueValues = (data,objectNum) => {
var uniqueValues = [];
data.forEach(function(item) {
var value = item[objectNum];
if (uniqueValues.indexOf(value) !== -1)
return false;
uniqueValues.push(value);
});
return uniqueValues;
}
//Chart creation function
createChart = (data) => {
//Margin conventions
console.log(data)
var margin = {top: 10, right: 50, bottom: 20, left: 70};
var widther = window.outerWidth;
var width = widther - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
//Appends the svg to the chart-container div
var svg = d3.select(".g-chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Creates the xScale
var xScale = d3.scale.linear()
.range([0,width]);
//Creates the yScale
var y0 = d3.scale.ordinal()
.rangeBands([height, 0], 0.2)
.domain(uniqueValues);
console.log(d3.scale.ordinal().rangeBands([height, 0], 0.2) );
//.domain(["Spain", "UK", "Germany", "France", "Italy"]);
//Defines the y axis styles
var yAxis = d3.svg.axis()
.scale(y0)
.orient("left");
//Defines the y axis styles
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(function(d) {return d; })
//Change axis values for percentage
//.tickFormat(function(d) {return d + "%"; })
.tickSize(height)
.ticks(numTicks(width));
//FORMAT data
data.forEach(function(d) {
d.num = +d.num;
});
//Sets the max for the xScale
var maxX = d3.max(data, function(d) { return d.num; });
//Defines the xScale max
xScale.domain([0, maxX ]);
//Appends the y axis
var yAxisGroup = svg.append("g")
.attr("class", "y axis")
.call(yAxis);
//Appends the x axis
var xAxisGroup = svg.append("g")
.attr("class", "x axis")
.call(xAxis);
//Binds the data to the bars
var categoryGroup = svg.selectAll(".g-category-group")
.data(data)
.enter()
.append("g")
.attr("class", "g-category-group")
.attr("transform", function(d) {
return "translate(0," + y0(d.Value) + ")";
});
//Appends first bar
var bars = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num")
.attr("transform", "translate(0,4)");
//Appends second bar
var bars2 = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num2); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num2")
.attr("transform", "translate(0,29)");
//Binds data to labels
var labelGroup = svg.selectAll("g-num")
.data(data)
.enter()
.append("g")
.attr("class", "g-label-group")
.attr("transform", function(d) {
return "translate(0," + y0(d.Value) + ")";
});
//Appends first bar labels
var barLabels = labelGroup.append("text")
.text(function(d) {return d.num;})
.attr("x", function(d) { return xScale(d.num) - 20; })
.attr("y", y0.rangeBand()/2.65 )
.attr("class", "g-labels");
//Appends second bar labels
var barLabels2 = labelGroup.append("text")
.text(function(d) {return d.num2;})
.attr("x", function(d) { return xScale(d.num2) - 20; })
.attr("y", y0.rangeBand()/1.25 )
.attr("class", "g-labels");
//Appends chart source
d3.select(".g-source-bold")
.text("SOURCE: ")
.attr("class", "g-source-bold");
d3.select(".g-source-reg")
.text("Chart source info goes here")
.attr("class", "g-source-reg");
//RESPONSIVENESS
d3.select(window).on("resize", resized);
function resized() {
//new margin
var newMargin = {top: 10, right: 80, bottom: 20, left: 50};
//Get the width of the window
var w = d3.select(".g-chart").node().clientWidth;
console.log("resized", w);
//Change the width of the svg
d3.select("svg")
.attr("width", w);
//Change the xScale
xScale
.range([0, w - newMargin.right]);
//Update the bars
bars
.attr("width", function(d) { return xScale(d.num); });
//Update the second bars
bars2
.attr("width", function(d) { return xScale(d.num2); });
//Updates bar labels
barLabels
.attr("x", function(d) { return xScale(d.num) - 20; })
.attr("y", y0.rangeBand()/2.65 )
//Updates second bar labels
barLabels2
.attr("x", function(d) { return xScale(d.num2) - 20; })
.attr("y", y0.rangeBand()/1.25 )
//Updates xAxis
xAxisGroup
.call(xAxis);
//Updates ticks
xAxis
.scale(xScale)
.ticks(numTicks(w));
};
//}
//Determines number of ticks base on width
function numTicks(widther) {
if (widther <= 400) {
return 4
console.log("return 4")
}
else {
return 10
console.log("return 5")
}
}
}
/* ----- !Functions ----- */
/* ----- Main ----- */
var data = createDict(dataRows);
//Calculate unique Values
var uniqueValues = uniqueValues(dataRows,1);
createChart(data);
/* ----- !Main ----- */
/*css to go here*/
#import url(https://fonts.googleapis.com/css?family=Karla);
body {
font-family: 'Karla', sans-serif;
font-size: 12px;
}
.g-hed {
text-align: left;
text-transform: uppercase;
font-weight: bold;
font-size:22px;
margin: 3px 0;
}
.g-source-bold {
text-align: left;
font-size:10px;
font-weight: bold;
}
.g-source {
margin: 10px 0;
}
.g-source-bold {
text-align: left;
font-size:10px;
}
.g-intro {
font-size: 16px;
margin: 0px 0px 10px 0px;
}
.g-num {
fill:#124;
}
.g-num2 {
fill:#ccc;
}
.g-labels {
fill: white;
font-weight: bold;
font-size: 13px;
}
.axis line {
fill: none;
stroke: #ccc;
stroke-dasharray: 2px 3px;
shape-rendering: crispEdges;
stroke-width: 1px;
}
.axis text {
font-size: 13px;
pointer-events: none;
fill: #7e7e7e;
}
.domain {
display: none;
}
.y.axis text {
text-anchor: end !important;
font-size:14px;
fill: #000000;
}
.y.axis line {
display: none;
}
.g-baseline line {
stroke:#000;
stroke-width: 1px;
stroke-dasharray:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" charset="utf-8"></script>
<body>
<h5 class="g-hed"></h5>
<p class="g-intro"></p>
<div class="g-chart"></div>
<div class="g-source"><span class="g-source-bold"></span><span class="g-source-reg"></span></div>
</div>
</body>
Your problem is with the offset of the bars from each other; if you hard-code a value, e.g. using translate(0,29), you are going to be in trouble when the chart data changes, and that makes the bars change size, too. To prevent that happening, set the translate value relative to the size of the bar:
//Appends first bar
var bars = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num")
var bars2 = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num2); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num2")
.attr("transform", "translate(0," + ( y0.rangeBand()/2.5 ) + ")");
This way, no matter how many or how few bars you have in the chart, the offset of bars2 will always be the same size as the height of the bars, i.e. y0.rangeBand()/2.5.
I'd advise you to standardise the bar label position in a similar manner, but add in a fixed y offset using the dy attribute:
//Appends first bar labels
var barLabels = labelGroup.append("text")
.text(function(d) {return d.num;})
.attr("x", function(d) { return xScale(d.num) - 20; })
.attr("y", y0.rangeBand()/2.5 ) // note: use 2.5, rather than 2.65
.attr('dy', '-0.35em') // fixed y offset, set relative to the text size
.attr("class", "g-labels");
//Appends second bar labels
var barLabels2 = labelGroup.append("text")
.text(function(d) {return d.num2;})
.attr("x", function(d) { return xScale(d.num2) - 20; })
.attr("y", y0.rangeBand()/1.25 )
.attr('dy', '-0.35em') // fixed y offset
.attr("class", "g-labels");
Here's your full example:
/* ----- Data ----- */
var json_data = {"headers":["Month","Country","Number"],"rows":[["2018-05-01 00:00:00.0","Germany",19],["2018-05-01 00:00:00.0","United Kingdom",23],["2018-04-01 00:00:00.0","Germany",21],["2018-04-01 00:00:00.0","United Kingdom",129] ]};
var dataRows = json_data.rows;
/* ----- !Data ----- */
/* ----- Functions ----- */
//Create dictionary function (transformed JSON)
createDict = (data) => {
let groups = data.reduce((acc, arr) => {
if (acc.hasOwnProperty(arr[1])) {
acc[arr[1]].push(arr);
} else {
acc[arr[1]] = [arr];
}
return acc;
}, {});
let results = [];
for (let g in groups) {
let obj = {Value: g};
let a = groups[g][0];
let b = groups[g][1];
if (a[0] <= b[0]) {
obj.num = a[2];
obj.num2 = b[2];
} else {
obj.num = b[2];
obj.num2 = a[2];
}
results.push(obj);
}
return results;
}
//Returns unique values of a specific object of a JSON string
uniqueValues = (data,objectNum) => {
var uniqueValues = [];
data.forEach(function(item) {
var value = item[objectNum];
if (uniqueValues.indexOf(value) !== -1)
return false;
uniqueValues.push(value);
});
return uniqueValues;
}
//Chart creation function
createChart = (data) => {
//Margin conventions
console.log(data)
var margin = {top: 10, right: 50, bottom: 20, left: 70};
var widther = window.outerWidth;
var width = widther - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
//Appends the svg to the chart-container div
var svg = d3.select(".g-chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Creates the xScale
var xScale = d3.scale.linear()
.range([0,width]);
//Creates the yScale
var y0 = d3.scale.ordinal()
.rangeBands([height, 0], 0.2)
.domain(uniqueValues);
console.log(d3.scale.ordinal().rangeBands([height, 0], 0.2) );
//.domain(["Spain", "UK", "Germany", "France", "Italy"]);
//Defines the y axis styles
var yAxis = d3.svg.axis()
.scale(y0)
.orient("left");
//Defines the y axis styles
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(function(d) {return d; })
//Change axis values for percentage
//.tickFormat(function(d) {return d + "%"; })
.tickSize(height)
.ticks(numTicks(width));
//FORMAT data
data.forEach(function(d) {
d.num = +d.num;
});
//Sets the max for the xScale
var maxX = d3.max(data, function(d) { return d.num; });
//Defines the xScale max
xScale.domain([0, maxX ]);
//Appends the y axis
var yAxisGroup = svg.append("g")
.attr("class", "y axis")
.call(yAxis);
//Appends the x axis
var xAxisGroup = svg.append("g")
.attr("class", "x axis")
.call(xAxis);
//Binds the data to the bars
var categoryGroup = svg.selectAll(".g-category-group")
.data(data)
.enter()
.append("g")
.attr("class", "g-category-group")
.attr("transform", function(d) {
return "translate(0," + y0(d.Value) + ")";
});
//Appends first bar
var bars = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num")
// .attr("transform", "translate(0,4)");
//Appends second bar
var bars2 = categoryGroup.append("rect")
.attr("width", function(d) { return xScale(d.num2); })
.attr("height", y0.rangeBand()/2.5 )
.attr("class", "g-num2")
.attr("transform", "translate(0," + ( y0.rangeBand()/2.5 ) + ")");
//Binds data to labels
var labelGroup = svg.selectAll("g-num")
.data(data)
.enter()
.append("g")
.attr("class", "g-label-group")
.attr("transform", function(d) {
return "translate(0," + y0(d.Value) + ")";
});
//Appends first bar labels
var barLabels = labelGroup.append("text")
.text(function(d) {return d.num;})
.attr("x", function(d) { return xScale(d.num) - 20; })
.attr("y", y0.rangeBand()/2.5 )
.attr('dy', '-0.35em')
.attr("class", "g-labels");
//Appends second bar labels
var barLabels2 = labelGroup.append("text")
.text(function(d) {return d.num2;})
.attr("x", function(d) { return xScale(d.num2) - 20; })
.attr("y", y0.rangeBand()/1.25 )
.attr('dy', '-0.35em')
.attr("class", "g-labels");
//Appends chart source
d3.select(".g-source-bold")
.text("SOURCE: ")
.attr("class", "g-source-bold");
d3.select(".g-source-reg")
.text("Chart source info goes here")
.attr("class", "g-source-reg");
//RESPONSIVENESS
d3.select(window).on("resize", resized);
function resized() {
//new margin
var newMargin = {top: 10, right: 80, bottom: 20, left: 50};
//Get the width of the window
var w = d3.select(".g-chart").node().clientWidth;
console.log("resized", w);
//Change the width of the svg
d3.select("svg")
.attr("width", w);
//Change the xScale
xScale
.range([0, w - newMargin.right]);
//Update the bars
bars
.attr("width", function(d) { return xScale(d.num); });
//Update the second bars
bars2
.attr("width", function(d) { return xScale(d.num2); });
//Updates bar labels
barLabels
.attr("x", function(d) { return xScale(d.num) - 20; })
.attr("y", y0.rangeBand()/2.65 )
//Updates second bar labels
barLabels2
.attr("x", function(d) { return xScale(d.num2) - 20; })
.attr("y", y0.rangeBand()/1.25 )
//Updates xAxis
xAxisGroup
.call(xAxis);
//Updates ticks
xAxis
.scale(xScale)
.ticks(numTicks(w));
};
//}
//Determines number of ticks base on width
function numTicks(widther) {
if (widther <= 400) {
return 4
console.log("return 4")
}
else {
return 10
console.log("return 5")
}
}
}
/* ----- !Functions ----- */
/* ----- Main ----- */
var data = createDict(dataRows);
//Calculate unique Values
var uniqueValues = uniqueValues(dataRows,1);
createChart(data);
/* ----- !Main ----- */
/*css to go here*/
#import url(https://fonts.googleapis.com/css?family=Karla);
body {
font-family: 'Karla', sans-serif;
font-size: 12px;
}
.g-hed {
text-align: left;
text-transform: uppercase;
font-weight: bold;
font-size:22px;
margin: 3px 0;
}
.g-source-bold {
text-align: left;
font-size:10px;
font-weight: bold;
}
.g-source {
margin: 10px 0;
}
.g-source-bold {
text-align: left;
font-size:10px;
}
.g-intro {
font-size: 16px;
margin: 0px 0px 10px 0px;
}
.g-num {
fill:#124;
}
.g-num2 {
fill:#ccc;
}
.g-labels {
fill: white;
font-weight: bold;
font-size: 13px;
}
.axis line {
fill: none;
stroke: #ccc;
stroke-dasharray: 2px 3px;
shape-rendering: crispEdges;
stroke-width: 1px;
}
.axis text {
font-size: 13px;
pointer-events: none;
fill: #7e7e7e;
}
.domain {
display: none;
}
.y.axis text {
text-anchor: end !important;
font-size:14px;
fill: #000000;
}
.y.axis line {
display: none;
}
.g-baseline line {
stroke:#000;
stroke-width: 1px;
stroke-dasharray:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" charset="utf-8"></script>
<body>
<h5 class="g-hed"></h5>
<p class="g-intro"></p>
<div class="g-chart"></div>
<div class="g-source"><span class="g-source-bold"></span><span class="g-source-reg"></span></div>
</div>
</body>
I am plotting a grouped bar chart with d3.js(v4). In this, I have made tool tip as hyperlink.
My requirement: When I hover over the tool tip, in the code, it should return the respective bar group x-axis tick value using which I'll set tool tip url to unique html files.
I am able to get xtick value using "data1.Week" likewise, however, it is not serving the purpose as I need this value to be mapped on run time with tool tip hovering behavior.
CSV file
Week,Total,Pass,Fail
w-32,4,4,0
w-33,2,1,1
w-34,2,0,2
w-37,2,1,1
w-38,1,1,0
w-39,1,1,0
Working Code
<!DOCTYPE html>
<html>
<meta http-equiv="content-type" content="text/html; charset=UTF8">
<style>
#-webkit-keyframes bounceIn {
0% {
opacity: 0;
-webkit-transform: scale(.3);
}
50% {
opacity: 1;
-webkit-transform: scale(1.05);
}
70% {
-webkit-transform: scale(.9);
}
100% {
-webkit-transform: scale(1);
}
}
#keyframes bounceIn {
0% {
opacity: 0;
transform: scale(.3);
}
50% {
opacity: 1;
transform: scale(1.05);
}
70% {
transform: scale(.9);
}
100% {
transform: scale(1);
}
}
.d3-tip.animate {
animation: bounceIn 0.2s ease-out;
-webkit-animation: bounceIn 0.2s ease-out;
}
.d3-tip span {
color: #ff00c7;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 8px;
background: #FFE4C4;
color: white;
border-radius: 2px;
text-decoration: none;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
<!--box-sizing: border-box;-->
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: #FFE4C4;
content: "\25BC";
position: absolute;
text-align: center;
}
/* Style northward tooltips differently */
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
<!-- For setting overall graph dimensions:Start -->
</style>
<body>
<svg width="600" height="400"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js"></script>
<script>
labels = ["Total", "Pass", "Fail"];
<!-- For setting overall graph dimensions:End -->
<!-- For setting graph margins:Start -->
var svg = d3.select("svg"),
margin = {top: 33, right: 10,bottom: 150, left: 24},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var colour = ["#a9a9a9", "#66cc00", "#ff3333"]
<!-- For gaping between bar groups-->
var x0 = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.2);
<!-- For gaping between bars in groups-->
var x1 = d3.scaleBand()
.padding(0.01);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#a9a9a9", "#66cc00", "#ff3333"]);
var timeout;
<!--Data read from csv and plot grouped bar chart-->
d3.csv("weekwise.csv", function(d, i, columns) {
for (var i = 1, n = columns.length; i < n; ++i) d[columns[i]] = +d[columns[i]];
return d;
}, function(error, data) {
if (error) throw error;
<!--console.log(data.length);-->
var tool_tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-8, 0])
.html(function(d) {
#Help needed here
#This is where tool tip is getting set dynamically. However, all the bar are poiting to same html file.
#I'll let each bar point to unique html file dynamically, if get the xtick value
return '' + d.value + "";
}
})
svg.call(tool_tip)
var keys = data.columns.slice(1);
x0.domain(data.map(function(d) { return d.Week; }));
<!--console.log(x0(d.Week));-->
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(d) { return d3.max(keys, function(key) { return d[key]; }); })]).nice();
g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x0(d.Week) + ",0)"; })
.selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
.enter().append("rect")
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x1.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return z(d.key); })
<!--// Tooltip stuff after this-->
.on('mouseover', function(d) {
<!--console.log(d);-->
var context = this;
var args = [].slice.call(arguments);
args.push(this);
clearTimeout(timeout);
timeout = setTimeout(function() {
tool_tip.show.apply(context, args);
}, 800);
})
<!--.on('mouseout', tool_tip.hide)-->
.on('mouseout', function(d) {
var context = this;
var args = [].slice.call(arguments);
args.push(this);
clearTimeout(timeout);
timeout = setTimeout(function() {
tool_tip.hide.apply(context, args);
}, 2000);
})
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x0));
console.log(g);
<!--Code for adding graph title-->
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y).ticks(null, "s"))
.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 1.5))
.attr("fill", "#000")
.attr("text-anchor", "middle")
.style("font-size", "14px")
.style("font-weight", "bold")
<!--.style("text-decoration", "underline")-->
.text("Build Statistics-v8.0.18");
<!--Code for defining and appending legend-->
var legend = g.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 100)
.attr('transform', 'translate(-5,' + (height + 50) + ')')
.style("font", "12px sans-serif");
legend.selectAll('rect')
.data(labels)
.enter()
.append("rect")
.attr("x", function(d, i){
var xPost = legendXPosition(labels, i, 6);
return xPost;
})
.attr("y", -12)
.attr("width", 12)
.attr("height", 12)
.style("fill", function(d, i) {
var color = colour[i];
return color;
});
legend.selectAll('text')
.data(labels)
.enter()
.append("text")
.attr("x", function(d, i){
var xPost = legendXPositionText(labels, i, 22, 6);
return xPost;
})
.attr("y", -1)
.text(function(d) {
return d;
});
function legendXPositionText(data, position, textOffset, avgFontWidth){
return legendXPosition(data, position, avgFontWidth) + textOffset;
}
function legendXPosition(data, position, avgFontWidth){
if(position == 0){
return 0;
} else {
var xPostiion = 0;
for(i = 0; i < position; i++){
xPostiion += (data[i].length * avgFontWidth + 40);
}
return xPostiion;
}
}
});
</script>
</body>
</html>
Please have a look at the graph generated with the above code.
d3-tip doesn't allow much fiddling with data or nodes, so you are stuck with either having d3-tip return the data from the node that triggered it (where you would be missing the context of the parent node) or the parent node (where you would be without the information on what node was hovered over). The solution is either to add more data to each node (i.e. include the week in the data you bind to each node), or to dynamically pull in the week data when you hover over the node, like so:
.on('mouseover', function(d) {
// if d.parent isn't defined, get the parent node of the current selection
// and add that data to the current node
if ( ! d.parent ) {
d.parent = d3.select( this.parentNode ).datum();
}
tool_tip.show(d);
}
If you try to manipulate the arguments to tip.show() in other ways, it'll throw up other errors as the arguments are used internally by d3-tip; they aren't passed straight through to the tip.html() function as you might have thought.
You would be better off including the week data in your bound data to circumvent this, though:
.data(function(d) {
return keys.map(function(key) { return {key: key, value: d[key], week: d.Week }; });
})
Ever considered adding the week number to your bound data?
.data(d => keys.map(key => ( {key: key, value: d[key], week:d.Week.slice(2)} )) )
I happened to play around with the D3js-Library to visualize some SQL-JSON_LD data and want to do the following:
attach individual id-TAG as well as data-set (Matrix with various elements) to each slice
My Code right now looks like this
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
transition: fill 250ms linear;
transition-delay: 150ms;
}
path:hover {
fill: #999;
stroke: #000;
transition-delay: 0;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var data = {
{"year":"2017-07-01","value":"1"},
{"year":"2017-07-02","value":"1"},
{"year":"2017-07-03","value":"2"},
{"year":"2017-07-04","value":"3"},
{"year":"2017-07-05","value":"5"},
{"year":"2017-07-06","value":"8"},
{"year":"2017-07-07","value":"13"},
{"year":"2017-07-08","value":"21"},
{"year":"2017-07-09","value":"24"},
{"year":"2017-07-10","value":"55"},
{"year":"2017-07-11","value":"89"},};
var width = 960,
height = 500;
arc_ids = d3.range(data.length); // for naming the arcs
var outerRadius = height / 2 - 20,
innerRadius = outerRadius / 3,
cornerRadius = 10;
var pie = d3.layout.pie()
.padAngle(.02);
var arc = d3.svg.arc()
.padRadius(outerRadius)
.innerRadius(innerRadius);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id","viz_pieChart") // adds an ID to the whole chart
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.selectAll("path")
.data(pie(data.map(function(d) { return parseInt(d.value); })))
.attr("id", function(d, i) { console.log('CP1'); return "arc-" + arc_ids[i]; }) // This was intended to add an individual id to each arc, but it doesn't
.attr("data", function(d) { return d.data; }) // attach data to arc according to value, as e.g.: {"year":"2017-07-01","value":"1"}
.enter().append("path")
.each(function(d) {
d.outerRadius = outerRadius - 20;
})
.attr("d", arc)
.on("mouseover", arcTween(outerRadius, 0))
on("click", function(d){console.log(d.id);console.log(d.data.toString())}); //print id of the clicked arc as well as saved data
.on("mouseout", arcTween(outerRadius - 20, 150));
function arcTween(outerRadius, delay) {
return function() {
d3.select(this).transition().delay(delay).attrTween("d", function(d) {
var i = d3.interpolate(d.outerRadius, outerRadius);
return function(t) {
d.outerRadius = i(t);
return arc(d);
};
});
};
}
//test whether an arc can be reached, e.g. the 2nd Element
console.log(document.getElementById('slice-1')); // gives an error
</script>
I also checked this1, this2 and this3 as they seemed promising, but it still does not work for me.
Afterwards I want to use the attached data of an arc to print it into another svg-graphic. But first adressing has to work.
And I'm sorry for the post with more than one specific question!
Thank you for your help!
you must append the path before give it an id or data
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
transition: fill 250ms linear;
transition-delay: 150ms;
}
path:hover {
fill: #999;
stroke: #000;
transition-delay: 0;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var data = [
{"year":"2017-07-01","value":"1"},
{"year":"2017-07-02","value":"1"},
{"year":"2017-07-03","value":"2"},
{"year":"2017-07-04","value":"3"},
{"year":"2017-07-05","value":"5"},
{"year":"2017-07-06","value":"8"},
{"year":"2017-07-07","value":"13"},
{"year":"2017-07-08","value":"21"},
{"year":"2017-07-09","value":"24"},
{"year":"2017-07-10","value":"55"},
{"year":"2017-07-11","value":"89"}];
var width = 960,
height = 500;
arc_ids = d3.range(data.length); // for naming the arcs
var outerRadius = height / 2 - 20,
innerRadius = outerRadius / 3,
cornerRadius = 10;
var pie = d3.layout.pie()
.padAngle(.02);
var arc = d3.svg.arc()
.padRadius(outerRadius)
.innerRadius(innerRadius);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id","viz_pieChart") // adds an ID to the whole chart
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.selectAll("path")
.data(pie(data.map(function(d) {
return parseInt(d.value);
})))
.enter().append("path")
.each(function(d) {
d.outerRadius = outerRadius - 20;
})
.attr("id", function(d, i) { return "arc-" + arc_ids[i]; })
// This was intended to add an individual id to each arc, but it doesn't
.attr("data", function(d) { return d.data; }) // attach data to arc according to value, as e.g.: {"year":"2017-07-01","value":"1"}
.attr("d", arc)
.on("mouseover", arcTween(outerRadius, 0))
.on("click", function(d){
console.log(this.id);
console.log(d.data.toString())
}) //print id of the clicked arc as well as saved data
.on("mouseout", arcTween(outerRadius - 20, 150));
function arcTween(outerRadius, delay) {
return function() {
d3.select(this).transition().delay(delay).attrTween("d", function(d) {
var i = d3.interpolate(d.outerRadius, outerRadius);
return function(t) {
d.outerRadius = i(t);
return arc(d);
};
});
};
}
//test whether an arc can be reached, e.g. the 2nd Element
console.log(document.getElementById('slice-1')); // gives an error
</script>
hi all i am using d3 chart with polygon i have one map structure d3 chart and plot one circle for the purpose of show tooltip now my need is i need to show one image 'https://i.stack.imgur.com/O9xB5.png' to replace the circle so when mouse over the image i shown tooltip and another need show 'State Abbr' inside polygon like Ak,TD,PD...
.help ow to do this here i attached my code files
code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<style type="text/css">
/* On mouse hover, lighten state color */
path:hover {
fill-opacity: .7;
}
/* Style for Custom Tooltip */
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: white;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
/* Legend Font Style */
body {
font: 11px sans-serif;
}
/* Legend Position Style */
.legend {
position:absolute;
left:800px;
top:350px;
}
</style>
</head>
<body>
<script type="text/javascript">
//Width and height of map
var width = 960;
var height = 500;
// D3 Projection
var projection = d3.geo.albersUsa()
.translate([width/2, height/2])
.scale([1000]);
// Define path generator
var path = d3.geo.path()
.projection(projection);
// Define linear scale for output
var color = d3.scale.linear()
.range(["green","red"]);
var legendText = ["Population Present", "Population Absent"];
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Load in my states data!
d3.csv("Population_education.csv", function(data) {
// Load GeoJSON data and merge with states data
d3.json("us-states.json", function(json) {
// Loop through each state data value in the .csv file
for (var i = 0; i < data.length; i++) {
// Grab State Name
var dataState = data[i].SiteState;
// Get Population
var dataPop = data[i].Population;
// Grab data value
if(data[i].Members > 0) {
var dataValue = 1;
}
else { var dataValue = 0;}
// Find the corresponding state inside the GeoJSON
for (var j = 0; j < json.features.length; j++) {
var jsonState = json.features[j].properties.name;
if (dataState == jsonState) {
// Copy the data value into the JSON
json.features[j].properties.MembersPresent = dataValue;
json.features[j].properties.pop = +dataPop;
// Stop looking through the JSON
break;
}
}
}
// Get Max and Min Population and update colorscale
var max = d3.max(json.features, function(d) { return d.properties.pop });
var min = d3.min(json.features, function(d) { return d.properties.pop })
color.domain([min, max]); // setting the range of the input data
// Bind the data to the SVG and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path)
.style("stroke", "#fff")
.style("stroke-width", "1")
.style("fill", function(d) {
return color(d.properties.pop)
});
// Map the cities I have lived in!
d3.csv("Population_education.csv", function(data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
if (d.AvgLng != 0 && d.AvgLat != 0)
return projection([d.AvgLng, d.AvgLat])[0];
})
.attr("cy", function(d) {
if (d.AvgLng != 0 && d.AvgLat != 0)
return projection([d.AvgLng, d.AvgLat])[1];
})
.attr("r", function(d) {
return 3;
})
.style("fill", "rgb(217,91,67)")
.style("opacity", 0.45)
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("State:" + d['State Abbr'] + "<br/>" + "Pop:" + d.Population)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
});
var legend = d3.select("body").append("svg")
.attr("class", "legend")
.attr("width", 140)
.attr("height", 200)
.selectAll("g")
.data(color.domain().slice().reverse())
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.data(legendText)
.attr("x", 24)
.attr("y", 9)
.attr("dy", ".35em")
.text(function(d) { return d; });
});
});
</script>
</body>
</html>
Data
Population_education.csv
RowID,SiteState,State Abbr,AvgLat,AvgLng,Population
1,Alabama,AL,32.806671,-86.79113,28
2,Arizona,AZ,33.729759,-111.431221,11704
3,California,CA,36.116203,-119.681564,4356448
4,Colorado,CO,39.059811,-105.311104,374435
5,Connecticut,CT,41.597782,-72.755371,455966
6,Florida,FL,27.766279,-81.68678300000001,442537
7,Georgia,GA,33.040619,-83.643074,1339081
8,Illinois,IL,40.349457,-88.986137,29
9,Indiana,IN,39.849426,-86.258278,1525124
10,Iowa,IA,42.011539,-93.210526,185146
11,Kansas,KS,38.5266,-96.72648599999999,129301
12,Kentucky,KY,37.66814,-84.670067,621047
13,Louisiana,LA,31.169546,-91.867805,170568
14,Maine,ME,44.693947,-69.381927,222966
15,Maryland,MD,39.063946,-76.80210099999999,256966
16,Massachusetts,MA,42.230171,-71.530106,27
17,Michigan,MI,43.326618,-84.536095,27
18,Minnesota,MN,45.694454,-93.900192,11
19,Missouri,MO,38.456085,-92.28836800000001,420415
20,Nevada,NV,38.313515,-117.055374,309799
21,New Hampshire,NH,43.452492,-71.563896,195948
22,New Jersey,NJ,40.298904,-74.521011,241039
23,New Mexico,NM,34.840515,-106.248482,1945
24,New York,NY,42.165726,-74.94805100000001,1075153
25,North Carolina,NC,35.630066,-79.80641900000001,14
26,Ohio,OH,40.388783,-82.764915,1526404
27,Oregon,OR,44.572021,-122.070938,11
28,Pennsylvania,PA,40.590752,-77.209755,197
29,South Carolina,SC,33.856892,-80.945007,45
30,Tennessee,TN,35.747845,-86.692345,446667
31,Texas,TX,31.054487,-97.563461,736672
32,Vermont,VA,37.769337,-78.169968,2324640
33,Washington,WA,47.400902,-121.490494,141319
34,West Virginia,WV,38.491226,-80.954453,128275
35,Wisconsin,WI,44.268543,-89.616508,405942
36,Alaska,AK,0,0,0
37,Arkansas,AR,0,0,0
38,Delaware,DE,0,0,0
39,District of Columbia,DC,0,0,0
40,Hawaii,HI,0,0,0
41,Idaho,ID,0,0,0
42,Mississippi,MS,0,0,0
43,Montana,MT,0,0,0
44,Nebraska,NE,0,0,0
45,North Dakota,ND,0,0,0
46,South Dakota,SD,0,0,0
47,Utah,UT,0,0,0
48,Virginia,VT,0,0,0
49,Wyoming,WY,0,0,0
50,Oklahoma,OK,0,0,0
51,Rhode Island,RI,0,0,0
My us-states.json is as in the following link https://raw.githubusercontent.com/alignedleft/d3-book/master/chapter_12/us-states.json
Code to add image and tooltip to each polygon.
paths.each(function(d) {
if (this.getTotalLength() > 0) {
var midPoint = path.centroid(d);
svg.append("svg:image")
.attr("height", "15px")
.attr("width", "15px")
.attr("xlink:href", "https://i.stack.imgur.com/O9xB5.png")
.attr("transform", "translate(" + midPoint[0] + ", " + midPoint[1] + ")")
.append("title")
.text(d.properties.abbr);
svg.append("svg:text")
.attr("x", midPoint[0])
.attr("y", midPoint[1])
.text(d.properties.abbr);
}
});
JSFiddle
To include abbr details to the data, code as shown below.
// Find the corresponding state inside the GeoJSON
for (var j = 0; j < json.features.length; j++) {
var jsonState = json.features[j].properties.name;
if (dataState == jsonState) {
// Copy the data value into the JSON
json.features[j].properties.MembersPresent = dataValue;
json.features[j].properties.pop = +dataPop;
json.features[j].properties.abbr = data[i]["State Abbr"];
// Stop looking through the JSON
break;
}
}