Converting D3 path to points and not understanding output - svg

I'm trying to convert a path (generated by D3) to points. I'm using getTotalLength() and getPointAtLength() but the values I'm getting back are really odd. getTotalLength() returns a huge value - 14594.1923828125. Also, if I look at the individual values what I get don't seem to be in the coordinate space. For example getPointAtLength(10000) returns negative values.
What am I doing wrong, or what am I not understanding about the returned values?
Here is my code
//The data for our line
var lineData = [ { "x": 1, "y": 30}, { "x": 20, "y": 50},
{ "x": 70, "y": 5}, { "x": 100, "y": 60}];
//This is the accessor function we talked about above
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("basis");
//The SVG Container
var svgContainer = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200);
//The line SVG Path we draw
var lineGraph = svgContainer.append("path")
.attr("d", lineFunction(lineData))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
mySVG = d3.select("svg")
myPath = mySVG.select("path").node()
console.log(myPath)
console.log(myPath.getTotalLength())
console.log(myPath.getPointAtLength(10000))
And here is the jsfiddle -http://jsfiddle.net/atTxL/1/
Thanks

Related

Client side D3 line chart from node.js server data

I'm trying to generate data on the server side (Node.JS) and then pass it via res.render to a view.ejs page for use in a simple D3 lineChart. I can hard code the data on the ejs page with a successful chart rendering. But, when I try to pass the data object from Node via res.render, D3.data doesn't seem to recognize the data.
Here is the server side route:
router.get("/view", function(req,res){
let data = [
{evalDate: 1598038171322, evalHypValue: 18},
{evalDate: 1608038171322, evalHypValue: 27},
{evalDate: 1618038171322, evalHypValue: 29},
{evalDate: 1628038171322, evalHypValue: 30},
];
res.render("view",
{
Data: JSON.stringify(data),
});
});
And here is the view.ejs with the D3 code:
<div class="" id="hyp_chart"></div>
<script>
//NOTE: hard coded data works with the D3
//let data = [
// {evalDate: 1598038171322, evalHypValue: 18},
// {evalDate: 1608038171322, evalHypValue: 27},
// {evalDate: 1618038171322, evalHypValue: 29},
// {evalDate: 1628038171322, evalHypValue: 30},
// ];
//But I can't get this next line to use my server side data as input to D3
let data = <%=JSON.parse(Data)%>
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the evalDate / time
var parseTime = d3.timeParse("%Q");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the line
var valueline = d3.line()
.x(function(d) { return x(d.evalDate); })
.y(function(d) { return y(d.evalHypValue); });
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#hyp_chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// gridlines in x axis function
function make_x_gridlines() {
return d3.axisBottom(x)
.ticks(5)
}
// gridlines in y axis function
function make_y_gridlines() {
return d3.axisLeft(y)
.ticks(5)
}
// Get the data
// format the data
data.forEach(function(d) {
d.evalDate = parseTime(d.evalDate);
d.evalHypValue = Number(d.evalHypValue);
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.evalDate; }));
y.domain([0, d3.max(data, function(d) { return d.evalHypValue; })]);
// add the X gridlines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_gridlines()
.tickSize(-height)
.tickFormat("")
)
// add the Y gridlines
svg.append("g")
.attr("class", "grid")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
)
// add the valueline path.
svg.append("path")
.data([data])
// .datum(dataset)
.attr("class", "line")
.attr("d", valueline);
// add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
</script>
Thank you!!!

Applying clipTo path on image in fabric.js incorrectly repositions the image

