I need to draw an inverted triangle inside another triangle, using Raphaël.js. Similar to the following ASCII art:
/\
/__\
/\ /\
/__\/__\
The only difference being that I'm drawing it sideways (with the top to the right), in order to apply rotations to it.
Depending on the order in which I draw the lines in the path string, the center may, or may not, be colored with the filling color.
HTML
<div id="paper"></div>
CSS
div#paper {
width: 200px;
height: 200px
}
JavaScript (filling the inside)
function triangles(paper, x, y, w, h, attr) {
var cx = x + w / 2,
cy = y + h / 2,
path = ["M", x, y, "L", x + w, cy, x, y + h, "Z",
"M", cx, y + h/4, "L", cx,
y + h*3/4, x, cy, "Z"].join(" ");
return paper.path(path).attr(attr);
}
var paper = Raphael(document.getElementById("paper"), 200, 200);
triangles(paper, 20, 20, 80, 80, {
fill: "#abcabd"
});
JavaScript (leaves the inside blank)
function triangles(paper, x, y, w, h, attr) {
var cx = x + w / 2,
cy = y + h / 2,
path = ["M", x + w, cy, "L", x, y, x, y + h, "Z",
"M", cx, y + h/4, "L", cx,
y + h*3/4, x, cy, "Z"].join(" ");
return paper.path(path).attr(attr);
}
var paper = Raphael(document.getElementById("paper"), 200, 200);
triangles(paper, 20, 20, 80, 80, {
fill: "#abcabd"
});
Note that the only difference is the first two points.
// Fills the inside:
"M", x, y, "L", x + w, cy
// Doesn't fill the inside:
"M", x + w, cy, "L", x, y
What is causing Raphaël to misbehave?
How do I determine beforehand if the figure is going to be filled?
Demo: jsFiddle
Just toggle the comments between the two blocks of JavaScript.
It's probably related to fill-rule: http://www.w3.org/TR/2011/REC-SVG11-20110816/painting.html#FillRuleProperty .
Related
Is it possible to change the border radius of a bounding box of a selected item? I have read through the documentation of the possible attributes that can be attributed to an object and haven't found anything that specifies the changing of the border radius of an object's bounding box. Is there perhaps an solution through CSS this can be done?
Here's a drop-in replacement for the fabric.Object.prototype.drawBorders method that handles the drawing of selection borders. I've extended it to use the property selectionRadius to determine the amount of border radius to use in the selection box.
var canvas = new fabric.Canvas("canvas");
canvas.add(new fabric.Rect({
width: 150,
height: 100,
left: 25,
top: 25,
fill: 'lightgreen',
strokeWidth: 0,
padding: 20,
selectionRadius: 20,
borderColor: 'red'
}));
fabric.Object.prototype.drawBorders = function(ctx, styleOverride) {
styleOverride = styleOverride || {};
var wh = this._calculateCurrentDimensions(),
strokeWidth = this.borderScaleFactor,
width = wh.x + strokeWidth,
height = wh.y + strokeWidth,
hasControls = typeof styleOverride.hasControls !== 'undefined' ?
styleOverride.hasControls : this.hasControls,
shouldStroke = false;
ctx.save();
ctx.strokeStyle = styleOverride.borderColor || this.borderColor;
this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null);
//start custom draw method with rounded corners
var rx = this.selectionRadius ? Math.min(this.selectionRadius, width / 2) : 0,
ry = this.selectionRadius ? Math.min(this.selectionRadius, height / 2) : 0,
w = width,
h = height,
x = -width / 2,
y = -height / 2,
isRounded = rx !== 0 || ry !== 0,
/* "magic number" for bezier approximations of arcs */
k = 1 - 0.5522847498;
ctx.beginPath();
ctx.moveTo(x + rx, y);
ctx.lineTo(x + w - rx, y);
isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry);
ctx.lineTo(x + w, y + h - ry);
isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h);
ctx.lineTo(x + rx, y + h);
isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry);
ctx.lineTo(x, y + ry);
isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y);
ctx.closePath();
ctx.stroke();
//end custom draw method with rounded corners
if (hasControls) {
ctx.beginPath();
this.forEachControl(function(control, key, fabricObject) {
// in this moment, the ctx is centered on the object.
// width and height of the above function are the size of the bbox.
if (control.withConnection && control.getVisibility(fabricObject, key)) {
// reset movement for each control
shouldStroke = true;
ctx.moveTo(control.x * width, control.y * height);
ctx.lineTo(
control.x * width + control.offsetX,
control.y * height + control.offsetY
);
}
});
if (shouldStroke) {
ctx.stroke();
}
}
ctx.restore();
return this;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>
<canvas id="canvas" height="300" width="400"></canvas>
I spotted this piece of code that generates a rectangle with rounded corners but I would like to be able to increase the size (height and width) of the rectangle as I want.
var canvas = document.getElementById('newCanvas');
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(20, 10);
ctx.lineTo(80, 10);
ctx.quadraticCurveTo(90, 10, 90, 20);
ctx.lineTo(90, 80);
ctx.quadraticCurveTo(90, 90, 80, 90);
ctx.lineTo(20, 90);
ctx.quadraticCurveTo(10, 90, 10, 80);
ctx.lineTo(10, 20);
ctx.quadraticCurveTo(10, 10, 20, 10);
ctx.stroke();
You need to convert your static values to (x, y) coordinates and [width × height] dimension variables.
I took what you had and reverse-engineered the formulas to calculate your static drawing. Take your existing variables and change them to x or y and add the width or height to them and optionally add or subtract the radius where necessary.
const drawRoundedRect = (ctx, x, y, width, height, radius) => {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.stroke();
};
const canvas = document.getElementById('new-canvas');
const ctx = canvas.getContext('2d');
ctx.strokeStyle = 'black';
ctx.strokeRect(10, 10, 80, 80);
ctx.strokeStyle = 'red';
drawRoundedRect(ctx, 10, 10, 80, 80, 10);
ctx.strokeStyle = 'green';
drawRoundedRect(ctx, 20, 20, 60, 60, 14);
<canvas id="new-canvas"></canvas>
Don't forget to join path ends
I noticed that you forgot to close the path. This can result in a slight seam or bump at the start / end of the path depending on the ctx.lineJoin setting.
The call to ctx.closePath connects the end to the start with a line
Visual design
Visual design rules for the type of curves to use.
Beziers for curves that are part of things that move quickly
Circle for things that are static or move slowly
Bezier curves can never exactly fit a circle. Quadratic beziers are very bad fits. If you must use a bezier curve use a cubic bezier to get a better fit.
Best approximation of a circle using a cubic bezier is to inset control points by c = 0.55191502449 as fraction of radius. This will result in the minimum possible radial error of 0.019608%
Example shows the difference between a cubic (black) and quadratic (red) curves.
const ctx = canvas.getContext('2d');
ctx.strokeStyle = 'red';
drawRoundedRectQuad(ctx, 10, 10, 180, 180, 70);
ctx.strokeStyle = 'black';
drawRoundedRect(ctx, 10, 10, 180, 180, 70);
function drawRoundedRect(ctx, x, y, w, h, r) {
const c = 0.55191502449;
const cP = r * (1 - c);
const right = x + w;
const bottom = y + h;
ctx.beginPath();
ctx.lineTo(right - r, y);
ctx.bezierCurveTo(right - cP, y, right, y + cP, right, y + r);
ctx.lineTo(right, bottom - r);
ctx.bezierCurveTo(right, bottom - cP, right - cP, bottom, right - r, bottom);
ctx.lineTo(x + r, bottom);
ctx.bezierCurveTo(x + cP, bottom, x, bottom - cP, x, bottom - r);
ctx.lineTo(x, y + r);
ctx.bezierCurveTo(x, y + cP , x + cP, y, x + r, y);
ctx.closePath();
ctx.stroke();
}
function drawRoundedRectQuad(ctx, x, y, w, h, r){
ctx.beginPath();
ctx.lineTo(x + w- r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h- r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h- r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath();
ctx.stroke();
};
<canvas id="canvas" width ="200" height="200"></canvas>
Rounded corners to match CSS border-radius
To get a true rounded rectangle (circles rather than approx curve) use ctx.arc to create the rounded corners.
Extending the 2D API with roundedRect
The code below draws a rounded rectangle by adding the functions strokeRoundedRect(x, y, w, [h, [r]]), fillRoundedRect(x, y, w, [h, [r]]), and roundedRect(x, y, w, [h, [r]]) to the 2D context prototype.
Arguments
x, y, w, [h, [r]]
x, y Top left of rounded rectangle
w, Width of rounded rectangle
h, Optional height of rectangle. Defaults to value of width (creates rounded square)
r Optional radius or corners. Default is 0 (no rounded corners). If value is negative then a radius of 0 is used. If r > than half the width or height then r is change to Math.min(w * 0.5, h * 0.5)
Example
Including implementation of round rectangle extensions.
function Extend2DRoundedRect() {
const p90 = Math.PI * 0.5;
const p180 = Math.PI;
const p270 = Math.PI * 1.5;
const p360 = Math.PI * 2;
function roundedRect(x, y, w, h = w, r = 0) {
const ctx = this;
if (r < 0) { r = 0 }
if (r === 0) {
ctx.rect(x, y, w, h);
return;
}
r = Math.min(r, w * 0.5, h * 0.5)
ctx.moveTo(x, y + r);
ctx.arc(x + r , y + r , r, p180, p270);
ctx.arc(x + w - r, y + r , r, p270, p360);
ctx.arc(x + w - r, y + h - r, r, 0 , p90);
ctx.arc(x + r , y + h - r, r, p90 , p180);
ctx.closePath();
}
function strokeRoundedRect(...args) {
const ctx = this;
ctx.beginPath();
ctx.roundedRect(...args);
ctx.stroke();
}
function fillRoundedRect(...args) {
const ctx = this;
ctx.beginPath();
ctx.roundedRect(...args);
ctx.fill();
}
CanvasRenderingContext2D.prototype.roundedRect = roundedRect;
CanvasRenderingContext2D.prototype.strokeRoundedRect = strokeRoundedRect;
CanvasRenderingContext2D.prototype.fillRoundedRect = fillRoundedRect;
}
Extend2DRoundedRect();
// Using rounded rectangle extended 2D context
const ctx = canvas.getContext('2d');
ctx.strokeStyle = "#000";
ctx.strokeRoundedRect(10.5, 10.5, 180, 180); // no radius render rectangle
ctx.strokeRoundedRect(210.5, 10.5, 180, 180, 20); // Draw 1px line along center of pixels
ctx.strokeRoundedRect(20, 20, 160, 160, 30);
ctx.fillRoundedRect(30, 30, 140, 140, 20);
ctx.fillRoundedRect(230, 30, 140, 40, 20); // Circle ends
ctx.fillRoundedRect(230, 80, 140, 20, 20); // Auto circle ends
ctx.fillRoundedRect(280, 120, 40, 40, 120); // circle all sides
var inset = 0;
ctx.beginPath();
while (inset < 80) {
ctx.roundedRect(
10 + inset, 210 + inset,
380 - inset * 2, 180 - inset * 2,
50 - inset
);
inset += 8;
}
ctx.fill("evenodd");
<canvas id="canvas" width="400" height="400"></canvas>
I'm trying to build a column chart with a custom tooltip position using Highcharts. I want to make tooltip's arrow (anchor) always visible at the bottom of the tooltip. For now it's only visible when I remove the custom tooltip positioner function.
I've tried to override the move method of the Tooltip class and set skipAnchor to false there. However, it didn't work.
Please see the example:
https://jsfiddle.net/jezusro6/
You should overwrite the callout symbol method:
H.SVGRenderer.prototype.symbols.callout = function(x, y, w, h, options) {
var arrowLength = 6,
halfDistance = 6,
r = Math.min((options && options.r) || 0, w, h),
safeDistance = r + halfDistance,
anchorX = options && options.anchorX,
anchorY = options && options.anchorY,
path;
path = [
'M', x + r, y,
'L', x + w - r, y, // top side
'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
'L', x + w, y + h - r, // right side
'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-rgt
'L', x + r, y + h, // bottom side
'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
'L', x, y + r, // left side
'C', x, y, x, y, x + r, y // top-left corner
];
path.splice(
23,
3,
'L', anchorX + halfDistance, y + h,
anchorX, y + h + arrowLength,
anchorX - halfDistance, y + h,
x + r, y + h
);
return path;
}
Live demo: https://jsfiddle.net/BlackLabel/g5h27mfx/
Docs: https://www.highcharts.com/docs/extending-highcharts
I am trying to draw an elliptical arc which can animate(bounce) using raphael. So far I have been following http://jsfiddle.net/jonhartmann/vm0etvz9/light/ to draw the arc. This is the output which I am getting from the link above.
But I need to get an output like an shell(below Image) starting the arc from 2nd quadrant with x=-30 and y=0(x can be anything on negative x axis depends on RADIUS and y should be 0).
I have attached my version of code to achieve the result.
http://jsfiddle.net/vssb7n25/
var archtype = Raphael('graph', 100, 100);
archtype.customAttributes.arc = function (value) {
var xloc = 50,
yloc = 50,
total = 100,
R = 30,
alpha = 180 / total * value,
a = (180 - alpha) * Math.PI / 180,
x = xloc + R * Math.cos(a),
y = yloc - R * Math.sin(a),
path;
if (total == value) {
path = [
["M", xloc, yloc + R],
["A", R, R, 0, +(alpha > 180), 1, x, y]
];
}else{
path = [
["M", xloc, yloc - R],
["A", R, R, 0, +(alpha > 180), 1, x, y]
];
}
return {
path: path
};
};
It works fine but adds an extra arc on the bottom.
Could anyone help me out in drawing raphael elliptical arc.
Thank you
From the original sample you need to change starting and ending points of the arc.
The starting point (-1,0) becomes
["M", xloc - R, yloc],
And the ending point is
x = xloc - R * Math.sin(a),
y = yloc - R * Math.cos(a),
Using HTML5 Canvas and Javascript I need to display different values (represented by a dot maybe) at different angles inside a circle.
Example data:
val 34% # 0°,
val 54% # 12°,
val 23% # 70°,
and so on...
If I have a canvas 300 x 300px and the center of the circle is located at x: 150px and y: 150px with a radius of 150px, how would I calculate where to set my dot for the value 54% at 12 degrees?
My math is kinda terrible xD
I'd appreciate any kind of help and please ask questions if I do not make myself clear enough.
Thank you for listening and thank you in advance for you deep insights :D
EDIT (to explain in more detail):
Here is an image to illustrate what I am trying to accomplish:
I hope this makes my question a little more understandable.
(As you can see, not the same values as above)
Ty for your patience!
You may use this to convert from polar (radius, angle) coordinates to cartesian ones :
// θ : angle in [0, 2π[
function polarToCartesian(r, θ) {
return {x: r*Math.cos(θ), y: r*Math.sin(θ)};
}
For example, if you want to draw at 12°, you may compute the point like this :
var p = polarToCartesian(150, 12*2*Math.PI/360);
p.x += 150; p.y += 150;
EDIT : my polarToCartesian function takes radians as input, as many function in the Canvas API. If you're more used to degrees, you may need this :
function degreesToRadians(a) {
return Math.PI*a/180;
}
Here you go (demo)
var can = document.getElementById('mycanvas');
var ctx = can.getContext('2d');
var drawAngledLine = function(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
var drawCircle = function(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}
var drawDot = function(x, y, length, angle, value) {
var radians = angle / 180 * Math.PI;
var endX = x + length*value/100 * Math.cos(radians);
var endY = y - length*value/100 * Math.sin(radians);
drawCircle(endX, endY, 2);
}
var drawText = function(x, y, length, angle, value) {
var radians = angle / 180 * Math.PI;
var endX = x + length*value/100 * Math.cos(radians);
var endY = y - length*value/100 * Math.sin(radians);
console.debug(endX+","+endY);
ctx.fillText(value+"%", endX+15, endY+5);
ctx.stroke();
}
var visualizeData = function(x, y, length, angle, value) {
ctx.strokeStyle = "#999";
ctx.lineWidth = "1";
drawAngledLine(x, y, length, angle);
ctx.fillStyle = "#0a0";
drawDot(x, y, length, angle, value);
ctx.fillStyle = "#666";
ctx.font = "bold 10px Arial";
ctx.textAlign = "center";
drawText(x, y, length, angle, value);
}
ctx.fillStyle = "#FFF0B3";
drawCircle(150, 150, 150);
visualizeData(150, 150, 150, 0, 34);
visualizeData(150, 150, 150, 12, 54);
visualizeData(150, 150, 150, 70, 23)
visualizeData(150, 150, 150, 120, 50);
visualizeData(150, 150, 150, -120, 80);
visualizeData(150, 150, 150, -45, 60);