I've been trying to build a multiline chart of three lines and display tooltip along with circles on data points when hovered over. However the circle is not following the line and at certain data points and it comes out of the line. Setting the element focus, for the particular line by adjusting transform, translate for the specific line solves the issue but then messes up the other lines. How to pass all the datasets instead of one dataset on focus element? I tried sorting all the datasets in one common dataset consisting of all the objects and pass it on transform of focus but that is also making the circle shift away from the lines at certain datapoints
drawAUCROCGraphExpanded(fprTpr_train, fprTpr_test, fprTpr_oot, fprTpr_default, trainroc, testroc, ootroc) {
var margin = { top: 20, right: 0, bottom: 20, left: 0 },
width = 560 - margin.left - margin.right,
height = 395 - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// Draw the X-axis on the DOM
this.aucRocSvgExpanded.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x).ticks(5));
// Draw the Y-axis on the DOM
this.aucRocSvgExpanded.append('g')
.attr('transform', 'translate(0, 0)')
.call(d3.axisLeft(y).ticks(10));
var valueline = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
var valueline2 = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
var valueline3 = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
var valueline4 = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
x.domain([0, 1]);
y.domain([0, 1]);
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #c1c2c2; stroke-width: 2; fill: none; stroke-dasharray: 3, 4.5;')
.attr("d", valueline(fprTpr_default))
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #2E4A71; stroke-width: 2; fill: none;')
//.style('fill', d => fprTpr_flag == true ? '#e6e8ed;' : null )
.attr("d", valueline2(fprTpr_train))
.attr("class", "line")
.attr("width", width)
.attr("height", height)
if (fprTpr_test.length > 0) {
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #517BAD; stroke-width: 2; fill: none;')
//.style('fill', d => fprTpr_flag == true ? '#e6e8ed;' : null )
.attr("d", valueline3(fprTpr_test))
.attr("class", "line")
.attr("width", width)
.attr("height", height)
}
if (fprTpr_oot.length > 0) {
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #74A0D8; stroke-width: 2; fill: none;')
.attr("d", valueline4(fprTpr_oot))
.attr("class", "line")
.attr("width", width)
.attr("height", height)
}
var focus = this.aucRocSvgExpanded.append("g")
.attr("class", "focus")
.style("display", "none")
// append the x line
focus.append("line")
.attr("class", "x")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("y1", 0)
.attr("y2", height);
// append the circles at the intersection
focus.append("circle")
.attr("id", "train_circle")
.style("fill", "#2E4A71")
.style("stroke", "#2E4A71")
.attr("r", 4);
if (fprTpr_test.length > 0) {
focus.append("circle")
.attr("id", "test_circle")
.style("fill", "#517BAD")
.style("stroke", "#517BAD")
.attr("r", 4);
}
if (fprTpr_oot.length > 0) {
focus.append("circle")
.attr("id", "oot_circle")
.style("fill", "#74A0D8")
.style("stroke", "#74A0D8")
.attr("r", 4);
}
var bisectData = d3.bisector((d: any) => d.fpr).right,
sortData_train = fprTpr_train.sort(function (a, b) { return d3.ascending(a.fpr, b.fpr) }),
sortData_test = fprTpr_test.sort(function (a, b) { return d3.ascending(a.fpr, b.fpr) }),
sortData_oot = fprTpr_oot.sort(function (a, b) { return d3.ascending(a.fpr, b.fpr) })
const mouseover = () => {
focus.style("display", null);
this.roc_tooltipExpanded.style("display", "block")
}
const mouseout = () => {
focus.style("display", "none");
this.roc_tooltipExpanded.style("display", "none")
}
const mousemove = (event) => {
var x0 = x.invert(d3.pointer(event)[0]),
i = bisectData(sortData_train, x0, 1),
j = bisectData(sortData_test, x0, 1),
k = bisectData(sortData_oot, x0, 1),
a = sortData_train[i].fpr,
d = sortData_train[i].tpr
if (fprTpr_test.length > 0) {
var b = sortData_test[j].fpr,
e = sortData_test[j].tpr
}
if (fprTpr_oot.length > 0) {
var c = sortData_oot[k].fpr,
f = sortData_oot[k].tpr
}
if (fprTpr_oot.length > 0) {
focus.attr("transform", "translate(" + x(sortData_oot[k].fpr) + "," + 0 + ")");
} else if (fprTpr_test.length > 0) {
focus.attr("transform", "translate(" + x(sortData_test[j].fpr) + "," + 0 + ")"); //selecting one of this fixes issue for the particular line but messes up others
} else {
focus.attr("transform", "translate(" + x(sortData_train[i].fpr) + "," + 0 + ")");
}
var roc_label;
if (fprTpr_oot.length > 0 && fprTpr_test.length > 0) {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;"><span>Train:</span> ${trainroc} - <b>${d}</b></div><div style="color: #517BAD;"><span style="padding: 0px 7px 0px 0px;">Test:</span> ${testroc} - <b>${e}</b></div><div style="color: #74A0D8;"><span style="padding: 0px 6px 0px 0px;">OOT:</span> ${ootroc} - <b>${f}</b></div></div>`;
} else if (fprTpr_test.length > 0 && fprTpr_oot.length <= 0) {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;><span>Train:</span> ${trainroc} - <b>${d}</b></div><div style="color: #517BAD;><span style="padding: 0px 7px 0px 0px;">Test:</span> ${testroc} - <b>${e}</b></div></div>`;
} else if (fprTpr_test.length <= 0 && fprTpr_oot.length > 0) {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;><span>Train:</span> ${trainroc} - <b>${d}</b></div><div style="color: #74A0D8;"><span style="padding: 0px 6px 0px 0px;">OOT:</span> ${ootroc} - <b>${f}</b></div></div>`;
} else {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;><span>Train:</span> ${trainroc} - <b>${d}</b></div></div>`;
}
this.roc_tooltipExpanded
.html(roc_label)
// .style("left", (d3.pointer(event)[0]+40) + "px")
.style("right", (d3.pointer(event)[0]) + "px")
.style("top", (d3.pointer(event)[1]) + "px")
.style("pointer-events", "none")
.style("z-index", "1")
.style("display", "block")
focus.select("circle#train_circle").attr("transform", "translate(" + x(0) + "," + y(sortData_train[i].tpr) + ")")
if (fprTpr_test.length > 0) {
focus.select("circle#test_circle").attr("transform", "translate(" + x(0) + "," + y(sortData_test[j].tpr) + ")")
}
if (fprTpr_oot.length > 0) {
focus.select("circle#oot_circle").attr("transform", "translate(" + x(0) + "," + y(sortData_oot[k].tpr) + ")")
}
}
this.aucRocSvgExpanded.append("g")
.append("rect")
.attr("class", "rect")
.attr("width", width)
.attr("height", height)
.style("fill", "transparent")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("mousemove", mousemove);
this.aucRocSvgExpanded.append("g")
.append("text")
.attr('y', 390)
.attr('x', 280)
.style("font-size", "14px")
.style("font-weight", "600")
.style("text-anchor", "middle")
.text("FPR");
this.aucRocSvgExpanded.append("g")
.append("text")
.attr("transform", "rotate(-90)")
.attr('y', -35)
.attr('x', -180)
.style("font-size", "14px")
.style("font-weight", "600")
.style("text-anchor", "middle")
.text("TPR");
}
Related
I want to create a dynamic .container where each circle preserves its size ratio and always distributes to fit the available space of the .container.
How do I modify the code so that the distribution of the data points always resizes to the bounds of the .container while maintaining the size ratio of each circle?
.container {
width: 400px;
height: 200px;
}
(function() {
var json = {
call_data: [
[
"Lifestyle",
1,
"https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/5bb3ce2f801fbc657f83dd57_pp-lifestyle(white).svg"
],
[
"Sports",
2,
"https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/5c9131911ad86f445cb5abc7_pp-sport(white).svg"
],
[
"Environment",
8,
"https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f2a4bef42fff000159ba7a_pp-environ(white).svg"
],
[
"Medical",
6,
"https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f2a4dc831e8500015fda53_pp-health(white).svg"
],
[
"Food",
4,
"https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f8c2cc78cc2d0001fd4a7e_pp-food(white).svg"
]
]
};
var width = 200;
var height = 200;
var tooltip = d3
.select(".bubble_chart")
.append("div")
.classed("tooltip", true);
var svg = d3
.select(".bubble_chart")
.append("svg")
//responsive SVG needs these 2 attributes and no width and height attr
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 " + width + " " + height);
var bubble = d3.layout
.pack()
.size([200, 200])
.value(function(d) {
return d.size;
})
.padding(12);
// generate data with calculated layout values
var nodes = bubble.nodes(processData(json)).filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var vis = svg.selectAll("circle").data(nodes, function(d, i) {
return d.name + i;
});
vis
.enter()
.append("circle")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("class", function(d) {
return d.className;
})
.attr("r", 0)
.transition()
.duration(1000)
.attr("r", function(d) {
return d.r;
});
vis
.enter()
.append("svg:image")
.attr("transform", d => "translate(" + d.x + "," + d.y + ")")
.attr("x", d => -(d.r / 1.5) / 2)
.attr("y", d => -(d.r / 1.5) / 2)
.attr("xlink:href", function(d) {
return d.img;
})
.attr("width", d => d.r / 1.5)
.transition()
.duration(1000)
.style("opacity", 1);
/*
vis
.enter()
.append("text")
.attr("transform", d => "translate(" + d.x + "," + d.y + ")")
.attr("fill", "white")
.attr("text-anchor", "middle")
.attr("font-size", d => d.r / (d.r * 5 / 100))
.text(d => d.name);
*/
/* vis
.enter()
.append("text")
.attr("transform", d => "translate(" + d.x + "," + d.y + ")")
.attr("fill", "white")
.attr("text-anchor", "middle")
.attr("font-size", d => d.r / (d.r * 3 / 100))
.text(d => d.value);
*/
vis
.on("mousemove", function(d) {
tooltip
.style("opacity", 1)
.style("left", d3.event.x - tooltip.node().offsetWidth / 2 + "px")
.style("top", d3.event.y + 25 + "px").html(`
<p>Category: ${d.name}</p>
<p>Ordered: ${d.value}</p>
`);
})
.on("mouseout", function() {
tooltip.style("opacity", 0);
});
function processData(data) {
var obj = data.call_data;
var newDataSet = [];
for (var prop in obj) {
newDataSet.push({
name: obj[prop][0],
className: obj[prop][0].toLowerCase(),
size: obj[prop][1],
img: obj[prop][2]
});
}
return {
children: newDataSet
};
}
var aspect = width / height,
chart = d3.select('.bubble_chart');
d3.select(window)
.on("resize", function() {
var targetWidth = chart.node().getBoundingClientRect().width;
chart.attr("width", targetWidth);
chart.attr("height", targetWidth / aspect);
});
})();
.container {
border: 2px solid red;
width: 400px;
height: 200px;
}
.bubble_chart {
flex: 1;
border: 2px solid
}
.lifestyle {
fill: #89BED3;
}
.sports {
fill: #2A83D4;
}
.environment {
fill: #6CC070;
}
.food {
fill: #665C9E;
}
.medical {
fill: #C13E40;
}
.tooltip {
opacity: 0;
position: absolute;
pointer-events: none;
background-color: #fafafa;
border-radius: 8px;
padding: 15px;
z-index: 999;
box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, .1)
}
.tooltip p {
margin: 0;
}
.tooltip:before {
content: " ";
position: absolute;
border: 12px solid transparent;
border-bottom-color: #fafafa;
top: -20px;
left: 50%;
margin-left: -12px;
}
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.5/d3.js"></script>
<div class="container">
<div class="bubble_chart"></div>
</div>
You should set your width and height to the bounding rect height and width at the outset. Right now you have it set as both 200px, which are the same.
Do that like this:
var width = document.querySelector(".container").getBoundingClientRect().width;
var height = document.querySelector(".container").getBoundingClientRect().height;
(function() {
var json = {
call_data: [
[
"Lifestyle",
1,
"https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/5bb3ce2f801fbc657f83dd57_pp-lifestyle(white).svg"
],
[
"Sports",
2,
"https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/5c9131911ad86f445cb5abc7_pp-sport(white).svg"
],
[
"Environment",
8,
"https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f2a4bef42fff000159ba7a_pp-environ(white).svg"
],
[
"Medical",
6,
"https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f2a4dc831e8500015fda53_pp-health(white).svg"
],
[
"Food",
4,
"https://uploads-ssl.webflow.com/59df9e77ad9420000140eafe/59f8c2cc78cc2d0001fd4a7e_pp-food(white).svg"
]
]
};
var width = document.querySelector(".container").getBoundingClientRect().width;
var height = document.querySelector(".container").getBoundingClientRect().height;
var tooltip = d3
.select(".bubble_chart")
.append("div")
.classed("tooltip", true);
var svg = d3
.select(".bubble_chart")
.append("svg")
//responsive SVG needs these 2 attributes and no width and height attr
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 " + width + " " + height);
var bubble = d3.layout
.pack()
.size([200, 200])
.value(function(d) {
return d.size;
})
.padding(12);
// generate data with calculated layout values
var nodes = bubble.nodes(processData(json)).filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var vis = svg.selectAll("circle").data(nodes, function(d, i) {
return d.name + i;
});
vis
.enter()
.append("circle")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("class", function(d) {
return d.className;
})
.attr("r", 0)
.transition()
.duration(1000)
.attr("r", function(d) {
return d.r;
});
vis
.enter()
.append("svg:image")
.attr("transform", d => "translate(" + d.x + "," + d.y + ")")
.attr("x", d => -(d.r / 1.5) / 2)
.attr("y", d => -(d.r / 1.5) / 2)
.attr("xlink:href", function(d) {
return d.img;
})
.attr("width", d => d.r / 1.5)
.transition()
.duration(1000)
.style("opacity", 1);
/*
vis
.enter()
.append("text")
.attr("transform", d => "translate(" + d.x + "," + d.y + ")")
.attr("fill", "white")
.attr("text-anchor", "middle")
.attr("font-size", d => d.r / (d.r * 5 / 100))
.text(d => d.name);
*/
/* vis
.enter()
.append("text")
.attr("transform", d => "translate(" + d.x + "," + d.y + ")")
.attr("fill", "white")
.attr("text-anchor", "middle")
.attr("font-size", d => d.r / (d.r * 3 / 100))
.text(d => d.value);
*/
vis
.on("mousemove", function(d) {
tooltip
.style("opacity", 1)
.style("left", d3.event.x - tooltip.node().offsetWidth / 2 + "px")
.style("top", d3.event.y + 25 + "px").html(`
<p>Category: ${d.name}</p>
<p>Ordered: ${d.value}</p>
`);
})
.on("mouseout", function() {
tooltip.style("opacity", 0);
});
function processData(data) {
var obj = data.call_data;
var newDataSet = [];
for (var prop in obj) {
newDataSet.push({
name: obj[prop][0],
className: obj[prop][0].toLowerCase(),
size: obj[prop][1],
img: obj[prop][2]
});
}
return {
children: newDataSet
};
}
var aspect = width / height,
chart = d3.select('.bubble_chart');
d3.select(window)
.on("resize", function() {
var targetWidth = chart.node().getBoundingClientRect().width;
chart.attr("width", targetWidth);
chart.attr("height", targetWidth / aspect);
});
})();
.container {
border: 2px solid red;
width: 400px;
height: 200px;
}
.bubble_chart {
flex: 1;
border: 2px solid
}
.lifestyle {
fill: #89BED3;
}
.sports {
fill: #2A83D4;
}
.environment {
fill: #6CC070;
}
.food {
fill: #665C9E;
}
.medical {
fill: #C13E40;
}
.tooltip {
opacity: 0;
position: absolute;
pointer-events: none;
background-color: #fafafa;
border-radius: 8px;
padding: 15px;
z-index: 999;
box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, .1)
}
.tooltip p {
margin: 0;
}
.tooltip:before {
content: " ";
position: absolute;
border: 12px solid transparent;
border-bottom-color: #fafafa;
top: -20px;
left: 50%;
margin-left: -12px;
}
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.5/d3.js"></script>
<div class="container">
<div class="bubble_chart"></div>
</div>
I've a d3js barchart as below. I'm adding a tooltip on click of each bar. I'm unable add event to close the tooltip which is inside the tip. Is there a way to add that?
The snippet as follows:
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 30
};
var width = 300 - margin.left - margin.right;
var height = 300 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var x;
var y;
var dataset;
var yTicks = 6;
var tooltipEl = function(d) {
return (
'<div class="tip__container">' +
'<div class="val">' +
d.val +
"</div>" +
'<div class="close">' +
"<button>×</button>" +
"</div>" +
"</div>"
);
};
dataset = [{
desc: "test1",
val: 40
},
{
desc: "some dummy text here",
val: 120
}
];
x = d3
.scaleBand()
.domain(
dataset.map(function(d) {
return d.desc;
})
)
.range([0, width]);
y = d3
.scaleLinear()
.range([height, 0])
.domain([0, 350]);
graph = d3
.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Tool Tip
const div = d3
.select("#graph")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
graph
.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll(".tick text")
.call(wrap, x.bandwidth());
graph
.append("g")
.attr("class", "y-scale")
.call(
d3
.axisLeft(y)
.ticks(yTicks)
.tickPadding(10)
);
graph
.append("g")
.attr("class", "graph-placeholder")
.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr("x", d => x(d.desc) + (x.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr("class", "graph-main")
.selectAll("bar1")
.data(dataset)
.enter()
.append("rect")
.attr("class", "bar2")
.attr("x", d => x(d.desc) + (x.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return y(d.val);
})
.attr("height", function(d) {
return height - y(d.val);
})
.attr("width", barWidth)
.on("click", d => {
div.html(tooltipEl(d));
div
.transition()
.duration(200)
.style("display", "block")
.style("opacity", 1);
div
.style("left", x(d.desc) + x.bandwidth() / 2 - 1 + "px")
.style("top", height + margin.top + 10 + "px");
});
graph
.append("g")
.attr("class", "bar-label")
.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(d => d.val + "%")
.attr("y", function(d) {
return y(d.val) - 5;
})
.attr("x", function(d) {
return x(d.desc) + x.bandwidth() / 2;
});
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text
.text()
.split(/\s+/)
.reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1,
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text
.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while ((word = words.pop())) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
text-anchor: middle;
}
.bar-label text {
text-anchor: middle;
}
path.domain {
stroke-width: 0;
display: none;
}
.tooltip {
background: #FFFFFF;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.33);
font-family: "Segoe UI";
line-height: normal;
padding: 15px;
width: 100px;
position: absolute;
display: none;
}
.tooltip__container {
display: flex;
}
.tooltip::before {
content: "";
position: absolute;
left: 22px;
top: -8px;
transition: all 0.5s ease;
border: 8px solid #fff;
box-shadow: -5px -5px 5px rgba(0, 0, 0, 0.1);
transform: rotate(45deg);
}
.tip__container {
display: flex;
justify-content: space-between;
}
.close {
margin-left: 20px;
}
button {
border: 1px solid #ccc;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
Fiddle
Because your div's HTML is created by the selection.html() method inside the callback of the click function for the bars, you have to set the listener inside the same callback:
div.select("button").on("click", function() {
div.style("opacity", 0)
});
Here is your code with that change:
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 30
};
var width = 300 - margin.left - margin.right;
var height = 300 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var x;
var y;
var dataset;
var yTicks = 6;
var tooltipEl = function(d) {
return (
'<div class="tip__container">' +
'<div class="val">' +
d.val +
"</div>" +
'<div class="close">' +
"<button>×</button>" +
"</div>" +
"</div>"
);
};
dataset = [{
desc: "test1",
val: 40
},
{
desc: "some dummy text here",
val: 120
}
];
x = d3
.scaleBand()
.domain(
dataset.map(function(d) {
return d.desc;
})
)
.range([0, width]);
y = d3
.scaleLinear()
.range([height, 0])
.domain([0, 350]);
graph = d3
.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Tool Tip
const div = d3
.select("#graph")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
graph
.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll(".tick text")
.call(wrap, x.bandwidth());
graph
.append("g")
.attr("class", "y-scale")
.call(
d3
.axisLeft(y)
.ticks(yTicks)
.tickPadding(10)
);
graph
.append("g")
.attr("class", "graph-placeholder")
.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr("x", d => x(d.desc) + (x.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr("class", "graph-main")
.selectAll("bar1")
.data(dataset)
.enter()
.append("rect")
.attr("class", "bar2")
.attr("x", d => x(d.desc) + (x.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return y(d.val);
})
.attr("height", function(d) {
return height - y(d.val);
})
.attr("width", barWidth)
.on("click", d => {
div.html(tooltipEl(d));
div.select("button").on("click", function() {
div.style("opacity", 0)
});
div
.transition()
.duration(200)
.style("display", "block")
.style("opacity", 1);
div
.style("left", x(d.desc) + x.bandwidth() / 2 - 1 + "px")
.style("top", height + margin.top + 10 + "px");
});
graph
.append("g")
.attr("class", "bar-label")
.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(d => d.val + "%")
.attr("y", function(d) {
return y(d.val) - 5;
})
.attr("x", function(d) {
return x(d.desc) + x.bandwidth() / 2;
});
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text
.text()
.split(/\s+/)
.reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1,
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text
.text(null)
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", dy + "em");
while ((word = words.pop())) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text
.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
text-anchor: middle;
}
.bar-label text {
text-anchor: middle;
}
path.domain {
stroke-width: 0;
display: none;
}
.tooltip {
background: #FFFFFF;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.33);
font-family: "Segoe UI";
line-height: normal;
padding: 15px;
width: 100px;
position: absolute;
display: none;
}
.tooltip__container {
display: flex;
}
.tooltip::before {
content: "";
position: absolute;
left: 22px;
top: -8px;
transition: all 0.5s ease;
border: 8px solid #fff;
box-shadow: -5px -5px 5px rgba(0, 0, 0, 0.1);
transform: rotate(45deg);
}
.tip__container {
display: flex;
justify-content: space-between;
}
.close {
margin-left: 20px;
}
button {
border: 1px solid #ccc;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
I'm trying to write a generic multiline chart function based on Mike Bostock's example (https://bl.ocks.org/mbostock/3884955).
I'm facing an issue wherein, the last tick label on my Monthly graph x-axis does not show up. The last tick appears fine on the Weekly graph x-axis.
JS Fiddle Link:
http://jsfiddle.net/Q5Jag/11879/
I'm suspecting the issue here could be due to the range specified for the x-axis which for some reason ignores the last value. But I'm not exactly sure what is going on here. Could anyone help me debug ?
Here is my code:
function renderMultiLineChart(datafile, chartDiv, xAxisLabel, yAxisLabel, graphCadence){
var margin = {top: 20, right: 60, bottom: 80, left: 60},
width = 760 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%m/%d/%y");
var x = d3.scaleUtc()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
//var xAxis = d3.axisBottom(x).tickFormat(function(d){ return d.x;});
switch(graphCadence) {
case "Daily":
var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Weekly":
var xAxis = d3.axisBottom(x).ticks(d3.timeSaturday.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Monthly":
//var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1))
var xAxis = d3.axisBottom(x).ticks(d3.timeMonth.every(1)).tickFormat(d3.utcFormat("%m/%d"))
break;
}
var yAxis = d3.axisLeft(y);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.count); })
var div = d3.select(chartDiv).append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select(chartDiv).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 + ")");
//https://pharos-rest-service-iad.iad.proxy.amazon.com/s3/tool.csv
d3.csv(datafile, function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
//console.log(d);
d.date = parseDate(d.date);
});
var datapoints = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, count: +d[name]};
})
};
});
console.log(data);
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(datapoints, function(c) { return d3.min(c.values, function(v) { return v.count; }); }),
d3.max(datapoints, function(c) { return d3.max(c.values, function(v) { return v.count; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.style("text-anchor", "end")
.attr("transform", "rotate(-45)");
// text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 30) + ")")
.style("text-anchor", "middle")
.text(xAxisLabel);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style("fill", "black") // set the line colour
.text(yAxisLabel);
var datapoint = svg.selectAll(".datapoint")
.data(datapoints)
.enter().append("g")
.attr("class", "datapoint");
datapoint.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
j = -1;
datapoint.selectAll("circle")
.data(function(d){return d.values})
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d,i) { return x(d.date); })
.attr("cy", function(d) { return y(d.count); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", 1);
div.html("<b>"+d.count+"</b>")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.style("fill", function(d,i) { if (i == 0) { j++ }; return color(datapoints[j].name); });
var legendRectSize = 8;
var legendSpacing = 80;
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (100+margin.left+margin.right) + ","+(height+margin.bottom-20)+")")
var legend = legendHolder.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr("transform", function (d, i) {
if (i === 0) {
dataL = legendRectSize + legendSpacing
return "translate(0,0)"
} else {
var newdataL = dataL
dataL += legendRectSize + legendSpacing
return "translate(" + (newdataL) + ",0)"
}
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + 5)
.attr('y', legendRectSize)
.text(function(d) { return d; });
});
}
renderMultiLineChart("https://gist.githubusercontent.com/techyogii/323024b01c1eb4d0c07637e183e1e6d7/raw/422ed207cc2c38426fa726795ecd963f153135dd/app_usage","div#multiChartMonthly","Snapshot Date","Metric Count","Monthly")
renderMultiLineChart("https://gist.githubusercontent.com/techyogii/8ed38bdb3b8e44194ee8570ef9cc5b75/raw/d0c85aaf9eaa7e8819fd6e6e210885b0cfa6f47d/app_usage_weekly","div#multiChartWeekly","Snapshot Date","Metric Count","Weekly")
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
/*display: none;*/
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
div.tooltip {
position: absolute;
text-align: center;
/*width: 60px;
height: 28px;*/
padding: 4px;
font: 14px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 10px;
pointer-events: none;
}
.legend {
font-size: 12px;
}
rect {
stroke-width: 2;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="multiChartWeekly">
<div id="multiChartMonthly">
Thanks,
Yogesh
I believe the problem lies in a misalignment between how d3.timeMonth works and how your data is formatted. You see that d3.timeMonth is labeling the first of every month, whereas the data is grouped on the last day of every month. So when you call x.domain(d3.extent(data, function(d) { return d.date; }));, the last data point's date is less than the next label would have been.
One potential solution is to change your parse function to bump all your month dates forward by one day to make them line up with the first of the month. See below.
To remove any dates that end mid-month, you can filter the data set after parsing dates to clean it up.
function renderMultiLineChart(datafile, chartDiv, xAxisLabel, yAxisLabel, graphCadence){
var margin = {top: 20, right: 60, bottom: 80, left: 60},
width = 760 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var parseDate = function(dateString){
if(graphCadence == "Monthly"){
var date = new Date(dateString);
date.setDate(date.getDate()+1);
return date;
}
return d3.timeParse("%m/%d/%y")(dateString);
}
var x = d3.scaleUtc()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
//var xAxis = d3.axisBottom(x).tickFormat(function(d){ return d.x;});
switch(graphCadence) {
case "Daily":
var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Weekly":
var xAxis = d3.axisBottom(x).ticks(d3.timeSaturday.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Monthly":
//var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1))
var xAxis = d3.axisBottom(x).ticks(d3.timeMonth.every(1)).tickFormat(d3.utcFormat("%m/%d"))
break;
}
var yAxis = d3.axisLeft(y);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.count); })
var div = d3.select(chartDiv).append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select(chartDiv).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 + ")");
//https://pharos-rest-service-iad.iad.proxy.amazon.com/s3/tool.csv
d3.csv(datafile, function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
if(graphCadence == "Monthly"){
data = data.filter(function(d){
return d.date.getDate() == 1
});
}
var datapoints = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, count: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(datapoints, function(c) { return d3.min(c.values, function(v) { return v.count; }); }),
d3.max(datapoints, function(c) { return d3.max(c.values, function(v) { return v.count; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.style("text-anchor", "end")
.attr("transform", "rotate(-45)");
// text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 30) + ")")
.style("text-anchor", "middle")
.text(xAxisLabel);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style("fill", "black") // set the line colour
.text(yAxisLabel);
var datapoint = svg.selectAll(".datapoint")
.data(datapoints)
.enter().append("g")
.attr("class", "datapoint");
datapoint.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
j = -1;
datapoint.selectAll("circle")
.data(function(d){return d.values})
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d,i) { return x(d.date); })
.attr("cy", function(d) { return y(d.count); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", 1);
div.html("<b>"+d.count+"</b>")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.style("fill", function(d,i) { if (i == 0) { j++ }; return color(datapoints[j].name); });
var legendRectSize = 8;
var legendSpacing = 80;
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (100+margin.left+margin.right) + ","+(height+margin.bottom-20)+")")
var legend = legendHolder.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr("transform", function (d, i) {
if (i === 0) {
dataL = legendRectSize + legendSpacing
return "translate(0,0)"
} else {
var newdataL = dataL
dataL += legendRectSize + legendSpacing
return "translate(" + (newdataL) + ",0)"
}
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + 5)
.attr('y', legendRectSize)
.text(function(d) { return d; });
});
}
renderMultiLineChart("https://gist.githubusercontent.com/JstnPwll/5a24137a36c9246cf065c58d7f5bb5a5/raw/ff986ee88338e99d10ab93035ffacd3ffe92fd4e/gistfile1.txt","div#multiChartMonthly","Snapshot Date","Metric Count","Monthly")
renderMultiLineChart("https://gist.githubusercontent.com/techyogii/8ed38bdb3b8e44194ee8570ef9cc5b75/raw/d0c85aaf9eaa7e8819fd6e6e210885b0cfa6f47d/app_usage_weekly","div#multiChartWeekly","Snapshot Date","Metric Count","Weekly")
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
/*display: none;*/
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
div.tooltip {
position: absolute;
text-align: center;
/*width: 60px;
height: 28px;*/
padding: 4px;
font: 14px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 10px;
pointer-events: none;
}
.legend {
font-size: 12px;
}
rect {
stroke-width: 2;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="multiChartWeekly">
<div id="multiChartMonthly">
Hopefully Mike Bostock comes swinging in to my rescue on this one.
(EDIT: With the help of Andrea Crawford's answer below this is the working version)
JS Fiddle: https://jsfiddle.net/dfcarter/kd0dkrL8/
I have written a chart in d3 to allow on the fly user changes (colors, selected data, etc)
Well it is mainly working but when you remove the lower ordinals it stops centering on the graph and starts to creep downward.
Looks Fine - removing the first 3 seems fine
Looks Unacceptable - removing the Last 3 and the chart is toward the bottom of the graph area
The Core of the D3 for reference:
(The Fiddle works correctly I don't see why the external reference to spectrum is not working so look at the fiddle)
var $pop = $('#my_custom_menu'),
notHov = 1; // Hover flag
$pop.hover(function() {
notHov ^= 1;
}); // Toggle flag on hover
$(document).on('mouseup keyup', function(e) {
if (notHov || e.which == 27) $pop.fadeOut();
});
/////// CALL POPUP
$('.my_custom_menu').click(function() {
$pop.stop().fadeIn();
});
function updateStream(color) {
var label1 = d3.selectAll(".layer")
.filter(function(d, i) {
return i === clickIndex;
})
.style("fill", color);
}
$("#showPalette").spectrum({
showPalette: true,
palette: [
['black', 'white', 'blanchedalmond'],
['rgb(255, 128, 0);', 'hsv 100 70 50', 'lightyellow']
],
change: updateStream
});
var clickIndex = 0;
$("#textDisplayName").keyup(function(event) {
if (event.keyCode == 13) {
//alert(eval($("#stream").val) + " , " + eval($("#textDisplayname").val));
var newValue = document.getElementById("textDisplayName").value;
var label1 = d3.selectAll("text")
.filter(function(d, i) {
return i === clickIndex;
})
.text(newValue);
}
});
chart("data.csv", "blue");
var datearray = [];
var colorrange = [];
function csv(url, callback) {
d3.text(url, function(text) {
callback(text && d3.csv.parse(text));
});
}
function drawgrid() {
}
function chart(csvpath, color) {
if (color == "blue") {
colorrange = ["#045A8D", "#2B8CBE", "#74A9CF", "#A6BDDB", "#D0D1E6", "#F1EEF6"];
} else if (color == "pink") {
colorrange = ["#980043", "#DD1C77", "#DF65B0", "#C994C7", "#D4B9DA", "#F1EEF6"];
} else if (color == "orange") {
colorrange = ["#B30000", "#E34A33", "#FC8D59", "#FDBB84", "#FDD49E", "#FEF0D9"];
}
strokecolor = colorrange[0];
var format = d3.time.format("%m/%d/%y");
var margin = {
top: 20,
right: 40,
bottom: 30,
left: 30
};
var width = document.body.clientWidth - margin.left - margin.right - 200;
var height = 400 - margin.top - margin.bottom;
var tooltip = d3.select("body")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "20")
.style("visibility", "hidden")
.style("top", "30px")
.style("left", "55px");
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height - 10, 0]);
var getColor = d3.scale.ordinal()
.range(colorrange);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.weeks);
var yAxis = d3.svg.axis()
.scale(y);
var yAxisr = d3.svg.axis()
.scale(y);
var stack = d3.layout.stack()
.offset("silhouette")
.values(function(d) {
return d.values;
})
.x(function(d) {
return d.date;
})
.y(function(d) {
return d.value;
});
var nest = d3.nest()
.key(function(d) {
return d.key;
});
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) {
return x(d.date);
})
.y0(function(d) {
return y(d.y0);
})
.y1(function(d) {
return y(d.y0 + d.y);
});
var svg = d3.select(".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 + ")");
function randomData() {
return Math.random() * 9;
}
var dates = ["07/08/2016", "07/09/2016", "07/10/2016", "07/11/2016", "07/12/2016", "07/13/2016", "07/14/2016", "07/15/2016", "07/16/2016", "07/17/2016", "07/18/2016"];
var numberOfSeries = 7,
numberOfDataPoint = 7,
data = [];
for (var i = 0; i < numberOfSeries; ++i) {
for (var j = 0; j < numberOfDataPoint; ++j) {
data.push({
key: i,
value: randomData(),
date: Date.parse(dates[j])
});
}
}
var layerData = nest.entries(data);
var checkholder = d3.select("#checkboxHolder")
var legend = checkholder.selectAll(".legend")
.data(layerData)
.enter().append("div")
legend.append("input")
.attr("type", "checkbox")
.attr("id", function(d, i) {
return "check" + i
});
legend.append("label")
.attr("for", function(d, i) {
return "check" + i
})
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.html(function(d) {
return d.key;
});
legend.on('change', function(d) {
d.disabled = !d.disabled;
d3.transition().duration(600).each(redraw);
});
var xaxisnode = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var yaxisnode = svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
.call(yAxis.orient("right"));
//svg.append("g")
// .attr("class", "y axis")
// .call(yAxis.orient("left"));
function redraw() {
var activeLayers = layerData.filter(function(d1) {
return !d1.disabled;
});
var layers = stack(activeLayers);
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
if (!d.disabled) return d.y0 + d.y;
})]);
svg.selectAll(".layergroup").remove();
var layergroup = svg.selectAll(".layergroup")
.data(layers)
.enter().append("g")
.attr("class", "layergroup");
var paths = layergroup.append("path")
.attr("class", "layer")
.attr("d", function(d) {
return area(d.values);
})
.style("fill", function(d, i) {
return getColor(i);
});
layergroup.append("text")
.datum(function(d) {
return {
name: d.key,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function(d) {
return "translate(" + x(d.value.date) + "," + y(d.value.y0 + d.value.y / 2) + ")";
})
.attr("x", -6)
.attr("dy", ".35em")
.attr("class", "pathLabel")
.text(function(d) {
return d.name;
});
//var clickIndex = 0;
//paths.on("click", function (d, i) {
// clickIndex = i;
// svg.selectAll("text").filter(function (d, i) { return i === clickIndex }).attr("transform", function (d) { var coords = d3.mouse(svg.node()); return "translate(" + coords[0] + "," + coords[1] + ")"; })
//});
xaxisnode.call(xAxis);
yaxisnode.call(yAxis.orient("right"));
const drag = d3.behavior.drag()
.origin(function(d) {
return d;
})
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended)
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
}
var dragIndex = 0;
function dragged(d, i) {
dragindex = i;
const elem = svg.selectAll(".pathLabel").filter(function(d, i) {
return i === dragindex;
});
elem.attr('transform', function(d) {
var coords = d3.mouse(svg.node());
return "translate(" + coords[0] + "," + coords[1] + ")";
});
//elem.attr('y', +elem.attr('y') + d3.event.dy)
}
function dragended(d) {}
svg.selectAll(".layer")
.attr("opacity", 1)
.on("mouseover", function(d, i) {
svg.selectAll(".layer").transition()
.duration(250)
.attr("opacity", function(d, j) {
return j != i ? 0.6 : 1;
})
})
.on("mousemove", function(d, i) {
mousex = d3.mouse(this);
mousex = mousex[0];
var invertedx = x.invert(mousex);
invertedx = invertedx.getMonth() + invertedx.getDate();
var selected = (d.values);
for (var k = 0; k < selected.length; k++) {
datearray[k] = selected[k].date
datearray[k] = datearray[k].getMonth() + datearray[k].getDate();
}
mousedate = datearray.indexOf(invertedx);
pro = d.values[mousedate].value;
mouse = d3.mouse(d3.select("#letable").node());
d3.select(this)
.classed("hover", true)
.attr("stroke", strokecolor)
.attr("stroke-width", "0.5px"),
tooltip.html("<p>" + d.key + "<br>" + pro + "</p>").style("visibility", "visible").style("left", (mouse[0] + 50) + "px").style("top", (mouse[1] + 30) + "px");
})
.on("mouseout", function(d, i) {
svg.selectAll(".layer")
.transition()
.duration(250)
.attr("opacity", "1");
d3.select(this)
.classed("hover", false)
.attr("stroke-width", "0px"), tooltip.html("<p>" + d.key + "<br>" + pro + "</p>").style("visibility", "hidden");
})
.on("contextmenu", function(data, index) {
var position = d3.mouse(this);
d3.select('#my_custom_menu')
.style('position', 'absolute')
.style('left', position[0] + "px")
.style('top', position[1] + "px")
.style('display', 'block');
d3.event.preventDefault();
//d3.select('#stream')
// .attr("value", index);
clickIndex = index;
$('#textDisplayName')
.val(data.key);
}).call(drag);
//d3.select('svg')
// .selectAll('text')
// .data(labels)
// .enter()
// .append('text')
// .text(d => d)
// .attr('fill', 'green')
// .attr('x', (d, i) => 10 + i * 30)
// .attr('y', (d, i) => 15 + i * 30)
// .call(drag)
var vertical = d3.select(".chart")
.append("div")
.attr("class", "remove")
.style("position", "absolute")
.style("z-index", "19")
.style("width", "1px")
.style("height", "380px")
.style("top", "10px")
.style("bottom", "30px")
.style("left", "0px")
.style("background", "#ccc");
d3.select(".chart")
.on("mousemove", function() {
mousex = d3.mouse(d3.select("#letable").node());
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px")
})
.on("mouseover", function() {
mousex = d3.mouse(d3.select("#letable").node());
mousex = mousex[0] + 5;
vertical.style("left", mousex + "px")
});
}
redraw();
}
body {
font: 10px "segoe ui";
}
.chart {
background: #fff;
}
p {
font: 12px "segoe ui";
}
.axis path,
.axis line {
fill: none;
stroke: #CCC;
stroke-width: 2px;
shape-rendering: crispEdges;
}
.pathLabel {
font: bold 16px "Segoe UI";
color: black;
text-shadow: 0 0 3px #FFF;
}
button {
position: absolute;
right: 50px;
top: 10px;
}
#my_custom_menu {
display: none;
padding: 15px 25px;
width: 220px;
background: #fff;
border-radius: 4px;
box-shadow: 0 3px 3px -2px #024;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/2.6.0/d3.min.js"></script>
<!DOCTYPE html>
<body>
<script src="https://bgrins.github.io/spectrum/spectrum.js"></script>
<script src="https://d3js.org/d3.v2.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<link href="https://bgrins.github.io/spectrum/spectrum.css" rel="stylesheet" />
<table id="letable">
<tr>
<td>
<div id="checkboxHolder" style="width:200px;height:400px;overflow:auto;">
</div>
</td>
<td>
<div class="chart" style="width:700px;height:400px">
</div>
</td>
</tr>
</table>
<div id="my_custom_menu">
<input id="textDisplayName" type="text" />
<br />
<input id="showPalette" type="text" />
<br />
<input type="hidden" id="stream" value="" />
</div>
</body>
I'm not an expert on D3 so you may still want to wait for Mr. Bostock, but I think your issue is that inside your redraw function, you have this code:
y.domain([0, d3.max(data, function(d) {
if (!d.disabled) return d.y0 + d.y;
})]);
but if you look at the value of d, it doesn't actually have a disabled property.
You could add a disabled property to the values at the same time you add it to the parent:
legend.on('change', function(d) {
d.disabled = !d.disabled;
$.each(d.values, function(){
this.disabled = d.disabled;
});
d3.transition().duration(600).each(redraw);
});
There's probably a more elegant way to do that, but it seems to fix the issue.
I have a problem with marks on my D3 map. When I set up zooming for each state the marks are not zooming within the map..
That's my first try for D3 map, so sorry if I messed up something in the code. If I did or missed something I'll really appreciate explanation.
If anyone can help with that I'll be really thankful.
var width = 960,
height = 500,
centered;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body")
.append("svg")
.attr("viewBox", "0 0 " + width + " " + height )
.attr("preserveAspectRatio", "xMinYMin");
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.on("click", clicked);
var g = svg.append("g");
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
d3.json("https://bl.ocks.org/mbostock/raw/4090846/us.json", function(error, us) {
if (error) throw error;
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.style("fill", "#26404b")
.on("click", clicked);
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("id", "state-borders")
.attr("d", path);
// CITIES
d3.json("https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json", function(data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.longitude, d.latitude])[0];
})
.attr("cy", function(d) {
return projection([d.longitude, d.latitude])[1];
})
.attr("r", function(d) {
if(d.population >= 1 && d.population < 10000){
return 1;
}
else if(d.population >= 10000 && d.population < 100000){
return 2;
}
else if(d.population >= 100000 && d.population < 500000){
return 3;
}
else if(d.population >= 500000 && d.population < 1000000){
return 4;
}
else if(d.population >= 1000000 && d.population < 5000000){
return 5;
}
else if(d.population < 5000000){
return 10;
}
else {
return 0;
};
})
.style("fill", "rgba(26, 188, 156,0.8)")
// HOVER
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(200)
.style('fill', 'rgba(26, 188, 156, 0.3)')
.style('stroke', '#1abc9c')
.style('stroke-width', 4);
div.transition()
.duration(200)
.style("opacity", .9);
div.html('<span id="place">City: ' + d.city + '</span><br><span id="people">Calls: ' + d.population + '</span>')
.style("left", (d3.event.pageX) + 35 + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(200)
.style('fill', 'rgba(26, 188, 156, 0.8)')
.style('stroke-width', 0);
div.transition()
.duration(200)
.style("opacity", 0);
});
});
});
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
.background {
fill: #435863;
pointer-events: all;
}
path:hover {
fill-opacity: .9;
}
#states .active {
fill-opacity: .9;
}
#state-borders {
fill: none;
stroke: rgba(22, 160, 133, .1);
stroke-width: 1px;
}
/* Style for Custom Tooltip */
div.tooltip {
position: absolute;
height: 28px;
padding: 5px 10px;
font: 12px sans-serif;
background: white;
border: 0px;
border-radius: 5px;
pointer-events: none;
}
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
The problem is that you are appending your circles to the svg directly, but your US state are being appended to a g element. Your zoom transition is only applied to the g, meaning the circles will be unaffected:
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
You should be able to correct this by simply appending your circles to the same g element itself, rather than the svg:
d3.json("cities.json", function(data) {
g.selectAll("circle")
.data(data)
.enter()
...