Display text w/ brushed values in d3.js - d3.js

I have some svg circles along a single axis and d3.brushX activated. In this small Observable example, I want displayText to respond to the brush, displaying the number of circles selected by the brush.
The values currently respond to the brush, but it just writes the text on top of the existing value. How do I clear it every time the brush moves?

Give the text a class, select all DOM elements of the class and remove them right before you append the text within your brushed function.
svg.selectAll(".label").remove()
svg.append("text")
.attr("class","label")
.attr("x", width/2)
.attr("y", 50)
.text(displayText)
OR create the text element outside your brushed function
svg.append("text")
.attr("class","label")
.attr("x", width/2)
.attr("y", 50)
...then, just change the text within the brushed function
svg.select(".label").text(displayText)
The second approach is probably better...

Related

D3 - Select element in this svg?

I have a codepen here - https://codepen.io/anon/pen/yvgJKB
I have two d3 charts with different data.
I want to have tooltips on the bars so when you rollover them it shows the value.
I want the tooltip to be separate html in the svg cos the actual charts are responsive and I dont want the tooltips to scale with the svg.
The tooltip works as expected in the top chart.
In the bottom chart the tooltip appears in the top chart.
I think this is becasue the let tooltip = d3.select('.tooltip').append("div") is selecting the first tooltip which is in the first chart.
The charts and tooltip is dynamically created.
This there a way in the D3 to say d3.select('.tooltip') in this svg something like d3.select(this)('.tooltip')
let tooltip = d3.select('.tooltip').append("div")
.attr("class", "tip")
.style("display", "none")
.attr("fill", "grey");
There is a lot of unnecessary repetition in your code.
Create the tooltip div only once. Also, since you are creating the div in your D3 code, you don't need any div with the tooltip class in your HTML. Remove both.
Besides that, use d3.event.pageX and d3.event.pageY to position your tooltip div anywhere in the page. A single tooltip div can be used to show the tooltip to any number of SVGs you have on that page.
Here is your updated CodePen: https://codepen.io/anon/pen/KQaBvE?editors=0010
Tooltips should be created separately by selecting different charts (or some other unique marker) and given unique variable names within the same scope:
let tooltip1 = d3.select('.chart1').append("div")
.attr("class", "tip")
.style("display", "none")
.attr("fill", "grey");
let tooltip2 = d3.select('.chart2').append("div")
.attr("class", "tip")
.style("display", "none")
.attr("fill", "grey");
https://codepen.io/mortonanalytics/pen/BYpwaz

D3js: two .append methods on the same selection can't be in the same line

The problem is that it would look prettier if the code here:
var nodesgroup = svg.append("g")
.attr('class', 'nodes')
.selectAll('circle')
.data(data['nodes'])
.enter()
.append('g');
nodesgroup.append('circle')
.attr('r', 20)
.call(d3.drag() //Define what to do on drag events
.on("start", dragStarted)
.on("drag", dragging)
.on("end", dragEnded));
was on one line. But when you remove the nodesgroup of the second paragraph and the ; after the first paragraph, the circles do not longer get drawn. I don't know why as chaining these two paragraphs should be no different from first taking the output of the first paragraph and then taking each of these items and doing operation on them this way.
My understanding of what happens in the above code: the result of append('g') (a D3-selection) is stored in nodesgroup.
Then, in the second paragraph, for each of the elements in the selection, (because d3 has overridden append I assume?) .append('circle') gets executed.
Link to JSFiddle
The circles are drawn; in your JSFiddle they can be seen in the top left corner. The problem is that by concatenating the two statements you are changing the contents of the selection contained in nodesgroup. Keeping it in two different statements, nodesgroup will contain a selection of newly appended g elements. If, on the other hand, concatenating both statements into one, the selection will contain the circle elements which are appended to the former group elements.
While the first approach works with your tick callback, the latter will break that tick function. The tick function expects the nodesgroup to contain group elements in order to select the circles and texts contained within. This is the reason why the circles are drawn but are not moving around according to the force layout.

D3 Trend Line Domain

I draw a line chart using D3 with some data from our database, I got some data for the entire year to calculate what would be our trendline (taking some values for 8am, 12m, 4pm and 9pm), I'm drawing this in the chart with path and values for each X (time).
Now my problem is the domain of the trendline is of course bigger than our current values (lets say its 2 pm and my trendline will always go to 9 pm). The closes I got was setting the trendline's domain to my current data domain, which returns this:
Test1
xTrend.domain(d3.extent(trendData, function (d) { return d.date; }));
How can I cut it so it doesn't go beyond the SVGs width? I tried setting the width attribute and it doesn't work, so my guess is it has something to do with the domain.
If I set the trendline's domain to its data, I get this:
Test2
xTrend.domain(d3.extent(data, function (d) { return d.date; }));
PS: While we are on this, if anyone can point me on how I could see if my line is above-below my trendline it would be great ;)
Update:
Thanks to #lhoworko
I added this
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
And this to my line path
.attr("clip-path", "url(#clip)")
Take a look at d3 clip paths. It will make sure the line beyond the end of the chart isn't displayed.
Take a look here.
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath

