D3.js: Mouseover is not triggered on zoomed topojson features - d3.js

I have a US map in topojson format. Users select the state and counties from
dropdownlists. When a user selects the county, the map zooms in to the selected
county.
To do: I need to display the county name when users mouseover the county. I'm
able to do this when the map is first loaded but not when the features are zoomed in.
I'm using a tooltip to display text on mouseover. Following is my code: (A sample version can be viewed at: https://realtimeceap.brc.tamus.edu).
<script>
//globals: create tooptip div
var div = d3.select("body").append("xhtml:div")
.attr("id", "divTip")
.style("position", "absolute")
.attr("class", "tooltip")
.style("opacity", 0);
function zoomToCounty(stname, cnty) {
d3.json("/topo/us-wgs84.json", function (us) {
var conus = topojson.feature(us, us.objects.counties);
//initialize selected county text
d3.select("text").remove();
//reset active to inactive size and color
d3.select("#svgMap2").select("#counties").select(".active")
.style("stroke-width", "0.5px")
.style("stroke", "#808080");
//initialize rect
svg.select("rect").remove();
//zoom to selected county
d3.selectAll(".county")
.classed("active", function (d) {
if (d.properties.StateName === stname && d.properties.County === cnty) {
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.call(d3.zoom()
.scaleExtent([.05, 8])
.on("zoom", moveWheel))
//zoom in to max scale extent
var zoom_in = d3.zoom()
.scaleExtent([.05, 8])
.on("zoom", zoomedIn);
//get path of selected county
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = Math.max(1, Math.min(8, 0.9 / Math.max(dx / width, dy / height))),
translate = [width / 2 - scale * x, height / 2 - scale * y];
//add text at coordinates inside a group
d3.select("#svgMap2").select("#counties")
.append("text")
.style("fill", "black")
.style("font-size", "20px")
.attr("x", width / 2) //center the text
.attr("y", height / 2)
.attr("text-anchor", "middle") //set anchor y justification
.text($("#countySelect").val());
d3.select("#svgMap2").select("#counties")
.transition()
.duration(1000)
.call(zoom_in.transform, d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale)); //center
return true;
}
});
//hover over counties
d3.select("#counties").selectAll(".county")
.enter().append("path")
.attr("d", path)
.attr("name", function (d) { return d.properties.County; }, true)
.attr("id", function (d) { return d.properties.FIPS; }, true)
.style("stroke-width", "0.5px") //inactive border width
.on("mouseover", function () {
d3.select(this)
.style("stroke", "#ff5800");
div.transition()
.duration(300)
div.text(function () {
if (d.properties.StateName === stname && d.properties.County === cnty) {
return $("#countySelect").val();
} else {
return d.properties.County;
}
})
.style("opacity", 1)
.style("class", "label")
.style("left", (d3.event.pageX) - 10 + "px")
.style("top", (d3.event.pageY) - 5 + "px");
})
.on("mouseout", function () {
d3.select(this)
.style("stroke", "#808080");
div.transition()
.duration(300)
.style("opacity", 0);
});
}); //end d3.json
}

Found a solution. I needed to "attach" the mouseover code on the "rect". I replaced the codes on "hover over counties" portion as follows:
//initialize rect
svg.select("rect").remove();
//hover over counties
d3.selectAll(".county", function (d) {
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.call(d3.zoom()
.scaleExtent([.05, 8])
.on("zoom", moveWheel))
.on("mouseover", function (d, i) {
d3.select(this)
.style("stroke", "#ff5800")
div.transition().duration(300)
div.text(function (d) { return conus.features[i].properties.County })
.style("opacity", 1)
.style("class", "label")
.style("left", (d3.event.pageX) - 10 + "px")
.style("top", (d3.event.pageY) - 5 + "px");
})
.on("mouseout", function (d) {
div.transition().duration(300)
.style("opacity", 0);
})
});

Related

Use svg as icon with D3 and htmlwidgets