Please take a look at this fiddle:
http://jsfiddle.net/2ktvyk4e/5/
var imgUrl, snapshotCanvas;
imgUrl = 'http://cdn-development.wecora.com/boards/backgrounds/000/001/388/cover/ocean-background.jpg';
snapshotCanvas = new fabric.StaticCanvas('snapshotCanvas', {
backgroundColor: '#e0e0e0',
width: 1000,
height: 1500
});
fabric.Image.fromURL(imgUrl, function(img) {
img.set({
width: 1000,
left: 0,
top: 0,
clipTo: function(ctx) {
return ctx.rect(0, 0, 1000, 400);
}
});
return snapshotCanvas.add(img).renderAll();
}, {
crossOrigin: 'Anonymous'
});
It's pretty simple. I'm loading an image and then trying to clip it so that the full width of the canvas but clipped so only the top 400 pixels are showing. For some reason, the clipTo causes the image to move and resize inexplicably:
As you can see, when the clipping path is applied the image is repositioned on the canvas inexplicably. If I remove the clipTo, then the image loads full canvas width no problem (of course its also full height, which we don't want).
I have no idea what is happening here or why this is occuring so any help is appreciated.
Make sure you have originX and originY left to top and left on both the canvas and your image. Also - you don't really need to clip anything to the canvas. The only real use I've found for clipTo was clipping collage images to their bounding shapes. If you want to restrict the image from being dragged above that 400px, I would recommend rendering a rectangle below it (evented = false, selectable = false) and then clipping to that rectangle.
I put this together without clipTo (and changed some numbers so I wasn't scrolling sideways). It renders the image half way down the canvas.
http://jsfiddle.net/2ktvyk4e/6/
Edit:
I dug through some source code to find the clipByName method and the two helper methods for finding stuff. I use this for keeping track of collage images and their bounding rectanlges ("images" and "clips"). I store them in an object:
imageObjects: {
'collage_0': http://some.tld/to/image.ext,
'collage_1': http://some.tld/to/image2.ext
}
Helper methods for finding either the clip or image:
findClipByClipName: function (clipName) {
var clip = _(canvas.getObjects()).where({ clipFor: clipName }).first();
return clip;
},
findImageByClipFor: function (clipFor) {
var image = _(canvas.getObjects()).where({ clipName: clipFor }).first();
return image;
},
Actual clipping method:
clipByName: function (ctx) {
this.setCoords();
var clipRect, scaleXTo1, scaleYTo1;
clipRect = collage.findClipByClipName(this.clipName);
scaleXTo1 = (1 / this.scaleX);
scaleYTo1 = (1 / this.scaleY);
ctx.save();
var ctxLeft, ctxTop;
ctxLeft = -(this.width / 2) + clipRect.strokeWidth;
ctxTop = -(this.height / 2) + clipRect.strokeWidth;
ctx.translate(ctxLeft, ctxTop);
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
ctx.rect(
clipRect.left - this.oCoords.tl.x,
clipRect.top - this.oCoords.tl.y,
clipRect.width,
clipRect.height
);
ctx.closePath();
ctx.restore();
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
},
And finally adding images to canvas where all of this comes together:
var clipName, clip, image;
clipName = helpers.findKeyByValue(url, this.imageObjects);
clip = this.findClipByClipName(clipName);
image = new Image();
image.onload = function () {
var collageImage = new fabric.Image(image, $.extend({}, collage.commonImageProps, {
left: clip.left,
top: clip.top,
clipName: clipName,
clipTo: function (ctx) {
return _.bind(collage.clipByName, collageImage)(ctx);
}
}));
collage.scaleImagesToClip(collageImage);
canvas.add(collageImage);
};

How can I connect coordinate points on a map using d3js?

I'm having trouble connecting points with a line on a map using d3. I think that I should use d3.svg.line() to create the points - but when I do it, I simply get a very small blob. Please see the link below for a screenshot of what I've been able to accomplish thus far - I want to connect the black dots with a line. Any help would be much appreciated.
Screenshot
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height*3 + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var group = svg.selectAll("g")
.data(data)
.enter()
.append("g")
var projection = d3.geo.mercator().scale(5000).translate([-2000,5900])
var path = d3.geo.path().projection(projection)
var graticule = d3.geo.graticule()
var line = d3.svg.line()
.interpolate("linear")
.x(function(d) { d.geometry.coordinates[0]; })
.y(function(d) { return d.geometry.coordinates[1] ; });
// this returns a parse error
// .x(function(d) { return projection(d.geometry.coordinates[0]); })
// .y(function(d) { return projection(d.geometry.coordinates[1]) ; });
var area = group.append("path")
.attr("d", path)
// .attr("d", line(data))
.attr("class", "area")
})
You have to pass both components of your coordinate to the d3.geo.mercator object, before taking each one separately as your x and y values. Your 'parse error' should go away if you use
.x(function(d) { return projection([d.lon, d.lat])[0]; })
.y(function(d) { return projection([d.lon, d.lat])[1]; });
instead. This post has a more complete example: D3 map Styling tutorial III: Drawing animated paths.
Hopefully once you are drawing the lines in the correct projection, they'll appear as you expect.

