Chartjs doughnut chart rounded corners for a cicle with a 3*Pi/2 circumference - reactjs

I'm working with React and ChartJS to draw a doughnut chart with a 3*Pi/2 circumference
and rounded corner.
I saw these two posts where they explain how to round corners for data sets and it is working as expected with a complete circle and with half a circle:
ChartJs - Round borders on a doughnut chart with multiple datasets
Chartjs doughnut chart rounded corners for half doghnut
One answer on this post is to change "y" or "x" translation by factor of n, for example 2 in the following case: ctx.translate(arc.round.x, arc.round.y*2);
With this in mind I started to change values for x and y but have not yet reach the correct set of values that will make it work.
For example I tried to use a factor of 3/2 on the translation of y and this is what I get.
ctx.translate(arc.round.x, (arc.round.y * 3) / 2);
with no factor I get the following:
ctx.translate(arc.round.x, arc.round.y);
The code to round the end corner is exactly the same as in the posts I refer. But here it is just in case:
let roundedEnd = {
// #ts-ignore
afterUpdate: function (chart) {
var a = chart.config.data.datasets.length - 1;
for (let i in chart.config.data.datasets) {
for (
var j = chart.config.data.datasets[i].data.length - 1;
j >= 0;
--j
) {
if (Number(j) == chart.config.data.datasets[i].data.length - 1)
continue;
var arc = chart.getDatasetMeta(i).data[j];
arc.round = {
x: (chart.chartArea.left + chart.chartArea.right) / 2,
y: (chart.chartArea.top + chart.chartArea.bottom) / 2,
radius:
chart.innerRadius +
chart.radiusLength / 2 +
a * chart.radiusLength,
thickness: (chart.radiusLength / 2 - 1) * 2.5,
backgroundColor: arc._model.backgroundColor,
};
}
a--;
}
},
// #ts-ignore
afterDraw: function (chart) {
var ctx = chart.chart.ctx;
for (let i in chart.config.data.datasets) {
for (
var j = chart.config.data.datasets[i].data.length - 1;
j >= 0;
--j
) {
if (Number(j) == chart.config.data.datasets[i].data.length - 1)
continue;
var arc = chart.getDatasetMeta(i).data[j];
var startAngle = Math.PI / 2 - arc._view.startAngle;
var endAngle = Math.PI / 2 - arc._view.endAngle;
ctx.save();
ctx.translate(arc.round.x, arc.round.y);
console.log(arc.round.startAngle);
ctx.fillStyle = arc.round.backgroundColor;
ctx.beginPath();
//ctx.arc(arc.round.radius * Math.sin(startAngle), arc.round.radius * Math.cos(startAngle), arc.round.thickness, 0, 2 * Math.PI);
ctx.arc(
arc.round.radius * Math.sin(endAngle),
arc.round.radius * Math.cos(endAngle),
arc.round.thickness,
0,
2 * Math.PI
);
ctx.closePath();
ctx.fill();
ctx.restore();
}
}
}, };
These are the options to configure the chart:
const chartJsOptions = useMemo<chartjs.ChartOptions>(() => {
if (data) {
return {
elements: {
center: {
text: `${data.impact > 0 ? "%"}`,
color: isDarkTheme ? darkText : greyAxis, // Default is #000000
fontStyle: "Open Sans Hebrew, sans-serif",
sidePadding: 20, // Default is 20 (as a percentage)
minFontSize: 15, // Default is 20 (in px), set to false and text will not wrap.
lineHeight: 20, // Default is 25 (in px), used for when text wraps
},
},
legend: {
display: false,
},
// rotation: Math.PI / 2,
rotation: (3 * Math.PI) / 4,
circumference: (3 * Math.PI) / 2,
maintainAspectRatio: false,
animation: {
duration: ANIMATION_DURATION,
},
plugins: {
datalabels: false,
labels: false,
},
cutoutPercentage: 90,
tooltips: {
enabled: false,
rtl: true,
},
};
} else {
return {};
} }, [data, isDarkTheme]);
Here is where I call the react component for the chart:
<Doughnut
data={chartJsData}
options={chartJsOptions}
plugins={[roundedEnd]} />
How can I correctly calculate the rounded edges on a 3*Pi/2 circumference or any other circumference between complete and half?
This issue may be more of a math than programing and my geometrical math is also a bit rusty.

