I used a react-konva Group with 3 Rect shapes inside it - they are all displayed at the same place and the only difference is their width so i can get some sort of a multi-color process bar.
This is my current code:
import React from 'react';
import {Group, Rect, Text} from 'react-konva';
import Konva from 'konva'
export function functionalTaskContainer(properties) {
function handleDragStart(e, properties) {
e.target.setAttrs({
width: ((Math.ceil(properties.length / 5)) * properties.cellWidth) - 10,
shadowOffset: {
x: 15,
y: 15
},
scaleX: 1.1,
scaleY: 1.1
});
}
function findWhereToLand(x, firstCellWidth, cellWidth) {
let cell = 0;
while (x > firstCellWidth + cell * cellWidth) {
cell++;
}
return cell;
}
function handleDragMove(e, properties) {
e.target.children.forEach((child) => {
if (child.className === "Rect") {
switch (child.getAttr("name").split(' ')[0]) {
case "mainRect":
child.to({
duration: 0.01,
width: ((Math.ceil(properties.length / 5)) * properties.cellWidth) - 10
});
break;
case "workingRect":
child.to({
duration: 0.01,
width: ((Math.ceil(properties.length / 5) * properties.cellWidth) - 10) * (properties.percentageDone + properties.percentageWorking)
});
break;
case "doneRect":
child.to({
duration: 0.01,
width: ((Math.ceil(properties.length / 5) * properties.cellWidth) - 10) * properties.percentageDone
});
break;
default:
break;
}
}
});
e.target.setAttrs({
y: (properties.row + 1) * properties.cellHeight
});
}
function handleDragEnd(e, properties) {
console.log(e);
properties.changeWeekHandler(findWhereToLand(e.target.attrs.x, properties.firstCellWidth, properties.cellWidth));
e.target.to({
duration: 0.05,
easing: Konva.Easings.EaseIn,
scaleX: 1,
scaleY: 1,
shadowOffsetX: 5,
shadowOffsetY: 5
});
}
return (
<Group
key={"container-" + properties.row}
x={properties.col > 0 ? (properties.firstCellWidth + (properties.col - 1) * properties.cellWidth) : 0}
y={(properties.row + 1) * properties.cellHeight}
draggable={properties.draggable || false}
onDragStart={(e) => handleDragStart(e, properties)}
onDragMove={(e) => handleDragMove(e, properties)}
onDragEnd={(e) => handleDragEnd(e, properties)}>
<Rect
name={"mainRect of " + properties.row}
x={5}
y={5}
width={properties.col === 0 ? properties.firstCellWidth - 10 : ((Math.ceil(properties.length / 5)) * properties.cellWidth) - 10}
height={properties.cellHeight - 10}
fill="#5BC0EB"
shadowBlur={10}
cornerRadius={5}
/>{/* blue general block*/}
<Rect
name={"workingRect of " + properties.row}
x={5}
y={5}
width={(properties.col === 0 ? properties.firstCellWidth - 10 : ((Math.ceil(properties.length / 5)) * properties.cellWidth) - 10) * (properties.percentageDone + properties.percentageWorking)}
height={properties.cellHeight - 10}
fill="#FDE74C"
cornerRadius={5}
/>{/* yellow working progress*/}
<Rect
name={"doneRect of " + properties.row}
x={5}
y={5}
width={(properties.col === 0 ? properties.firstCellWidth - 10 : ((Math.ceil(properties.length / 5)) * properties.cellWidth) - 10) * properties.percentageDone}
height={properties.cellHeight - 10}
fill="#9BC53D"
cornerRadius={5}
/>{/* green done progress*/}
<Text
x={15}
y={15}
text={properties.containerName}
fill={"white"}
fontFamily={'david'}
fontStyle={'bold'}
shadowBlur={5}
fontSize={20}
/>
</Group>
)
}
For dragging events I would find it way more simple to set a single attribute to the group object and then use it when constructing the sub Shapes. But I can't find the syntax to do that.
what I am trying to do is something like:
<Group
key={"container-" + properties.row}
width={SomeValue}
x={properties.col > 0 ? (properties.firstCellWidth + (properties.col - 1) * properties.cellWidth) : 0}
y={(properties.row + 1) * properties.cellHeight}
draggable={properties.draggable || false}
onDragStart={(e) => handleDragStart(e, properties)}
onDragMove={(e) => handleDragMove(e, properties)}
onDragEnd={(e) => handleDragEnd(e, properties)}>
<Rect
name={"mainRect of " + properties.row}
x={5}
y={5}
width={properties.col === 0 ? properties.firstCellWidth - 10 : this.parent.getAttr("width") - 10}
height={properties.cellHeight - 10}
fill="#5BC0EB"
shadowBlur={10}
cornerRadius={5}
/>{/* blue general block*/}
</Group>
Is there a way to do?
Related
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.
how to specify the position of next note to be added
i tried with following code but it is generating randomly even over the previous note .but the next note should not be on the position of previous note.
randomBetween: function (min, max) {
return min + Math.ceil(Math.random() * max);
},
componentWillMount: function () {
this.style = {
right: this.randomBetween(0, window.innerWidth - 150) + 'px',
top: this.randomBetween(0, window.innerHeight - 150) + 'px',
transform: 'rotate(' + this.randomBetween(-15, 15) + 'deg)' };
},
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
Here I am trying to move a touchable element by changing "top" and "left" values as can be seen below. The problem is, for "(number > 4)" it doesn't work as it should. To add to this, only "top" value change has an effect on the element, whereas change in "left" has no effect. I am clueless here, please help
Move function:
moveTo(index, number, onMoveFinish) {
const x = index % 9;
const y = (index - x) / 9;
const gap = 2;
var left = CellSize * x - gap
var top = CellSize * (y - 9) - CellSize - gap
if(number > 4){
left = (BoardWidth / 9 * (number + 1) + (BoardWidth / 9 - CellSize) / 2)
top = top - ((2 + CellSize * 1.1) + (CellSize / 3))
}
LayoutAnimation.configureNext(spring);
this.setState({ left, top });
setTimeout(() => {
onMoveFinish && onMoveFinish();
}, 25000);
}
Render function:
render() {
if (this.state.hide) return null;
const { number } = this.props;
var { left, top } = this.state;
if(number > 4){
top = top + ((2 + CellSize * 1.1) + (CellSize / 3))
left = (BoardWidth - (CellSize * 4.4 + ((CellSize / 3) * 3) + 16)) / 2 +
(number - 5) * ((CellSize / 3) + (CellSize * 1.1))
}
return (
<Touchable onPress={this.onPress} style={[styles.container, {top, left}]} >
<View style={styles.cell} >
<Text style={styles.text}>{number + 1}</Text>
</View>
</Touchable>
);
}
}
Style:
const styles = StyleSheet.create({
container: {
position: 'absolute',
width: CellSize,
height: CellSize,
},
cell: {
width: CellSize * 1.1,
height: CellSize * 1.1,
backgroundColor: 'transparent',
alignItems: 'center',
justifyContent: 'center',
},
});
I am trying to collapse a set of foreign objects so I do that like this.
$scope.render = function(center) {
$scope.direction = $scope.direction ? $scope.direction : "RL";
var nodes = $scope.container.selectAll(".subNode")
.data($scope.node.nodes);
nodes.exit().remove();
$scope.nodeElements = nodes.enter().append("g")
.attr({
class: "subNode",
})
$scope.nodeElements.append("foreignObject")
.attr("width", 300)
.attr("height", 100)
.append("xhtml:body")
.style("font", "14px 'Helvetica Neue'")
.html(function(d) {
return d.label
});
$scope.container.select("rect.mainNode")
.remove();
var mainNode = $scope.container.append("g")
.attr({
x: 0,
y: 0,
class: "mainNode",
})
.on({
click: function(d) {
$scope.open = !$scope.open;
$scope.container.selectAll(".subNode").transition()
.attr({
transform: function(d, i) {
var r = 0;
if ($scope.open) {
r = i * 100 + i * 10 + 110;
}
if ($scope.direction == "RL") {
return "translate(" + r + ", 0)"
} else {
return "translate(0, " + r + ")"
}
}
})
}
})
mainNode.append("foreignObject")
.attr("width", 10)
.attr("height", 10)
.append("xhtml:body")
.style("font", "14px 'Helvetica Neue'")
.html($scope.node.label);
mainNode.selectAll("foreignObject").transition()
.attr({
x: function(d, i) {
return 0
},
y: 0,
height: 100,
width: 300
});
}
When i inspect the DOM everything looks correct but I do not see the text. What am I missing?
Looks like the transform does not move the viewable window. If I add an x/y to the foreign object instead it works.