On hover effect on a SVG group - d3.js

I'm working on a pretty big map with lots of different areas and text on top of them, sort of like countries. I want to add a on mouse hover effect when hovering over this area. The effect should add a shadow and change its opacity. When I hover out of the area the opacity should go back to its original and the shadow effect should go away. I managed to do this but when I add text into this area it messes things up. The on hover effect gets called again when I hover on the text that's inside area when it shouldn't be called again at all. I made a fiddle that works until i hover on the text as well. I'm using the D3.js library for the coding.
var groupAreas = "";
groupAreas = d3.selectAll(".area-group");
groupAreas.on("mouseover", fadeInArea)
.on("mouseout", fadeOutArea)
.on("click", zoomIn);
var initialOpacity = 0;
function fadeInArea() {
//Get its initial opacity
initialOpacity = d3.select(this).select("path").style("opacity");
d3.select(this).transition(500).attr("filter", "url(#shadow-filter-1)").style("cursor", "pointer");
d3.select(this).select("path").transition(500).style("opacity", 0.7);
}
function fadeOutArea() {
d3.select(this).transition(500).attr("filter", null).style("cursor", "default");
d3.select(this).select("path").transition(500).style("opacity", initialOpacity);
}
https://jsfiddle.net/qx5u9uo4/6/
the area has an original opacity of 0.4. When i hover over the area it changes to 0.7 and the shadow effect is added. When I go out, it goes back to 0.4 as it should. Yet if i go in the area and also hover over the text the OnMouseOver gets called again and it then sets 0.7 as original opacity and thus never goes back to 0.4 again when I actually go outside the area.
I think the problem may lie with my order of DOM svg elements?
<g class="area-group">
<path id="area-1" d="M502.2 581.4h-53.5v-61l167.4 70.4v34.6H502.2z" />
<text transform="translate(542.4 614)"> FOO </text>
<text transform="translate(459.4 571.2)"> BAR </text>
<text transform="translate(521.1 592.9)"> / </text>
</g>

Sounds like you just want to stop the text elements from receiving pointer events:
.area-group>text {
pointer-events: none;
}

Related

Is it possible to have a scrolling div that adjusts to the number of bars in a dc.js rowchart

I have several rowcharts that are connected to each other with dc.js.
These rowcharts have the x axis at the top in a different div, as explained here. However these rowcharts also implement filtering and removing, therefore, whenever I filter in one rowchart, the number of bars in the others reduce, but it maintains the size of the scrollable div, even though there are no bars below what is shown. Also, I'm pretty sure it is easy, but I haven't figured out how to put the reset button below the chart, because it shows between the chart div and the axis div, as seen below.
Is there a way to correct these issues?
This is what I have in each rowchart div:
<div id='axis'></div>
<div id="chart" style="overflow-y:auto; height:200px;">
<div>
<span class="reset" style="display: none;">Phylum seleccionado(s):
<span class="filter"></span>
<a class="reset" href="javascript:Chart.filterAll();dc.redrawAll();" style="display">Reset</a>
</span>
</div>
</div>
And this is what a I have in each rowchart in the main.js:
Chart
.fixedBarHeight(20)
.height(nonEmpty.all().length * 20 + 1)
.margins({top: 0, right: 20, bottom: 0, left: 20})
.width(600)
.xAxis(d3.axisTop())
.elasticX(true)
.ordinalColors(['#e41a1c'])
.gap(1)
.dimension(Dim)
.group(nonEmpty) //this removes the ones that don't match the filter of the other rowchart
.on('pretransition', function () {
Chart.select('g.axis').attr('transform', 'translate(0,0)');
Chart.selectAll('line.grid-line').attr('y2', Chart.effectiveHeight());
});
Partial answer here. Well, it answers what you asked but doesn't answer the next question I expect. :(
You are currently sizing the chart based on the number of bars, and you will need to resize the chart when the number of bars change.
This should be done in a preRedraw handler, but unfortunately preRedraw is currently fired after resizing is done. So currently you have to override .redraw():
dc.override(chart, 'redraw', function() {
chart.height(chart.group().all().length * 21);
return chart._redraw();
})
As for putting the info & controls after the chart, there are two issues and I was only able to solve one of them.
dc.js will append an svg element to the chart div. It'd be nice to have more control over this but I'm not sure what to do. For the moment, the easiest workaround is to re-append the info div:
chart.on('postRender', function() {
chart.root().node().appendChild(chart.select('div.info').node())
})
However, what you really want is probably to pull the info div out of the scrolling div, and I can't figure that out at the moment. The problem is that the chart expects the controls/info to be inside the chart div, and that's the same one that needs to scroll.
I tried to mess around with adding another scrolling div inside the chart div, and then moving the svg under it. This works, but it creates some annoying flashing.
Here's what I got so far. I may return to this later if I think of a better solution.
Update: controls outside of the scroller
To solve this right, we need the controls outside of the chart div.
We can port baseMixin.turnOnControls to a filtered event handler, and do the same showing and populating that the chart would do:
function turnOnControls(_chart, controls) {
var attribute = _chart.controlsUseVisibility() ? 'visibility' : 'display';
controls.selectAll('.reset').style(attribute, null);
controls.selectAll('.filter').text(dc.printers.filters(_chart.filters())).style(attribute, null);
}
function turnOffControls(_chart, controls) {
var attribute = _chart.controlsUseVisibility() ? 'visibility' : 'display';
var value = _chart.controlsUseVisibility() ? 'hidden' : 'none';
controls.selectAll('.reset').style(attribute, value);
controls.selectAll('.filter').style(attribute, value).text(_chart.filter());
}
function filter_function(controls) {
return chart => {
chart.hasFilter() ?
turnOnControls(chart, controls) :
turnOffControls(chart, controls);
}
}
chart.on('filtered', filter_function(d3.select('#info')));
Also, controlsUseVisibility and visibility: hidden is better when the controls will affect layout when shown/hidden.
(fiddle)