Related

The app gets slower with every change in props [duplicate]

This question already has an answer here:
React won't draw anything in the canvas in useEffect
(1 answer)
Closed 16 days ago.
I use Matter js for physics and canvas for rendering 100 items, after every change in props the app gets slower and slower. At the same time, subsequent downloads with the same props occur instantly, i.e. past images and elements remained and were not deleted, which loads the application.
enter image description here
"Top" dropdown menu changes props for bubbles component, code:
const Bubbles = ({ coins }) => {
var winwidth = window.innerWidth;
var winheight = window.innerHeight - 80;
const [physics, setPhysics] = useState({
frictionAir: 0,
friction: 0.2,
frictionStatic: 0.2,
restitution: 0.1,
inertia: Infinity,
density: 0.8,
});
useEffect(() => {
setSize(coins);
// create an engine
const engine = Engine.create();
const world = engine.world;
// create a renderer
const render = Render.create({
element: document.querySelector(".bubbles"),
engine: engine,
options: {
width: winwidth,
height: winheight,
showAngleIndicator: false,
background: "transparent",
wireframeBackground: "transparent",
wireframes: false,
showDebug: false,
},
});
const bubbles = [];
// create a canvas
const canvas = document.createElement("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.querySelector(".bubbles").appendChild(canvas);
const ctx = canvas.getContext("2d");
// create 100 random bubbles
for (let i = 0; i < coins.length; i++) {
const x = Math.random() * window.innerWidth;
const y = Math.random() * window.innerHeight;
const bubble = Bodies.circle(x, y, coins[i].size / 2, {
...physics,
render: {
fillStyle: "transparent",
},
});
Composite.add(engine.world, bubble);
// store the bubble and its content data in an object
bubbles.push({
bubble,
content: {
radius: coins[i].radius,
text: `${i + 1}`,
},
});
}
// update the canvas on each engine update
Matter.Events.on(engine, "afterUpdate", function () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < bubbles.length; i++) {
const bubble = coins[i];
const pos = bubbles[i].bubble.position;
const size = bubble.size;
const text = bubble.symbol;
const priceChange = bubble.price_change_percentage_24h.toFixed(1);
const image = new Image();
image.src = bubble.image;
if (priceChange > 0) {
ctx.fillStyle = "rgba(5, 222, 5, 0.15)";
ctx.strokeStyle = "rgba(5, 222, 5, 0.6)";
} else if (priceChange < 0) {
ctx.fillStyle = "rgba(166, 53, 53, 0.15)";
ctx.strokeStyle = "rgba(166, 53, 53, 0.8)";
} else {
ctx.fillStyle = "rgba(255, 255, 89, 0.069)";
ctx.strokeStyle = "rgba(187, 231, 11, 0.443)";
}
ctx.beginPath();
ctx.arc(pos.x, pos.y, size / 2, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.drawImage(
image,
pos.x - size / 5,
pos.y - size / 2.5,
size / 2.5,
size / 2.5
);
ctx.font = `${size / 6}px Acme`;
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(text, pos.x, pos.y + size / 6.5);
ctx.font = `${size / 6.5}px Acme`;
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(priceChange + "%", pos.x, pos.y + size / 3);
}
});
// fit the render viewport to the scene
window.addEventListener("onload", () => {
Render.lookAt(render, {
min: { x: 0, y: 0 },
max: { x: 100000, y: 10000 },
});
});
// run the engine
Runner.run(engine);
// run the renderer
Render.run(render);
return () => {
// stop the engine and renderer
Engine.clear(engine);
Render.stop(render);
Runner.stop(engine);
// remove the canvas from the DOM
canvas.remove();
};
}, [coins]);
return <></>;
};
export default Bubbles;
after each update of the coins props, I deleted the canvas and stopped the engine, but still the speed worsened
return () => {
// stop the engine and renderer
Engine.clear(engine);
Render.stop(render);
Runner.stop(engine);
// remove the canvas from the DOM
canvas.remove();
};
I want to either stop re-creating the engine and the matter js world, canvas with images.
Only without window.location.reload(), because I need to save props like "Top" and "Earth, Trampoline", etc.
I solved the problem. I didn't properly clear the js canvas value and also didn't set events.off when unmounting the component.
Here is the solution to my problem
return () => {
//set Matter Js events off. drawCanvas - afterUpdate event function
Events.off(engine, 'afterUpdate', drawCanvas);
Render.stop(render);
Composite.clear(engine.world);
Runner.stop(engine)
Engine.clear(engine);
render.canvas.remove();
render.canvas = null;
render.context = null;
// remove the canvas from the DOM
canvas.remove();
};

