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)' };
},
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.
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 have two (or more) similiar graph on one page inside an Ionic2 app. I use d3-ng2-service for wrapping the d3 types for Angular2. My problem is the following: When I try to place the two graphs in two different div elements each inside their respective custom element the drawing fails for both. When I did select the first div in the page the second graph overrides the first one, but it does get drawn.
Is there a clever way to place graphs more the the one graph? The examples always give the outer container a unique id, which is, what I try to do too:
import { Component, Input, OnInit, ElementRef } from '#angular/core';
import { D3Service, D3, Selection, ScaleLinear, ScaleTime, Axis, Line } from 'd3-ng2-service'; // <-- import the D3 Service, the type alias for the d3 variable and the Selection interface
#Component({
selector: 'd3-test-app',
templateUrl: 'd3-test-app.html',
providers: [D3Service],
})
export class D3TestAppComponent {
//Time is on x-axis, value is on y-axis
#Input('timeSeries') timeSeries: Array<{isoDate: string | Date | number | {valueOf(): number}, value: number}>;
#Input('ref') ref: string;
/* the size input defines, how the component is drawn */
#Input('size') size: string;
private d3: D3;
private margin: {top: number, right: number, bottom: number, left: number};
private width: number;
private height: number;
private d3ParentElement: Selection<any, any, any, any>; // <-- Use the Selection interface (very basic here for illustration only)
constructor(element: ElementRef,
d3Service: D3Service) { // <-- pass the D3 Service into the constructor
this.d3 = d3Service.getD3(); // <-- obtain the d3 object from the D3 Service
this.d3ParentElement = element.nativeElement;
}
ngOnInit() {
let x: ScaleTime<number, number>;
let y: ScaleLinear<number, number>;
let minDate: number;
let maxDate: number;
let minValue: number = 0;
let maxValue: number;
// set the dimensions and margins of the graph
switch (this.size) {
case "large":
this.margin = {top: 20, right: 20, bottom: 30, left: 50};
this.width = 640 - this.margin.left - this.margin.right;
this.height = 480 - this.margin.top - this.margin.bottom;
break;
case "medium":
this.margin = {top: 20, right: 0, bottom: 20, left: 20};
//golden ratio
this.width = 420 - this.margin.left - this.margin.right;
this.height = 260 - this.margin.top - this.margin.bottom;
break;
case "small":
this.margin = {top: 2, right: 2, bottom: 3, left: 5};
this.width = 120 - this.margin.left - this.margin.right;
this.height = 80 - this.margin.top - this.margin.bottom;
break;
default:
this.margin = {top: 20, right: 20, bottom: 30, left: 50};
this.width = 640 - this.margin.left - this.margin.right;
this.height = 480 - this.margin.top - this.margin.bottom;
}
// ...
if (this.d3ParentElement !== null) {
let d3 = this.d3; // <-- for convenience use a block scope variable
//THIS FAILS...
let selector: string = '#' + this.ref + ' .graphContainer';
console.log(selector);
let svg = d3.select( selector).append("svg")
.attr("width", this.width + this.margin.left + this.margin.right)
.attr("height", this.height + this.margin.top + this.margin.bottom)
.append("g")
.attr("transform",
"translate(" + this.margin.left + "," + this.margin.top + ")");
this.timeSeries.forEach((d) => {
d.isoDate = +d3.isoParse(d.isoDate as string);
d.value = +d.value;
if (minDate == null || minDate >= d.isoDate) {
minDate = d.isoDate as number;
}
if (maxDate == null || maxDate <= d.isoDate) {
maxDate = d.isoDate as number;
}
// if (minValue == null || minValue >= d.value) {
// minValue = d.value as number;
// }
if (maxValue == null || maxValue <= d.value) {
maxValue = d.value as number;
}
});
// TODO magic numbers to real min max
x = d3.scaleTime().domain([minDate, maxDate]).range([0,this.width]);
y = d3.scaleLinear().domain([0, maxValue]).range([this.height, 0]);
let xAxis: Axis<number | Date | {valueOf() : number;}> = d3.axisBottom(x);
let yAxis: Axis<number | {valueOf(): number;}> = d3.axisLeft(y);
let valueLine: Line<{isoDate: number; value: number}> = d3.line<{ isoDate: number; value: number }>()
.x(function (d) { return x(d.isoDate)})
.y(function (d) { return y(d.value)});
// Add the valueline path.
svg.append("path")
.data([this.timeSeries as {isoDate: number, value: number}[]])
.attr("class", "line")
.attr("d", valueLine);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + this.height + ")")
.call(xAxis)
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
}
}
myParser() : (string) => Date {
return this.d3.utcParse("%Y-%m-%dT%H:%M:%S.%LZ");
}
}
The HTML:
<div class='graphContainer'>
</div>
The HTML file where the custom component is used:
<ion-header>
<ion-navbar #dashboardNav>
<ion-title>Dashboard</ion-title>
<button ion-button menuToggle="favMenu" right>
<ion-icon name="menu"></ion-icon>
</button>
</ion-navbar>
</ion-header>
<ion-content>
<ion-item *ngFor="let entry of dashboard">
{{ entry.name }}
<d3-test-app [id]='entry.name' [timeSeries]='entry.timeSeries' [ref]='entry.name' size='medium'></d3-test-app>
</ion-item>
</ion-content>
Hard to debug without seeing a stack trace but it looks like this is failing because of how you select the element. I will base my answer on that assumption.
Querying by ID is handy when you have to look inside the DOM for a specific element and when you are sure there is only one element with that ID. Since you are inside an Angular element you already have the reference you need, it's the element itself, no need to dynamically create ID references.
I am not an expert in ng2 at all, but take a look a at how to select the raw element in Angular and choose the best approach for the framework. Say you go for something like the example shown on this answer:
constructor(public element: ElementRef) {
this.element.nativeElement // <- your direct element reference
}
NB - looks like there are various way of achieving this... not sure this is the best/correct one, but the goal is to get the ref anyway
Then simply select it via the D3 dsl like you are already doing by passing that raw reference
// At this point this.element.nativeElement
// should contain the raw element reference
if (this.d3ParentElement !== null) {
const { d3, element } = this; // <-- for convenience use block scope variables
const svg = d3.select(element.nativeElement).append("svg")...
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.