HighchartsReact change link radius for specific nodes - css

I'm using highcharts-react-official (v. ^3.1.0)
for drawing org-chart, but I have a problem with rounding the links between nodes (some specific nodes).
I want to change the linkRadius property for only the end corners[Please check the attached image].
const initialTree = [
{ from: 'block1', to: 'block2'},
{ from: 'block1', to: 'block3' },
{ from: 'block1', to: 'block4' },
],
const [treeOptions, updateTreeOptions] = useState({
chart: {
height: expandAllCards && treeType === 'BRAND_SUITABILITY' ? 545 : 325,
width: expandAllCards ? HTREE_WIDTH[treeType].expandAll : HTREE_WIDTH[treeType].collapsed,
inverted: true,
spacing: [0, 0, 0, 0],
marginTop: -82,
marginLeft: expandAllCards && treeType === 'BRAND_SUITABILITY' ? 25 : undefined,
marginRight: expandAllCards && treeType === 'BRAND_SUITABILITY' ? 25 : undefined,
animation: false,
},
exporting: {
enabled: false,
},
credits: {
enabled: false,
},
title: {
text: null,
},
series: [
{
type: 'organization',
name: 'Highsoft',
keys: ['from', 'to'],
borderRadius: 0,
nodePadding: 0,
borderWidth: 0,
linkRadius: 0,
minNodeLength: 106,
nodeWidth: 188,
linkColor: '#e8e9eb',
data: [...initialTree],
marker: {
radius: 10,
},
levels: [
{
level: 0,
color: 'none',
linkColor: '#b3b3b3',
},
{
level: 1,
linkColor: 'transparent',
color: 'none',
},
{
level: 2,
color: 'none',
},
],
colorByPoint: false,
color: '#007ad0',
events: {
click: (
e: ChartClickEventObject & {
point: {
id: string | number;
linksFrom: string[];
reportURL: string;
name: string;
incidents: number;
dashboardId: string;
treeType: TreeType;
schemaName: string;
slideOutAlias: string;
};
path: Array<{ id: string }>;
},
) => {
if (!e || !e.point) return;
const clickType = (() => {
const isCorrectClick = e.composedPath && typeof e.composedPath === 'function';
if (!isCorrectClick) return;
if (e.composedPath()?.some((el: Element) => el.id === `node-toggle-${e.point.id}`)) return 'toggle';
if (e.composedPath()?.some((el: Element) => el.id === `node-incidents-${e.point.id}`))
return 'incidents-slideout';
})();
if (!clickType) return;
const {
// eslint-disable-next-line #typescript-eslint/no-shadow
point: { id, linksFrom, reportURL, name, treeType: currentTreeType, schemaName, slideOutAlias = '' },
} = e;
if (!currentTreeType) return;
const currentFullTreeConnections = Connections[TreeTypeProps[currentTreeType].connectionsId];
const currentInitialTree = currentFullTreeConnections.filter(
object => object.from === TreeTypeProps[currentTreeType].rootId,
);
switch (clickType) {
case 'toggle': {
if (linksFrom.length === 0) {
const updatedConnections = [
...currentInitialTree,
...currentFullTreeConnections.filter(connection => connection.from === id),
];
const updatedTreeOptionsState = {
...treeOptions,
chart: {
...treeOptions.chart,
height: 544,
width: HTREE_WIDTH[currentTreeType].expanded,
},
series: [
{
...treeOptions.series[0],
data: updatedConnections,
levels: [
...treeOptions.series[0].levels,
{
level: 1,
linkColor: '#b3b3b3',
color: 'none',
},
],
},
],
};
updateTreeOptions(updatedTreeOptionsState);
} else {
const updatedTreeOptionsState = {
...treeOptions,
chart: {
...treeOptions.chart,
height: 325,
width: HTREE_WIDTH[currentTreeType].collapsed,
},
series: [
{
...treeOptions.series[0],
data: currentInitialTree,
levels: [
...treeOptions.series[0].levels,
{
level: 1,
linkColor: 'transparent',
color: 'none',
},
],
},
],
};
updateTreeOptions(updatedTreeOptionsState);
}
sendVisualizationManipulation({
alias: slideOutAlias,
params: {
height:
linksFrom.length === 0
? VisualizationManipulationChangeHeight.expand
: VisualizationManipulationChangeHeight.collapse,
},
});
break;
}
case 'incidents-slideout': {
if (reportURL === '' || !reportURL) break;
SlideOut.open({
targetId: reportURL,
title: [`${name} ${SCHEMA_TYPES.get(schemaName)}`],
});
break;
}
default:
break;
}
},
},
dataLabels: {
padding: 0,
borderWidth: 0,
color: '#fff',
nodeFormatter: function nodeFormatterFn() {
// eslint-disable-next-line react/no-this-in-sfc
if (!this.point) return null;
// eslint-disable-next-line react/no-this-in-sfc
const { options, level, id, linksFrom } = this.point;
const currentTreeType = options.treeType as TreeType;
if (!currentTreeType) return null;
const isRoot = level === 0;
const isOtherCard = id === 'OTHER';
const { connectionsId } = TreeTypeProps[currentTreeType];
const hasChildren = Connections[connectionsId].find(object => object.from === id);
const isCollapsible = !isRoot && hasChildren;
const reportURLExists = !!options.reportURL && options.reportURL !== '';
let arrowColor;
const isRateChangeValid = (options.rateChange && +options.rateChange !== 0) || undefined;
if (Number(options.rateChange || 0) === 0) {
arrowColor = ARROW_GRAY_COLOR;
} else {
arrowColor =
+options.rateChange * (options.positiveValues ? -1 : 1) > 0
? ARROW_NEGATIVE_COLOR
: ARROW_POSITIVE_COLOR;
}
const cardTree = (
<MainTreeCard
border={`1px solid ${isRoot ? options.rootBorderColor : options.generalBorderColor}`}
borderLeft={`4px solid ${options.borderLeftColor}`}
width={`${options.width}px`}
height={`${options.height}px`}
backgroundColor={options.bgColor}
padding={isRoot ? '7px 9px 14px 10px' : '11px 7px 8px 10px'}>
<TopLabel
fontSize={`${options.fontSize}px}`}
letterSpacing={!isRoot ? '0.25px' : 'normal'}
// eslint-disable-next-line no-nested-ternary
marginBottom={isRoot ? '19px' : isOtherCard ? '16px' : '17px'}
lineHeight={isRoot ? '22px' : '18px'}
height={`${options.labelHeight}px}`}>
{options.name}
</TopLabel>
<FlexContainer
flexDirection={isRoot ? 'row' : 'column'}
height={isRoot ? '45px' : ''}
justifyContent='space-between'>
<FlexContainer
flexDirection='column'
justifyContent='flex-start'
paddingLeft={isRoot ? '2px' : ''}
marginBottom={!isRoot ? '12px' : ''}>
<TopValueLabel
id={`node-incidents-${id}`}
fontSize={`${!isOtherCard ? options.fontSizeIncidents : 12}px`}
fontWeight={!isOtherCard ? '400' : '700'}
marginBottom={isRoot ? '0' : '5px'}
lineHeight={isRoot ? '31px' : '17px'}
letterSpacing={isOtherCard ? '0.2px' : 'normal'}
color={reportURLExists ? '#7740bf' : '#14113b'}
cursor={reportURLExists ? 'pointer' : 'auto'}
isLinked={reportURLExists ? 1 : 0}>
{!isOtherCard ? formatNumber(options.incidents, options.incidentsType) : 'View Report'}
</TopValueLabel>
{!isOtherCard && (
<TopMeasureLabel
marginTop={isRoot ? '0' : '-3px'}
lineHeight={isRoot ? '12px' : 'normal'}
letterSpacing={isRoot ? '0.2px' : 'normal'}>
{options.amountLabel}
</TopMeasureLabel>
)}
</FlexContainer>
{!isOtherCard && (
<FlexContainer flexDirection='row' width={isRoot ? '102px' : '82px'} justifyContent='space-between'>
<FlexContainer flexDirection='column'>
<TopSecondValueLabel
fontSize={!isRoot ? '20px' : '28px'}
lineHeight={isRoot ? '31px' : '17px'}
marginBottom={isRoot ? 0 : '2px'}>
{formatNumber(options.incidentRate, options.incidentRateType)}
</TopSecondValueLabel>
<TopMeasureLabel
marginTop='0'
letterSpacing={isRoot ? '0.2px' : 'normal'}
lineHeight={isRoot ? '12px' : 'normal'}>
{options.rateLabel}
</TopMeasureLabel>
</FlexContainer>
<FlexContainer justifyContent='center' alignSelf='end'>
<FlexContainer flexDirection='column' marginBottom='1px'>
{/* eslint-disable-next-line no-nested-ternary */}
{isRateChangeValid ? (
+options.rateChange > 0 ? (
<ICONS.ARROW_UP
width={11}
height={6}
marginTop='3px'
marginRight='2px'
fill={arrowColor}
/>
) : (
<ICONS.ARROW_DOWN
width={11}
height={6}
marginTop='3px'
marginRight='2px'
fill={arrowColor}
/>
)
) : null}
</FlexContainer>
<ChangeRateLabel color={arrowColor}>
{getFormattedChangeRateLabel(options.rateChange, false)}
</ChangeRateLabel>
</FlexContainer>
</FlexContainer>
)}
</FlexContainer>
{isCollapsible && hasChildren && !options.expandAllCards && (
<ToggleLabel id={`node-toggle-${id}`} marginLeft={isRoot ? '7px' : 0}>
{linksFrom.length === 0 ? (
<>
See More
<Arrow
rotate='rotate(45deg)'
position='relative'
borderWidth='0 2px 2px 0'
left='6px'
bottom='2px'
/>
</>
) : (
<>
See Less
<Arrow
position='relative'
borderWidth='0 2px 2px 0'
top='2px'
left='1px'
marginLeft='5px'
rotate='rotate(-135deg)'
/>
</>
)}
</ToggleLabel>
)}
</MainTreeCard>
);
return ReactDOMServer.renderToString(cardTree);
},
},
},
],
tooltip: {
enabled: false,
},
});
I've tried to change the linkRadius property, but it has changed all the links radius.
Any suggestions how I can do it ?
Thanks !