Media query in d3.js only partially successful

I want to resize a d3.js chart using media queries, and apply transitions. The graph setup in html is:
<g id="d3js_graph">
<svg class="d3svg"></svg>
<script src="JS\D3_BarChart.js"></script>
</g>
I applied a media query to the d3svg class and used a transform:
#media (min-width: 1080px) {
.d3svg {
transform: translate(-30px,-30px); }
}
That works to move the graph, but now the graph shows with the left half blanked out, which implies that the graph but not its container have been moved. So I reversed it and applied the media query to the d3js_graph ID (in the opening g tag above):
#media (min-width: 1080px) {
#d3js_graph {
transform: translate(-30px,-30px); }
}
But that does nothing, so that’s not the element. It’s the .d3svg class shown above. I could use a transition in the d3.js file:
d3.select("body").transition().style("color", "red");
but how to I do d3.select within a media query in the d3.js file? I added a media query in the file, but it simply caused the graph to disappear from the screen.
So my question is: why is the graph partially blanked out on the left side when I move it using a transform? What’s the controlling element for a media query on d3.js?
Thanks for any help.
This...
<g id="d3js_graph">
<svg class="d3svg"></svg>
<script src="JS\D3_BarChart.js"></script>
</g>
... is invalid. <g> elements are SVG elements, and can only be used inside SVGs.
What you want is an HTML <div>:
<div id="d3js_graph">
<svg class="d3svg"></svg>
<script src="JS\D3_BarChart.js"></script>
</div>
Also, I'd consider moving that <script> to outside the div.

d3 append copy of g element on click to separate div