Add a circle to a d3.js map

I have generated a map of Phoenix from this GeoJson and made it show as I would like it to.
Now I would like to add circles to the map to represent something of interest, but the circles never show up. Here is the code:
<script type="text/javascript">
var h = 1280;
var w = 1280;
var projection = d3.geo.albers().scale(80000).center([0, 33.44]).rotate([112.07, 0]).translate([920, 850]);
var path = d3.geo.path().projection(projection);
var svg = d3.select("body").append("svg").attr("width", w).attr("height", h);
d3.json("data/phoenix.json", function(json) {
svg.selectAll("path").data(json.features).enter().append("path")
.attr("d", path).style("fill", "grey");
var coordinates = projection([33.46764,112.0785]);
svg.append("circle")
.attr("cx", coordinates[0])
.attr("cy", coordinates[1])
.attr("r", 5)
.style("fill", "red");
});
</script>
I have tried following different tutorial and howto's like from bost.ocks.org and this where it's with a csv file, but no matter what I do it won't draw the circle, what am I missing?
Adam Pearce is correct that the coordinates are [33.46764, -112.0785], however there is another problem: when translating from lat-long to the coords, you need to pass longitude as the first parameter, not latitude!
The tricky thing is that the albers projection, if called with a value not in (lower 48, alaska, hawaii) returns null silently.
Trying to translate [33.46764, -112.0785] in the console:
> proj = d3.geo.albersUsa()
function albersUsa(coordinates) {
var x = coordinates[0], y = coordinates[1];
point = null;
(lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y);
return point;
} d3.v3.js:3257
> proj( [33.46764, -112.0785] )
null
> proj( [-112.0785, 33.46762] )
[241.08874867733104, 327.6295325563234]
Bingo. In this case, it was useful to take a look at the actual function we are calling by using the console (in this case, in Chrome).
This was done using d3 version 3.3.8.
Schimmy's answer is correct, however I didn't understand at first. Here's how I added a circle on an Albers map:
//var projection = d3.geo.albersUsa();
var coordinates = projection([-112.0785,33.46764]);
svg.append("circle")
.attr("cx", coordinates[0])
.attr("cy", coordinates[1])
.attr("r", 5)
.style("fill", "red");
You may also want to use attr("transform", "translate") rather than attr("cx", coor[0].attr("cy", coor[1]).
If you have a GeoJson fie of the US and you want to plot a circle on each county:
// us = the geoJson file
svg.append("circle")
.data(topojson.feature(us, us.objects.counties).features)
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("r", 5)
.style("fill", "red");
You may find this much more efficient than "cx" and "cy".
From http://bost.ocks.org/mike/bubble-map/

Pop-Up Window, Center Screen

I'm using the following code to open a pop-up window in a Google Chrome extension, my one question is, how do I get the pop-up window to open in the centre of the users screen?
<script>
chrome.browserAction.onClicked.addListener(function() {
var left = (screen.width/2)-(w/2);
var top = (screen.height/2)-(h/2);
chrome.windows.create({'url': 'redirect.html', 'type': 'popup', 'width': 440, 'height': 220, 'left': '+left+', 'top': '+top+', } , function(window) {
});
});
</script>
I've also tried this, which resulted in no such luck.
<script>
chrome.browserAction.onClicked.addListener(function() {
chrome.windows.create({'url': 'redirect.html', 'type': 'popup', 'width': 440, 'height': 220, 'left': (screen.width/2)-(w/2), 'top': (screen.height/2)-(h/2), } , function(window) {
});
});
</script>
When you see var obj = {property: value} structure in JS it is an object creation. In your code you are trying to pass an object containing window properties to chrome.windows.create() function.
Correct code should be:
chrome.browserAction.onClicked.addListener(function() {
var w = 440;
var h = 220;
var left = (screen.width/2)-(w/2);
var top = (screen.height/2)-(h/2);
chrome.windows.create({'url': 'redirect.html', 'type': 'popup', 'width': w, 'height': h, 'left': left, 'top': top} , function(window) {
});
});
If you want the centering to also work with a dual monitor, you'll need to get the current window object from the extension and center your popup relative to that. Like so:
chrome.browserAction.onClicked.addListener(function() {
chrome.windows.getCurrent(function(win) {
var width = 440;
var height = 220;
var left = ((screen.width / 2) - (width / 2)) + win.left;
var top = ((screen.height / 2) - (height / 2)) + win.top;
chrome.windows.create({
url: 'redirect.html',
width: width,
height: height,
top: Math.round(top),
left: Math.round(left),
type: 'popup'
});
});
});
chrome.windows.create expects an integer for top and left, so it is recommended to wrap those values in Math.round.
If you want the screen to appear in the middle of the browser (not the center of the screen), and dynamic window size, you can do this.
chrome.windows.getCurrent((tabWindow) => {
const width = Math.round(tabWindow.width * 0.5) // dynamic width
const height = Math.round(tabWindow.height * 0.75) // dynamic height
const left = Math.round((tabWindow.width - width) * 0.5 + tabWindow.left)
const top = Math.round((tabWindow.height - height) * 0.5 + tabWindow.top)
chrome.windows.create( // https://developer.chrome.com/docs/extensions/reference/windows/#method-create
{
focused: true,
url: targetURL,
type: 'popup', // https://developer.chrome.com/docs/extensions/reference/windows/#type-WindowType
width, height,
left, top
},
(subWindow) => {}
)
})
Desc
Image
Show you what is the tabWindow.top and left
And you can use some filters to check whether the window has been created or not, to determine to create a new one, or show the window that you made.
chrome.windows.getAll({populate : true, windowTypes:['popup']}, (windowArray)=>{})
example code
chrome.windows.getCurrent((tabWindow) => { // https://developer.chrome.com/docs/extensions/reference/windows/#type-Window
const targetURL = 'yourTemplates/yourFile.html'
chrome.windows.getAll({populate : true, windowTypes:['popup']}, (windowArray)=>{
const queryURL = `chrome-extension://${chrome.runtime.id}/${targetURL}`
const target = windowArray.find(item=>item.tabs[0].url === queryURL) // ❗ make sure manifest.json => permissions including "tabs"
if (windowArray.length > 0 && target !== undefined) {
// Show the window that you made before.
chrome.windows.update(target.id, {focused: true}) // https://developer.chrome.com/docs/extensions/reference/windows/#method-update
return
}
// Otherwise, Create
const width = Math.round(tabWindow.width * 0.5)
const height = Math.round(tabWindow.height * 0.75)
const left = Math.round((tabWindow.width - width) * 0.5 + tabWindow.left)
const top = Math.round((tabWindow.height - height) * 0.5 + tabWindow.top)
chrome.windows.create( // https://developer.chrome.com/docs/extensions/reference/windows/#method-create
{
focused: true,
url: targetURL,
type: 'popup', // https://developer.chrome.com/docs/extensions/reference/windows/#type-WindowType
width, height,
left, top
},
(subWindow) => {
}
)
})
})
As an addendum to this answer, if you want to retrieve popup dimensions from localstorage--which are saved as strings--this will convert the variables to the necessary integers for the pop-up to work.
var w = parseInt(localStorage.getItem('key'));
var h = parseInt(localStorage.getItem('key'));

Resources