First of all, the linkRadius option is deprecated, you should use link.radius. However, Highcharts API only allows setting the same radius for all of the links. To set the radius individually, you need to modify Highcharts core, for example by the below plugin:
(function(H) {
H.wrap(H.seriesTypes.organization.prototype, 'translateLink', function(proceed, point) {
if (point.from === 'CSO') {
const linkOptions = this.options.link;
const originalRadius = linkOptions.radius;
// set custom radius for a specific point
linkOptions.radius = 0;
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
// restore the original option
linkOptions.radius = originalRadius;
} else {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
}
});
}(Highcharts));
Live demo: https://jsfiddle.net/BlackLabel/zdxe7423/
API Reference: https://api.highcharts.com/highcharts/series.organization.link.radius
Docs: https://www.highcharts.com/docs/extending-highcharts/extending-highcharts

Related

OpenLayers cluster get incorrect style

I want to paint several clusters red, but when zoom the red flags disappear. I code on vue3 optionAPI.
script.js
import View from 'ol/View'
import Map from 'ol/Map'
import TileLayer from 'ol/layer/Tile'
import OSM from 'ol/source/OSM'
import VectorLayer from 'ol/layer/Vector'
import Point from 'ol/geom/Point';
import LineString from 'ol/geom/LineString';
import VectorSource from 'ol/source/Vector';
import Feature from 'ol/Feature'
import {
Circle as CircleStyle,
Fill,
Stroke,
Style,
Text,
} from 'ol/style';
import Overlay from 'ol/Overlay';
import Cluster from "ol/source/Cluster"
import { useGeographic } from 'ol/proj';
import 'ol/ol.css'
import './style.css'
import axios from 'axios'
export default {
name: 'MapContainer',
components: {},`your text`
props: {},
data() {
return {
place: [43.984506, 56.305298],
data: [],
featuresPoints: [],
featuresLines: [],
}
},
methods: {
async getData() {
axios("https://someRestApiLink.com").then(res => {
this.data = res.data;
this.createFeachers();
this.renderMap();
});
},
setCircleStyle(feature) {
const size = feature.get('features').length;
let style = styleCache[size];
if (!style) {
style = new Style({
image: new CircleStyle({
radius: 10,
stroke: new Stroke({
color: '#fff',
}),
fill: new Fill({
color: '#3399CC',
}),
}),
text: new Text({
text: size.toString(),
fill: new Fill({
color: '#fff',
}),
}),
});
styleCache[size] = style;
}
return style;
},
createFeachers() {
for (let item of this.data) {
let coords = JSON.parse(item.coords);
if (coords.length === 1) {
let feature = new Feature(new Point(coords[0].reverse()));
feature.mydata = item;
this.featuresPoints.push(feature);
} else {
let rightCoords = coords.map(el => el.reverse());
let isValidFeacture = true;
for (let i = 0; i < rightCoords.length - 1; i++) {
if (Math.abs(rightCoords[i][0] - rightCoords[i + 1][0]) > .01) {
isValidFeacture = false;
break
}
}
if (!isValidFeacture) continue;
let feature = new Feature({
geometry: new LineString(rightCoords)
});
feature.setStyle(new Style({
stroke: new Stroke({
color: '#0000ff',
width: 3
})
}))
feature.mydata = item
this.featuresLines.push(feature);
}
}
},
createMap() {
return new Map({
target: this.$refs['map-root'],
view: new View({
zoom: 12,
center: this.place
}),
layers: [
new TileLayer({
source: new OSM()
}),
this.createLineLayer(),
this.createPointLayer(),
],
});
},
createPointLayer() {
const styleCache = {};
let cluster = new Cluster({
distance: 15,
minDistance: 6,
source: new VectorSource({
features: this.featuresPoints,
})
});
const mainCluster = new VectorLayer({
source: cluster,
style: function (feature) {
function calculateFired(cluster, length) {
let pointList = cluster.values_.features;
let countFired = 0;
for (let point of pointList) {
if (point.mydata.status === "Просрочен") {
countFired++;
}
}
return countFired === length ? "full" : countFired > 0 ? "several" : "none";
}
const size = feature.get('features').length;
let style = styleCache[size];
if (!style) {
let hasFired = calculateFired(feature, size);
style = new Style({
image: new CircleStyle({
radius: 10,
stroke: new Stroke({
color: hasFired === "none" ? '#fff' : "#f00",
// color: '#fff',
}),
fill: new Fill({
color: hasFired === "full" ? "#f00" : '#3399CC',
// color: '#3399CC',
}),
}),
text: new Text({
text: size.toString(),
fill: new Fill({
color: '#fff',
}),
}),
});
styleCache[size] = style;
// console.log(style);
}
return style;
},
});
mainCluster.mydata = this.featuresPoints.map(el => el.mydata);
return mainCluster
},
createLineLayer() {
return new VectorLayer({
source: new VectorSource({
features: this.featuresLines,
}),
})
},
renderMap() {
useGeographic();
const createPopUp = this.createPopUp;
var container = document.getElementById("popup");
var content = document.getElementById("popup-content");
const map = this.createMap();
const overlay = new Overlay({
element: container,
autoPan: true
});
map.on('click', function (e) {
let pixel = map.getEventPixel(e.originalEvent);
if (document.getElementsByClassName('popup-content').length != 0) {
document.getElementsByClassName('popup-content')[0].style.display = 'none'
}
map.forEachFeatureAtPixel(pixel, function (feature) {
let data = feature.mydata ?? feature.values_.features.map(el => el.mydata);
let coodinate = e.coordinate;
content.innerHTML = createPopUp(data);
overlay.setPosition(coodinate);
map.addOverlay(overlay);
});
});
},
createPopUp(data) {
let content = "";
if (data.length) {
let sortedData = data.sort((a, b)=> {
if (a.time.split(".").reverse().join("-") > b.time.split(".").reverse().join("-")) return 1;
if (a.time.split(".").reverse().join("-") < b.time.split(".").reverse().join("-")) return -1;
return 0;
});
for (let el of sortedData) {
content += `
<div class="popup-content__valueBlock">
<div class="popup-content__valueBlock__organization">
${el.organization}:
</div>
<div class="popup-content__valueBlock__aimOfWorks">
Тип: ${el.aim_of_works}
</div>
<div class="popup-content__valueBlock__stripSurface">
Работы ведутся над: ${el.strip_surface}
</div>
<div class="popup-content__valueBlock__status">
${el.status} - ${el.finish_date}
</div>
</div>
`;
}
} else {
content = `
<div class="popup-content__valueBlock">
<div class="popup-content__valueBlock__organization">
${data.organization}:
</div>
<div class="popup-content__valueBlock__aimOfWorks">
Тип: ${data.aim_of_works}
</div>
<div class="popup-content__valueBlock__stripSurface">
Работы ведутся над: ${data.strip_surface}
</div>
<div class="popup-content__valueBlock__status">
${data.status} - ${data.time}
</div>
</div>
`;
}
return `
<div class="popup-content">
<span class="close" onclick="closePopup()">x</span>
<span class="count">Количество выбранных ордеров: ${data.length ?? 1}</span>
${content}
</div>
`;
},
},
mounted() {
this.getData();
},
}
I checked ol_uid of cluster when I rezoomed, and I was so surprised by changed this property at the same cluster. Also I tried to rerender map at every time when I changed zoom, but this not work too.
I think that I mb do something wrong on creating or rendering map or clusters.

