Has anyone run into the issue of removing padding (or margin?) from a chartjs chart?
Below is my code (in jsFiddle)...and image (notice the bottom? UGLY sauce).
Here's a JSFiddle that highlights the issue. Notice the padding at the bottom of the white box. https://jsfiddle.net/mre1p46x/
You can wrap a bit of logic around the fit method using the beforeFit and afterFit handlers to correct this padding when the number of labels is 3 (the fit function starts off by assuming a maximum radius of half the chart height. For a triangle, we actually have a bit more space)
All we do is scale the height property to compensate for this assumption, like so
...
options: {
scale: {
beforeFit: function (scale) {
if (scale.chart.config.data.labels.length === 3) {
var pointLabelFontSize = Chart.helpers.getValueOrDefault(scale.options.pointLabels.fontSize, Chart.defaults.global.defaultFontSize);
scale.height *= (2 / 1.5)
scale.height -= pointLabelFontSize;
}
},
afterFit: function (scale) {
if (scale.chart.config.data.labels.length === 3) {
var pointLabelFontSize = Chart.helpers.getValueOrDefault(scale.options.pointLabels.fontSize, Chart.defaults.global.defaultFontSize);
scale.height += pointLabelFontSize;
scale.height /= (2 / 1.5);
}
},
...
The scaling factor 2 / 1.5 is pretty easy to figure out
With h = distance from center of triangle to a corner
Total height of the triangle = h + distance from center of triangle to a side
= h + h * sin 30
= 1.5 h
h currently = chart height / 2
We want to scale this by r, such that
1.5 * chart height / 2 * r = chart height
This gives us
r = 2 / 1.5
Fiddle - https://jsfiddle.net/zqp525gf/
Related
I'm trying to animate a given element to go around a pre-defined radius and I'm having trouble getting the position of the element at a Y point given.
I'm trying to find each point with the circle equation, but I can only get one point out of the two possible ones.
In Javascript, I use Math.sqrt( Math.pow(radius, 2) - Math.pow(y, 2) , 2) to get the point. assuming the center of the of the circle is 0,0.
but then I need to translate it to pixels on the screen since there are no negative pixels in positions on the browser.
All the sizing is relative to the window. so the radius, for example, is 80% of the height of the window in my tests.
Also, I'm trying to calculate what the distance of the element between each frame should be for the duration, but I'm not using it yet because I try to fix the issue above first.
This is what I have(a cleaned up version):
let height = window.innerHeight * 0.8,
radius = height / 2,
circumferance = (radius * 2) * Math.PI,
container = document.getElementById('container'),
rotating = document.querySelector('.rotating'),
centerX = radius - (rotating.offsetWidth / 2),
centerY = radius - (rotating.offsetHeight / 2),
duration = 10,
stepDistance = circumferance / 16;
// Setting the dimensions of the container element.
container.style.height = height + 'px';
container.style.width = height + 'px';
// return positive X of any given Y.
function getXOffset(y) {
return Math.sqrt( Math.pow(radius, 2) - Math.pow(y, 2) , 2);
}
// Setting the position of the rotating element to the start.
rotating.style.top = 0 + 'px';
rotating.style.left = centerX + 'px';
setInterval(() => {
let top = parseInt(rotating.style.top),
y = radius - top;
rotating.style.top = (top + 1) + 'px';
rotating.style.left = (centerX + getXOffset(y)) + 'px';
}, 16);
Here is a fiddle with a bit more code for trying to get the right amount of distance between points for a smoother animation(currently needs fixing, but it doesn't bother me yet.)
https://jsfiddle.net/shock/1qcfvr4y/
Last note: I know that there might be other ways to do this with CSS, but I chose to use javascript for learning purposes.
Math.sqrt would only return the positive root. You'll have to account for the negative value based on the application. In this case, you need the positive x value during the 1st half of the cycle and negative during the 2nd half.
To do that, you should implement a method to track the progress and reverse the sign accordingly.
Here is a sample. I modified upon yours.
edit:
Instead of Math.sqrt( Math.pow(radius, 2) - Math.pow(y, 2) , 2) You can use the full formula to get x if you do not want to assume origin as center, which in this case is Math.sqrt( Math.pow(radius, 2) - Math.pow((actualY - centerY), 2) , 2)
explanation:
The original equation (x-a)² + (y'-b)² = r²
becomes x = √(r² - (y'-b)²) + a
Assuming .rotating box have 0 width and height.
The variable equivalents in your code are centerX = a, centerY = b.
By assuming origin as center you're basically doing a pre-calculation so that your y value becomes the equivalent of (y'-b). Hence x = √(r² - y²) + a is valid.
At initial state top = 0
i.e (y'-b) => height - centerY.
In your code y = radius => height/2.
Now (height - centerY) being equal to (height/2) is a side effect of your circle being bound by a square container whose height determines the y value.
In other words, when you use origin as center, you are taking the center offsets outside of circle equation and handling it separately. You could do the same thing by using the whole formula, that is, x = √(r² - (y'-b)²) + a
So I've built a small graph application with JavaScript to help me practice using the canvas. I've spent the last 10 hours trying to scale between two points on the X-Axis and can't for the life of me figure it out. I've learned that to scale you need to translate > scale > translate. This works fine when I scale to the far left/right using the following type code.
let x = 0;
let y = this.getCanvasHeight() / 2;
this.getCanvasContext().clearRect(0, 0, this.getCanvas().width, this.getCanvas().height);
this.setCanvas();
ctx.translate(x, y);
ctx.scale(scale, 1);
ctx.translate(-x, -y);
this.resetCanvasLines();
this.renderGraph(this.state.points, scale);
This piece of code simply allows me to zoom into the far left of the graph. So now I'm trying to pick two points on this graph and zoom in on top of them, so that they fit evenly on the screen. The Y-Axis will always be the same.
My thinking was to get the midpoint between the two points and zoom in on that location, which I feel should work but I just can't get it working. My graph width is 3010px and split into 5 segments of 602px. I want to zoom let's say from x1 = 602 and x2 = 1806, which has the midpoint of 1204. Is there a technique to properly calculating the scale amount?
rangeFinder(from, to) {
let points = this.state.points;
if (points.length === 0) {
return;
}
let ctx = this.getCanvasContext();
let canvasWidth = this.getCanvasWidth();
let canvasHeight = this.getCanvasHeight() / 2;
let seconds = this.state.seconds;
let second = canvasWidth / seconds;
let scale = 1;
// My graph starts from zero, goes up to 5 and the values are to represent seconds.
// This gets the pixel value for the fromX value.
let fromX = from * second;
to = isNaN(to) ? 5 : to;
// Get the pixel value for the to location.
let toX = parseInt(to) * second;
let y = canvasHeight / 2;
// get the midpoint between the two points.
let midpoint = fromX + ((toX - fromX) / 2);
// This is where I really go wrong. I'm trying to calculate the scale amount
let zoom = canvasWidth - (toX - fromX);
let zoomPixel = (zoom / 10) / 1000;
let scaleAmount = scale + ((zoom / (canvasWidth / 100)) / 100) + zoomPixel;
ctx.clearRect(0, 0, this.getCanvas().width, this.getCanvas().height);
this.setCanvas();
// translate and scale.
ctx.translate(midpoint, y);
ctx.scale(scaleAmount, 1);
ctx.translate(-midpoint, -y);
this.resetCanvasLines();
this.renderGraph(points);
}
Any help would be great, thanks.
Scale = 5/3 = total width / part width.
After scale, x = 602 should have moved to 602 * 5/3 ~ 1000. Translate the new image by -1000. There is no need to find mid-point.
I have a problem with ChartJS: I need to use a Polar graph for my project and I have to show this graphic in a PDF.
I also need to display the tooltips without hover. The problem is that these tooltips are at the center of each data.
I want this specific one to be found outside of the graph. I modified the Chart.js a lot and now I have:
Unfortunately when the labels are long, the display is not good:
My method is not good. Has someone already managed to display tooltips outside the circle?
Currently the center of your label text is at the position where you want to show the label. If you change it to the start of your label or end of your label for labels on the right and left of your chart, you'll have much better layout.
You could also align your labels closer to the sector end point instead of the outermost edge of the scale.
Here's how you do it
1. Override your scale
So that the chart does not take up the full canvas. I've hardcoded these values for a sample dataset, you could just as easily use the input data to get suitable values
scaleOverride: true,
scaleStartValue: 0,
scaleStepWidth: 40,
scaleSteps: 10,
2. Draw your Labels
The best place would be the end of your animation.
onAnimationComplete: function () {
this.segments.forEach(function (segment) {
Figure out the outer edge of each sector - this is not that difficult. We just use the same function that the tooltip position uses
var outerEdge = Chart.Arc.prototype.tooltipPosition.apply({
x: this.chart.width / 2,
y: this.chart.height / 2,
startAngle: segment.startAngle,
endAngle: segment.endAngle,
outerRadius: segment.outerRadius * 2 + 10,
innerRadius: 0
})
outerRadius decides how far away from the center you want your labels to appear. The x 2 is because the tooltip normally appears in the middle of the sector. The + 10 is padding so that the label does not stick too close to end of the sector
If you want the labels to all appear on the outer edge of the scale use outerRadius = self.scale.drawingArea * 2 (with self set to the Chartjs chart object)
3. Set the text alignment
This is based on whether you are on the right or left side of the graph (or the top or bottom).
For this, first normalize the angle (so that it is within 0 to 2 * PI)
var normalizedAngle = (segment.startAngle + segment.endAngle) / 2;
while (normalizedAngle > 2 * Math.PI) {
normalizedAngle -= (2 * Math.PI)
}
Then simply set the text position depending on the range of the angle (0 radians is on the right side middle and the radians increase anticlockwise).
if (normalizedAngle < (Math.PI * 0.4) || (normalizedAngle > Math.PI * 1.5))
ctx.textAlign = "start";
else if (normalizedAngle > (Math.PI * 0.4) && (normalizedAngle < Math.PI * 0.6)) {
outerEdge.y += 5;
ctx.textAlign = "center";
}
else if (normalizedAngle > (Math.PI * 1.4) && (normalizedAngle < Math.PI * 1.6)) {
outerEdge.y -5;
ctx.textAlign = "center";
}
else
ctx.textAlign = "end";
The "center" makes labels that appear near the top and bottom of the graph have the middle of their text align to the sector edge. The +5 and -5 are padding so that they don't stick too close.
ctx.fillText(segment.label, outerEdge.x, outerEdge.y);
Fiddle - http://jsfiddle.net/nyjodx4v/
And here's how it looks
I n number of divs which are arranged in a circle using JavaScript. Right now I set the dimension of each div to 40×40. Below is what I am able to achieve so far. This is how I find X & Y of each div.
x = 100 * Math.cos(angle) + hCenter;
y = 100 * Math.sin(angle) + vCenter;
where hCenter & vCenter are center point of the screen
When there are many circles they start overlapping each other. How
can I find the height & width of each div so that they fit in circle
with a little space between each other.
How can I arrange the same circles in the square. Means animate from
circle to square. How to find new X,Y position of each div.
How can I find the height & width of each div so that they fit in circle with a little space between each other.
The width and height of each circle are the same as its diameter, which (plus the little splace) is equivalent to the length of the sides of the polygon formed by their positions. You know the size ("diameter") of the large square/circle in which the are arranged, so you can easily compute the length of the sides from that and the number of items. Then subtract a small constant or factor, and you've got your result.
How to find new X,Y position of each div so that they are arranged in a square?
Compute from the angle on which side of the square they will sit. You've got your first coordinate. Then, use sin/cos to compute the position on that side.
var dir = Math.round(angle / Math.PI * 2) % 4,
dis = dir<2 ? 100 : -100;
if (dir % 2 == 0) {
x = hCenter + dis;
y = vCenter + dis * Math.tan(angle);
} else {
x = hCenter + dis / Math.tan(angle);
y = vCenter + dis;
}
I'm trying to find the row, column in a 2d isometric grid of a screen space point (x, y)
Now I pretty much know what I need to do which is find the length of the vectors in red in the pictures above and then compare it to the length of the vector that represent the bounds of the grid (which is represented by the black vectors)
Now I asked for help over at mathematics stack exchange to get the equation for figuring out what the parallel vectors are of a point x,y compared to the black boundary vectors. Link here Length of Perpendicular/Parallel Vectors
but im having trouble converting this to a function
Ideally i need enough of a function to get the length of both red vectors from three sets of points, the x,y of the end of the 2 black vectors and the point at the end of the red vectors.
Any language is fine but ideally javascript
What you need is a base transformation:
Suppose the coordinates of the first black vector are (x1, x2) and the coordinates of the second vector are (y1, y2).
Therefore, finding the red vectors that get at a point (z1, z2) is equivalent to solving the following linear system:
x1*r1 + y1*r2 = z1
x2*r1 + y2*r2 = z2
or in matrix form:
A x = b
/x1 y1\ |r1| = |z1|
\x2 y2/ |r2| |z2|
x = inverse(A)*b
For example, lets have the black vector be (2, 1) and (2, -1). The corresponding matrix A will be
2 2
1 -1
and its inverse will be
1/4 1/2
1/4 -1/2
So a point (x, y) in the original coordinates will be able to be represened in the alternate base, bia the following formula:
(x, y) = (1/4 * x + 1/2 * y)*(2,1) + (1/4 * x -1/2 * y)*(2, -1)
What exactly is the point of doing it like this? Any isometric grid you display usually contains cells of equal size, so you can skip all the vector math and simply do something like:
var xStep = 50,
yStep = 30, // roughly matches your image
pointX = 2*xStep,
pointY = 0;
Basically the points on any isometric grid fall onto the intersections of a non-isometric grid. Isometric grid controller:
screenPositionToIsoXY : function(o, w, h){
var sX = ((((o.x - this.canvas.xPosition) - this.screenOffsetX) / this.unitWidth ) * 2) >> 0,
sY = ((((o.y - this.canvas.yPosition) - this.screenOffsetY) / this.unitHeight) * 2) >> 0,
isoX = ((sX + sY - this.cols) / 2) >> 0,
isoY = (((-1 + this.cols) - (sX - sY)) / 2) >> 0;
// isoX = ((sX + sY) / isoGrid.width) - 1
// isoY = ((-2 + isoGrid.width) - sX - sY) / 2
return $.extend(o, {
isoX : Math.constrain(isoX, 0, this.cols - (w||0)),
isoY : Math.constrain(isoY, 0, this.rows - (h||0))
});
},
// ...
isoToUnitGrid : function(isoX, isoY){
var offset = this.grid.offset(),
isoX = $.uD(isoX) ? this.isoX : isoX,
isoY = $.uD(isoY) ? this.isoY : isoY;
return {
x : (offset.x + (this.grid.unitWidth / 2) * (this.grid.rows - this.isoWidth + isoX - isoY)) >> 0,
y : (offset.y + (this.grid.unitHeight / 2) * (isoX + isoY)) >> 0
};
},
Okay so with the help of other answers (sorry guys neither quite provided the answer i was after)
I present my function for finding the grid position on an iso 2d grid using a world x,y coordinate where the world x,y is an offset screen space coord.
WorldPosToGridPos: function(iPosX, iPosY){
var d = (this.mcBoundaryVectors.upper.x * this.mcBoundaryVectors.lower.y) - (this.mcBoundaryVectors.upper.y * this.mcBoundaryVectors.lower.x);
var a = ((iPosX * this.mcBoundaryVectors.lower.y) - (this.mcBoundaryVectors.lower.x * iPosY)) / d;
var b = ((this.mcBoundaryVectors.upper.x * iPosY) - (iPosX * this.mcBoundaryVectors.upper.y)) / d;
var cParaUpperVec = new Vector2(a * this.mcBoundaryVectors.upper.x, a * this.mcBoundaryVectors.upper.y);
var cParaLowerVec = new Vector2(b * this.mcBoundaryVectors.lower.x, b * this.mcBoundaryVectors.lower.y);
var iGridWidth = 40;
var iGridHeight = 40;
var iGridX = Math.floor((cParaLowerVec.length() / this.mcBoundaryVectors.lower.length()) * iGridWidth);
var iGridY = Math.floor((cParaUpperVec.length() / this.mcBoundaryVectors.upper.length()) * iGridHeight);
return {gridX: iGridX, gridY: iGridY};
},
The first line is best done once in an init function or similar to save doing the same calculation over and over, I just included it for completeness.
The mcBoundaryVectors are two vectors defining the outer limits of the x and y axis of the isometric grid (The black vectors shown in the picture above).
Hope this helps anyone else in the future