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
Related
Lets start from the end, I want to make my column chart look like this:
This is very easy to make using chart.js
Credit: https://devsheet.com/code-snippet/bar-chart-with-circular-shape-from-corner-in-chartjs/
I read already this question: Highcharts: is it possible to set up top border radius for the columns in Column chart?
That suggest to use the package: "highcharts-border-radius".
But this package can make round corners but it will be hardcoded, for example i changed topLeft and topRight corners to have border-radius, then when i will have negative values, it will looks horrible:
I find a solution in pure javascript: http://jsfiddle.net/BlackLabel/vd2Ly9eh/
$(function() {
Highcharts.wrap(Highcharts.seriesTypes.column.prototype, 'translate', function(proceed) {
proceed.apply(this, [].slice.call(arguments, 1));
var series = this,
cpw = 0.166,
shapeArgs,
x, y, h, w;
Highcharts.each(series.points, function(point) {
shapeArgs = point.shapeArgs;
x = shapeArgs.x;
y = shapeArgs.y;
h = shapeArgs.height;
w = shapeArgs.width;
point.shapeType = 'path';
if (point.negative) {
point.shapeArgs = {
d: [
'M', x, y,
'L', x, y + h - w / 2,
'C', x, y + h + w / 5, x + w, y + h + w / 5, x + w, y + h - w / 2, 'L', x + w, y,
'L', x, y
]
};
} else {
point.shapeArgs = {
d: [
'M', x, y + w / 2,
'L', x, y + h,
'L', x + w, y + h,
'L', x + w, y + w / 2,
'C', x + w, y - w / 5, x, y - w / 5, x, y + w / 2
]
};
}
});
});
$('#container').highcharts({
chart: {
type: 'column'
},
series: [{
data: [2, 3, -2, 3, 2]
}]
});
});
But I can't figure out how to solve this problem in React.
So I would like to get help how to make such Column chart with Highcharts in React.
Thanks in advance
You can add the wrap for column series in React almost in the same way as in pure JS:
// Import Highcharts
import Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";
Highcharts.wrap(Highcharts.seriesTypes.column.prototype, "translate", function (
proceed
) {
...
});
Live demo: https://codesandbox.io/s/highcharts-react-demo-ct4d6m?file=/demo.jsx
Docs: https://www.npmjs.com/package/highcharts-react-official
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 have made canvas Shapes(squares) in kinetic js having 4 control points on each of their vertices.A user can click and drag these control points and distort the shape any way he/she pleases.The fiddle link is
http://jsfiddle.net/Lucy1/opsy1pn9/2/
The corresponding js code is
var room = new Kinetic.Shape({
x: 0,
y: 0,
width: w,
height: h,
stroke: "blue",
fill: 'red',
drawFunc: function (context) {
var x = this.x();
var y = this.y();
var w = this.width();
var h = this.height();
var tlX = this.anchorTL.x();
var tlY = this.anchorTL.y();
context.beginPath();
context.moveTo(tlX, tlY);
// top
context.bezierCurveTo(x + w / 3, y, x + w * 2 / 3, y, this.anchorTR.x(), this.anchorTR.y());
// right
context.bezierCurveTo(x + w, y + h / 3, x + w, y + h * 2 / 3, this.anchorBR.x(), this.anchorBR.y());
// bottom
context.bezierCurveTo(x + w * 2 / 3, y + h, x + w / 3, y + h, this.anchorBL.x(), this.anchorBL.y());
// left
context.bezierCurveTo(x, y + h * 2 / 3, x, y + h / 3, tlX, tlY);
context.closePath();
context.fillStrokeShape(this);
}
});
The problem is that i want to add more control points on each square edge. I want the canvas shape to look like this
The square above has 2 control points on each side in addition to 4 control points in the vertices, so each square has total 12 points .I want the squares in the js fiddle also to have 12 control points each.I can't understand where and what modifications to make so that each of the canvas squares has 12 control points..Please Help..
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 .