Anticipating and validating tracking events with Cypress

I am having problems using this plug with events that are triggered from the batch url. The plugin is as follows:
Cypress.Commands.add('eventCall' , function ({
alias = 'postEvent',
name,
event,
properties = {},
} = {})
const eventType = event ? 'track' : 'page';
const url =
eventType === 'track'
? 'https://api.segment.io/v1/t'
: 'https://api.segment.io/v1/batch';
const propertyNames = Object.keys(properties);
cy.wrap(alias).as('eventAlias');
if (propertyNames.length > 0) {
cy.wrap(propertyNames).as(`${alias}PropertyNames`);
cy.wrap(properties).as(`${alias}ExpectedProperties`);
cy.wrap({}).as(`${alias}ActualProperties`);
}
cy.intercept('POST', url, (req) => {
const requestBody = JSON.parse(req.body);
if (
(eventType === 'track' && requestBody.event === event) ||
(eventType === 'page' && requestBody.name === name) ||
(eventType === 'page' && !name)
) {
req.alias = alias;
if (propertyNames.length > 0) {
this[`${alias}ActualProperties`] = requestBody.properties;
}
}
});
});
the type of event I am trying to validate is the following:
analytics.track({
anonymousId: '123213123',
userId: '10000000000111111',
event: 'EVENT NUMBER1',
properties: {
cart_id: '4069313',
name: 'aaa',
price: 0,
product_id: '1234'
}
});
And in Cypress I am trying to access as follows:
cy.SegmentCall({
event: 'EVENT NUMBER1',
properties: {
cart_id: '4069313',
name: 'aaa',
price: 0,
product_id: '1234'
},
});
});
But I can't get it to work. Does anyone have any idea

