I used the Pie component from Recharts js and the problem is that I get labels overlapping for labels with the same value.
here is some of my code:
<PieChart>
<Pie dataKey="value"
data={data}
fill="#536A6D"
label nameKey="name"
>
<LabelList dataKey="name" position="insideStart" />
<Pie>
</PieChart>
Is it possible to arrange the labels so that they do not collide with each other?
Thank you in advance!
Yes, you will have to conditionally render the labelline and label attribute. In my case only the zero values overlap so when the value is zero I do not render the value. Other examples online will help with the custom label but there is nothing over the little label line left over, I had this problem and had to dig through the source code to come up with the custom code /:
<Pie
data={dataZ}
cx={150 + wid - pad / 2}
cy={150}
innerRadius={70 + scaler}
outerRadius={100 + scaler}
fill="#8884d8"
paddingAngle={1}
dataKey="value"
label={RenderLabel2}
labelLine={RenderCustomizedLabelLine}
>
let RenderCustomizedLabelLine = function (props: any) {
return (props.value != 0 ? <path stroke={props.stroke} d={`M${props.points[0].x},${props.points[0].y}L${props.points[1].x},${props.points[1].y}`} className="customized-label-line" /> : <polyline stroke={props.stroke} fill="none" />)
}
let RenderLabel2 = function (props: any) {
const RADIAN = Math.PI / 180;
const radius = 25 + props.innerRadius + (props.outerRadius - props.innerRadius);
const x = props.cx + radius * Math.cos(-props.midAngle * RADIAN);
const y = props.cy + radius * Math.sin(-props.midAngle * RADIAN);
return (props.value != 0 ? <text
className="recharts-text recharts-pie-label-text"
x={x}
y={y}
fontSize='16'
fontFamily='sans-serif'
dominantBaseline="central"
cy={props.cy}
cx={props.cx}
fill="#666"
textAnchor={props.x > props.cx ? 'start' : 'end'}
>{Number.isInteger(props.value) ? Number(props.value) : Number(props.value).toFixed(1)}%</text> : <g>
<text x={500} y={y} fill="#transparent" rotate="90"></text>
</g>)
}
Related
Following is my code, I hope make tooltip follow mousemove like echart(render select svg) tooltip
but the result transition become stuck, how to make transition smooth
var it: SVGTextElement
var c: SVGGElement
ReactDOM.createRoot(document.querySelector("#root")).render(
<svg>
<g ref={e => c = e}
onMouseMove={(ev: any) => {
var r = c.getBoundingClientRect()
var x = ev.clientX - r.x
var y = ev.clientY - r.y
it.setAttribute("transform", `translate(${x} ${y})`)
}}>
<rect x={0} y={0} width={300} height={300} fill={"transparent"} stroke={"black"}></rect>
<text ref={e => it = e} style={{transition: "all 0.5s ease"}}>tooltip</text>
</g>
</svg>)
The mouse move handler should be attached to the SVG, not the tooltip. As it is now, if you move the mouse quickly enough to escape the tooltip, the event will no longer fire. And the tooltip will stop and appear to get stuck.
I find transition "ease" has a little stuck, use "linear" instead will make move smooth, following is my correct code
var it: SVGTextElement
var c: SVGGElement
ReactDOM.createRoot(document.querySelector("#root")).render(
<svg width={1000} height={500}>
<g ref={e => c = e}
onMouseMove={(ev: any) => {
var r = c.getBoundingClientRect()
var x = ev.clientX - r.x
var y = ev.clientY - r.y
it.setAttribute("transform", `translate(${x} ${y})`)
}}>
<rect x={0} y={0} width={1000} height={500} fill={"transparent"} stroke={"black"}></rect>
<text ref={e => it = e} style={{transition: "all 0.5s linear"}}>tooltip</text>
</g>
</svg>
)
I'm attempting to create a crude database diagram generator using D3, but I can't figure out how to get connectors between fields. I can get straight lines going from two points, but I wanted it to be rounded and like a path I guess.
I've tried to put together an example of just that specific issue, linking two text fields:
https://codesandbox.io/s/gifted-bardeen-5hbw2?fontsize=14&hidenavigation=1&theme=dark
Here's an example from dbdiagram.io of what I'm referring to:
I've been reading up on the d attribute and the various commands, but nothing seems even close. I suspect the forceSimulation method, especially the forceCenter function might be messing up the relative positioning when I use the lower-cased commands. But not 100% on that.
You can compute a connector path between 2 points by connectorPath routine:
const source = {x: 200, y: 120};
const target = {x: 50, y: 20};
const MAX_RADIUS = 15;
const connectorPath = (from, to) => {
if (from.y === to.y || from.x === to.x)
return `M ${from.x},${from.y} L ${to.x},${to.y}`;
const middle = (from.x + to.x) / 2;
const xFlag = from.x < to.x ? 1 : -1;
const yFlag = from.y < to.y ? 1 : -1;
const dX = Math.abs(from.x - to.x);
const dY = Math.abs(from.y - to.y);
const radius = Math.min(dX / 2, dY / 2, MAX_RADIUS);
return `M ${from.x},${from.y} H ${middle - radius * xFlag} Q ${middle},${from.y} ${middle},${from.y + radius * yFlag} V ${to.y - radius * yFlag} Q ${middle},${to.y} ${middle + radius * xFlag},${to.y} H ${to.x}`;
};
d3.select('#source').attr('cx', source.x).attr('cy', source.y);
d3.select('#target').attr('cx', target.x).attr('cy', target.y);
d3.select('#connector').attr('d', connectorPath(source, target));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="300" height="200">
<path id="connector" stroke="blue" fill="none" />
<circle id="source" fill="red" r="5"/>
<circle id="target" fill="green" r="5"/>
</svg>
I'm building a circular progress bar as a react component with SVG elements.
Desired output:
Instead, I get it like this
My question is, how to rotate the score/text in the yellow circle to match the first image?
React component
const Gauge = ({ width, height, thick, progress, score }) => {
let angle = (progress / 100 * 360);
let progressValue = angle / 2;
let ticks = [];
for (let i = 1; i <= 180; i++) {
let tickClass = (progressValue > i) ? styles.tickActive : styles.tick;
ticks.push(<use key={i} className={tickClass} href="#tick" transform={`rotate(${i * 2} 60 60)`}> </use>)
}
ticks.push(<use key={0} className={styles.tickActive } href="#tick" transform={`rotate(0 60 60)`}> </use>);
return (
<div className={ styles.svgContainer }>
<svg className={styles.progress} width={`${width}`} height={`${height}`} viewBox="-20 -20 160 160">
<defs>
<line id="tick" x1="-5" y1="60" x2={`${thick}`} y2="60"> </line>
</defs>
<g id="ticks">
{ticks}
</g>
<g transform={`rotate(${progressValue * 2} 60 60)`}>
<line className={styles.scoreCircle} x1="0" y1="60" x2="0" y2="60"> </line>
<text className={styles.score2} fontSize={8} x={-10} y={63}>
{score}K
</text>
</g>
</svg >
</div>
);
};
I'm trying to expand the Connected Objects demo by allowing two nodes (shapes of Circle class) to be double referenced (A connects to B with Arrow1 and B connects to A with Arrow2). I work with react-konva package.
I have implemented a demo on Code Sandbox with some basic functionality.
On line 5, 6 you'll find the Nodes info, on line 21 there exists a high-order component that creates the Arrow based on the start Node and end Node position.
In the default example, the arrows are working as expected. If you try to set the value of redNode.x to 300 the arrows overlap. The same happens when blueNode.x is equal to -100. This has something to do with the way I calculate the arrows (I suspect the equations on line 38).
Also note that as redNode.x moves to value 300, the two arrows approach each other (this happens on other values too), which is something I do not want to happen. I expect the arrows to have the same shape when the two nodes change position and not to overlap or approach each other. Unfortunately, my lack of mathematics does not help me solve the problem. I also tried to create a custom shape using quadraticCurveTo method without success.
Thanks in advance for the help. I appreciate all the solutions.
There are many ways to make curved lines. Here is my attempt to make it better:
import React from "react";
import ReactDOM from "react-dom";
import { Stage, Layer, Circle, Arrow, Text } from "react-konva";
const BLUE_DEFAULTS = {
x: 100,
y: 100,
fill: "blue",
width: 30,
height: 30,
draggable: true
};
const RED_DEFAULTS = {
x: 100,
y: 300,
fill: "red",
width: 30,
height: 30,
draggable: true
};
const Edge = ({ node1, node2 }) => {
const dx = node1.x - node2.x;
const dy = node1.y - node2.y;
let angle = Math.atan2(-dy, dx);
const radius = 20;
const curvePower = 30;
const arrowStart = {
x: node2.x + -radius * Math.cos(angle + Math.PI),
y: node2.y + radius * Math.sin(angle + Math.PI)
};
const arrowEnd = {
x: node1.x + -radius * Math.cos(angle),
y: node1.y + radius * Math.sin(angle)
};
const arrowCurve = {
x:
(arrowStart.x + arrowEnd.x) / 2 +
curvePower * Math.cos(angle + Math.PI / 2),
y:
(arrowStart.y + arrowEnd.y) / 2 +
curvePower * Math.sin(angle - Math.PI / 2)
};
return (
<Arrow
tension={0.2}
points={[
arrowStart.x,
arrowStart.y,
arrowCurve.x,
arrowCurve.y,
arrowEnd.x,
arrowEnd.y
]}
stroke="#000"
fill="#000"
strokeWidth={3}
pointerWidth={6}
/>
);
};
const App = () => {
const [blueNode, updateBlueNode] = React.useState(BLUE_DEFAULTS);
const [redNode, updateRedNode] = React.useState(RED_DEFAULTS);
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Text text="Drag any node to see connections change" />
<Edge node1={blueNode} node2={redNode} />
<Edge node1={redNode} node2={blueNode} />
<Circle
{...blueNode}
onDragMove={e => {
updateBlueNode({ ...blueNode, ...e.target.position() });
}}
/>
<Circle
{...redNode}
onDragMove={e => {
updateRedNode({ ...redNode, ...e.target.position() });
}}
/>
</Layer>
</Stage>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Demo: https://codesandbox.io/s/react-konva-double-connected-objects-m5g22
I'm using React, and what I want to achieve is to have an animation on SVG arc path when they change. Basically, I've a gauge that show a certain value between 0 and 100, and the value can change (in the following example it changes every second).
I've created this codepen that simulate what I want (code below): https://codepen.io/Gesma94/pen/oJvjwe
As you can see in the example, I've a Gauge created with d3 inside an SVG, where the blue bar can take more or less space in time; as you can see, when the Gauge is re-rendered, the new blue bar is just rendered, without any animation between the "old point" and "new point".
What I would like to achieve is having a smooth movement between the point the bar was before, and the point where the bar is going to be (hope I've been clear).
class MyComponent extends React.Component {
render() {
console.log("Rendering");
const value = (this.props.value * Math.PI / 100) - Math.PI/2;
const currentValueFilledCircle = d3.arc()
.innerRadius(37.5)
.outerRadius(49.5)
.startAngle(-Math.PI/2)
.endAngle(value)(null);
const currentValueEmptyCircle = d3.arc()
.innerRadius(37.5)
.outerRadius(49.5)
.startAngle(value)
.endAngle(Math.PI/2)(null);
return (
<div style={{width: "300px", height: "300px"}}>
<svg height="100%" width="100%" viewBox="-50 -50 100 100">
<g>
<path d={currentValueFilledCircle} fill="blue" />
<path d={currentValueEmptyCircle} fill="gray" />
</g>
</svg>
</div>
);
};
}
class App extends React.Component {
constructor() {
super();
this.value = 77;
}
componentDidMount() {
this.interval = setInterval(() => {
const diff = Math.floor(Math.random() * 7) - 3;
let newCurrentValue = this.value + diff;
if (newCurrentValue > 100) newCurrentValue = 100;
else if (newCurrentValue < 0) newCurrentValue = 0;
this.value = newCurrentValue;
this.forceUpdate();
}, 500);
}
render() {
return (<MyComponent value={this.value} />)
}
}
ReactDOM.render(<App />, document.getElementById('app'));
So, I struggled for some times, but I found a solution using react-move/Animate: https://react-move.js.org/#/documentation/animate
Since I couldn't make it work on Codepen, I recreate the situation in a sandbox, there it is: https://codesandbox.io/embed/0qyrmyrw
The gist is the following part of code:
<Animate
start={{ value: this.props.value }}
update={{
value: [this.props.value], // Before the sqaure brackets!!
timing: { duration: 750 }
}}
>
{(state: { value: number }) => {
const scaledValue = (state.value * Math.PI) / 100 - Math.PI / 2;
const currentValueFilledCircle = arc()
.innerRadius(37.5)
.outerRadius(49.5)
.startAngle(-Math.PI / 2)
.endAngle(scaledValue)(null);
const currentValueEmptyCircle = arc()
.innerRadius(37.5)
.outerRadius(49.5)
.startAngle(scaledValue)
.endAngle(Math.PI / 2)(null);
return (
<React.Fragment>
<path d={currentValueFilledCircle} fill="blue" />
<path d={currentValueEmptyCircle} fill="gray" />
</React.Fragment>
);
}}
</Animate>
Basically, by writing update={{value: [this.props.value] ... }}, the Animate Component just run a set of render() method with different values, from the previous to the current, and so it gives a smooth-movement effect.