draw vizualizations/colour changes based on the shape of an oscillator wave in a tone.js react project?

I'd like to animate/change colour based on the current section of an oscillator wave, to create a pulsing colour effect that matches the wave? The waves can be any of the types e.g. sine, triangle etc. so this would create different pulsing depending on the wave type and the frequency can be changed for the oscillator too (which I would then like to also change the pulsing timings).
Presumably I need to get the amplitude and wavelength (are these even the right terms for what i need?) from the oscillator object and use it in correlation with the Draw object while using some css animation but I am a bit stuck as to where to start? Specifically around the tone.js part of how I would get the values needed and how I would morph from colour A to B and back etc over time. Would I need to include an external library like p5 or can I do this all through tone.js with draw alone?
cheers
david
EDIT - #paulwheeler - Thanks so much for all your comments, that is a big help. To be clearer, and to answer your queries, here is what I have and need, should you have any more input -
i) I have two oscillators continuously playing from Tone.js. They are 'playing' at frequency A and B, to create a binaural sound. The difference between the two i will call the range. So for example, Osc1 might be 100hz, osc2 might be 104hz and the range is 4hz. These oscillators can be of any type allowed by tone.js (sawtooth, sine, triangle..)
ii) When the sound is playing i want to take the range freqency and attach it to two colours. So at the peak of the frequency colour A will be showing as a background colour and at the trough of the frequency colour B will be showing. Inbetween those times the colour will be morphing between the two i.e. at any time t the colour will be a representation of the Y position on the wave, that is reflective of the distance from 0. This will then make it appear that the colour is changing to match the shape of the waves. kind of like here in css (but just using keyframes alone in this example and just to give an example of what i mean visually) -
https://jsfiddle.net/CF3Np/4/
#keyframes animation {
0% {background-color:red;}
50.0% {background-color:green;}
100.0% {background-color:red;}
}
From a cursory scan of the tone.js documentation it looks like it has everything you could want to synthesize and analyze sounds. However it doesn't appear have any facility for graphics. As far as drawing graphics goes, p5.js is certainly one option. However, the reference to "css animation" is a bit out of place here, because libraries like p5.js aren't really designed with css styling in mind. Instead you would be doing all of the animation yourself with calls to drawing functions for each frame.
The thing you are looking for when it comes to analyzing sounds in order to process them as data, and potentially visualize them, is a Fast Fourier Transform. This is an algorithm that decomposes a sample of a sound wave into separate sine wave components, and you can use the amplitudes of those sine waves to measure the sound in terms of component frequencies. Both p5.js (via the p5.sound add-in) and tone.js have FFT classes. If you're already using tone.js you'll probably want to stick with that for analyzing sounds, but you can certainly create visualizations for them using p5.js. Just as a conceptual example, here's a pure p5.js example:
const notes = [{
name: 'c',
freq: 261.6
},
{
name: 'c#',
freq: 277.2,
sharp: true
},
{
name: 'd',
freq: 293.7
},
{
name: 'd#',
freq: 311.1,
sharp: true
},
{
name: 'e',
freq: 329.6
},
{
name: 'f',
freq: 349.2
},
{
name: 'f#',
freq: 370.0,
sharp: true
},
{
name: 'g',
freq: 392.0
},
{
name: 'g#',
freq: 415.3,
sharp: true
},
{
name: 'a',
freq: 440.0
},
{
name: 'a#',
freq: 466.2,
sharp: true
},
{
name: 'b',
freq: 493.9
},
];
let playing = {};
let fft;
let bg;
function setup() {
createCanvas(windowWidth, windowHeight);
colorMode(HSB);
bg = color('lightgray');
for (let note of notes) {
note.osc = new p5.Oscillator();
note.osc.freq(note.freq);
note.osc.amp(0);
}
fft = new p5.FFT();
}
function toggleNote(name) {
let note = notes.filter(n => n.name === name)[0];
if (playing[name] === undefined) {
// First play
note.osc.start();
}
if (playing[name] = !playing[name]) {
// fade in a little
note.osc.amp(0.2, 0.2);
} else {
// fade out a little
note.osc.amp(0, 0.4);
}
}
function playNote(name) {
let note = notes.filter(n => n.name === name)[0];
if (playing[name] === undefined) {
// First play
note.osc.start();
}
playing[name] = true;
note.osc.amp(0.2, 0.2);
}
function releaseNote(name) {
let note = notes.filter(n => n.name === name)[0];
playing[name] = false;
note.osc.amp(0, 0.4);
}
function draw() {
background(bg);
let w = width / 3;
let h = min(height, w * 0.8);
drawSpectrumGraph(w, 0, w, h);
drawWaveformGraph(w * 2, 0, w, h);
drawKeyboard();
bg = color((fft.getCentroid() * 1.379) % 360, 30, 50);
}
function drawSpectrumGraph(left, top, w, h) {
let spectrum = fft.analyze();
stroke('limegreen');
fill('darkgreen');
strokeWeight(1);
beginShape();
vertex(left, top + h);
for (let i = 0; i < spectrum.length; i++) {
vertex(
left + map(log(i), 0, log(spectrum.length), 0, w),
top + map(spectrum[i], 0, 255, h, 0)
);
}
vertex(left + w, top + h);
endShape(CLOSE);
}
function drawWaveformGraph(left, top, w, h) {
let waveform = fft.waveform();
stroke('limegreen');
noFill();
strokeWeight(1);
beginShape();
for (let i = 0; i < waveform.length; i++) {
let x = map(i * 5, 0, waveform.length, 0, w);
let y = map(waveform[i], -1, 2, h / 10 * 8, 0);
vertex(left + x, top + y);
}
endShape();
}
function drawKeyboard() {
let w = width / 3;
let h = min(height, w * 0.8);
let x = 1;
let keyWidth = (w - 8) / 7;
let sharpWidth = keyWidth * 0.8;
noStroke();
let sharpKeys = [];
for (let note of notes) {
fill(playing[note.name] ? 'beige' : 'ivory');
if (note.sharp) {
sharpKeys.push({
fill: playing[note.name] ? 'black' : 'dimgray',
rect: [x - sharpWidth / 2, 0, sharpWidth, h / 2, 0, 0, 4, 4]
});
} else {
rect(x, 0, keyWidth, h - 1, 0, 0, 4, 4);
x += keyWidth + 1;
}
}
for (let key of sharpKeys) {
fill(key.fill);
rect(...key.rect);
}
}
let keymap = {
'z': 'c',
's': 'c#',
'x': 'd',
'd': 'd#',
'c': 'e',
'v': 'f',
'g': 'f#',
'b': 'g',
'h': 'g#',
'n': 'a',
'j': 'a#',
'm': 'b',
}
function keyPressed(e) {
let note = keymap[e.key];
if (note) {
playNote(note);
}
}
function keyReleased(e) {
let note = keymap[e.key];
if (note) {
releaseNote(note);
}
}
function mouseClicked() {
if (mouseX < width / 3) {
let w = width / 3;
let h = w * 0.8;
let x = 1;
let keyWidth = (w - 8) / 7;
let sharpWidth = keyWidth * 0.8;
let naturalKeys = [];
let sharpKeys = [];
for (let note of notes) {
if (note.sharp) {
sharpKeys.push({
name: note.name,
bounds: {
left: x - sharpWidth / 2,
top: 0,
right: x - sharpWidth / 2 + sharpWidth,
bottom: h / 2
}
});
} else {
naturalKeys.push({
name: note.name,
bounds: {
left: x,
top: 0,
right: x + keyWidth,
bottom: h - 1
}
});
x += keyWidth + 1;
}
}
for (let {
bounds,
name
} of sharpKeys.concat(naturalKeys)) {
if (mouseX > bounds.left && mouseX < bounds.right &&
mouseY > bounds.top && mouseY < bounds.bottom) {
toggleNote(name);
break;
}
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/addons/p5.sound.min.js"></script>

how to show speedometer on Doughnut chart in chart.js using reactjs

I've created a half doughnut chart and wanted to show like a speedometer, but couldn't able to make it. I have gone through this link I want to display my chart Either as similar to the above link or as per this snippet
Here is my code:
import React, { Component } from "react";
import { Doughnut } from "react-chartjs-2";
import Data from "./Data.json";
const data = {
// labels: ["Red", "Green", "Yellow"],
datasets: [
{
data: [500, 500, 500],
backgroundColor: ["red", "#FFCE56", "lightgreen"],
hoverBackgroundColor: ["red", "#FFCE56", "lightgreen"]
}
],
options: {
rotation: 1 * Math.PI,
circumference: 1 * Math.PI,
legend: {
display: false
},
tooltip: {
enabled: false
},
cutoutPercentage: 70
}
};
export default class DonutChart extends Component {
render() {
return (
<div>
<h2>Sample</h2>
<Doughnut height="100px" data={data} options={data.options} />
</div>
);
}
}
Here is the sample code
Can anyone help me in this query to achieve in this format?
Base on the code sample from Konstantin S., you could register a plugin globally inside the componentDidMount() method and implement an afterDraw function as follows:
componentDidMount() {
Chart.pluginService.register({
afterDraw: chart => {
var needleValue = chart.chart.config.data.datasets[0].needleValue;
var dataTotal = chart.chart.config.data.datasets[0].data.reduce((a, b) => a + b, 0);
var angle = Math.PI + (1 / dataTotal * needleValue * Math.PI);
var ctx = chart.chart.ctx;
var cw = chart.chart.canvas.offsetWidth;
var ch = chart.chart.canvas.offsetHeight;
var cx = cw / 2;
var cy = ch - 6;
ctx.translate(cx, cy);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(0, -3);
ctx.lineTo(ch - 10, 0);
ctx.lineTo(0, 3);
ctx.fillStyle = 'rgb(0, 0, 0)';
ctx.fill();
ctx.rotate(-angle);
ctx.translate(-cx, -cy);
ctx.beginPath();
ctx.arc(cx, cy, 5, 0, Math.PI * 2);
ctx.fill();
}
});
}
This expects the property needleValue to be defined inside the dataset.
datasets: [{
data: [500, 500, 500],
needleValue: 580,
...
}],
Please have a look at your amended CodeSandbox

React Native dynamically adjustable Pie Chart

I'm building a react native app where the user should be able to move an adjuster around a pie chart to adjust the start and end angles of the pie slices. I'm using 3 panResponders and used the idea by /users/681830/val:
react native circle transform translate animation
I'm calculating the shortest distance to either of the 36 snapshots then set the animated value to that point however it is super inefficient and laggy. Can anyone suggest any better performing solution?
'''
this._panResponder1 = PanResponder.create(
{
onStartShouldSetPanResponder: (evt, gesture) =>true,
onPanResponderMove: (evt, gesture) => {
//we need the distance between the points and get the index of the minimum distance
distances = [];
for(var i = 0; i < 36; i++){
var a = this.outputRangeX[i] - gesture.moveX;
var b = this.outputRangeY[i] - gesture.moveY + 120;
distances.push(Math.sqrt(a*a + b*b));
}
var minInd = distances.indexOf(Math.min(...distances));
this.setState({indexOfAdj1 : minInd});
this.adj1Anim.setValue((1/36)* minInd);
var isPos1 = minInd/36;
var isPos2 = (minInd)/36;
if(minInd>24){
isPos1 = -1 * ((36-minInd)/36);
isPos2 = minInd/36;
this.setState({data: [
{
number: 1,
startAngle: isPos1* Math.PI * 2,
endAngle: this.state.data[0].endAngle,
},
{
number: 30,
startAngle: this.state.data[1].startAngle,
endAngle: this.state.data[1].endAngle,
},
{
number: 1,
startAngle: this.state.data[1].endAngle,
endAngle: isPos2* Math.PI * 2,
},
]});
}else{
this.setState({data: [
{
number: 1,
startAngle: isPos1* Math.PI * 2,
endAngle: this.state.data[0].endAngle,
},
{
number: 30,
startAngle: this.state.data[1].startAngle,
endAngle: this.state.data[1].endAngle,
},
{
number: 1,
startAngle: -((Math.PI * 2)-this.state.data[1].endAngle),
endAngle: isPos2* Math.PI * 2,
},
]});
}
}
'''

clip-path dynamic transformations to fill container

I'm working on a react component I would like to be able to pass any image along with its dimensions, a clip-path calculated from an x, y width, and height for a rectangular region. This part I have working well. Then I would like to scale this clipped region back to fill a div, at the moment I have this div just the original image dimensions to keep it simple. I have the scaling part calculating properly but cannot work out the math to translate the scaled clip-path region. Here is my component (in Typescript):
interface RootProps {
links: Link[];
}
interface RootState {
}
class Root extends React.Component<RootProps, RootState> {
constructor(props) {
super(props);
this.state = {
}
}
pp = (x: number, y: number): string => {
return x + "% " + y + "%"
}
renderClippedImage = (name: string, x: number, y: number, width: number, height: number) => {
let iWidth = 600;
let iHeight = 360;
let p1 = {
x: (x / iWidth) * 100,
y: (y / iHeight) * 100
}
let p2 = {
x: (x / iWidth) * 100,
y: ((y + height) / iHeight) * 100
}
let p3 = {
x: ((x + width) / iWidth) * 100,
y: ((y + height) / iHeight) * 100
}
let p4 = {
x: ((x + width) / iWidth) * 100,
y: (y / iHeight) * 100
}
let clipPathString = 'polygon(' +
this.pp(p1.x, p1.y) + ', ' +
this.pp(p2.x, p2.y) + ', ' +
this.pp(p3.x, p3.y) + ', ' +
this.pp(p4.x, p4.y) + ')';
let pX = (x) / iWidth;
let pY = (y) / iHeight;
let portionCoverageX = (width) / iWidth;
let portionCoverageY = (height) / iHeight;
let scaleX = 1;
let scaleY = 1;
if (portionCoverageX > 0) {
scaleX = 1 / portionCoverageX;
}
if (portionCoverageY > 0) {
scaleY = 1 / portionCoverageY;
}
let translateX = -(((pX * scaleX) / 2) * 100); //this doesn't work
let translateY = 0; //similar issues getting this to work
let pathClipping = {
WebkitClipPath: clipPathString,
clipPath: clipPathString,
transform: 'translateX(' + translateX + '%) translateY(' + translateY + '%) scaleX(' + scaleX + ') scaleY(' + scaleY + ')'
}
console.log({
name: name,
pX: pX,
pY: pY,
portionCoverageX: portionCoverageX,
portionCoverageY: portionCoverageY,
scaleX: scaleX,
scaleY: scaleY,
translateX: translateX,
translateY: translateY
})
return (
<div style={{ textAlign: 'center', width: '100%', backgroundColor: 'lightseagreen' }}>
<b>{name}</b><br />
<div style={{ display: 'inline-block', width: 640, height: 360, backgroundColor: 'darkslateblue' }}>
<img width="640" height="360" src="https://placekitten.com/640/360" style={pathClipping} />
</div>
</div>
)
}
render() {
return (
<div style={{textAlign: 'center'}}>
<b>Original</b><br />
<img width="640" height="360" src="https://placekitten.com/640/360" />
<br />
{this.renderClippedImage("one", 80, 100, 200, 100)}
<br />
{this.renderClippedImage("two", 50, 50, 150, 100)}
<br />
{this.renderClippedImage("three", 300, 10, 300, 340)}
</div>
)
}
}
ReactDOM.render(<Root />, document.getElementById('mount-node'));
And here is a codepen, where you can see, my current math actually works for scenario 3, but it needs to work for 1, 2 and any other legitimate region combo as well.
https://codepen.io/anon/pen/xMWGRO
The specific line I think I am strugling with is this:
let translateX = (((pX * iWidth) / 2)); //this doesn't work for all examples
translateY is a similar issue, any help would be most appreciated.
I think you can set transform-origin as top left, so you can have simpler calculation for transform positions & scale.
let scaleX = iWidth / width;
let scaleY = iHeight / height;
let pX = x / iWidth;
let pY = y / iHeight;
let translateX = -pX * 100 * scaleX;
let translateY = -pY * 100 * scaleY;
let pathClipping = {
WebkitClipPath: clipPathString,
clipPath: clipPathString,
transformOrigin: `top left`,
transform: `
translate(${translateX}%, ${translateY}%)
scale(${scaleX}, ${scaleY})
`
};
See codesandbox here

Resources