Reputation: 18781
SVGs are ugly please review my:
HTML:
<svg version="1.1" class="overlap-svg" id="alaska"></svg>
<svg version="1.1" class="overlap-svg" id="grid"></svg>
CSS:
.overlap-svg {
position: absolute;
left:0;
top: 0;
}
If we overlap these 2 svgs, What would the JS function be to highlight only the svg circles that have parts of alaska(red)in them in them?
review description below for more info
How do I transform this:
Into something like this:
the circle should be filled red if any portion of alaska(red) is inside the area of the circle.
Again please review my JSFiddle link above.
Upvotes: 10
Views: 533
Reputation: 7389
You can take the svg and load it into a canvas element. Take the element, and because it is a canvas element you can get an array of its pixels.
Your circle abstraction could be built by building the grid out of the pixels of an appropriately resized canvas.
First a helper: Grid Manager.
function GridManager(configIn) {
var gm_ = {};
gm_.config = {
'gridWidth': 10,
'gridHeight': 10,
'gridCellWidth': 10,
'gridCellHeight': 10,
'gridHeight': 100,
'dataSrc': []
};
// Load new config over defaults
for (var property in configIn) {
gm_.config[property] = configIn[property];
}
/**
* Creates an array using the module's config building a 2d data array
* from a flat array. Loops over GridManager.config.dataSrc
*
* Render a checkerboard pattern:
* GridManager.config.dataSrc = ["#"," "]
*
* Render you can load a image by passing in its full pixel array,
* provided image height and width match GridManager.config.gridHeight
* and GridManager.config.gridWidth.
*/
gm_.createGridSrc = function() {
var height = this.config.gridHeight;
var width = this.config.gridWidth;
var output = [];
for (var i = 0; i < height; i++) {
output[i] = [];
for (var ii = 0; ii < width; ii++) {
if (this.config.dataSrc !== undefined) {
var dataSrc = this.config.dataSrc;
output[i][ii] = dataSrc[i*width + ii % dataSrc.length];
}
}
}
return output;
};
/**
* Creates a SVG with a grid of circles based on
* GridManager.config.dataSrc.
*
* This is where you can customize GridManager output.
*/
gm_.createSvgGrid = function() {
var cellWidth = this.config.gridCellWidth;
var cellHeight = this.config.gridCellHeight;
var svgWidth = 1000;
var svgHeight = 1000;
var radius = 3
var cellOffset = radius / 2;
//create svg
var xmlns = 'http://www.w3.org/2000/svg';
var svgElem = document.createElementNS (xmlns, 'svg');
svgElem.setAttributeNS (null, 'viewBox', '0 0 ' + svgWidth + ' ' + svgHeight);
svgElem.setAttributeNS (null, 'width', svgWidth);
svgElem.setAttributeNS (null, 'height', svgHeight);
svgElem.style.display = 'block';
//create wrapper path
var g = document.createElementNS (xmlns, 'g');
svgElem.appendChild (g);
//create grid
var data = this.createGridSrc();
var count = 0;
for (var i = data.length - 1; i >= 0; i--) {
for (var ii = data[i].length - 1; ii >= 0; ii--) {
// This svgHeight and svgWidth subtraction here flips the image over
// perhaps this should be accomplished elsewhere.
var y = svgHeight - (cellHeight * i) - cellOffset;
var x = svgWidth - (cellWidth * ii) - cellOffset;
var cell = document.createElementNS (xmlns, 'circle');
var template = data[i][ii];
// Machine has averaged the amount of fill per pixel
// from 0 - 255, so you can filter just the red pixels like this
// over a certain strength.
if (template[0] > 10 ) {
cell.setAttributeNS (null, 'fill', '#ff0000');
// Consider stashing refs to these in this.groups['red'] or something
// similar
} else {
cell.setAttributeNS (null, 'fill', 'none');
}
cell.setAttributeNS (null, 'stroke', '#000000');
cell.setAttributeNS (null, 'stroke-miterlimit', '#10');
cell.setAttributeNS (null, 'cx', x);
cell.setAttributeNS (null, 'cy', y);
cell.setAttributeNS (null, 'r', radius);
g.appendChild (cell);
}
}
return svgElem;
}
return gm_;
}
And then in main.js
var wrapper = document.getElementById('wrapper');
var mySVG = document.getElementById('alaska').outerHTML;
mySVG = mySVG.slice(0, 4) + ' height="100" ' + mySVG.slice(4);
// Create a Data URI based on the #alaska element.
var mySrc = 'data:image/svg+xml;base64,' + window.btoa(mySVG);
// Create a new image to do our resizing and capture our pixel data from.
var source = new Image();
source.onload = function() {
var svgRasterStage = document.createElement('canvas');
svgRasterStage.width = 1000;
svgRasterStage.height = 1000;
svgRasterStage.classList.add('hidden');
// You may not need this at all, I didn't test it.
wrapper.appendChild(svgRasterStage);
// Get drawing context for the Canvas
var svgRasterStageContext = svgRasterStage.getContext('2d');
// Draw the SVG to the stage.
svgRasterStageContext.drawImage(source, 0, 0);
// We can now get array of rgba pixels all concatinated together:
// [ r, g, b, a, r, g, b, a, (...) r, g, b, a, r, g, b, a]
var rgbaConcat = svgRasterStageContext.getImageData(0, 0, 100, 100).data;
// Which sucks, so here's a way to convert them to pixels that we can
// use with GridManager.createSvgGrid.
var pixels = [];
var count = 0;
// NOTE: this is a for with a weird step: i=i-4. i-4 is an infinte loop.
// anything else just jumbles the pixels.
for (var i = rgbaConcat.length - 1; i >= 0; i=i-4) {
var r = rgbaConcat[i - 0];
var g = rgbaConcat[i - 1];
var b = rgbaConcat[i - 2];
var a = rgbaConcat[i - 3];
pixels.push([r, g, b, a]);
}
// We create our GridManager (finally).
var gm = new GridManager({
'gridWidth': 100,
'gridHeight': 100,
'dataSrc': pixels
});
// And let her rip!
wrapper.appendChild(gm.createSvgGrid());
}
Upvotes: 4
Reputation: 1130
I tried to quick-solve this issue, and did some research, but still it is not finished / complete (you can finish it in your fina implementation).
You need to have a function that check If a point is inside a path. I found 2 libraries in JS: Raphael and SnapSVG.
I forked and edited your JSFiddle, and fast-tried to solve it. My first attempt was with SnapSVG's function but it returned me a lesser-than-expected result than Raphael's function.
Open the fiddle and check: https://jsfiddle.net/edmundo096/7sjLb956/4/. Beware that the scale of 2 will slow your browser, although I used it to see a correct result but takes time to see something (mobile browsers may hang up).
var alaska = $('#alaska');
var grid = $('#grid');
var path = alaska.find('path').first().attr('d');
grid.children().each(function(){
var circle = $(this);
var scale = 2;
// SnapSVG version: var isInside = Snap.path.isPointInside(path,
var isInside = Raphael.isPointInsidePath(path,
circle.attr('cx') * scale,
circle.attr('cy') * scale);
if (isInside) {
circle.attr('fill', 'blue');
}
});
(I used jQuery, and 2 external resources: Raphael and SnapSvg from Cloudflare CDN)
As you can see on the next image, it generates a kind of dot map, but still you need to correct the mapping, placement, scale, etc. of the Path.
Raphael first quick-try result:
SnapSVG first quick-try result:
You can cache your result; save the resulted map in a JSON map object, and then load it separately to save the calculation Time from this complex Paths.
Hope it can help you.
Upvotes: 1
Reputation: 101800
You could take the circle center coordinates and use something like Raphael's isPointInsidePath()
function to test if it is inside the map path.
http://raphaeljs.com/reference.html#Raphael.isPointInsidePath
Upvotes: 0