Prevent JointJS elements from generating off paper - reactjs

I'm using JointJS in a React environment to create a Directed graph from some Neo4j data. My problem is that elements are being generated off the paper As pictured here, "test6" is generated mostly off the page and "test10" isn't even shown. I would like all elements to be displayed on the paper, without overlapping each other or links if possible.
My paper is defined with only a width dimension set equal to the div width and the div is styled to be 100% width
...
width: $('#paper').width(),
...
and
render(){
return(
<React.Fragment>
<div id="paper" style={{width:'100%'}}></div>
</React.Fragment>
)
}
The code for generating an element is as follows:
function makeElement(node) {
var maxLineLength = _.max(node.name.split('\n'), function(l) { return l.length; }).length;
var letterSize = 12;
var width = 2 * (letterSize * (0.6 * maxLineLength + 1));
var height = 2 * ((node.name.split('\n').length + 1) * letterSize);
return new joint.shapes.basic.Rect({
id: node.id,
size: { width: 100, height: height },
attrs: {
type:'node',
text: {
text: node.name,
'font-size': letterSize,
'font-family': 'monospace' },
rect: {
width: width, height: height,
rx: 5, ry: 5,
stroke: '#555'
}
}
});
}
Thanks in advance :)
EDIT: I don't have the exact solution yet, but in the meantime I used this to make the paper draggable to view all nodes

Related

Filling a chart with a color above a specific Y value

I've been trying to fill a chart with color above certain Y value and I can't do it.
What I tried to do is writing a condition that if the value is >3.5 that area of the chart will be filled with a different color.
splineSeries.fill(function() {
if (this.value > 3.5)
return '#d3f335 0.4'
else
return '#cdf0a7 0.6'})
However, this doesn't work for me as it fills the whole area of the chart which values are >3.5 and not only the area that it's above the line.
This is how that chunk of code is working in my chart.1
If you know how this can be solved I would really appreciate if you help me :)
Thanks!!
It does not seem AnyChart supports this kind of filling out of the box. You can however get creative with gradients:
var cmin = chart.getStat("yScalesMin");
var cmax = splineSeries.getStat('seriesMax');
var cutoff = 3.5;
splineSeries.fill({
angle: 90,
keys: [{
color: '#cdf0a7',
opacity: 0.6,
offset: (cutoff-cmin) / (cmax-cmin)
}, {
color: '#d3f335',
opacity: 0.4,
offset: (cutoff-cmin) / (cmax-cmin)
}],
thickness: 3
});
Here's a working example:
chart = anychart.area();
var series = chart.area(generateRandomData());
var cmin = chart.getStat("yScalesMin");
var cmax = series.getStat('seriesMax');
var cutoff = 3.5;
series.fill({
angle: 90,
keys: [{
color: '#cdf0a7',
opacity: 0.6,
offset: (cutoff-cmin) / (cmax-cmin)
}, {
color: '#d3f335',
opacity: 0.4,
offset: (cutoff-cmin) / (cmax-cmin)
}],
thickness: 3
});
chart.container("chart");
chart.draw();
function generateRandomData() {
var data = [];
for (let i=0; i<16; i++) {
data.push({x:i, value:Math.random() * 7.5});
}
return data;
}
#chart {
height: 400px;
}
<script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-core.min.js"></script>
<script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-cartesian.min.js"></script>
<div id="chart"></div>
This should also work with charts with yScalesMin > 0

Need dynamic icon for MarkerClusterer in #react-google-maps/api