I have in-line svg code three rect elements I've copied into my html document from Inkscape. I would like to be able to click any one of these rects and have all three of them appear in a separate div on the right side of the screen, zoomed in. I have enclosed each individual rect in a g element with an ID. I have also enclosed all three rects in a class tag of "section1".
Here's the fiddle: https://jsfiddle.net/hh2ek44m/
I have tried all sorts of combinations like this to append the clicked on g element or group of g elements to the #zoombox div.
d3.selectAll("g")
.on("click", function(d) {
d3.select("#zoombox").append("#section1")
});
I have also experimented with using 'this'.
In my working file (very large), I have many sections of rows that appear too small on the screen to visually inspect, and I would like the user to be able to click to zoom in on a section of rows in a separate space on the screen. I'd like to the original rows to remain unchanged, and for the zoomed in section image area to update when a new section is clicked.
I know I'm asking a lot here, so if you could push me in the right direction at all I'd appreciate it.
I've looked at many examples of on-click behavior such as http://bl.ocks.org/eesur/4e0a69d57d3bfc8a82c2 and http://bl.ocks.org/d3noob/5d621a60e2d1d02086bf. I eventually want my three rows to be on the left side of the page, and the zoomed in image of the three on the left, similar to this beautiful d3 vis, http://bl.ocks.org/syntagmatic/0613ee9324e989a6fb6b. Thanks.
This code has some problems:
d3.selectAll("g").on("click", function(d) {
d3.select("#zoombox").append("#section1")
});
First, since #zoombox is a <div>, you cannot append an SVG element directly to it. You have to append an SVG first:
d3.select("#zoombox").append("svg")
But, even doing that, the next step is not simple: you cannot append an ID to it. So, you could probably append an selection:
var myGroup = d3.select("#section1")
But this will not work as well, because the selection is an array. The logical solution would be using the SVG element itself:
var myGroup = d3.select("#section1").node()
But, again, this will not work!
Solution (out of many): Use the SVG use element:
var myGroup = d3.select(".section1");
var myGroupId = d3.select(myGroup).node().attr("id");//get the ID of the group
myGroup.on("click", function(){
d3.select("#zoombox")
.append("svg")
.append("use")
.attr("xlink:href", "#" + myGroupId);//the ID of the cloned group
});
Here is a demo (I changed your SVG a bit, it was too big for this snippet), click on your group to clone it:
var myGroup = d3.select(".section1");
var myGroupId = d3.select(myGroup).node().attr("id");
myGroup.on("click", function(){
d3.select("#zoombox").append("svg").append("use").attr("xlink:href", "#" + myGroupId);
});
div {
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id ="zoombox"></div>
<svg width="100">
<g class = "section1" id="myUniqueID">
<g id="section114r1">
<rect
fill="0000ff"
id="section114r1"
width="3"
height="30"
x="35.0462"
y="58.15918" /></g>
<g id="section114r2"><rect
fill="#0000ff"
id="section114r2"
width="3"
height="30"
x="30.88818"
y="58.159" /></g>
<g id="section114r3">
<rect
fill="#0000ff"
id="section114r3"
width="3"
height="30"
x="26.73694"
y="58.15927" /></g>
<g id="section114r4"><rect
fill="#0000ff"
id="section114r4"
width="3"
height="30"
x="22.56696"
y="58.17471" /></g>
</g>
</svg>

d3.js - how do chords become visible again on mouseout event

I am adapting the following chords example using d3.js to display relationships between groups. Can somebody explain how the chords appear back after they have faded out.
I'm interested in hooking up a handler into this to display information and have it go away on mouseout.
There is a mouseover handler that adds a fade class to the path element but I don't see any code to remove it.
<path class="chord fade" d=<elided> style="fill: rgb(247, 129, 191);">
<title>Financial District → Western Addition: 1.1%Western Addition → Financial District: 1.1%</title>
</path>
Despite this when I mouse out of a group the chords that were faded out reappear. My question is how is it achieved?
For convenience here is a jsfiddle to play with: http://jsfiddle.net/huynhjl/hxby165d/7/
I am aware of this question/answer D3.js Chord diagram - Have text appear/disappear when hovering over an arc, but I am wondering how the original showcase does it.
If you are using Bootstrap on your site, then this same problem may occur even with adding
#circle:hover path.fade {
display:none;
}
This is due to Bootstrap already having a fade class!
To remedy this, I changed my D3 class to be "fadechord" and now have:
#circle:hover path.fadechord {
display:none;
}
and then in the mouseover listener I have:
function mouseover(d, i) {
chord.classed("fadechord", function(p) {
return p.source.index != i
&& p.target.index != i;
});
}
This seemingly odd behavior is accomplished with the css.
What's happening in the script is that when one of the paths fires the mouseover event, the handler selects all of the chords, and for each one, switches the fade class off or on, based on whether that chord is connected to the hovered element or not.
So, how is the fade class being handled? Check out the selector that actually hides the elements:
#circle:hover path.fade {
display: none;
}
What that css rule is really saying is:
when there is a <path> element with a class of "fade" that is a descendant of a hovered element with an id of "circle", set its "display" property to "none".
In other words, the fade class doesn't hide the elements unless the g element that has id #circle is being hovered. Therefore, when the mouse is no longer over that group, the paths are visible again, even if they still have the fade class.

Using a CSS image sprite for hover effect without the scrolling animation

I'm using a sprite image to change the background on hover and click (the .keepImage class is for the click). It all works, but when the background picture changes it scrolls over to the correct position. Is there a way to do it without the scrolling motion?
JS:
<script>
$(document).ready(function(){
$("a.doing").click(function() {
$(this).siblings(".keepImage").removeClass("keepImage");
$(this).addClass("keepImage");
});
});
</script>
CSS:
a.doing {
width: 229px;
height: 202px;
margin-right: 8px;
background: url(http://localhost:8000/img/manifesto/spr_doing.png) 0 0;
}
a.doing:hover, a.doing.keepImage {
background: url(http://localhost:8000/img/manifesto/spr_doing.png) -229px 0;
}
I think, somewhere in your css you have the transition property specified. Usually when you have a transition property specified like this: "transition: all 500ms ease;", the background position will change with a scrolling effect. If you want to prevent this scrolling from happening, then you can either remove the transition property completely, or you can use transition only for the properties you want to animate like - border, color etc.. but not background. If you can somehow provide a link to your page, or give the html mark up and css, it will help. Thanks.

Resources