DimpleJS barchart styling columns

I'm basically using a modified version of : http://dimplejs.org/advanced_examples_viewer.html?id=advanced_bar_labels .
I'd like to be able to add for each value a border on the left as high as the value (with a specific color for that border).
I'm not really sure where to start for adding that.
Any ideas?
Thanks.
More details : This is what I'd like to obtain : https://dl.dropboxusercontent.com/u/2227188/Image%202.png - the border on the left is the issue. (jsfiddle.net/mkzTk/5/ this what I currently have which is pretty much what's in the example - I don't know where to start really for adding a border)
You could append a rectangle after drawing for each element of the series as follows:
mySeries.afterDraw = function (s, d) {
var shape = d3.select(s);
svg.append("rect")
.attr("x", shape.attr("x"))
.attr("y", shape.attr("y"))
.attr("height", shape.attr("height"))
.attr("width", "10px")
.style("fill", shape.style("stroke"))
.style("pointer-events", "none");
};
The example you mention already uses the afterDraw function so just add the contents above to the existing method for labelling.
It looks nice, here's an example:
http://jsbin.com/lorin/9/edit?js,output#J:L20
I would set up each bar + edge pair as its own group based on a certain data point, and then append two rect elements to that group. Differences in color can be used to give them their distinctive colors.
Your code would look something like this:
var monthBars = d3.selectAll('.monthBar') //These will be for each chart
.data(allMyData, idFunction) //Assign and key your data
.enter()
.append('g')
.classed('monthBar', true);
.each(function(d){
var taskGroups = d3.select(this).selectAll('.taskGroup')
.data(d.dataForThisMonth, taskIdFn)
.enter()
.append('g')
.classed('.taskGroup', true);
.attr('transform', ...) //Define the x and y positioning for the group
taskGroups.append('rect')
//Make this the 'body' rect with the text in it
taskGroups.append('rect')
//Make this the edge rect
})

How to properly add and use D3 Events?

I'm having trouble understanding using D3 events and dispatch functions. I have a chart example that I've been working on called: "Vertical Bar Charts With Legends."
Drawing the charts and the legends was easy enough but I'd like to add the ability to highlight each bar as I mouseover its correlating text legend, located to the right of the chart.
I've read through all of the event documentation and even looked at a number of examples, most of which are pretty complicated, but I seem to be missing something. Would anyone know how to best accomplish the text legend mouseover functionality that dispatches events to automatically change colors of the corresponding vertical bars?
This question is similar to the one you posted in the d3-js Google Group. Without duplicating what I wrote there, I would reiterate that you probably don't want d3.dispatch; that is intended for custom event abstractions (such as brushes and behaviors). It'll be simpler to use native events.
If you want your legend to change the color of the corresponding bar on mouseover, then breakdown the problem into steps:
Detect mouseover on the legend.
Select the corresponding bar.
Change the bar's fill color.
First, use selection.on to listen for "mouseover" events on the legend elements. Your listener function will be called when the mouse goes over a legend element, and will be called with two arguments: the data (d) and the index (i). You can use this information to select the corresponding bar via d3.select. Lastly, use selection.style to change the "fill" style with the new color.
If you're not sure how to select the corresponding bar on legend mouseover, there are typically several options. The most straightforward is to select by index, assuming that the number of legend elements and number of rect elements are the same, and they are in the same order. In that case, if a local variable rect contains the rect elements, you could say:
function mouseover(d, i) {
d3.select(rect[0][i]).style("fill", "red");
}
If you don't want to rely on index, another option is to scan for the matching bar based on identical data. This uses selection.filter:
function mouseover(d, i) {
rect.filter(function(p) { return d === p; }).style("fill", "red");
}
Yet another option is to give each rect a unique ID, and then select by id. For example, on initialization, you could say:
rect.attr("id", function(d, i) { return "rect-" + i; });
Then, you could select the rect by id on mouseover:
function mouseover(d, i) {
d3.select("#rect-" + i).style("fill", "red");
}
The above example is contrived since I used the index to generate the id attribute (in which case, it's simpler and faster to use the first technique of selecting by index). A more realistic example would be if your data had a name property; you could then use d.name to generate the id attribute, and likewise select by id. You could also select by other attributes or class, if you don't want to generate a unique id.
Mike's answer is great.
I used it come up with this for selecting a cell in a grid I was drawing:
.on('click', (d, i) ->
console.log("X:" + d.x, "Y:" + d.y) #displays the cell x y location
d3.select(this).style("fill", "red");
So when I am entering the data in I added the event listener and using d3.select(this).
See the code in context below:
vis.selectAll("rect")
.data(singleArray)
.enter().append("svg:rect")
.attr("stroke", "none")
.attr("fill", (d) ->
if d.lifeForm
return "green"
else
return "white")
.attr("x", (d) -> xs(d.x))
.attr("y", (d) -> ys(d.y))
.attr("width", cellWidth)
.attr("height", cellHeight)
.on('click', (d, i) ->
console.log("X:" + d.x, "Y:" + d.y)
d3.select(this).style("fill", "red");
return
)

Resources