highcharts- chart - highcharts

Could someone guide me on how to get the no of bubbles in highcharts

You can use pointFormatter to calculate the content of the tooltip dynamically. In the callback you can check if the hovered bubble overlaps with other bubbles.
function areOverlapping(bubble1, bubble2) {
const {translateX: tx, translateY: ty } = bubble1.series.group
const x1 = tx + bubble1.plotX
const y1 = ty + bubble1.plotY
const r1 = bubble1.marker.radius
const x2 = tx + bubble2.plotX
const y2 = ty + bubble2.plotY
const r2 = bubble2.marker.radius
const x = x1 - x2
const y = y1 - y2
const r = r1 + r2
return x * x + y * y <= r * r
}
Tooltip.pointFormatter:
series: [{
tooltip: {
pointFormatter: function () {
const overlapCount = this.series.data.reduce((sum, point) => {
return sum + (point !== this && areOverlapping(this, point))
}, 0)
return 'Overlapping bubbles: ' + overlapCount
}
},
example: https://jsfiddle.net/0yzkfdjr/

Related

Is there a image pattern stroke option for lines?

I've been trying to look over the Konva shape library and haven't found a stroke reapeating pattern method. I've been trying to look for a way to implement https://stackoverflow.com/a/32323610/20557085 into the shape's sceneFunc, but ended up with a static version that keeps itself in the top right corner of the canvas at all times, even if the canvas/camera is moved/dragged.
The end-goal would be to have a image that repeats itself following a line's bezier curve of points, that I can change the width of.
The question would be if there is something I am missing that is already a part of Konva, or if I should continue to trial my way through the sceneFunc?
The class component used in my attempt, that ended up static:
import React, { Component } from 'react';
import { createRoot } from 'react-dom/client';
import { Stage, Layer, Image, Shape } from 'react-konva';
var PI = Math.PI;
class URLImageStroke extends React.Component {
constructor(props) {
super(props)
this.state = {
image: null,
points: [{ x: 0, y: 0 }, { x: 100, y: 100 }, { x: 150, y: 50 }, { x: 200, y: 200 }]
};
}
componentDidMount() {
this.loadImage();
this.getPoints()
}
loadImage() {
// save to "this" to remove "load" handler on unmount
this.image = new window.Image();
this.image.src = this.props.src;
this.image.addEventListener('progress', (e) => console.log(e))
this.image.addEventListener('load', this.handleLoad);
}
handleLoad = () => {
this.setState({
image: this.image,
});
};
getPoints = () => {
let points = [];
//for (let i = 0; this.state.points.length > i; i++) {
const s = this.state.points[0];
const c1 = this.state.points[1];
const c2 = this.state.points[2];
const e = this.state.points[3];
for (var t = 0; t <= 100; t += 0.25) {
var T = t / 100;
// plot a point on the curve
var pos = getCubicBezierXYatT(s, c1, c2, e, T);
// calculate the tangent angle of the curve at that point
var tx = bezierTangent(s.x, c1.x, c2.x, e.x, T);
var ty = bezierTangent(s.y, c1.y, c2.y, e.y, T);
var a = Math.atan2(ty, tx) - PI / 2;
// save the x/y position of the point and the tangent angle
// in the points array
points.push({
x: pos.x,
y: pos.y,
angle: a
});
}
this.setState({
points: points
});
}
render() {
return (
<Shape
x={50}
y={50}
width={this.props?.width}
height={this.props?.height}
image={this.state.image}
points={this.state?.points}
sceneFunc={(ctx, shape) => {
const img = shape.attrs.image;
if (!img) {
console.log("no image")
return;
}
const points = shape.attrs.points;
if (!points) {
console.log("no points")
return;
}
// Note: increase the lineWidth if
// the gradient has noticable gaps
ctx.lineWidth = 8;
ctx.strokeStyle = 'skyblue';
let sliceCount = 0;
// draw a gradient-stroked line tangent to each point on the curve
for (let i = 0; i < points.length; i++) {
let p = points[i];
ctx.translate(p.x, p.y);
ctx.rotate(p.angle - PI / 2);
// draw multiple times to fill gaps on outside of rope slices
ctx.drawImage(img, sliceCount, 0, 1, img.height, 0, 0, 1, img.height);
ctx.drawImage(img, sliceCount, 0, 1, img.height, 0, 0, 1, img.height);
ctx.drawImage(img, sliceCount, 0, 1, img.height, 0, 0, 1, img.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
++sliceCount;
if (sliceCount > (img.width - 1)) { sliceCount = 0; }
}
//ctx.strokeShape(this);
}
}
/>
);
}
}
//////////////////////////////////////////
// helper functions
//////////////////////////////////////////
// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)
function getCubicBezierXYatT(startPt, controlPt1, controlPt2, endPt, T) {
var x = CubicN(T, startPt.x, controlPt1.x, controlPt2.x, endPt.x);
var y = CubicN(T, startPt.y, controlPt1.y, controlPt2.y, endPt.y);
return ({ x: x, y: y });
}
// cubic helper formula at T distance
function CubicN(T, a, b, c, d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T
+ (3 * b + T * (-6 * b + b * 3 * T)) * T
+ (c * 3 - c * 3 * T) * t2
+ d * t3;
}
// calculate the tangent angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};
export default URLImageStroke;

Histogram based on image as vector graphic

I would like to transform histograms based on images to vector graphics.
This could be a start:
function preload() {
img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Cirrus_sky_panorama.jpg/1200px-Cirrus_sky_panorama.jpg");
}
function setup() {
createCanvas(400, 400);
background(255);
img.resize(0, 200);
var maxRange = 256
colorMode(HSL, maxRange);
image(img, 0, 0);
var histogram = new Array(maxRange);
for (i = 0; i <= maxRange; i++) {
histogram[i] = 0
}
loadPixels();
for (var x = 0; x < img.width; x += 5) {
for (var y = 0; y < img.height; y += 5) {
var loc = (x + y * img.width) * 4;
var h = pixels[loc];
var s = pixels[loc + 1];
var l = pixels[loc + 2];
var a = pixels[loc + 3];
b = int(l);
histogram[b]++
}
}
image(img, 0, 0);
stroke(300, 100, 80)
push()
translate(10, 0)
for (x = 0; x <= maxRange; x++) {
index = histogram[x];
y1 = int(map(index, 0, max(histogram), height, height - 300));
y2 = height
xPos = map(x, 0, maxRange, 0, width - 20)
line(xPos, y1, xPos, y2);
}
pop()
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
But I would need downloadable vector graphic files as results that are closed shapes without any gaps between. It should look like that for example:
<svg viewBox="0 0 399.84 200"><polygon points="399.84 200 399.84 192.01 361.91 192.01 361.91 182.87 356.24 182.87 356.24 183.81 350.58 183.81 350.58 184.74 344.91 184.74 344.91 188.19 339.87 188.19 339.87 189.89 334.6 189.89 334.6 185.29 328.93 185.29 328.93 171.11 323.26 171.11 323.26 172.55 317.59 172.55 317.59 173.99 311.92 173.99 311.92 179.42 306.88 179.42 306.88 182.03 301.21 182.03 301.21 183.01 295.54 183.01 295.54 179.04 289.87 179.04 289.87 175.67 284.21 175.67 284.21 182.03 278.54 182.03 278.54 176 273.5 176 273.5 172.42 267.83 172.42 267.83 179.42 262.79 179.42 262.79 182.03 257.12 182.03 257.12 183.01 251.45 183.01 251.45 178.63 245.78 178.63 245.78 175.21 240.11 175.21 240.11 182.03 234.86 182.03 234.86 150.42 229.2 150.42 229.2 155.98 223.53 155.98 223.53 158.06 217.86 158.06 217.86 167.44 212.19 167.44 212.19 162.58 206.52 162.58 206.52 155.98 200.85 155.98 200.85 158.06 195.18 158.06 195.18 167.44 189.51 167.44 189.51 177.46 183.84 177.46 183.84 166.93 178.17 166.93 178.17 153.69 172.5 153.69 172.5 155.87 166.82 155.87 166.82 158.05 161.78 158.05 161.78 155.63 156.11 155.63 156.11 160.65 150.84 160.65 150.84 146.59 145.17 146.59 145.17 109.63 139.49 109.63 139.49 113.67 133.82 113.67 133.82 61.48 128.15 61.48 128.15 80.59 123.11 80.59 123.11 93.23 117.44 93.23 117.44 97.97 111.76 97.97 111.76 78.07 106.09 78.07 106.09 61.66 100.42 61.66 100.42 93.23 94.75 93.23 94.75 98.51 89.7 98.51 89.7 85.4 84.03 85.4 84.03 111.03 78.99 111.03 78.99 120.57 73.32 120.57 73.32 124.14 67.65 124.14 67.65 23.48 61.97 23.48 61.97 0 56.3 0 56.3 120.57 50.63 120.57 50.63 167.01 45.38 167.01 45.38 170.83 39.71 170.83 39.71 172.26 34.03 172.26 34.03 178.7 28.36 178.7 28.36 175.36 22.69 175.36 22.69 170.83 17.02 170.83 17.02 172.26 11.34 172.26 11.34 178.7 5.67 178.7 5.67 103.85 0 103.85 0 200 399.84 200"/></svg>
Has anyone an idea how to program that? It doesn't necessarily need to be based on p5.js, but would be cool.
Closing Gaps
In order to have a gapless histogram, you need to meet the following condition:
numberOfBars * barWidth === totalWidth
Right now you are using the p5 line() function to draw your bars. You have not explicitly set the width of your bars, so it uses the default value of 1px wide.
We know that the numberOfBars in your code is always maxRange which is 256.
Right now the total width of your histogram is width - 20, where width is set to 400 by createCanvas(400, 400). So the totalWidth is 380.
256 * 1 !== 380
If you have 256 pixels of bars in a 380 pixel space then there are going to be gaps!
We need to change the barWidth and/or the totalWidth to balance the equation.
For example, you can change your canvas size to 276 (256 + your 20px margin) and the gaps disappear!
createCanvas(276, 400);
However this is not an appropriate solution because now your image is cropped and your pixel data is wrong. But actually...it was already wrong before!
Sampling Pixels
When you call the global loadPixels() function in p5.js you are loading all of the pixels for the whole canvas. This includes the white areas outside of your image.
for (var x = 0; x < img.width; x += 5) {
for (var y = 0; y < img.height; y += 5) {
var loc = (x + y * img.width) * 4;
It is a 1-dimensional array, so your approach of limiting the x and y values here is not giving you the correct position. Your loc variable needs to use the width of the entire canvas rather than the width of just the image, since the pixels array includes the entire canvas.
var loc = (x + y * width) * 4;
Alternatively, you can look at just the pixels of the image by using img.loadPixels() and img.pixels.
img.loadPixels();
for (var x = 0; x < img.width; x += 5) {
for (var y = 0; y < img.height; y += 5) {
var loc = (x + y * img.width) * 4;
var h = img.pixels[loc];
var s = img.pixels[loc + 1];
var l = img.pixels[loc + 2];
var a = img.pixels[loc + 3];
b = int(l);
histogram[b]++;
}
}
The pixel values are always returned in RGBA regardless of the colorMode. So your third channel value is actually the blue, not the lightness. You can make use of the p5.js lightness() function to compute the lightness from the RGBA.
Updated Code
The actual lightness histogram looks dumb because 100% dwarfs all of the other bars.
function preload() {
img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Cirrus_sky_panorama.jpg/1200px-Cirrus_sky_panorama.jpg");
}
function setup() {
const barCount = 100;
const imageHeight = 200;
createCanvas(400, 400);
background(255);
colorMode(HSL, barCount - 1);
img.resize(0, imageHeight);
imageMode(CENTER);
image(img, width / 2, imageHeight / 2);
img.loadPixels();
const histogram = new Array(barCount).fill(0);
for (let x = 0; x < img.width; x += 5) {
for (let y = 0; y < img.height; y += 5) {
const loc = (x + y * img.width) * 4;
const r = img.pixels[loc];
const g = img.pixels[loc + 1];
const b = img.pixels[loc + 2];
const a = img.pixels[loc + 3];
const barIndex = floor(lightness([r, g, b, a]));
histogram[barIndex]++;
}
}
fill(300, 100, 80);
strokeWeight(0);
const maxCount = max(histogram);
const barWidth = width / barCount;
const histogramHeight = height - imageHeight;
for (let i = 0; i < barCount; i++) {
const count = histogram[i];
const y1 = round(map(count, 0, maxCount, height, imageHeight));
const y2 = height;
const x1 = i * barWidth;
const x2 = x1 + barWidth;
rect(x1, y1, barWidth, height - y1);
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
But the blue channel histogram looks pretty good!
function preload() {
img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Cirrus_sky_panorama.jpg/1200px-Cirrus_sky_panorama.jpg");
}
function setup() {
const barCount = 100;
const imageHeight = 200;
createCanvas(400, 400);
background(255);
img.resize(0, imageHeight);
imageMode(CENTER);
image(img, width / 2, imageHeight / 2);
img.loadPixels();
const histogram = new Array(barCount).fill(0);
for (let x = 0; x < img.width; x += 5) {
for (let y = 0; y < img.height; y += 5) {
const loc = (x + y * img.width) * 4;
const r = img.pixels[loc];
const g = img.pixels[loc + 1];
const b = img.pixels[loc + 2];
const a = img.pixels[loc + 3];
const barIndex = floor(barCount * b / 255);
histogram[barIndex]++;
}
}
fill(100, 100, 300);
strokeWeight(0);
const maxCount = max(histogram);
const barWidth = width / barCount;
const histogramHeight = height - imageHeight;
for (let i = 0; i < barCount; i++) {
const count = histogram[i];
const y1 = round(map(count, 0, maxCount, height, imageHeight));
const y2 = height;
const x1 = i * barWidth;
const x2 = x1 + barWidth;
rect(x1, y1, barWidth, height - y1);
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
Just to add to Linda's excellent answer(+1), you can use p5.svg to render to SVG using p5.js:
let histogram;
function setup() {
createCanvas(660, 210, SVG);
background(255);
noStroke();
fill("#ed225d");
// make an array of 256 random values in the (0, 255) range
histogram = Array.from({length: 256}, () => int(random(255)));
//console.log(histogram);
// plot the histogram
barPlot(histogram, 0, 0, width, height);
// change shape rendering so bars appear connected
document.querySelector('g').setAttribute('shape-rendering','crispEdges');
// save the plot
save("histogram.svg");
}
function barPlot(values, x, y, plotWidth, plotHeight){
let numValues = values.length;
// calculate the width of each bar in the plot
let barWidth = plotWidth / numValues;
// calculate min/max value (to map height)
let minValue = min(values);
let maxValue = max(values);
// for each value
for(let i = 0 ; i < numValues; i++){
// map the value to the plot height
let barHeight = map(values[i], minValue, maxValue, 0, plotHeight);
// render each bar, offseting y
rect(x + (i * barWidth),
y + (plotHeight - barHeight),
barWidth, barHeight);
}
}
<script src="https://unpkg.com/p5#1.3.1/lib/p5.js"></script>
<script src="https://unpkg.com/p5.js-svg#1.0.7"></script>
(In the p5 editor (or when testing locally) a save dialog should pop up.
If you use the browser's Developer Tools to inspect the bar chart it should confirm it's an SVG (not <canvas/>))

Highcharts Negative Values Column Graph Bottom Radius

I am having column graph which contains positive and negative values column graph, I need to give the border radius top only for the positive and negative Graph. But if I'm trying to add the border radius top for the negative column graph it was not working. Kindly let me know how to give the only border radius top for negative column graph using highcharts in react js.
refer image here
For example ,in the image provided, for I want border radius at -5% for negative value .For positive value I want border radius at top of bar.
I prepared a custom code that adds the wanted border radius for positive value - on the top, for negative - on the bottom of the column.
$(function() {
'use strict';
(function(factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory;
} else {
factory(Highcharts);
}
}(function(Highcharts) {
(function(H) {
H.wrap(H.seriesTypes.column.prototype, 'translate', function(proceed) {
const options = this.options;
const topMargin = options.topMargin || 0;
const bottomMargin = options.bottomMargin || 0;
proceed.call(this);
H.each(this.points, function(point) {
console.log(point)
if (options.customRadius) {
const w = point.shapeArgs.width;
const h = point.shapeArgs.height;
const x = point.shapeArgs.x;
const y = point.shapeArgs.y;
let radiusTopLeft,
radiusTopRight,
radiusBottomRight,
radiusBottomLeft;
if (point.y > 0) {
radiusTopLeft = H.relativeLength(options.customRadius, w);
radiusTopRight = H.relativeLength(options.customRadius, w);
radiusBottomLeft = 0;
radiusBottomRight = 0;
} else {
radiusTopLeft = 0;
radiusTopRight = 0;
radiusBottomRight = H.relativeLength(options.customRadius, w);
radiusBottomLeft = H.relativeLength(options.customRadius, w);
}
const maxR = Math.min(w, h) / 2
radiusTopLeft = radiusTopLeft > maxR ? maxR : radiusTopLeft;
radiusTopRight = radiusTopRight > maxR ? maxR : radiusTopRight;
radiusBottomRight = radiusBottomRight > maxR ? maxR : radiusBottomRight;
radiusBottomLeft = radiusBottomLeft > maxR ? maxR : radiusBottomLeft;
point.dlBox = point.shapeArgs;
point.shapeType = 'path';
point.shapeArgs = {
d: [
'M', x + radiusTopLeft, y + topMargin,
'L', x + w - radiusTopRight, y + topMargin,
'C', x + w - radiusTopRight / 2, y, x + w, y + radiusTopRight / 2, x + w, y + radiusTopRight,
'L', x + w, y + h - radiusBottomRight,
'C', x + w, y + h - radiusBottomRight / 2, x + w - radiusBottomRight / 2, y + h, x + w - radiusBottomRight, y + h + bottomMargin,
'L', x + radiusBottomLeft, y + h + bottomMargin,
'C', x + radiusBottomLeft / 2, y + h, x, y + h - radiusBottomLeft / 2, x, y + h - radiusBottomLeft,
'L', x, y + radiusTopLeft,
'C', x, y + radiusTopLeft / 2, x + radiusTopLeft / 2, y, x + radiusTopLeft, y,
'Z'
]
};
}
});
});
}(Highcharts));
}));
Demo: http://jsfiddle.net/BlackLabel/okn8qhdb/

Is there a way to check if an XYZ triplet is a valid color?

The XYZ color space encompasses all possible colors, not just those which can be generated by a particular device like a monitor. Not all XYZ triplets represent a color that is physically possible. Is there a way, given an XYZ triplet, to determine if it represents a real color?
I wanted to generate a CIE 1931 chromaticity diagram (seen bellow) for myself, but wasn't sure how to go about it. It's easy to, for example, take all combinations of sRGB triplets and then transform them into the xy coordinates of the chromaticity diagram and then plot them. You cannot use this same approach in the XYZ color space though since not all combinations are valid colors. So far the best I have come up with is a stochastic approach, where I generate a random spectral distribution by summing a random number of random Gaussians, then converting it to XYZ using the standard observer functions.
Having thought about it a little more I felt the obvious solution is to generate a list of xy points around the edge of spectral locus, corresponding to pure monochromatic colors. It seems to me that this can be done by directly inputting the visible frequencies (~380-780nm) into the CIE XYZ standard observer color matching functions. Treating these points like a convex polygon you could determine if a point is within the spectral locus using one algorithm or another. In my case, since what I really wanted to do is simply generate the chromaticity diagram, I simply input these points into a graphics library's polygon drawing routine and then for each pixel of the polygon I can transform it into sRGB.
I believe this solution is similar to the one used by the library that Kel linked in a comment. I'm not entirely sure, as I am not familiar with Python.
function RGBfromXYZ(X, Y, Z) {
const R = 3.2404542 * X - 1.5371385 * Y - 0.4985314 * Z
const G = -0.969266 * X + 1.8760108 * Y + 0.0415560 * Z
const B = 0.0556434 * X - 0.2040259 * Y + 1.0572252 * Z
return [R, G, B]
}
function XYZfromYxy(Y, x, y) {
const X = Y / y * x
const Z = Y / y * (1 - x - y)
return [X, Y, Z]
}
function srgb_from_linear(x) {
if (x <= 0.0031308) {
return x * 12.92
} else {
return 1.055 * Math.pow(x, 1/2.4) - 0.055
}
}
// Analytic Approximations to the CIE XYZ Color Matching Functions
// from Sloan http://jcgt.org/published/0002/02/01/paper.pdf
function xFit_1931(x) {
const t1 = (x - 442) * (x < 442 ? 0.0624 : 0.0374)
const t2 = (x -599.8) * (x < 599.8 ? 0.0264 : 0.0323)
const t3 = (x - 501.1) * (x < 501.1 ? 0.0490 : 0.0382)
return 0.362 * Math.exp(-0.5 * t1 * t1) + 1.056 * Math.exp(-0.5 * t2 * t2) - 0.065 * Math.exp(-0.5 * t3 * t3)
}
function yFit_1931(x) {
const t1 = (x - 568.8) * (x < 568.8 ? 0.0213 : 0.0247)
const t2 = (x - 530.9) * (x < 530.9 ? 0.0613 : 0.0322)
return 0.821 * Math.exp(-0.5 * t1 * t1) + 0.286 * Math.exp(-0.5 * t2 * t2)
}
function zFit_1931(x) {
const t1 = (x - 437) * (x < 437 ? 0.0845 : 0.0278)
const t2 = (x - 459) * (x < 459 ? 0.0385 : 0.0725)
return 1.217 * Math.exp(-0.5 * t1 * t1) + 0.681 * Math.exp(-0.5 * t2 * t2)
}
const canvas = document.createElement("canvas")
document.body.append(canvas)
canvas.width = canvas.height = 512
const ctx = canvas.getContext("2d")
const locus_points = []
for (let i = 440; i < 650; ++i) {
const [X, Y, Z] = [xFit_1931(i), yFit_1931(i), zFit_1931(i)]
const x = (X / (X + Y + Z)) * canvas.width
const y = (Y / (X + Y + Z)) * canvas.height
locus_points.push([x, y])
}
ctx.beginPath()
ctx.moveTo(...locus_points[0])
locus_points.slice(1).forEach(point => ctx.lineTo(...point))
ctx.closePath()
ctx.fill()
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
for (let y = 0; y < canvas.height; ++y) {
for (let x = 0; x < canvas.width; ++x) {
const alpha = imageData.data[(y * canvas.width + x) * 4 + 3]
if (alpha > 0) {
const [X, Y, Z] = XYZfromYxy(1, x / canvas.width, y / canvas.height)
const [R, G, B] = RGBfromXYZ(X, Y, Z)
const r = Math.round(srgb_from_linear(R / Math.sqrt(R**2 + G**2 + B**2)) * 255)
const g = Math.round(srgb_from_linear(G / Math.sqrt(R**2 + G**2 + B**2)) * 255)
const b = Math.round(srgb_from_linear(B / Math.sqrt(R**2 + G**2 + B**2)) * 255)
imageData.data[(y * canvas.width + x) * 4 + 0] = r
imageData.data[(y * canvas.width + x) * 4 + 1] = g
imageData.data[(y * canvas.width + x) * 4 + 2] = b
}
}
}
ctx.putImageData(imageData, 0, 0)

Fix position mouse cursor

When I drag the svg(red) to another position on the screen, the cursor mouse loses the original angular position (lag) and points to blank area ... I used a var dx and dy in "var point e.clientx" to fix it, unsuccessfully ... any suggestions?
code: http://jsfiddle.net/rebecavascon/evgLhdz3/2/
show: http://jsfiddle.net/rebecavascon/evgLhdz3/2/show/
function updateSVG(e) {
if (follow) {
var centerPoint = new Point(center[0].getAttribute("cx"), center[0].getAttribute("cy"));
var point = new Point(e.clientX, e.clientY);
var angle = Math.round(100 * getAngleFromPoint(point, centerPoint)) / 100;
var distance = Math.round(getDistance(point, centerPoint));
var d = "M " + centerPoint.X + " " + centerPoint.Y + " L " + point.X + " " + point.Y;
path.attr("d", d);
txt.attr("x", point.X);
txt.attr("y", point.Y);
txt.html(distance + arrows + " (" + angle + degree + ")");
txt.attr("transform", "rotate(" + angle + " " + point.X + " " + point.Y + ")");
dynamic.attr("r", distance);
}
fitSVG();
}
Creating an offset worked for my testing.
Code: http://jsfiddle.net/Twisty/8zx8p2wf/19/
Working: http://jsfiddle.net/Twisty/8zx8p2wf/19/show/
Added Function getCenter()
function getCenter(target) {
var b, x, y, w, h, cx, cy;
b = target[0].getBoundingClientRect();
console.log(target, b);
x = b.x;
y = b.y;
w = b.width;
h = b.height;
cx = x + (w / 2);
cy = y + (h / 2);
console.log(x, y, w, h, cx, cy);
return {
X: cx,
Y: cy
};
}
This gets the true center of an SVG Object. Looks like the cx and cy attributes do not get updated.
Updated function updateSVG()
function updateSVG(e) {
if (follow) {
var centerPoint = getCenter(center);
var point = new Point(e.clientX, e.clientY);
var angle = Math.round(100 * getAngleFromPoint(point, centerPoint)) / 100;
var distance = Math.round(getDistance(point, centerPoint));
var od = {
p: {
X: point.X - offset.X,
Y: point.Y - offset.Y
},
cp: {
X: centerPoint.X - offset.X,
Y: centerPoint.Y - offset.Y
}
};
var d = "M" + od.p.X + "," + od.p.Y + " L" + od.cp.X + "," + od.cp.Y;
path.attr("d", d);
txt.attr("x", point.X);
txt.attr("y", point.Y);
txt.html(distance + arrows + " (" + angle + degree + ")");
txt.attr("transform", "rotate(" + angle + " " + point.X + " " + point.Y + ")");
dynamic.attr("r", distance);
}
fitSVG();
}
This uses a new offset constant variable and the correct center points.
JavaScript
$(function() {
var center = $("#center"),
dynamic = $("#dynamic"),
path = $("#deg"),
svg = $("svg"),
txt = $("#txt"),
svgNS = svg[0].namespaceURI,
degree = String.fromCharCode(176),
arrows = String.fromCharCode(845),
follow = true,
startPos,
endPos,
offset = {
X: 0,
Y: 0
};
function Point(x, y) {
return {
"X": x,
"Y": y
};
}
function getCenter(target) {
var b, x, y, w, h, cx, cy;
b = target[0].getBoundingClientRect();
console.log(target, b);
x = b.x;
y = b.y;
w = b.width;
h = b.height;
cx = x + (w / 2);
cy = y + (h / 2);
console.log(x, y, w, h, cx, cy);
return {
X: cx,
Y: cy
};
}
// Credits goes to Stackoverflow: http://stackoverflow.com/a/14413632
function getAngleFromPoint(point, centerPoint) {
var dy = (point.Y - centerPoint.Y),
dx = (point.X - centerPoint.X);
var theta = Math.atan2(dy, dx);
var angle = (((theta * 180) / Math.PI)) % 360;
angle = (angle < 0) ? 360 + angle : angle;
return angle;
}
// Credits goes to http://snipplr.com/view/47207/
function getDistance(point1, point2) {
var xs = 0;
var ys = 0;
xs = point2.X - point1.X;
xs = xs * xs;
ys = point2.Y - point1.Y;
ys = ys * ys;
return Math.sqrt(xs + ys);
}
function fitSVG() {
var width = window.innerWidth,
height = window.innerHeight;
svg.width(width);
svg.height(height);
}
function updateSVG(e) {
if (follow) {
//var centerPoint = new Point(center[0].getAttribute("cx"), center[0].getAttribute("cy"));
var centerPoint = getCenter(center);
var point = new Point(e.clientX, e.clientY);
var angle = Math.round(100 * getAngleFromPoint(point, centerPoint)) / 100;
var distance = Math.round(getDistance(point, centerPoint));
var od = {
p: {
X: point.X - offset.X,
Y: point.Y - offset.Y
},
cp: {
X: centerPoint.X - offset.X,
Y: centerPoint.Y - offset.Y
}
};
var d = "M" + od.p.X + "," + od.p.Y + " L" + od.cp.X + "," + od.cp.Y;
$("#mouse").html(e.clientX + "," + e.clientY);
$("#svgPos").html(svg.position().left + "," + svg.position().top);
$("#offset").html(offset.X + "," + offset.Y);
$("#centerPoint").html(centerPoint.X + "," + centerPoint.Y);
$("#point").html(point.X + "," + point.Y);
$("#path").html(d);
$("#angle").html(angle);
$("#distance").html(distance);
path.attr("d", d);
txt.attr("x", point.X);
txt.attr("y", point.Y);
txt.html(distance + arrows + " (" + angle + degree + ")");
txt.attr("transform", "rotate(" + angle + " " + point.X + " " + point.Y + ")");
dynamic.attr("r", distance);
}
fitSVG();
}
grid_size = 10;
svg
.mousemove(updateSVG)
.click(function() {
follow = !follow;
return true;
});
$(".img").draggable({
handle: "svg",
classes: {
"ui-draggable-dragging": "opac"
},
cursor: "grab",
grid: [grid_size, grid_size],
start: function(e, ui) {
$(this).find(".text").hide();
follow = false;
startPos = ui.position;
},
stop: function() {
follow = true;
endPos = svg.position();
offset.X = endPos.left;
offset.Y = endPos.top;
}
});
});
Through testing, I adjusted the draggable a little bit, such that, the div.img wrapper is the draggable and the svg inside is the handle. I'm not sure if there is a benefit here, yet I didn't want it to go unnoticed.

Resources