Solution application does not crash

I have a large amount of data that needs to be added to the firestore, which makes my app crash onto the main screen of my iPhone 6.
After taking steps like preventing continuous request sending or notifying the user of the progress I have minimized the application crash but not thoroughly.
upload complete image => add data to firestore
I am using expo + iphone 6
Is there an application on the PC that can test expo app performance?
save = async () => {
if (this.state.ready === true) {
this.setState({ showAlert: true, ready: false })
const a = []
this.state.image.map(async (res, index) => {
a.push(new Promise((resolve, reject) => {
resolve(Fire.shared.uploadanh(res, `product/${Fire.shared.uid}/${Date.now() + index}`))
}))
})
Promise.all(a).then((res) => {
firebase.firestore().collection("user").doc(Fire.shared.uid).collection('Product').add({
image: res,
name: this.state.name,
danhmuc: this.state.danhmuc,
nghanhhang: this.state.nghanhhang,
giaban: this.state.giaban,
xuatxu: this.state.xuatxu,
soluong: this.state.soluong,
phiban: this.state.phiban,
mota: this.state.mota,
thuonghieu: this.state.thuonghieu,
kg: this.state.kg,
...(this.state.hansudung && this.state.hansudung !== "" ? { hansudung: this.state.hansudung } : {}),
...(this.state.dungtich && this.state.dungtich !== "" ? { dungtich: this.state.dungtich } : {}),
...(this.state.baohanh && this.state.baohanh !== "" ? { baohanh: this.state.baohanh } : {}),
...(this.state.pin && this.state.pin !== "" ? { pin: this.state.pin } : {}),
...(this.state.hdh && this.state.hdh !== "" ? { hdh: this.state.hdh } : {}),
...(this.state.sizePhone && this.state.sizePhone !== "" ? { sizePhone: this.state.sizePhone } : {}),
...(this.state.memory && this.state.memory !== "" ? { memory: this.state.memory } : {}),
...(this.state.color.length > 0 ? { color: this.state.color } : {}),
...(this.state.size.length > 0 ? { size: this.state.size } : {}),
...(this.state.Material.length > 0 ? { Material: this.state.Material } : {}),
}).then((docRef) => {
alert(this.state.Material);
this.setState({ showAlert: false, ready: true })
}).catch(error => alert(error));
});
}
}

