I've got an SVG map of the world (based on a public domain file from wikipedia), with generated CSS to highlight individual countries.
I need to a add mouseover tooltips containing additional data about each country, the data will be provided by PHP.
I'm an experienced web programmer, but have never worked with SVG until now. What is the best approach to display a text tooltip under the user's mouse, with info about the specific SVG node under the mouse cursor?
Note this SVG is full of nice id and class attributes designed to facilitate this kind of use.
I need this to be as cross browser as practical, but am happy to disable the feature for some browsers (old versions of Internet Explorer).
Detect the mouseover event
Access the title information
Create-or-show a group of title elements
Append them to the end of the SVG document so that they draw above all other content
Populate the elements with the appropriate data based on title
Position the elements accordingly
You'll want to transform the cursor point from screen space to SVG space, or calculate the bounding box of your source element and transform that from possibly-transformed object space to global SVG space.
Detect the mouseout event
Hide-or-destroy the title elements.
Is there any part of the above that you cannot do?
Edit: Answering the wealth of questions from the comment below:
You can place your script inside the SVG or outside in an embedding HTML; if they are self-contained to the SVG (as yours would be) it's better to place them here, so that you can embed your SVG externally and have it still work.
Finding a list of anything is easiest of you place a common class="foo" attribute on them, via one of:
var foos = document.querySelectorAll('.foo');
var foos = document.getElementsByClassName('foo');
Or you can query based on structure; for example, if every country is a <path> contained within a <g id="countries"> then you can use:
var countries = document.querySelectorAll('#countries path');
But if all you have is an array of IDs, then you'll need to do something like:
var countryIDs = [ "usa", "brazil", … ];
// Old school
var countries = [];
for (var i=countryIDs.length; i--; ){
countries[i]=document.getElementById(countryIDs[i]);
}
// New school
var countries = countryIDs.map(function(id){
return document.getElementById(id);
});
To attach an event handler to each:
function showTooltip(evt){
var element = evt.target;
// your code here
}
countries.forEach(function(el){
el.addEventListener('mouseover',showTooltip,false);
});
Alternatively, you can just attach the event handler to a common ancestor once, and handle it there:
svg.addEventListener('mouseover',function(evt){
var element = evt.target;
if (element.hasAttribute('title')){
// your code here
}
}
The <script> is easiest when placed right before the </svg>, but you can put it at the top if you want and it only do its work when the file is done loading, for example:
window.addEventListener('load',function(){
// Put all your code here
},false);
It's easy to find information on embedding SVG, and you are right, there are many ways to do it. Here's one nice article about it. I personally advocate either SVG in XHTML or, if you must, SVG in HTML5.
You can use append svg title to view additional data.
// Here we add an SVG title element the contents of which is effectively rendered in a tooltip
.append("svg:title")
.text(function(d) {return "Name:"+d.Name;});
We can use this tooltip along with all svg element. Here d is a json object form this we are parsing the data.
https://gist.github.com/ilyabo/1339996
Related
I've read various examples on how to use the Highcharts.SVGRenderer to create custom SVG text etc.
However, I'm wondering what is the correct practices to create and clean them up?
Currently I make them at the events.redraw hook (because I want to update the SVG text based on the dynamic data), however it looks like the redraw hook is being called at various occasions multiple times (it looks like it'd redraw on add series and data being added) and then the SVG does not get cleaned up... so I ended up having multiple copies of the same SVG elements in the chart. Any subsequence redraw would obviously keep adding more SVGElements into the chart.
Questions:
Do i need to keep track of all the generated SVGElements myself and call destroy() manually every time I redraw them?
Is there any internal trackers in Highcharts that tracks the created SVG using SVGRenderer? (so that I could maybe use the internal ref to call destroy myself)
If I am not supposed to draw them in redraw event, what is the recommended hook to draw my SVG elements where I need dynamic update on those generated SVG elements.
Do I absolutely need to keep track of my own references on the SVG elements? Seems a bit too much of the overhead/hassle to do that.
Yes, you need to keep track of all the generated SVG elements, which you want to update, but better than destroying them is to update their attributes.
No, there is no such trackers.
You can draw the elements in the load event and redraw them when you need to (for example when you change your data).
No, you have to keep track only those elements that you need update. Maybe some of your problems will be resolved by by annotations? https://www.highcharts.com/docs/advanced-chart-features/annotations-module
events: {
render: function() {
var chart = this,
series = chart.series[0],
x = series.points[2].plotX,
y = series.points[2].plotY - 50;
if (!chart.customElement) {
chart.customElement = chart.renderer.text().add();
}
chart.customElement.attr({
text: 'some text ' + Math.random(),
x,
y
});
}
}
Live demo: http://jsfiddle.net/BlackLabel/t250crx1/
API Reference: https://api.highcharts.com/class-reference/Highcharts.SVGElement#attr
I am using PaperJS and I need to use an existing set of icons that I can receive as a base64 string (that's a given I can't change). I have no problem rendering the rasters.
However when I set them a position like so:
const myRaster = new Raster(<someBase64>);
myRaster.position = new Point(receivedX, recievdY);
myRaster.onClick = () => console.log('raster click');
The onClick event would only work if I click exactly on [x,y] point assigned to the position property.
The X and Y are received form a data base and are in accordance to other visible elements.
I played around in the PaeprJS website, and without using a new Point position the onClick works all around the image.
My solution for now is creating an invisible polygon (new Path.RegularPolygon) and to attach to onClick event to it and it works.
However, there's must be a simpler solution and would love to understand what I am missing here.
Thanks!
Apparently, the SVG was partially created programmaticly and had a lot of whitespace around it.
Testing with base64 encoded SVGs from various different sources worked as expected
i'm representing some data with the parallel coordinates library (based on d3.js) ( https://github.com/syntagmatic/parallel-coordinates#parallel-coordinates )
main functional part of the code is the following:
var parcoords = d3.parcoords()("#example") //#example is the div for the drowing
.data(eingabe) // eingabe is the var, which contains the data
.render()
.reorderable()
.shadows()
.brushMode("1D-axes")
so far it works fine :)
but now i want to hide one specific axis of the parallel coordinates; i only don't want to show it.
I want that the axis is completeley removed, so that it takes no place and the lines don't be affected of the values of this dimension, but i don´t want to do that by manipulating the data. i only want to manipulate the plot (thats the reason i called it hide).
i've searched on the api description but didn't find something. I searched in the internet but didn't find anything, too. But i think i remember that i've seen a peace of code, where the first axis was hidden, but i can't find it again.
Can anybody tell me, how i can hide an axis or where i can find the solution?
thank you, greetings
Jones
You're missing a .hideAxis(array) method call, where the array is a list of keys of the exact column names in your dataset that you want to hide. This method does exactly what you said you wanted:
"...hide one specific axis of the parallel coordinates; i only don't want to show it."
"I want that the axis is completley [sic] removed, so that it takes no place"
"the lines dont [sic] be affected of the values of this dimension, but i don´t want to do that by manupulating [sic] the data. i only want to manipulate the plot."
To implement this method, your code would need to be
var parcoords = d3.parcoords()("#example") //#example is the div for the drowing
.data(eingabe) // eingabe is the var, which contains the data
.hideAxis(["col1", "col2"])
.render()
.reorderable()
.shadows()
.brushMode("1D-axes")
Note: .hideAxis can take in a blank array if you don't want to hide any axis, i.e. .hideAxis([])
You can implement this method in some sort of update/redraw function if you want to allow the user to specify which axis(es) to remove; else, removing it completely via the DOM is impossible. This, performing the axis(es) hide via code, is your only option to implement what you want.
To see this method in action, take a look at the API documentation, specifically, their "example1" graph and code at http://syntagmatic.github.io/parallel-coordinates/index.html#example1
Assuming you just want to hide the axes (i.e. make it invisible) and not remove it completely
d3.selectAll("#example0 > svg > g > g.dimension:nth-child(3)").attr("opacity", "0");
Assuming example0 is the id of the wrapper around your svg element. You could also do the same thing directly on your svg element if you have it.
The above would still leave you able to interact with the filter of the hidden axis. To make it non-interactable, use .attr("display", "none");
Don't have a fiddle, but you should be able to copy paste the above line and run it from the console at http://syntagmatic.github.io/parallel-coordinates/ and see the effect on the chart in the Bundling section.
Here is the effect - before and after respectively
I am trying to load my selection like the picture below, where the marching ants go around the edges of my object:
However, i only know how to select the whole layer using this code:
doc.selection.selectAll();
Also, i want to contract the selection by a few pixels and paste it into a channel layer. Any help with that would be great but the most important thing is how to load the selection in the first place.
If you look up in the Javascript Scripting Reference document in your Photoshop install directory you'll see that there are a number of methods available on the selection object to do what you need.
var doc = app.activeDocument;
var channelRef = doc.channels.getByName("TestSelection");
doc.selection.load(channelRef, SelectionType.REPLACE);
doc.selection.contract(new UnitValue(20, 'px'));
Is there a way to just generate an svg and get it as a string without actually drawing it? I thought about render svg to a hidden div and then reading the inner html, but is there a cleaner way?
I would think you could do this:
var svg = d3.select("body").append("svg")
svg = d3.select(svg).remove()
Append the svg to the body, but immediately remove it. This will, of course, give you the 'd3 selection' object, not a string.
You can create a whole other DOM tree in javascript using createDocumentFragment(). This is commonly used to create a complex section of the document and then add it all to the page in one step, so the browser only has to re-calculate layout once.
I haven't tested it, but you should also be able to use .innerHTML() to extract everything you've created as a string that can be saved to file. Answers to this SO Question suggest that you can't call innerHTML() directly on the document fragment itself, so you would have to add a body element to the fragment, add your SVG to it, and then get the inner HTML from the body node.
One way to approach this problem is by checking how it would be done on the server side -- where there is no DOM.
As it turns out, the way to do this on the server side involves more or less the same "hack" used in the question. This is because d3 expects to work on a DOM; writing that DOM to HTML is really the only way to render it to a string.
Brief example:
var svg = window.d3.select('body')
.append('div').attr('class','container')
.append('svg'); // and lots of other stuff
//etc etc
svg.selectAll('.arc').doSomething('blah blah');
var svgAsString = d3.select('.container').html();