TL;DR: In #react-google-maps/api, I want to be able to make dynamic cluster icons/symbol in the style of pie charts, based on the markers in the cluster, but it seems I can only make icons from a static array, and cannot pass the the markers as parameters.
Full Description:
I am using typescript react with the package #react-google-maps/api, and I'm trying to find a way with the ClustererComponent/MarkerClusterer to take a callback or similar in order to be able to be able to create an svg for each cluster based on the markers in the given cluster.
The current issue is that the way I understand it, I am limited to a static array of urls to icons, and thought I can make an svg in those, I have no way to pass parameters into those svgs, as the only way the package allows me to chose a style is thought index in the style array.
I have read thought the following material, but have not been able to get find a way to make an icon dynamically based on the markers:
Documentation for #react-google-maps/api: https://react-google-maps-api-docs.netlify.app/#markerclustere
Documentation for google maps markerclusterer: https://developers.google.com/maps/documentation/javascript/marker-clustering
I have found libraries like this: https://github.com/hassanlatif/google-map-chart-marker-clusterer, that should be able to be used as a solution, but they don't seem to work with the #react-google-maps/api, only with earlier versions of google map. If this is not the case, and these can be used directly, then I would be more then happy with an answer describing how to use libraries like the one above with #react-google-maps/api, as that should allow be to make clusters in the same way as the picture below.
EDIT: as I got reminded in the comments, here is the code I have so far:
What I've tried: I have tried to find any way to set in an svg element instead of a url, but have since just decided to make a url with the svg data, as shown below. I have tried to edit the url of the clusters under the MarkerClusterer thought the callback for onClusteringBegin, onClusteringEnd and onLoad, but so far, no luck.
How I make the svg into url-data, so it can be used for img src
/*
* Pie Chart SVG Icon in URL form
*
* Inspiration taken from: https://medium.com/hackernoon/a-simple-pie-chart-in-svg-dbdd653b6936
*
* Note: As of right now, I am identifying the difference in marker types by setting the type-number I use in the title of the marker
*/
const serializeXmlNode = (xmlNode: any) => {
if (typeof window.XMLSerializer != "undefined") {
return (new window.XMLSerializer()).serializeToString(xmlNode);
} else if (typeof xmlNode.xml != "undefined") {
return xmlNode.xml;
}
return "";
}
function getCoordinatesForPercent(percent: number) {
const x = Math.cos(2 * Math.PI * percent);
const y = Math.sin(2 * Math.PI * percent);
return [x, y];
}
const makePieChartIcon = (slices: any[]) => {
const svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg')
svg.setAttribute('viewBox', '-1.1 -1.1 2.2 2.2')
svg.setAttribute('style', 'transform: rotate(-90deg)')
svg.setAttribute('height', '60')
var circle = document.createElementNS(svgNS, 'circle')
circle.setAttribute('r', '1.1')
circle.setAttribute('fill', 'white')
svg.appendChild(circle);
let cumulativePercent = 0;
slices.map((slice: any) => {
const [startX, startY] = getCoordinatesForPercent(cumulativePercent);
cumulativePercent += slice.percent;
const [endX, endY] = getCoordinatesForPercent(cumulativePercent);
const largeArcFlag = slice.percent > .5 ? 1 : 0;
const pathData = [
`M ${startX} ${startY}`, // Move
`A 1 1 0 ${largeArcFlag} 1 ${endX} ${endY}`, // Arc
`L 0 0`, // Line
].join(' ');
const path = document.createElementNS(svgNS, 'path');
path.setAttribute('d', pathData);
path.setAttribute('fill', slice.color);
svg.appendChild(path);
})
var svgUrl = 'data:image/svg+xml;charset=UTF-8,' + serializeXmlNode(svg)
return svgUrl
}
const makeDynamicClusterIcon = (markers: any[]) => {
var numMarkers = markers.length;
var slices = markers.reduce((acc: any, marker: any) => {
acc[parseInt(marker.title)].percent += 1 / numMarkers;
return acc;
}, [
{ percent: 0, color: 'Green' },
{ percent: 0, color: 'Blue' },
{ percent: 0, color: 'Red' },
])
var newIconURL = makePieChartIcon(slices)
return newIconURL;
}
How I use the MarkerClusterer Component
<MarkerClusterer
options={{
averageCenter: true,
styles: clusterStyles,
}}
>
{(clusterer) =>
markerData.map((marker: any) => (
<Marker
key={marker.key}
title={String(marker.type)}
position={{ lat: marker.lat, lng: marker.lng }}
clusterer={clusterer}
/>
))
}
</MarkerClusterer>
Right now, I can only use some static styles, but I have them as the following for testing:
const clusterStyles = [
{
height: 50, textColor: '#ffffff', width: 50,
url: 'data:image/svg+xml;charset=UTF-8,%3Csvg xmlns="http://www.w3.org/2000/svg" height="50" width="100"%3E%3Ccircle cx="25" cy="25" r="20" stroke="black" stroke-width="3" fill="green" /%3E%3C/svg%3E',
},
{
height: 50, textColor: '#ffffff', width: 50,
url: 'data:image/svg+xml;charset=UTF-8,%3Csvg xmlns="http://www.w3.org/2000/svg" height="50" width="100"%3E%3Ccircle cx="25" cy="25" r="20" stroke="black" stroke-width="3" fill="red" /%3E%3C/svg%3E',
}
];
I found a solution, by finding out that the style array for each cluster (ClusterStyles) can be changed, and then I have change it with the data from the specific markers in the given cluster. I ended up doing this in the callback onClusteringEnd, as here:
{/* Added to the MarkerClusterer */}
onClusteringEnd={(clusterer) => {
clusterer.clusters.map((cluster) => {
cluster.clusterIcon.styles = makeDynamicClusterIcon(cluster.markers)
})
}}
And I changed the last line with return of the makeDynamicClusterIcon function I showed above to instead say:
return [{ url: newIconURL, height: 60, width: 60, textColor: '#FFFFFF', textSize: 22 }];

