I have the following code which should to display drowned line in the canvas element.
var initCanvas = function () {
var episodeLengthInPixels = moment.duration(episodeLogLength).asSeconds() * episodeWidthMultiplication;
console.log("Length of chart is "+episodeLengthInPixels +" px");
try {
canvas = d3.select("body").append("canvas")
.attr("width", 500)
.attr("height", canvasHeight)
.attr("class", canvasSelector);
//Draw the Line
canvas.append("line") // attach a line
.style("stroke", "black") // colour the line
.attr("x1", 0) // x position of the first end of the line
.attr("x2", 500)
.attr("y1", waveHeight)
.attr("y2", waveHeight) ;
} catch (e) {
console.error(e);
}
}
Problem is that canvas and the line are available in the DOM model but are not visible (no exception is throwned). When i tried to work with SVG instead of the canvas, everything works fine.
How can I display the content in canvas using the D3.js library please? I tried to find any examples, but without the luck. Should i use D3.js fro canvas usage or something else (pure drawing to canvas in example)?
Many thanks for any advice.
Canvas and SVG are way different. It's not just a matter of changing "svg" for "canvas" in your d3.select("body").append() code. You should study the canvas documentation and the SVG documentation.
This, for instance, is how to draw a line in canvas:
var chart = d3.select("body").append("canvas")
.attr("width", 400)
.attr("height", 300);
var context = chart.node().getContext("2d");
context.beginPath();
context.moveTo(0,100);//here you set the equiv. to X1 and Y1 in SVG
context.lineTo(400,100);//here you set the equiv. to X2 and Y2 in SVG
context.stroke();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Also, keep in mind that the fact that you see a given element when inspecting the DOM doesn't mean that the element will show up. You can make this very simple test using d3:
d3.select("body").append("div").append("charlesdarwin");
You're gonna see this inspecting the DOM:
<div>
<charlesdarwin></charlesdarwin>
</div>
But, of course, you don't expect that this have any result.
Here is kinda an example taken from here.
https://bocoup.com/weblog/d3js-and-canvas
d3 and canvas are not the same.
var base = d3.select("#foo");
var chart = base.append("canvas")
.attr("width", 400)
.attr("height", 300);
var context = chart.node().getContext("2d");
var data = [1,2,13,20,23];
var scale = d3.scale.linear()
.range([10, 390])
.domain([1,23]);
data.forEach(function(d, i) {
context.beginPath();
context.rect(scale(d), 150, 10, 10);
context.fillStyle="red";
context.fill();
context.closePath();
});
// Your line here...
context.beginPath();
context.moveTo(10,10);
context.lineTo(40,60); // x2,y2 ...
context.stroke();
context.closePath();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Examples here
<div id="foo"></div>
Related
Some background:
I have plotted a map and about 35k circles on it with zoom and tooltips working fine on SVG. However, due to the amount of circles that need to be drawn (and may be not the best written code; i'm a beginner) I see performance issues while getting the page to run.
And so, I wanted to try out the same page on a canvas to improve performance.
Problem:
I got the map itself working on canvas but I have been trying to add the zoom feature but in vain. Any help in getting this fixed will be greatly appreciated.
Sample with SVG - https://bl.ocks.org/sharad-vm/af74ae5932de1bcf5a39b0f3f849d847
The code I have for Canvas is as below:
//Width and height
var w = 700;
var h = 600;
//Create Canvas element
var canvas = d3.select('#map')
.append('canvas')
.attr('width', w)
.attr('height', h);
var context = canvas.node().getContext('2d');
//Define map projection
var projection = d3.geo.mercator()
.translate([w/2, h/1.72])
.scale([100]);
//Define path generator
var path = d3.geo.path()
.projection(projection)
.context(context);
var init = 0;
canvas.call(zoom);
var zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.scaleExtent([1, 30])
.on("zoom", zoomed);
//function to zoom
function zoomed() {
context.save();
context.clearRect(0, 0, w, h);
context.translate(d3.event.transform.x, d3.event.transform.y);
context.scale(d3.event.transform.k, d3.event.transform.k);
draw();
context.restore();
};
draw();
//Load in GeoJSON data
function draw() {
...
}
When using projections the secret to get the zoom working is to transform the projection itself. For your example you can just adjust your projection before redrawing with something like:
projection.translate([w/2 + d3.event.transform.x, h/1.72 + d3.event.transform.y])
.scale([100*d3.event.transform.k]);
Another option is to scale the canvas itself, like in this example I made
I have three shapes all in the same group. This group have been transformed. I want to draw a line from one of the elements within that group. I am trying to access this elements coordinates by:
s.select("#hitboxHeel").getBBox().cx and s.select("#hitboxHeel").getBBox().cy
However this gives some weird coordinates, that are far off from where they should be. How do i get the actual position of the points, thus being able to draw a line?
I had a similar problem and found the solution from this post : Rectangle coordinates after transform
Here, you basically want to apply the 'aggregated' transform matrix of your shape to coordinates that are not transformed (sorry for the awkward phrasing). This matrix also incorporates the transformations of parent elements (group nodes here), so you shouldn't have to worry about them.
So, given :
your native svg element node
your native svg container svg
your original point of interest coordinates (before transforms) x and y that you want transformed
the expected transformed coordinates of your original point transformedPoint
`
// get the component transform matrix
var ctm = node.getCTM();
var svgPoint = svg.createSVGPoint();
svgPoint.x = x;
svgPoint.y = y;
// apply the matrix to the point
var transformedPoint = svgPoint.matrixTransform(ctm);
// an example using d3.js ( svg > g > rect )
// get the center of the rectangle after tansformations occured
var svg = d3.select('body').append('svg')
.attr('width', 500)
.attr('height', 500)
.attr('id', 'myCanvas')
.style('margin', 100)
var g = svg.append('g')
.attr('transform', 'translate(-10,10)')
var r = g.append('rect')
.attr('x', 300).attr('y', 100).attr('width', 79).attr('height', 150)
.attr('transform', 'translate(-54,300)rotate(-30,30,20)')
.attr('stroke', 'black')
.attr('fill', 'red')
var pt = svg.node().createSVGPoint()
pt.x = parseInt(r.attr('x')) + parseInt(r.attr('width')) / 2
pt.y = parseInt(r.attr('y')) + parseInt(r.attr('height')) / 2
var ctm = r.node().getCTM()
var center = pt.matrixTransform(ctm)
console.log('the transformed rectangle center', center)
// draw the center to confirm the accuracy of the process
svg.append('circle')
.attr('cx', center.x).attr('cy', center.y).attr('r', 5)
.attr('stroke', 'black')
.attr('fill', 'blue')
`
are a few questions on here and on the D3 site about how you'd find the centre point (or any point) along a path, however I can't seem to find how to do it with a line.
I've done a simple jsfiddle here. Essentially I need to add a shape (using text in the jsfiddle to make it clearer) at a point along a line (lets say the middle for simplicity)
So I have a svg:
var canvas = d3.select('body').append('svg').attr('width', 500).attr('height', 500);
And add a line (the position is fixed and doesnt come from data)
var line = canvas.append('line').attr('x1', 50).attr('y1', 50).attr('x2', 250).attr('y2', 150);
The I add some text just to demo to the top and bottom of that line
canvas.append('text').attr('x', line.attr('x1')).attr('y', line.attr('y1')).text('top');
canvas.append('text').attr('x', line.attr('x2')).attr('y', line.attr('y2')).text('bottom');
path's have methods to get the centre point and width/BBox etc, but line doesnt seem to.
Anyone have any ideas how this can be achieved?
My initial though was to just get the difference between the x1/x2 values, like this:
canvas.append('text')
.attr('x', parseInt(line.attr('x2') - line.attr('x1')))
.attr('y', parseInt(line.attr('y2') - line.attr('y1')))
.text('just looks a bit off');
But as you'll see from the jsfiddle, it's just off somehow.
Anyone want to point out my mistake?
I guess, this will work:
var lineData = {x1: 50, y1: 50, x2: 250, y2: 150};
var canvas = d3.select('body').append('svg').attr('width', 500).attr('height', 500);
var line = canvas.append('line').attr('x1', lineData.x1).attr('y1', lineData.y1).attr('x2', lineData.x2).attr('y2', lineData.y2);
console.log(line);
var x = lineData.x1 + Math.abs(lineData.x2 - lineData.x1) / 2;
var y = lineData.y1 + Math.abs(lineData.y2 - lineData.y1) / 2;
console.log([x,y]);
canvas.append('text').attr('x', x).attr('y', y).text('X');
Line
Use simple mathematics, distance formula.
var canvas = d3.select('body').append('svg').attr('width', 500).attr('height', 500);
var line = canvas.append('line').attr('x1', 50).attr('y1', 50).attr('x2', 250).attr('y2', 150);
var x1 = parseInt(line.attr("x1"));
var y1 = parseInt(line.attr("y1"));
var x2 = parseInt(line.attr("x2"));
var y2 = parseInt(line.attr("y2"));
var midPoint = { x: (x1+x2)/2, y: (y1+y2)/2 };
canvas.append('text').attr('x', midPoint.x).attr('y', midPoint.y).text('X');
line{
stroke:#444;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Path
var canvas = d3.select('body').append('svg').attr('width', 500).attr('height', 500);
var line = canvas.append('path').attr('d', "M 50 50 L 250 150");
var path = line.node();
var midPoint = path.getPointAtLength(path.getTotalLength()/2);
canvas.append('text').attr('x', midPoint.x).attr('y', midPoint.y).text('X');
path{
stroke: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Resolved the issue so thought I'd answer it incase anyone else found the question.
Used getBBox() of the node (using .node()). Used that to get the width, height and xy:
var canvas = d3.select('body').append('svg')
.attr('width', 500)
.attr('height', 500);
var line = canvas.append('line')
.attr('x1', 50)
.attr('y1', 150)
.attr('x2', 50)
.attr('y2', 250);
Then the middle x and y are:
var midX = line.node().getBBox().x + line.node().getBBox().width / 2;
var midY = line.node().getBBox().y + line.node().getBBox().height / 2;
I am trying to get zoom to work by dragging a rectangle over my series plot to identify the interval of zooming. Here is my plunkr
http://plnkr.co/edit/isaHzvCO6fTNlXpE18Yt?p=preview
You can see the issue by drawing a rectangle with the mouse over the chart - The new chart overshoots the boundary of the X and Y axes. I thought my group under the svg would take care of the bounds of the series (path) but I am clearly mistaken. After staring at it for a long time, I could not figure it out. Please ignore the angular aspect of the plunkr. I think the issue is somewhere in the
//Build series group
var series = svgGroup.selectAll(".series")
.data(data)
.enter().append("g")
.attr("class", "series");
//Build each series using the line function
series.append("path")
.attr("class", "line")
.attr("d", function (d) {
return line(d.series);
})
.attr("id", function (d) {
//While generating the id for each series, map series name to the path element.
//This is useful later on for dealing with legend clicks to enable/disable plots
legendMap[d.name] = this;
//Build series id
return buildPathId(d.name);
})
.style("stroke", function (d) {
//Use series name to get the color for plotting
return colorFcn(d.name);
})
.style("stroke-width", "1px")
.style("fill", "none");
Any help with this is appreciated.
Thank you very much.
I think the method renderChartWithinSpecifiedInterval(minX, maxX, minY, maxY, pixelCoordinates) maybe has some problem there.
It seems the parameter like max_x passed in line 130 are a very big value like time seconds
var svg = renderChartWithinSpecifiedInterval(min_X, max_X, min_Y, max_Y, false);
max_X,min_X are value like 1415171404335
min_Y = 0, max_Y = 100
But in dragDrop call in line 192
function gEnd(d,i){
svg.selectAll(".zoom-rect").remove();
var svgGp = svg.select("g");
var groupTransform = d3.transform(svgGp.attr("transform"));
var xOffset = groupTransform.translate[0];
var yOffset = groupTransform.translate[1];
var xBegin = Math.min(xStart,xDyn) - xOffset;
var xEnd = Math.max(xStart,xDyn) - xOffset;
var yBegin = Math.min(yStart,yDyn) - yOffset;
var yEnd = Math.max(yStart,yDyn) - yOffset;
renderChartWithinSpecifiedInterval(xBegin, xEnd, yBegin, yEnd, true);
//It seems here the parameters values are all pixels
like xBegin = 100, xEnd = 200
}
hope it helps!
I'm trying to make a chorolpleth with a rotating globe in d3. I can get the globe to render out fine but I can't get the countries to fill with the proper color scale.
Longer explanation. I basically started with Mike Bostock's code for the spinning globe found here:
http://bl.ocks.org/mbostock/6747043
I've got some economic data for about 85 countries that I'm reading in from an external csv. And I'm trying to get the colors to map to the countries per the values in the csv. There's another Bostock example of a choropleth here (static and just the US and referenced frequently in SO d3 questions):
http://bl.ocks.org/mbostock/4060606
What I end up with are solid white (#fff) countries on the face of the globe. Which is not what I'm trying to get.
I added the ISO 3166-1 numeric codes to my csv so that I could match them to the same ids inside the topojson data. So my csv looks like:
country id curracct
Germany 276 260.9
Sweden 752 7.24
Etc.
My first thought was just to create a variable that was a function, which went through the length of the 'countries' from the topojson data and found the countries where the id equaled the id from the csv countries, then assigned the scaled color to them. Then I set 'context.fillStyle' equal to that variable/function. That didn't work.
Then I just put 'context.fillStyle' directly inside of a function (which is the code as it's currently written below). That didn't work either.
Again, I'm trying to get the 85 or so countries with data in the csv to appear color-coded on the front side spinning globe according to the scale I've set up.
My guess is that there is something I don't understand about the variable 'context' and what it's handling. If this were .style("fill", [put my function here to map the colors]) syntax I would be okay. So, anyone got any thoughts?
I'm not a coder. Actually I guess I am as I am trying to write some code. Maybe I should just say I'm a self-taught and mostly terrible coder. Although through examples, the JS console, and other questions on SO, I can usually work out where the errors are. This time I've reached a wall. Any help is appreciated. Thanks.
var width = 560,
height = 560,
speed = -1e-2,
start = Date.now();
var sphere = {type: "Sphere"};
var color = d3.scale.quantize()
.range(["#ffffd9", "#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"]);
var projection = d3.geo.orthographic()
.scale(width / 2.1)
.clipAngle(90)
.translate([width / 2, height / 2]);
var graticule = d3.geo.graticule();
var canvas = d3.select("body")
.append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
var path = d3.geo.path()
.projection(projection)
.context(context);
queue()
.defer(d3.json, "/d3/world-110m.json")
.defer(d3.csv, "trade.csv")
.await(globeTrade);
function globeTrade(error, topo, data) {
var land = topojson.feature(topo, topo.objects.land),
countries = topojson.feature(topo, topo.objects.countries),
borders = topojson.mesh(topo, topo.objects.countries, function(a, b) { return a !== b; }),
grid = graticule();
color.domain([0, d3.max(data, function(d){return d.curracct})]);
d3.timer(function() {
var λ = speed * (Date.now() - start),
φ = -15;
context.clearRect(0, 10, width, height);
context.beginPath();
path(sphere);
context.lineWidth = 2.5;
context.strokeStyle = "#000";
context.stroke();
context.fillStyle = "#fff";
context.fill();
context.save();
context.translate(width / 2, 0);
context.scale(-1, 1);
context.translate(-width / 2, 0);
projection.rotate([λ + 180, -φ]);
context.beginPath();
path(land);
context.fillStyle = "#ddd" //changed to a nuetral gray
context.fill();
context.beginPath();
path(grid);
context.lineWidth = .5;
context.strokeStyle = "rgba(119,119,119,.5)";
context.stroke();
context.beginPath();
path(borders);
context.lineWidth = .25;
context.strokeStyle="#fff";
context.stroke();
context.restore();
projection.rotate([λ, φ]);
context.beginPath();
path(grid);
context.lineWidth = .5;
context.strokeStyle = "rgba(119,119,119,.5)";
context.stroke();
// This is where I am failing
context.beginPath();
path(countries);
function render (d){
for (var j = 0; j < countries.features.length; j++) {
if (d.id == countries.features[j].id) {
context.fillStyle = color(d.curracct)
}
else {
context.fillStyle = "#737368"; //left Bostock's color for now
}
}
}
context.fill();
context.lineWidth = .1;
context.strokeStyle = "#000";
context.stroke();
});
data.forEach(function(d, i) {
d.curracct = +d.curracct;
d.id = +d.id;
});
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>