My question is as follows:
(a) I am plotting two paths in D3 that have exactly the same coordinates. The same paths.
(b) I would now like to transform one of the paths so that they run parallel and that the 'width' between the paths is always equal to x pixels. My code is as follows:
d3.select("#path2")
.attr("transform", "translate(15,0)");
Unfortunately, the code yields the following:
As you can see the lines are not parallel and are intersecting. In fact it's a bit of a dog's breakfast. I have a feeling this could be alot more complicated than it sounds to achieve. Or is it? Any ideas?
Thank you all
I have a different approach to propose: create two identical paths, like you're doing right now. Then, if you want a distance of, for instance, 16px, set the stroke-width of one of the paths bigger than that and use the other path as an SVG mask, with a stroke-width of 16, at the very same position of the other path.
That way, one path will make a "hollow space" in the other one, without any complicated math.
Here is a demo. I put a circle just for showing that the path is hollow:
var svg = d3.select("svg")
var dataset = [
[0, 30],
[20, 30],
[50, 55],
[60, 70],
[100, 120],
[110, 90],
[135, 121],
[200, 70],
[300, 130]
];
var line = d3.line()
.x(function(d) {
return d[0];
})
.y(function(d) {
return d[1];
});
svg.append("circle")
.attr("cx", 200)
.attr("cy", 70)
.attr("r", 30)
.attr("fill", "teal")
var defs = svg.append("defs");
var mask = defs.append("mask")
.attr("id", "pathMask");
mask.append("rect")
.attr("width", 300)
.attr("height", 150)
.attr("fill", "white");
mask.append("path")
.attr("d", line(dataset))
.attr("stroke", "black")
.attr("fill", "none")
.attr("stroke-width", 16);
var path = svg.append("path")
.attr("d", line(dataset))
.attr("mask", "url(#pathMask)")
.attr("stroke", "red")
.attr("stroke-width", 20)
.attr("fill", "none");
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg>
</svg>
If you don't need a transparent fill, the solution is even easier: draw two paths, one over the other, with different stroke-width, and fill the upper one (with the smaller stroke width) with the same colour of the background.
One approach would be to say that each segment of the 'parallel' path should be parallel to the corresponding segment of the original path. So, for each segment of the original you compute a parallel line, the given distance away, and then intersect successive lines. You should choose the directions, that is the normals to the line segments, consistently; for example be computing the normal direction as a 90 degree clocwise rotation of the tangent direction in each case, or as an anticlockwise rotation in each case.
In nice cases -- where the angles between successivve segments are small and the distance small compared with the smallest segment length -- this may well give yu what you want. In other cases you'll get a mess.
Related
I have a big data set of coordinates and would like to place them into a groups falling under 30 mile radius circles. And I need these circles to cover entire US land area. Overlapping circles are allowed. Is there a way to do this? Any help would be much appreciated. Thank you.
I wrote an npm package a while back that will help you work with locations on earth.
You can see a jsfiddle I made that makes random points on a circle centered at some location. The code below is pasted from the jsfiddle because SO wants code when you include a fiddle link, but you are better off to go experiment with the fiddle. The function pointAtDistance() implements the Haversine formula.
For the packing, I'd attempt a hexagon formation - make a grid and eliminate those that don't intersect land. As the earth is a sphere, you should probably find the number of degrees longitude closest to the equator that represents your offset so that 30 mile circles still overlap. Then using that angle, the circles further north will overlap more than needed, but at least there won't be gaps, and the structure is easy to reason about.
function pointAtDistance(inputCoords, distance) {
const result = {}
const coords = toRadians(inputCoords)
const sinLat = Math.sin(coords.latitude)
const cosLat = Math.cos(coords.latitude)
const bearing = Math.random() * TWO_PI
const theta = distance/EARTH_RADIUS
const sinBearing = Math.sin(bearing)
const cosBearing = Math.cos(bearing)
const sinTheta = Math.sin(theta)
const cosTheta = Math.cos(theta)
result.latitude = Math.asin(sinLat*cosTheta+cosLat*sinTheta*cosBearing);
result.longitude = coords.longitude +
Math.atan2( sinBearing*sinTheta*cosLat, cosTheta-sinLat*Math.sin(result.latitude )
);
result.longitude = ((result.longitude+THREE_PI)%TWO_PI)-Math.PI
return toDegrees(result)
}
I'm sorry I don't know what to call these kind of chart. I've already made a square grid map, rectangle located on every centroid. (pic 1)
Pic 1
Now I want to make like a percentage map (isotype?), but I don't know how to do it. (pic 2--photoshopped)
Pic 2
This is the code, I modified it from a simple map code
d3.json(url).then(function(dataset){
group = canvas.selectAll("g")
.data(dataset.features)
.enter()
.append("g")
areas = group.append("path")
.attr("d", path)
.attr("class","area")
.style("fill","white")
.style("opacity",0)
;
group.append("rect")
.attr("x",function (d) { return path.centroid(d)[0];})
.attr("y",function (d) { return path.centroid(d)[1];})
.attr("width",10)
.attr("height",10)
.style("fill","orange");
});
I already have an idea, by manually adding the data on the JSON, but I want to know how to do it from dynamic input. I want to google it but don't have any clue or keyword.
Thanks
I want to get a result like this while the white circle is being actually a punch:
However, I'm getting the following result when I follow the boolean operation example:
// this works okay
var via = outer.exclude(hole)
project.activeLayer.addChild(via)
// this works weird
var drilledY = y.exclude(hole)
project.activeLayer.addChild(drilledY)
Here the only problem seems to be creating the hole inside the Path. How can I create a hole in the path?
I don't think you can get the result you want using Path.Line.
Punching through implies that you want to remove some internal area, which an open Path such as Path.Line lacks.
So what you can do is the following:
Replace those thick Lines with Path.Rectangles.
unite the 2 rectangles, to get your cross, so you have one Path to operate on.
Use subtract instead of exclude to "punch through".
Here's an example:
var x = new paper.Path.Rectangle({
from: [100, 100],
to: [120, 200],
fillColor: 'red',
strokeWidth: 1
});
var y = x.clone().rotate(90).set({ fillColor: 'blue' })
// Unite x/y to get a single path.
var cross = y.unite(x)
// Remove x,y we no longer need them, we got the cross.
x.remove()
y.remove()
var hole = new paper.Path.Circle({
center:[110, 150],
radius: 6,
strokeColor: 'red',
fillColor: 'red'
})
// Subtract (instead of exclude), to "punch through".
var drilled = cross.subtract(hole)
// Remove hole/cross, we no longer need them.
hole.remove()
cross.remove()
console.log(drilled)
and here's a Sketch.
If you don't want to unite your shapes, you can still loop through them
and subtract the hole from them, just remember to use closed Paths.
Objective:
To simulate a reflective floor(like this) in three js.
Idea:
Make the floor translucent by setting opacity to 0.5.
Place a Mirror below it to reflect the meshes above it.
Expected Output:
To be able to see reflections of the house via the floor mirror.
Obtained Output:
Doesn't reflect the meshes which is part of the house.
Instead, reflects only the skybox and that too only in certain angles.
Screenshots:
Mirror reflecting skybox fully - http://prntscr.com/6yn52y
Mirror reflecting skybox partially - http://prntscr.com/6yn5f7
Mirror not reflecting anything - http://prntscr.com/6yn5qy
Questions:
Why aren't the other meshes of the house reflected through the mirror?
Why is the mirror not reflecting in certain orientations of the camera?
Code Attached:
.......
.......
function getReflectiveFloorMesh(floorMesh) {
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
floorMirror = new THREE.Mirror( renderer, firstPerson.camera,
{ clipBias: 0.003,
textureWidth: WIDTH,
textureHeight: HEIGHT,
color: 0x889999 } );
var mirrorMesh = floorMesh.clone();
mirrorMesh.position.y -= 10; // Placing the mirror just below the actual translucent floor; Fixme: To be tuned
mirrorMesh.material = floorMirror.material;
mirrorMesh.material.side = THREE.BackSide; // Fixme: Normals were flipped. How to decide on normals?
mirrorMesh.material.needsUpdate = true;
mirrorMesh.add(floorMirror);
return mirrorMesh;
}
function getSkybox() {
var urlPrefix = "/img/skybox/sunset/";
var urls = [urlPrefix + "px.png", urlPrefix + "nx.png",
urlPrefix + "py.png", urlPrefix + "ny.png",
urlPrefix + "pz.png", urlPrefix + "nz.png"];
var textureCube = THREE.ImageUtils.loadTextureCube(urls);
// init the cube shadder
var shader = THREE.ShaderLib["cube"];
shader.uniforms["tCube"].value = textureCube;
var material = new THREE.ShaderMaterial({
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
uniforms: shader.uniforms,
side: THREE.BackSide
});
// build the skybox Mesh
var skyboxMesh = new THREE.Mesh(new THREE.CubeGeometry(10000, 10000, 10000, 1, 1, 1, null, true), material);
return skyboxMesh;
}
function setupScene(model, floor) {
scene.add(model); // Adding the house which contains translucent floor
scene.add(getSkybox()); // Adding Skybox
scene.add(getReflectiveFloorMesh(floor)); // Adds mirror just below floor
scope.animate();
}
....
....
this.animate = function () {
// Render the mirrors
if(floorMirror)
floorMirror.render();
renderer.render(scene, firstPerson.camera);
};
You have to attach the mirror to the mesh before doing any transformation.
So the code would be:
floorMirror = new THREE.Mirror( ... );
var mirrorMesh = floorMesh.clone();
mirrorMesh.add(floorMirror); // attach first!
mirrorMesh.position.y -= 10;
...
But another problem here is that you are cloning mirrorMesh from floorMesh, which has already been (probably) transformed.
At creation, a mirror object has the same default transform matrix as a regular Mesh with plane geometry (which is by default 'vertical').
When you attach the mirror to a floor (or any horizontal mesh), the matrix doesn't match with the mesh one and that's why you don't see the reflections, or only from a certain angle.
So, always attach a mirror to a non-transformed plane mesh, before you apply your transformations (translations or rotations).
Idea:
"Make the floor translucent by setting opacity to 0.5.
Place a Mirror below it to reflect the meshes above it".
I suggest another way, make floor as solid add mirror on top and change the alpha of the mirror instead, I think you have issues with the translucent floor restricting the mirror projection through the alpha..
If you move the mirror over from under the translucent floor or into an empty scene with just a cube or sphere geometry attached basic material does it reflect as expected?
You may need 2 mirrors, one for the room assuming you want polished floor boards and one for outside general reflection
I'm trying to update my axis with custom labels because I want my users to be able to use different datasets. The current array of label names i load in have 17 elements in it.
When loading the page the graph only shows 11 elements as it can be seen here: http://servers.binf.ku.dk/hemaexplorerbeta/
How do I update my xScale to be able to all of the elements at once?
Here's the code for my scale:
var xScale = d3.scale.linear()
.domain([-25, genWidth])
.range([0, width-margin.right]);
And for my update function:
function updateAxisX(arr) {
var formatAxis = function(d) {
return arr[d % arr.length];
}
xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(formatAxis);
SVG.select(".x.axis")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("transform", function(d) {
return "rotate(-65)"
});
}
I also have a zoom function where you can drag the graph around as well. As I mentioned the X Axis has 17 element in total, although on my graph they are repeated over and over. Is there any way I can prevent the zoom from letting the user drag the graph out of my "maximum" frame? In other words I'd like to be able to drag ONLY IF there has been zoomed in on the graph and ONLY until you reach the "edges" of the graph and no more.
Here's the code for my zoom function:
var zoom = d3.behavior.zoom()
.x(xScale)
.y(yScale)
.scaleExtent([1, 10])
.on("zoom", zoomTargets);
function zoomTargets() {
SVG.select(".x.axis").call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("transform", function(d) {
return "rotate(-65)"
});
SVG.select(".y.axis").call(yAxis);
SVG.selectAll("circle").attr("cx",function(d){return xScale(d)}).attr("cy",function(d){return yScale(d)});
}
Also don't mind the huge red circles, they are just randomly made for testing something.
I should also mention that my graph is supposed be a scatter plot by the time I get everything done.
One quick question as well. Since I'm not using numbers, but custom labels where I've inserted strings. How do I insert the my circles accordingly to each spot on the x axis?
Thank you very much for your help in advance!