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.
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());
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With