Why does my Animated view not stay in the correct place in React Native?

I created a "circle bar" (group of views) that switch from grey to yellow as the images move. The circles start to transform correctly but then go back to the first spot instead of going to the next circle spot to indict that you are on the next image. The yellow circle just always goes back to the first spot no matter what. Does anyone know why this might be happening?? How do I get it to stay in the next spot?
PLEASE see this video that I made of it: https://youtu.be/BCaSgNexPAs
Here is how I create and handle the circle view transformations:
let circleArray = [];
if (this.state.imgArray) {
this.state.imgArray.forEach((val, i) => {
const scrollCircleVal = this.imgXPos.interpolate({
inputRange: [deviceWidth * (i - 1), deviceWidth * (i + 1)],
outputRange: [-8, 8],
extrapolate: 'clamp',
});
const thisCircle = (
<View
key={'circle' + i}
style={[
styles.track,
{
width: 8,
height: 8,
marginLeft: i === 0 ? 0 : CIRCLE_SPACE,
borderRadius: 75
},
]}
>
<Animated.View
style={[
styles.circle,
{
width: 8,
height: 8,
borderRadius: 75,
transform: [
{translateX: scrollCircleVal},
],
backgroundColor: '#FFD200'
},
]}
/>
</View>
);
circleArray.push(thisCircle)
});
}
And here's my code for how I handle the image swiping:
handleSwipe = (indexDirection) => {
if (!this.state.imgArray[this.state.imgIndex + indexDirection]) {
Animated.spring(this.imgXPos, {
toValue: 0
}).start();
return;
}
this.setState((prevState) => ({
imgIndex: prevState.imgIndex + indexDirection
}), () => {
this.imgXPos.setValue(indexDirection * this.width);
Animated.spring(this.imgXPos, {
toValue: 0
}).start();
});
}
imgPanResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (e, gs) => {
this.imgXPos.setValue(gs.dx);
},
onPanResponderRelease: (e, gs) => {
this.width = Dimensions.get('window').width;
const direction = Math.sign(gs.dx);
if (Math.abs(gs.dx) > this.width * 0.4) {
Animated.timing(this.imgXPos, {
toValue: direction * this.width,
duration: 250
}).start(() => this.handleSwipe(-1 * direction));
} else {
Animated.spring(this.imgXPos, {
toValue: 0
}).start();
}
}
});
Html:
<Animated.Image
{...this.imgPanResponder.panHandlers}
key={'image' + this.state.imgIndex}
style={{left: this.imgXPos, height: '100%', width: '100%', borderRadius: 7}}
resizeMethod={'scale'}
resizeMode={'cover'}
source={{uri: this.state.imgArray[this.state.imgIndex]}}
/>
<View style={styles.circleContainer}>
{circleArray}
</View>
It's hard to get your code at first as it's all in the same spot.
I would first suggest breaking your code into smaller parts: ImageView, CirclesView
Now after we got that done, I would want to try to make the CirclesView a bit simpler (unless you really want this animation view inside the circle).
But from your code, I didn't see the difference for the "selectedImage" (imgIndex)
As it has calculations for each index, but with no reference to the selected image.
Rough ballpark code I would think to do with the current code:
let circleArray = [];
if (this.state.imgArray) {
this.state.imgArray.forEach((val, i) => {
const scrollCircleVal = this.imgXPos.interpolate({
inputRange: [deviceWidth * (i - 1), deviceWidth * (i + 1)],
outputRange: [-8, 8],
extrapolate: 'clamp',
});
const thisCircle = (
<View
key={'circle' + i}
style={[
styles.track,
{
width: 8,
height: 8,
marginLeft: i === 0 ? 0 : CIRCLE_SPACE,
borderRadius: 75
},
]}
>
// LOOK AT THIS LINE BELOW
{this.state.imgIndex == i &&
<Animated.View
style={[
styles.circle,
{
width: 8,
height: 8,
borderRadius: 75,
transform: [
{translateX: scrollCircleVal},
],
backgroundColor: '#FFD200'
},
]}
/>}
</View>
);
circleArray.push(thisCircle)
});
}
checkout the line I added above the "animated" circle for the "selected" circle
I would agree with most of Tzook notices ... but from what I see in code (intentions) all circles are animated (but in fact only one pair affected - one hides, one shows) - animating only selected one doesn't give that.
I animated hundreds 'sliders/galleries' in flash ;) - what with prev/next/neighborh images - shouldn't be animated at the same time?
Images with the same width (or when you can accept margins between them) are easy, you can animate container with 3 (or even only 2) images from longer (looped of course?) list/array. It's like virtual list scrolling.
Having this working circles will be easy, too - animate only 3 of them: prev/curr/next ones.
Simple animations are easy - only swapping images at the end of animation. It could look harder with f.e. bouncing effects (when image slides more/farther than it's target position and next img should be visible for a while) but you can just swap them earlier, just after recognizing direction.

Get left and top position of highcharts scrollbar

I wanted to put custom label on highcharts which should be placed on left
side of scrollbar. How can I get top and left position of scrollbar.?
I have put label with following code
chart.renderer.text('<span style="font-weight:600;"> 1-21 </span>', 20, 120)
.css({
color: 'green',
fontSize: '12px'
})
.add();
You can get position of the scrollbar using chart.xAxis[0].scrollbar.group.translateX and chart.xAxis[0].scrollbar.group.translateY, for example: https://codepen.io/anon/pen/KeBxNj?editors=1010
Snippet:
var chart = Highcharts.chart('container', {
chart: {
type: 'bar',
marginLeft: 150,
events: {
load: function () {
var scrollbar = this.xAxis[0].scrollbar,
bbox;
// Render:
this.customLabel = this.renderer.text('<span style="font-weight:600;"> 1-21 </span>', 0, 0).attr({
zIndex: 5
}).add();
// Get bbox
bbox = this.customLabel.getBBox();
// Position label
this.customLabel.attr({
x: scrollbar.group.translateX - bbox.width,
y: scrollbar.group.translateY + bbox.height
});
}
}
},
...
});
You can determine the scrollbar's left position using the chart.plotWidth + chart.plotLeft - 25. Also, the top position can be got using chart.plotTop + 10.
The numeric values are just top and left paddings.
Please have a look at this codepen.
https://codepen.io/samuellawrentz/pen/eKjdpN?editors=1010
Hope this helps :)