I was able to create my first htmlwidget that creates this animated plot:
I would like to replace the "B" and "D" buttons with a single icon that uses an svg as the icon. In particular, I want to use this icon.. The icon should be black when selected, light gray when unselected, and a darker gray when hovering over.
To start, I'm not sure where to save the file so my code can see it.
This is the yaml for my htmlwidget package:
# (uncomment to add a dependency)
dependencies:
- name: D3
version: 4
src: htmlwidgets/lib/D3
script: d3.v4.js
stylesheet: style.css
- name: d3tip
version: 0.7.1
src: htmlwidgets/lib/d3-tip
script: d3-tip.min.js
stylesheet: style.css
And this is the js file:
HTMLWidgets.widget({
name: 'IMPosterior',
type: 'output',
factory: function(el, width, height) {
// TODO: define shared variables for this instance
return {
renderValue: function(opts) {
//transition
var transDuration = 1000;
var dataDiscrete = opts.bars.map((b, i) => {
b.y = Number(b.y);
b.desc = opts.text[i];
return b;
});
var distParams = {
min: d3.min(opts.data, d => d.x),
max: d3.max(opts.data, d => d.x)
};
distParams.cuts = [-opts.MME, opts.MME, distParams.max];
opts.data = opts.data.sort((a,b) => a.x - b.x);
var dataContinuousGroups = [];
distParams.cuts.forEach((c, i) => {
let data = opts.data.filter(d => {
if (i === 0) {
return d.x < c;
} else if (i === distParams.cuts.length - 1) {
return d.x > distParams.cuts[i - 1];
} else {
return d.x < c && d.x > distParams.cuts[i - 1];
}
});
data.unshift({x:data[0].x, y:0});
data.push({x:data[data.length - 1].x, y:0});
dataContinuousGroups.push({
color: opts.colors[i],
data: data
});
});
var margin = {
top: 50,
right: 20,
bottom: 80,
left: 70
},
dims = {
width: width - margin.left - margin.right,
height: height - margin.top - margin.bottom
};
var xContinuous = d3.scaleLinear()
.domain([distParams.min - 1, distParams.max + 1])
.range([0, dims.width]);
var xDiscrete = d3.scaleBand()
.domain(dataDiscrete.map(function(d) { return d.x; }))
.rangeRound([0, dims.width]).padding(0.1);
var y = d3.scaleLinear()
.domain([0, 1])
.range([dims.height, 0]);
var svg = d3.select(el).append("svg")
.attr("width", dims.width + margin.left + margin.right)
.attr("height", dims.height + margin.top + margin.bottom);
var g = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xAxis = d3.axisBottom()
.scale(xDiscrete);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(10)
.tickFormat(d3.format(".0%"));
var yLabel = g.append("text")
.attr("class", "y-axis-label")
.attr("transform", "rotate(-90)")
.attr("y", -52)
.attr("x", -160)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style("font-size", 14 + "px")
.text("Probability");
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + dims.height + ")")
.call(xAxis);
g.append("g")
.attr("class", "y axis")
.call(yAxis);
var areas = g.selectAll(".area")
.data(dataDiscrete)
.enter().append("path")
.attr("class", "area")
.style("fill", function(d) { return d.color; })
.attr("d", function(d, i) {
let numPts = dataContinuousGroups[i].data.length - 2;
var path = d3.path()
path.moveTo(xDiscrete(d.x), y(0));
for (j=0; j<numPts; j++) {
path.lineTo(xDiscrete(d.x) + j*xDiscrete.bandwidth()/(numPts-1), y(d.y))
}
path.lineTo(xDiscrete(d.x) + xDiscrete.bandwidth(), y(0));
return path.toString();
});
var tooltip = d3.tip()
.attr('class', 'd3-tip chart-data-tip')
.offset([30, 0])
.direction('s')
.html(function(d, i) {
return "<span>" + dataDiscrete[i].desc + "</span>";
});
g.call(tooltip);
areas
.on('mouseover', tooltip.show)
.on('mouseout', tooltip.hide);
var thresholdLine = g.append("line")
.attr("stroke", "black")
.style("stroke-width", "1.5px")
.style("stroke-dasharray", "5,5")
.style("opacity", 1)
.attr("x1", 0)
.attr("y1", y(opts.threshold))
.attr("x2", dims.width)
.attr("y2", y(opts.threshold));
var updateXAxis = function(type, duration) {
if (type === "continuous") {
xAxis.scale(xContinuous);
} else {
xAxis.scale(xDiscrete);
}
d3.select(".x").transition().duration(duration).call(xAxis);
};
var updateYAxis = function(data, duration) {
var extent = d3.extent(data, function(d) {
return d.y;
});
extent[0] = 0;
extent[1] = extent[1] + 0.2*(extent[1] - extent[0]);
y.domain(extent);
d3.select(".y").transition().duration(duration).call(yAxis);
};
var toggle = function(to, duration) {
if (to === "distribution") {
updateYAxis(dataContinuousGroups[0].data.concat(dataContinuousGroups[1].data).concat(dataContinuousGroups[2].data), 0);
updateXAxis("continuous", duration);
areas
.data(dataContinuousGroups)
.transition()
.duration(duration)
.attr("d", function(d) {
var gen = d3.line()
.x(function(p) {
return xContinuous(p.x);
})
.y(function(p) {
return y(p.y);
});
return gen(d.data);
});
thresholdLine
.style("opacity", 0);
g.select(".y.axis")
.style("opacity", 0);
g.select(".y-axis-label")
.style("opacity", 0);
} else {
y.domain([0, 1]);
d3.select(".y").transition().duration(duration).call(yAxis);
updateXAxis("discrete", duration);
areas
.data(dataDiscrete)
.transition()
.duration(duration)
.attr("d", function(d, i) {
let numPts = dataContinuousGroups[i].data.length - 2;
var path = d3.path()
path.moveTo(xDiscrete(d.x), y(0));
for (j=0; j<numPts; j++) {
path.lineTo(xDiscrete(d.x) + j*xDiscrete.bandwidth()/(numPts-1), y(d.y))
}
path.lineTo(xDiscrete(d.x) + xDiscrete.bandwidth(), y(0));
return path.toString();
});
thresholdLine
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1)
.attr("y1", y(opts.threshold))
.attr("y2", y(opts.threshold));
g.select(".y.axis")
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1);
g.select(".y-axis-label")
.transition()
.duration(0)
.delay(duration)
.style("opacity", 1);
}
};
// Add buttons
//container for all buttons
var allButtons = svg.append("g")
.attr("id", "allButtons");
//fontawesome button labels
var labels = ["B", "D"];
//colors for different button states
var defaultColor = "#E0E0E0";
var hoverColor = "#808080";
var pressedColor = "#000000";
//groups for each button (which will hold a rect and text)
var buttonGroups = allButtons.selectAll("g.button")
.data(labels)
.enter()
.append("g")
.attr("class", "button")
.style("cursor", "pointer")
.on("click", function(d, i) {
updateButtonColors(d3.select(this), d3.select(this.parentNode));
d3.select("#numberToggle").text(i + 1);
if (d === "D") {
toggle("distribution", transDuration);
} else {
toggle("discrete", transDuration);
}
})
.on("mouseover", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill", hoverColor);
}
})
.on("mouseout", function() {
if (d3.select(this).select("rect").attr("fill") != pressedColor) {
d3.select(this)
.select("rect")
.attr("fill", defaultColor);
}
});
var bWidth = 40; //button width
var bHeight = 25; //button height
var bSpace = 10; //space between buttons
var x0 = 20; //x offset
var y0 = 10; //y offset
//adding a rect to each toggle button group
//rx and ry give the rect rounded corner
buttonGroups.append("rect")
.attr("class", "buttonRect")
.attr("width", bWidth)
.attr("height", bHeight)
.attr("x", function(d, i) {
return x0 + (bWidth + bSpace) * i;
})
.attr("y", y0)
.attr("rx", 5) //rx and ry give the buttons rounded corners
.attr("ry", 5)
.attr("fill", defaultColor);
//adding text to each toggle button group, centered
//within the toggle button rect
buttonGroups.append("text")
.attr("class", "buttonText")
.attr("x", function(d, i) {
return x0 + (bWidth + bSpace) * i + bWidth / 2;
})
.attr("y", y0 + bHeight / 2)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.attr("fill", "white")
.text(function(d) {
return d;
});
function updateButtonColors(button, parent) {
parent.selectAll("rect")
.attr("fill", defaultColor);
button.select("rect")
.attr("fill", pressedColor);
}
toggle("distribution", 0);
setTimeout(() => {
toggle("discrete", transDuration);
}, 1000);
},
resize: function(width, height) {
// TODO: code to re-render the widget with a new size
}
};
}
});
Once I save the svg in the right folder, I'm also not sure how can I use it to replace the two buttons that I have.
It will probably be easiest and most self contained to grab the svg paths (and in this case a rect) and attach them to the svg with svg.append("defs") - no need to access any image file from the script. Inserting an svg straight from a file makes it trickier, for example, to color, .attr("fill",) won't work in this case.
Open the icon in a text editor, the data we want from the icon is:
<path d="M37.92,42.22c3.78-8,7-14.95,12.08-14.95h0c5,0,8.3,6.93,12.08,14.95,6.12,13,13.73,29.13,33.48,29.13h0v-2h0c-18.48,0-25.79-15.51-31.67-28C59.82,32.74,56.3,25.28,50,25.28h0c-6.3,0-9.82,7.46-13.89,16.09-5.88,12.47-13.19,28-31.67,28h0v2h0C24.18,71.35,31.8,55.2,37.92,42.22Z"/>
<rect y="72.72" width="100" height="2"/>
Then we can append them to the svg as defs, using a parent g, with:
var symbol = svg.append("defs")
.append("g")
.attr("id","bellcurve");
symbol.append("path")
.attr("d", "M37.92,42.22c3.78-8,7-14.95,12.08-14.95h0c5,0,8.3,6.93,12.08,14.95,6.12,13,13.73,29.13,33.48,29.13h0v-2h0c-18.48,0-25.79-15.51-31.67-28C59.82,32.74,56.3,25.28,50,25.28h0c-6.3,0-9.82,7.46-13.89,16.09-5.88,12.47-13.19,28-31.67,28h0v2h0C24.18,71.35,31.8,55.2,37.92,42.22Z" )
symbol.append("rect")
.attr("y", 72.72)
.attr("width",100)
.attr("height",2);
To use the icon, we only need to append it as a child of a g element (this allows us to scale it too, and since it's width is 100 pixels, this allows for easy scaling to any width:
svg.append("g")
.attr("transform","scale(0.4)")
.append("use")
.attr("xlink:href","#bellcurve")
Like any other svg element, we can set the stroke, fill, and stroke-width attributes. If setting the stroke-width to more than 2, you probably won't need to set the fill: the stroke will overlap it.
Here's a quick demonstration using your icon, scaling it and coloring it, and for fun, transitioning it:
var svg = d3.select("body").append("svg")
.attr("width", 400)
.attr("height", 400);
var symbol = svg.append("defs")
.append("g")
.attr("id","bellcurve");
symbol.append("path")
.attr("d", "M37.92,42.22c3.78-8,7-14.95,12.08-14.95h0c5,0,8.3,6.93,12.08,14.95,6.12,13,13.73,29.13,33.48,29.13h0v-2h0c-18.48,0-25.79-15.51-31.67-28C59.82,32.74,56.3,25.28,50,25.28h0c-6.3,0-9.82,7.46-13.89,16.09-5.88,12.47-13.19,28-31.67,28h0v2h0C24.18,71.35,31.8,55.2,37.92,42.22Z" )
symbol.append("rect")
.attr("y", 72.72)
.attr("width",100)
.attr("height",2);
svg.append("g")
.append("use")
.attr("xlink:href","#bellcurve")
.attr("fill","steelblue")
.attr("stroke","steelblue")
svg.append("g")
.attr("transform","translate(100,0)scale(0.5)")
.append("use")
.attr("xlink:href","#bellcurve")
.attr("fill","steelblue")
.attr("stroke","steelblue")
.attr("stroke-width",2)
svg.append("g")
.attr("transform","translate(100,50)scale(0.5)")
.append("use")
.attr("xlink:href","#bellcurve")
.attr("fill","steelblue")
.attr("stroke","steelblue")
.attr("stroke-width",5)
var transition = function() {
d3.select(this)
.transition()
.attr("stroke","orange")
.attr("fill","orange")
.duration(1000)
.transition()
.attr("stroke","steelblue")
.attr("fill","steelblue")
.duration(500)
.on("end",transition)
}
d3.selectAll("g").selectAll("use")
.each(transition);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
With that it should be fairly easy to append the image straight to a button. And when toggling which visualization is showing, you can toggle the button's fill.
Slick looking app by the way.

Unable to change x position of legend text inside a group element using D3

I am trying to create a horizontal graph legend in D3.js. I am using a group element (g) as a container for all the legends and the individual legends (text) are also each wrapped inside a "g" element. The result is that the individual legends are stacked on top of each other rather than spaced out.
I have tried changing the x attribute on the legends and also transform/translate. Whilst the DOM shows that the x values are applied the legends don't move. So if the DOM shows the legend / g element is positioned at x = 200 it is still positioned at 0.
I have spent two days trying to solve this and probably looked at over 50 examples including anything I could find on StackExchange.
Below code is my latest attempt. It doesn't through any error and the x values are reflected in the DOM but the elements just won't move.
I have included the code covering the relevant bits (but not all code).
The legend container is added here:
/*<note>Add container to hold legends. */
var LegendCanvas = d3.select("svg")
.append("g")
.attr("class", "legend canvas")
.attr("x", 0)
.attr("y", 0)
.attr("width", Width)
.style("fill", "#ffcccc")
.attr("transform", "translate(0,15)");
There is then a loop through a json array of objects:
var PrevElemLength = 0;
/*<note>Loop through each data series, call the Valueline variable and plot the line. */
Data.forEach(function(Key, i) {
/*<note>Add the metric line(s). */
Svg.append("path")
.attr("class", "line")
.attr("data-legend",function() { return Key.OriginId })
/*<note>Iterates through the data series objects and applies a different color to each line. */
.style("stroke", function () {
return Key.color = Color(Key.UniqueName); })
.attr("d", Valueline(Key.DataValues));
/*<note>Add a g element to the legend container. */
var Legend = LegendCanvas.append("g")
.attr("class", "legend container")
.attr("transform", function (d, i) {
if (i === 0) {
return "translate(0,0)"
} else {
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength) + ",0)"
}
});
/*<note>Adds a rectangle to pre-fix each legend. */
Legend.append("rect")
.attr("width", 5)
.attr("height", 5)
.style("fill", function () {
return Key.color = Color(Key.UniqueName); });
/*<note>Adds the legend text. */
Legend.append("text")
.attr("x", function() {
return this.parentNode.getBBox().width + 5;
})
/*.attr("y", NetHeight + (Margin.bottom/2)+ 10) */
.attr("class", "legend text")
.style("fill", function () {
return Key.color = Color(Key.UniqueName); })
.text(Key.UniqueName);
Here is a screen shot of what the output looks like:
enter image description here
Any help on how to create a horizontal legend (without over lapping legends) would be much appreciated. Chris
The problem is you are using local variables d and i as function parameters while setting the transform attribute. Parameter i in local scope overrides the actual variable. The value of local variable i would be always zero as there is no data bind to that element.
var Legend = LegendCanvas.append("g")
.attr("class", "legend container")
.attr("transform", function (d, i) { //Remove i
if (i === 0) {
return "translate(0,0)"
} else {
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength) + ",0)"
}
});
I have also made slight updates to the code for improvements.
var LegendCanvas = d3.select("svg")
.append("g")
.attr("class", "legend canvas")
.attr("x", 0)
.attr("y", 0)
.attr("width", 500)
.style("fill", "#ffcccc")
.attr("transform", "translate(0,15)");
var PrevElemLength = 0;
var Data = [{
OriginId: 1,
UniqueName: "Some Long Text 1"
}, {
OriginId: 2,
UniqueName: "Some Long Text 2"
}];
/*<note>Loop through each data series, call the Valueline variable and plot the line. */
var Color = d3.scale.category10();
Data.forEach(function(Key, i) {
/*<note>Add a g element to the legend container. */
var Legend = LegendCanvas.append("g")
.attr("class", "legend container")
.attr("transform", function() {
if (i === 0) {
return "translate(0,0)"
} else {
var marginLeft = 5;
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength + marginLeft) + ",0)"
}
});
/*<note>Adds a rectangle to pre-fix each legend. */
Legend.append("rect")
.attr("width", 5)
.attr("height", 5)
.style("fill", function() {
return Key.color = Color(Key.UniqueName);
});
/*<note>Adds the legend text. */
Legend.append("text")
.attr("x", function() {
return this.parentNode.getBBox().width + 5;
})
.attr("dy", "0.4em")
.attr("class", "legend text")
.style("fill", function() {
return Key.color = Color(Key.UniqueName);
})
.text(Key.UniqueName);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg height=500 width=500></svg>
The d3 way of implementation(Using data binding) would be as follows
var LegendCanvas = d3.select("svg")
.append("g")
.attr("class", "legend canvas")
.attr("x", 0)
.attr("y", 0)
.attr("width", 500)
.style("fill", "#ffcccc")
.attr("transform", "translate(0,15)");
var Data = [{
OriginId: 1,
UniqueName: "Some Long Text 1"
}, {
OriginId: 2,
UniqueName: "Some Long Text 2"
}];
var Color = d3.scale.category10();
var Legend = LegendCanvas.selectAll(".legend")
.data(Data)
.enter()
.append("g")
.attr("class", "legend container");
Legend.append("rect")
.attr("width", 5)
.attr("height", 5)
.style("fill", function(Key) {
return Key.color = Color(Key.UniqueName);
});
Legend.append("text")
.attr("x", function() {
return this.parentNode.getBBox().width + 5;
})
.attr("dy", "0.4em")
.attr("class", "legend text")
.style("fill", function(Key) {
return Key.color = Color(Key.UniqueName);
})
.text(function(Key){ return Key.UniqueName });
var PrevElemLength = 0;
Legend.attr("transform", function(d, i) {
if (i === 0) {
return "translate(0,0)"
} else {
var marginLeft = 5;
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength + marginLeft) + ",0)"
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width=500 height=500></svg>
Try this :
//Legend
var legend = vis.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("image")
.attr("x", 890)
.attr("y", 70)
.attr("width", 20)
.attr("height", 18)
.attr("xlink:href",function (d) {
return "../assets/images/dev/"+d+".png";
})
legend.append("text")
.attr("x", 910)
.attr("y", 78)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d) {
return d;
});

Can't add title to stacked bar graph on D3

I creating a stacked bar chart using this example. The chart works and renders but I can't add a mouseover label.
I tried this...
DATE.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
.append("svg:title")
.text(functino(d){return "foo"});
But this after adding the .append("svg:title... the graph stops rendering. If I remove the .style("fill... line, the graph renders, however it's not stacked and there's no mouseover feature.
I have also tried using the tooltip route. (Source)
.on("mouseover", function() { tooltip.style("display", null); })
.on("mouseout", function() { tooltip.style("display", "none"); })
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
But still not luck. Is there a library I need to load? Not sure what's going on.
The graph stop rendering when you try to append the title because you have a typo: it's function, not functino.
Besides that, this is what you need to get the value of each stacked bar:
.append("title")
.text(function(d){
return d[1]-d[0]
});
Here is the demo: https://bl.ocks.org/anonymous/raw/886d1749c4e01e191b94df23d97dcaf7/
But I don't like <title>s. They are not very versatile. Thus, instead of creating another <text>, as the second code you linked does, I prefer creating a div:
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
Which we position and set the HTML text this way:
.on("mousemove", function(d) {
tooltip.html("Value: " + (d[1] - d[0]))
.style('top', d3.event.pageY - 10 + 'px')
.style('left', d3.event.pageX + 10 + 'px')
.style("opacity", 0.9);
}).on("mouseout", function() {
tooltip.style("opacity", 0)
});
And here is the demo: https://bl.ocks.org/anonymous/raw/f6294c4d8513dbbd8152770e0750efd9/

D3 drag error 'Cannot read property x of undefined'

I'm trying to get drag functionality to work on D3, and have copied the code directly from the developer's example.
However it seems the origin (what is being clicked) is not being passed correctly into the variable d, which leads to the error: 'Cannot read property 'x' of undefined'
The relevant code:
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
d.x += d3.event.dx
d.y += d3.event.dy
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d.x,d.y ] + ")"
})
});
var svg = d3.select("body").append("svg")
.attr("width", 1000)
.attr("height", 300);
var group = svg.append("svg:g")
.attr("transform", "translate(10, 10)")
.attr("id", "group");
var rect1 = group.append("svg:rect")
.attr("rx", 6)
.attr("ry", 6)
.attr("x", 5/2)
.attr("y", 5/2)
.attr("id", "rect")
.attr("width", 250)
.attr("height", 125)
.style("fill", 'white')
.style("stroke", d3.scale.category20c())
.style('stroke-width', 5)
.call(drag);
Usually, in D3 you create elements out of some sort of datasets. In your case you have just one (perhaps, one day you'll want more than that). Here's how you can do it:
var data = [{x: 2.5, y: 2.5}], // here's a dataset that has one item in it
rects = group.selectAll('rect').data(data) // do a data join on 'rect' nodes
.enter().append('rect') // for all new items append new nodes with the following attributes:
.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; })
... // other attributes here to modify
.call(drag);
As for the 'drag' event handler:
var drag = d3.behavior.drag()
.on('drag', function (d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this)
.attr('transform', 'translate(' + d.x + ',' + d.y + ')');
});
Oleg's got it, I just wanted to mention one other thing you might do in your case.
Since you only have a single rect, you can bind data directly to it with .datum() and not bother with computing a join or having an enter selection:
var rect1 = svg.append('rect')
.datum([{x: 2.5, y: 2.5}])
.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; })
//... other attributes here
.call(drag);