filter by category work not properly with remote search?

Hello I have problem compute vue filter by category it work not properly when I add item on Service tab and then click on Medicine tab remote search item by category it will stuck and filter not work and when I click in Service tab back it show only number 2.
this my template
<el-table
:data="itemFilters"
row-key="no"
style="width: 100%"
>
This my script
computed: {
itemFilters() {
const self = this
let item = self.form.items.filter(o => {
return o.categoryName === self.tabActive || o.itemId === ''
})
return item
},
},
this Method
methods: {
_getItemOpts(query, type) {
let exp = new RegExp(query)
let selector = {
$or: [
{ name: { $regex: exp, $options: 'i' } },
{ refNo: { $regex: exp, $options: 'i' } },
{ barcode: { $regex: exp, $options: 'i' } },
],
activityType: { $in: ['Sale'] },
status: 'Active',
'catDoc.name': this.tabActive,
}
// For form Sale Return and receipts refund
if (this.formName == 'Sale_Return' || this.formName == 'Receipt_Refund') {
selector.itemType = { $ne: 'Bundle' }
}
findItems
.callPromise({ selector: selector })
.then(result => {
// this.itemOpts = result
if (type == 'remote') {
// For remote
this.form.items[this.activeIndex].itemOpts = result
} else {
// For scan barcode
let item = result[0]
if (item) {
let row = {
itemId: item._id,
itemType: item.itemType,
memo: '',
qty: 1,
qtyRate: 1,
unitId: item.units[0].unitId,
units: item.units,
price: item.price,
amountBeforeDiscount: item.price,
discountRate: 0,
discountValue: 0,
amountAfterDiscount: item.price,
taxId: '',
taxAmount: 0,
// For expand bundle item
subItems: [],
expand: 'expand',
// For sales only(cash sales and invoice)
// refSaleOrderId: '',
// refSaleOrderDetailId: '',
// refNo: '',
itemOpts: result,
}
this.form.items.push(row)
} else {
Notify.warning({ message: 'This item do not set barcode!' })
}
}
this.loading = false
})
.catch(err => {
this.loading = false
this.$notify.error(err.reason)
})
},
}
Please help...