SheetJS xlsx-cell styling

I am referring this example to export a worksheet https://github.com/SheetJS/js-xlsx/issues/817. How to do cell
styling like background coloring,font size and increasing the width of the
cells to make the data fit exactly.I have gone through the documentation but couldn't find any proper examples to use fill etc.Is there a way to do the formatting?
Below is the code snippet:
/* make the worksheet */
var ws = XLSX.utils.json_to_sheet(data);
/* add to workbook */
var wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "People");
/* write workbook (use type 'binary') */
var wbout = XLSX.write(wb, {bookType:'xlsx', type:'binary'});
/* generate a download */
function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
saveAs(new Blob([s2ab(wbout)],{type:"application/octet-
stream"}),"sheetjs.xlsx");
Styling is only available in Pro Version of SheetJS. But I think you are using community version(free version). In Community version styling is not available.
You can check here official information:
We also offer a pro version with performance enhancements, additional
features like styling, and dedicated support.
There are a bunch of community forks that allow styling. My personal favorite is xlsx-js-style. It is up to date and works well compared to other libraries.
sheetjs-style is also up to date, but i had some problems with it. See: Styles not working
xlsx-style is not up to date. Currently 397 commits behind SheetJS:master. I would not use it if possible.
All of these libraries share the same styling options. Here is a bunch of examples:
for (i in ws) {
if (typeof(ws[i]) != "object") continue;
let cell = XLSX.utils.decode_cell(i);
ws[i].s = { // styling for all cells
font: {
name: "arial"
},
alignment: {
vertical: "center",
horizontal: "center",
wrapText: '1', // any truthy value here
},
border: {
right: {
style: "thin",
color: "000000"
},
left: {
style: "thin",
color: "000000"
},
}
};
if (cell.c == 0) { // first column
ws[i].s.numFmt = "DD/MM/YYYY HH:MM"; // for dates
ws[i].z = "DD/MM/YYYY HH:MM";
} else {
ws[i].s.numFmt = "00.00"; // other numbers
}
if (cell.r == 0 ) { // first row
ws[i].s.border.bottom = { // bottom border
style: "thin",
color: "000000"
};
}
if (cell.r % 2) { // every other row
ws[i].s.fill = { // background color
patternType: "solid",
fgColor: { rgb: "b2b2b2" },
bgColor: { rgb: "b2b2b2" }
};
}
}
I used sheetjs-style (which is a fork of sheetjs) to add formatting to cells in excel file.
ws["A1"].s = // set the style for target cell
font: {
name: '宋体',
sz: 24,
bold: true,
color: { rgb: "FFAA00" }
},
};
It's very easy. However, you have to add style to each individual cell. It's not convenient to add style to a range of cells.
UPDATE: The official example use color "FFFFAA00". But I removed the first "FF" and it still works as before. The removed part is used for transparency (see COLOR_SPEC in Cell Styles), but somehow it has no effect when I change it or remove it.
After testing all the above options. For ReactJS I finally found a package that worked perfectly.
https://github.com/ShanaMaid/sheetjs-style
import XLSX from 'sheetjs-style';
var workbook = XLSX.utils.book_new();
var ws = XLSX.utils.aoa_to_sheet([
["A1", "B1", "C1"],
["A2", "B2", "C2"],
["A3", "B3", "C3"]
])
ws['A1'].s = {
font: {
name: 'arial',
sz: 24,
bold: true,
color: "#F2F2F2"
},
}
XLSX.utils.book_append_sheet(workbook, ws, "SheetName");
XLSX.writeFile(workbook, 'FileName.xlsx');
Note following points while adding styling:-
Cell should not be empty
First add data into the cell, then add styling to that cell.
For 2 days I was struck and did not got any styling appearing on my excel file since I was just adding styling before adding the data.Don't do that it won't appear.
I used xlsx-js-style Package and added the styles to my excel in the following way :-
XLSX.utils.sheet_add_aoa(worksheet, [["Firstname"]], { origin: "A1"
});
const LightBlue = {
fgColor: { rgb: "BDD7EE" }
};
const alignmentCenter = { horizontal: "center", vertical: "center", wrapText: true };
const ThinBorder = {
top: { style: "thin" },
bottom: { style: "thin" },
left: { style: "thin" },
right: { style: "thin" }
};
const fillAlignmentBorder = {
fill: LightBlue,
alignment: alignmentCenter,
border: ThinBorder
};
worksheet["A1"].s = fillAlignmentBorder;
Hope this helps.....Happy Coding :-)

Resources