Create Paired Bar Chart d3.js

I need help adding a second series of data to my bar chart. Currently I'm populating the bars from the glob key in my dataset. This will be the first series. The second series I would like to be is local.
How do I go about adding that?
Play with my JSFiddle here.
var w = 300;
var h = 200;
var colors = ["#377EB8", "#4DAF4A"];
var dataset = [
{"keyword": "payday loans", "glob": 1500000, "local": 673000, "cpc": "14.11"},
{"keyword": "title loans", "glob": 165000, "local": 165000, "cpc": "12.53" },
{"keyword": "personal loans", "glob": 550000, "local": 301000, "cpc": "6.14"}
];
var data = [[1500000, 165000, 550000],
[673000, 165000, 301000]];
var tdata = d3.transpose(dataset.glob, dataset.local);
var series = 2; // Global & Local
var x0Scale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
var x1Scale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {return d.glob;})])
.range([0, h]);
var glob = function(d) {
return d.glob;
};
var cpc = function(d) {
return d.cpc;
};
var commaFormat = d3.format(',');
//SVG element
var svg = d3.select("#searchVolume")
.append("svg")
.attr("width", w)
.attr("height", h);
// Graph Bars
svg.selectAll("rect")
.data(dataset, cpc, glob)
.enter()
.append("rect")
.attr("x", function(d, i){
return x0Scale(i);
})
.attr("y", function(d) {
return h - yScale(d.glob);
})
.attr("width", x0Scale.rangeBand())
.attr("height", function(d) {
return yScale(d.glob);
})
.attr("fill", colors[1])
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
var xPosition = parseFloat(d3.select(this).attr("x")) + x0Scale.rangeBand() / 3;
var yPosition = parseFloat(d3.select(this).attr("y")) + yScale;
//Update Tooltip Position & value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#cpcVal")
.text(d.cpc);
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Remove the tooltip
d3.select("#tooltip").classed("hidden", true);
});
//Create labels
svg.selectAll("text")
.data(dataset, glob)
.enter()
.append("text")
.text(function(d) {
return commaFormat(d.glob);
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return x0Scale(i) + x0Scale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d.glob) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
It's easiest to create an svg group (<g>) element for each set of data, and then add the individual parts to each group. For example, roughly:
http://jsfiddle.net/WXMwv/1/
var sets = svg.selectAll(".set")
.data(dataset)
.enter().append("g")
.attr("class","set")
.attr("transform",function(d,i){
return "translate(" + x0Scale(i) + ",0)"
})
.on("mouseover", function(d,i) {
//Create x value from data index
var xPosition = parseFloat(x0Scale(i) + x0Scale.rangeBand() / 6);
var yPosition = 0;
//Update Tooltip Position & value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#cpcVal")
.text(d.cpc);
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Remove the tooltip
d3.select("#tooltip").classed("hidden", true);
});
sets.append("rect")
.attr("class","glob")
.attr("width", x0Scale.rangeBand()/2)
.attr("y", function(d) {
return yScale(d.glob);
})
.attr("height", function(d){
return h - yScale(d.glob);
})
.attr("fill", colors[1])
sets.append("rect")
.attr("class","local")
.attr("width", x0Scale.rangeBand()/2)
.attr("y", function(d) {
return yScale(d.local);
})
.attr("x", x0Scale.rangeBand()/2)
.attr("height", function(d){
return h - yScale(d.local);
})
.attr("fill", colors[0])
The text elements are left as an exercise for the reader :)

Resources