React-Table: How to change the background color of a row if it is clicked (selected) with the mouse?

I have the following code for retrieving data of the clicked row:
<ReactTable
getTdProps={(state, rowInfo, column, instance) => {
return {
onClick: (e, handleOriginal) => {
if (typeof rowInfo !== "undefined") this.rowClick(rowInfo.row.RecipeName);
if (handleOriginal) {
handleOriginal()
}
}
}
}}
How can I change the background color of the clicked row? Or what is the best way to highlight the clicked row?
Please see here for an answer: Select row on click react-table
Here is my code:
First of all you need a state:
this.state = {
selected: -1
};
-1 is important because otherwise the row with the index 0 will be highlighted without clicking on it.
And getTdProps looks like this:
getTrProps={(state, rowInfo, column, instance) => {
if (typeof rowInfo !== "undefined") {
return {
onClick: (e, handleOriginal) => {
this.setState({
selected: rowInfo.index
});
if (handleOriginal) {
handleOriginal()
}
},
style: {
background: rowInfo.index === this.state.selected ? '#00afec' : 'white',
color: rowInfo.index === this.state.selected ? 'white' : 'black'
},
}
}
else {
return {
onClick: (e, handleOriginal) => {
if (handleOriginal) {
handleOriginal()
}
},
style: {
background: 'white',
color: 'black'
},
}
}
}}

Resources