How can I simulate a click on Highchart - javascript

This is my DashboardPage component:
export const DashboardPage = (props) => {
const [mounted, setMounted] = useState(false);
const { index } = props;
let navigate = useNavigate();
useEffect(() => {
if (mounted === false) {
setMounted(true);
index();
}
}, [mounted, index]);
const optionNUsersPerCompany = {
chart: {
plotBackgroundColor: null,
plotBorderWidth: null,
plotShadow: false,
type: 'pie'
},
title: {
text: 'Utenti per azienda'
},
tooltip: {
pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
},
accessibility: {
point: {
valueSuffix: '%'
}
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
enabled: true,
format: '<b>{point.name}</b>: {point.percentage:.1f} %'
},
}
},
series: [{
name: 'Utenti',
colorByPoint: true,
data: getDataUsersPerCompany(props.items),
point: {
events: {
click: (e) => {
console.log(e);
props.setCompanyIdInUsersStore(e.target.point.idCompany);
return navigate(PATH_USERS);
}
}
}
}]
}
return (
<Chart options={optionNUsersPerCompany} />
)
}
const mapDispatchToProps = (dispatch) => {
return {
index: () => dispatch(statsActions.index()),
setCompanyIdInUsersStore: (id) => dispatch(companiesActions.setCompanyIdInUsersStore(id)),
};
}
const mapStateToProps = (state) => {
return {
items: state.stats.items,
}
};
export default connect(mapStateToProps, mapDispatchToProps)(DashboardPage);
And this is the simple Highchart Component:
import React, { useState } from "react";
import HighchartsReact from "highcharts-react-official";
import Highcharts from 'highcharts';
export default React.memo(function Chart(props) {
const [currentOptions, setCurrentOptions] = useState({});
/**
* Questo controllo serve a non re-draware per due volte di seguito i containers
*/
if (JSON.stringify(props.options) !== JSON.stringify(currentOptions)) {
setCurrentOptions(props.options);
}
return (
<HighchartsReact
highcharts={Highcharts}
options={currentOptions}
immutable={true}
/>
);
});
I want to test the click on plotOptions with Jest in React Highcharts. This is the resulting HTML page from Jest:
<div>
<div
data-highcharts-chart="1"
style="overflow: hidden;"
>
<div
class="highcharts-container "
dir="ltr"
id="highcharts-r3a94y8-149"
style="position: relative; overflow: hidden; width: 600px; height: 400px; text-align: left; line-height: normal; z-index: 0; user-select: none; outline: none;"
>
<svg
class="highcharts-root"
height="400"
style="font-family: \"Lucida Grande\", \"Lucida Sans Unicode\", Arial, Helvetica, sans-serif; font-size: 12px;"
version="1.1"
viewBox="0 0 600 400"
width="600"
xmlns="http://www.w3.org/2000/svg"
>
<desc>
Created with Highcharts 10.1.0
</desc>
<defs>
<clippath
id="highcharts-r3a94y8-151-"
>
<rect
fill="none"
height="375"
width="580"
x="0"
y="0"
/>
</clippath>
</defs>
<rect
class="highcharts-background"
fill="#ffffff"
height="400"
rx="0"
ry="0"
width="600"
x="0"
y="0"
/>
<rect
class="highcharts-plot-background"
fill="none"
height="375"
width="580"
x="10"
y="10"
/>
<rect
class="highcharts-plot-border"
data-z-index="1"
fill="none"
height="375"
width="580"
x="10"
y="10"
/>
<g
class="highcharts-series-group"
data-z-index="3"
>
<g
class="highcharts-series highcharts-series-0 highcharts-pie-series highcharts-tracker"
data-z-index="0.1"
opacity="1"
style="cursor: pointer;"
transform="translate(10,10) scale(1 1)"
>
<path
class="highcharts-point highcharts-color-0"
d="M 290 175 A 0 0 0 0 1 290 175 L 290 175 A 0 0 0 0 0 290 175 Z"
fill="#7cb5ec"
opacity="1"
stroke="#ffffff"
stroke-linejoin="round"
stroke-width="1"
transform="translate(0,0)"
/>
</g>
<g
class="highcharts-markers highcharts-series-0 highcharts-pie-series"
data-z-index="0.1"
opacity="1"
transform="translate(10,10) scale(1 1)"
/>
</g>
<text
class="highcharts-title"
data-z-index="4"
style="color: rgb(51, 51, 51); font-size: 18px; fill: #333333;"
text-anchor="middle"
x="300"
y="24"
>
Utenti per azienda
</text>
<text
class="highcharts-subtitle"
data-z-index="4"
style="color: rgb(102, 102, 102); fill: #666666;"
text-anchor="middle"
x="300"
y="10"
/>
<text
class="highcharts-caption"
data-z-index="4"
style="color: rgb(102, 102, 102); fill: #666666;"
text-anchor="start"
x="10"
y="397"
/>
<g
class="highcharts-data-labels highcharts-series-0 highcharts-pie-series highcharts-tracker"
data-z-index="6"
opacity="0"
style="cursor: pointer;"
transform="translate(10,10) scale(1 1)"
>
<path
class="highcharts-data-label-connector highcharts-color-0"
d="M 295 370 C 290 370 290 352 290 346 L 290 340"
fill="none"
stroke="#7cb5ec"
stroke-width="1"
/>
<g
class="highcharts-label highcharts-data-label highcharts-data-label-color-0"
data-z-index="1"
style="cursor: pointer;"
transform="translate(300,360)"
>
<text
data-z-index="1"
style="color: rgb(0, 0, 0); font-size: 11px; font-weight: bold; fill: #000000;"
x="5"
y="16"
>
<tspan
style="font-weight: bold;"
>
acme
</tspan>
: 100.0 %
</text>
</g>
</g>
<g
class="highcharts-legend highcharts-no-tooltip"
data-z-index="7"
>
<rect
class="highcharts-legend-box"
fill="none"
height="8"
rx="0"
ry="0"
visibility="hidden"
width="8"
x="0"
y="0"
/>
<g
data-z-index="1"
>
<g />
</g>
</g>
<text
class="highcharts-credits"
data-z-index="8"
style="cursor: pointer; color: rgb(153, 153, 153); font-size: 9px; fill: #999999;"
text-anchor="end"
x="590"
y="395"
>
Highcharts.com
</text>
</svg>
</div>
</div>
</div>
I'm thinking of get the g element and click on it
render(<BrowserRouter><DashboardPage store={store} /></BrowserRouter>);
const company = screen.getAllByRole('g');
but I got:
TestingLibraryElementError: Unable to find an accessible element with the role "g"
There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole
I'm trying to get element by class name:
it('Can handle handleOnUserClick on Chart', async () => {
let initialState = {
stats: {
items: {
nUsersPerCompany: [
{
company: "demolitori",
qty: 5,
id: 16
}
]
}
},
setCompanyIdInUsersStore: () => {},
};
let store = mockStore(initialState);
const { container } = render(<BrowserRouter><DashboardPage store={store} /></BrowserRouter>);
// This console.log outputs the previous HTML code
console.log(prettyDOM(container));
const company = await container.getElementsByClassName("highcharts-label");
console.log(company);
fireEvent.click(company);
});
But with last console.log(company) I get an empty HTML collection:
HTMLCollection {}
I tried also to remove the async/await mode without luck.
I tried to use:
const {container} = render(<BrowserRouter><DashboardPage store={store} /></BrowserRouter>);
console.log(prettyDOM(container))
const path = container.getElementsByClassName("highcharts-color-0");
console.log(prettyDOM(path[0]))
fireEvent.click(path[0]);
The test itself works, but coverage complain with missing the arrow function in click e
point: {
events: {
click: (e) => {
props.setCompanyIdInUsersStore(e.target.point.idCompany);
return navigate(PATH_USERS);
}
}
}

The coverage statistic is correct, as Highcharts render the elements using SVG and other components (path, rect etc.) that do not support click events simulation by DOM. If you replace click with mouseOver, your test coverage would hit 100%, as it is supported.
Highcharts team might be doing some custom implementation to achieve click handlers and we need to utilize that to solve your use case. #ppotaczek pointed out the correct approach.
Code modifications:
Assume DashboardPage component accepts an optional prop, onAfterChartCreated.
It passes that prop over to Chart component as value of callback.
import { useState, useEffect, useRef } from "react";
import {useNavigate} from 'react-router-dom';
import Chart from "./Chart";
import {connect} from 'react-redux';
export const DashboardPage = (props) => {
// previous code
return (
<Chart options={optionNUsersPerCompany} callback={props.onAfterChartCreated} />
)
}
// follow up code
export default connect(mapStateToProps, mapDispatchToProps)(DashboardPage);
Chart component should be modified to:
import React, { useState } from "react";
import HighchartsReact from "highcharts-react-official";
import Highcharts from 'highcharts';
export default React.memo(function Chart(props) {
const [currentOptions, setCurrentOptions] = useState({});
/**
* Questo controllo serve a non re-draware per due volte di seguito i containers
*/
if (JSON.stringify(props.options) !== JSON.stringify(currentOptions)) {
setCurrentOptions(props.options);
}
return (
<HighchartsReact
highcharts={Highcharts}
options={currentOptions}
immutable={true}
callback={props.callback}
/>
);
});
callback is a prop on HighCharts react wrapper, that executes when chart is rendered and returns an instance of the chart. You could refer to their docs for more info or check out How do i access highcharts api after component renders?
Now we modify the test case for DashboardPage component as follows:
const afterChartCreatedCallback = (chart) => {
// We can now trigger click on any data point using Highcharts API
chart.series[0].data[0].firePointEvent('click');
}
render(<DashboardPage onAfterChartCreated={afterChartCreatedCallback} />);
So, we only use the callback in the test, to improve the coverage. This should increase your DashboardPage component coverage to 100%.

Related

SVG Text are being truncated

I currently working on the integration of an SVG map,i tried two technique to render this map, first one, first try was working well but really messy stuff regarding how the data was organized. Second one, really cleaner, but now it the text is being truncated when i render the map.
As you see we have the city Renn that is supposed to be Rennaz, Chess that is supposed to be Chessel and Corbeyrie that is supposed to be Corbeyrier
Here is how i organize the data now:
[
{
id: generateRandomId(),
name: 'Rennaz',
map: {
positionX: 42.7303,
positionY: 230.0168,
polygonClassModifier: `--lighter`,
polygonPoints: `61.1,214.8 61.1,220.1 58.2,232.6 58.2,234.7 55.3,237 53.9,234.7 50.8,234.7 42.8,240 42.8,229.7
42.8,224.4 50.8,222.2 47.8,220.1 55.3,214.8`,
},
},
{
id: generateRandomId(),
name: 'Roche',
map: {
positionX: 50.8895,
positionY: 249.4768,
polygonClassModifier: `--medium-lighter`,
polygonPoints: `58.2,232.6 65.6,234.7 70.7,232.6 73.6,232.6 75.8,234.7 75.8,237 78.1,240 78.1,242.9 81.1,240
83.2,242.9 78.1,247.3 70.7,255.4 65.6,256.8 65.6,259.8 50.8,262.8 45.7,262.8 40.5,259.8 42.8,256.8 45.7,255.4 38.3,244.3
40.5,240 42.8,240 50.8,234.7 53.9,234.7 55.3,237 58.2,234.7`,
},
},
...
]
How i render it (Vue.js)
<svg
:class="INTERACTIVE_MAP_CSS_CLASSES.svg"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 0 345.7 468.1"
xml:space="preserve"
pointer-events="auto"
>
<g v-for="city in communes" :key="city.id">
<g>
<polygon
class="interactive-map__polygon"
:points="city.map.polygonPoints"
#click="$emit('onCommuneClick', city)"
/>
</g>
<text
:transform="`matrix(1 0 0 1 ${city.map.positionX} ${city.map.positionY})`"
>
{{ city.name }}
</text>
<g
v-if="city.map.hasPoint"
:transform="`matrix(
1 0 0 1 ${city.map.positionX - 6} ${city.map.positionY - 3.5}
)`"
stroke-width="3"
>
<circle cx="1" cy="1" r="2.5" />
</g>
</g>
</svg>
Before when it was working (messy solution)
I would have 3 properties in a object
the citiesName
the pins(black filled circle)
the polygons
export const interactiveMap = {
citiesName: [
{
id: generateRandomId(),
name: 'Rennaz',
transform: 'matrix(0.92 0 0 1 42.7303 230.0168)',
},
{
id: generateRandomId(),
name: 'Roche',
transform: 'matrix(0.92 0 0 1 50.8895 249.4768)',
},
],
pins: [
{
id: generateRandomId(),
paths: [
`M169.5,384.4L169.5,384.4c1.4,0,2.5,1.1,2.5,2.5l0,0c0,1.4-1.1,2.5-2.5,2.5l0,0c-1.4,0-2.5-1.1-2.5-2.5l0,0
C167,385.5,168.1,384.4,169.5,384.4z`,
`M169.5,386.1L169.5,386.1c0.4,0,0.8,0.3,0.8,0.7l0,0c0,0.4-0.3,0.7-0.7,0.7l0,0c-0.4,0-0.7-0.3-0.7-0.7l0,0
C168.7,386.5,169,386.1,169.5,386.1z`,
],
},
],
polygons: [
{
id: generateRandomId(),
cssClass: `${POLYGONS_BASE_CSS_CLASS}--lighter`,
points: `61.1,214.8 61.1,220.1 58.2,232.6 58.2,234.7 55.3,237 53.9,234.7 50.8,234.7 42.8,240 42.8,229.7
42.8,224.4 50.8,222.2 47.8,220.1 55.3,214.8`,
},
{
id: generateRandomId(),
cssClass: `${POLYGONS_BASE_CSS_CLASS}--medium-lighter`,
points: `58.2,232.6 65.6,234.7 70.7,232.6 73.6,232.6 75.8,234.7 75.8,237 78.1,240 78.1,242.9 81.1,240
83.2,242.9 78.1,247.3 70.7,255.4 65.6,256.8 65.6,259.8 50.8,262.8 45.7,262.8 40.5,259.8 42.8,256.8 45.7,255.4 38.3,244.3
40.5,240 42.8,240 50.8,234.7 53.9,234.7 55.3,237 58.2,234.7`,
},
]
}
Messy solution render:
<svg
id="aas-map"
class="interactive-map"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 0 345.7 468.1"
xml:space="preserve"
pointer-events="auto"
>
<g>
<polygon
v-for="polygon in polygons"
:key="polygon.id"
class="interactive-map__polygon"
:points="polygon.points"
/>
</g>
<text
v-for="city in cities"
:key="city.id"
:transform="city.transform"
class="interactive-map__city-name"
>
{{ city.name }}
</text>
<g
v-for="pin in pins"
:key="pin.id"
class="interactive-map__pin"
>
<path v-for="path in pin.paths" :key="path" :d="path" />
</g>
</svg>
I believe it has something to do with the main where i do my v-for, that's the only difference in the organisation between the two compared logic.
I've tried various things so far:
edit some css properties (overflow:visible, z-index,...) not working
tried to put the text before the polygon, but they are just being hidden by the polygon afterward
Edit the matrix in the transform, not helped me too
If anyone has a clue, thanks for reaching out :)
Thanks
As #enxaneta has pointed out:
you have to make sure, your labels are "on top" (decreasing font-size also makes sense).
In svg (like in html) these label elements would be written at the end/bottom of your svg markup.
So you need to seperate label output from your regular shape loop.
Probably there's a better way in vue.js to avoid a second list rendering.
The main idea of this snippet: both city areas and labels are added to a corresponding <g> element. (<g id="city-areas" > and <g id="city-labels">).
var cities =
[
{
id: 123,
name: 'Rennaz',
map: {
positionX: 42.7303,
positionY: 230.0168,
polygonClassModifier: `--lighter`,
polygonPoints: `61.1,214.8 61.1,220.1 58.2,232.6 58.2,234.7 55.3,237 53.9,234.7 50.8,234.7 42.8,240 42.8,229.7
42.8,224.4 50.8,222.2 47.8,220.1 55.3,214.8`,
},
},
{
id: 456,
name: 'Roche',
map: {
positionX: 50.8895,
positionY: 249.4768,
polygonClassModifier: `--medium-lighter`,
polygonPoints: `58.2,232.6 65.6,234.7 70.7,232.6 73.6,232.6 75.8,234.7 75.8,237 78.1,240 78.1,242.9 81.1,240
83.2,242.9 78.1,247.3 70.7,255.4 65.6,256.8 65.6,259.8 50.8,262.8 45.7,262.8 40.5,259.8 42.8,256.8 45.7,255.4 38.3,244.3
40.5,240 42.8,240 50.8,234.7 53.9,234.7 55.3,237 58.2,234.7`,
},
},
];
var svgmap = new Vue({
el: '#svgmap',
data: {
communes: cities
}
})
text {
font-size: 7px;
fill: red;
font-weight: bold;
font-family: 'Segoe UI'
}
svg{
border:1px solid red;
max-width :50vw;
}
polygon{
fill: #ccc;
stroke: #000;
stroke-width: 0.5;
}
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
<svg id="svgmap"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 200 200 400"
xml:space="preserve"
pointer-events="auto"
>
<g id="city-areas" >
<polygon v-for="city in communes"
class="interactive-map__polygon"
:points="city.map.polygonPoints"
/>
</g>
<g id="city-labels">
<text v-for="city in communes" :key="city.id"
:transform="`matrix(1 0 0 1 ${city.map.positionX} ${city.map.positionY})`"
>
{{ city.name }}
</text>
</g>
</svg>

React SVG style element on hover

I have an array of squares inside SVG-based React component and I need to apply custom style (fill color set to purple) to square under cursor on hover.
I tried that with both CSS :hover and onMouseOver events, neither did work.
Any clues are much appreciated
const { render } = ReactDOM,
rootNode = document.getElementById('root')
const Matrix = ({m, n}) => (
<svg
viewBox={`0 0 ${m*10+10} ${n*10+10}`} xmlns="http://www.w3.org/2000/svg"
>
<defs>
<rect
id="cell"
width="5"
height="5"
className="cell"
onMouseOver={({target}) => target.style.cssText="fill:purple"}
/>
</defs>
<g>
{
Array(n).fill().map((row, rowIdx) => (
<g key={rowIdx}>
{
Array(m).fill().map((col, colIdx) => (
<g key={colIdx}>
<use
x={5+colIdx*5}
y={5+rowIdx*5}
xlinkHref="#cell"
fill="lightgray"
stroke="white"
strokeWidth=".4"
/>
</g>
))
}
</g>
))
}
</g>
</svg>
)
render (
<Matrix m={10} n={10} />,
rootNode
)
#cell:hover {
fill: purple;
}
.cell:hover {
fill: purple;
}
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script></script><div id="root"></div>
If you have to color each square you must have to use your class .cell inside the <use /> of each cell:
<use
className="cell"
x={5+colIdx*5}
y={5+rowIdx*5}
xlinkHref="#cell"
fill="grey"
stroke="white"
strokeWidth=".4"
/>
And now you can use your css style:
.cell:hover {
fill: purple;
}
Snippet:
const { render } = ReactDOM,
rootNode = document.getElementById('root')
const Matrix = ({m, n}) => (
<svg
viewBox={`0 0 ${m*10+10} ${n*10+10}`} xmlns="http://www.w3.org/2000/svg"
>
<defs>
<rect
id="cell"
width="5"
height="5"
/>
</defs>
<g>
{
Array(n).fill().map((row, rowIdx) => (
<g key={rowIdx}>
{
Array(m).fill().map((col, colIdx) => (
<g key={colIdx}>
<use
className="cell"
x={5+colIdx*5}
y={5+rowIdx*5}
xlinkHref="#cell"
fill="grey"
stroke="white"
strokeWidth=".4"
/>
</g>
))
}
</g>
))
}
</g>
</svg>
)
render (
<Matrix m={10} n={10} />,
rootNode
)
.cell:hover {
fill: purple;
}
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script></script><div id="root"></div>

What should I use to show live icons in React OR Javascript? Like how much coffee is filled in a mug?

I want to create dashboard with lots of live icons. Live icons means icons content will change dynamically.
For example:- Let say i have an icon of a coffee cup, based upon number of coffee sold the coffee level in cup goes up.
I tried solving it using SVG & CSS. Below is the live code-sandbox DEMO example. (same code I am posting below)
https://codesandbox.io/s/fill-coffee-to-svg-1sfb0
what exactly I am doing is on top of SVG component i using div node and applying css on the div tag.
Issue is that, It is very difficult to manage path/points dynamically in SVG, also for different screen version it will be difficult.
Can anyone suggest any better approach for this.
import ReactDOM from "react-dom";
import React, { Component } from "react";
class Todo extends Component {
state = { height: 4 };
setHeight = (e, fillType) => {
e.preventDefault();
console.log(this.state.height);
if (this.state.height < 100) {
if (fillType === "fill") {
this.setState({
height: this.state.height + 2
});
}
}
if (this.state.height >= 2) {
if (fillType === "drink") {
this.setState({
height: this.state.height - 2
});
}
}
};
render() {
return (
<div>
<svg width="255" height="224" xmlns="http://www.w3.org/2000/svg">
<g>
<rect
fill="#fff"
id="canvas_background"
height="226"
width="257"
y="-1"
x="-1"
/>
<g
display="none"
overflow="visible"
y="0"
x="0"
height="100%"
width="100%"
id="canvasGrid"
>
<rect
fill="url(#gridpattern)"
stroke-width="0"
y="0"
x="0"
height="100%"
width="100%"
/>
</g>
</g>
<g>
<title>Layer 1</title>
<path
stroke="#000"
id="svg_1"
d="m78.82963,176.75921l97.93778,0c14.11708,-0.03733 23.74788,-17.00704 23.70086,-34.46505l0,-11.73873c27.94999,-0.03136 48.22814,-30.02253 48.21769,-64.99381c0.01045,-35.59398 -19.86965,-64.83701 -43.00946,-64.81162l-150.95938,0l0,141.54714c0.02194,19.22158 11.60543,34.42772 24.1125,34.46207zm121.63551,-149.38391l0,0l5.20823,0c19.14875,-0.04331 25.29102,25.83983 25.19908,38.38045c0.01881,20.24897 -10.47393,39.66916 -30.40731,37.78463l0,-76.16508zm-199.71514,158.00316c0.01776,26.16387 13.9729,38.29683 25.20535,38.37149l202.59351,0c13.39827,-0.07466 25.14161,-15.13147 25.20117,-38.37149l-253.00002,0z"
stroke-width="1.5"
fill="#fff"
/>
</g>
</svg>
<div
style={{
position: "absolute",
left: "63px",
top: "9px",
height: "175px",
width: "145px",
borderBottomLeftRadius: "25px",
borderBottomRightRadius: "25px",
overflow: "auto"
}}
>
<div
style={{
height: this.state.height + "%",
width: "100%",
position: "absolute",
bottom: "0",
zIndex: 1000,
background: "green",
transition: "all .4s"
}}
/>
</div>
<button onClick={e => this.setHeight(e, "fill")}>
fill more cofee +
</button>
<button onClick={e => this.setHeight(e, "drink")}>drink cofee -</button>
</div>
);
}
}
ReactDOM.render(<Todo />, document.getElementById("root"));
Have you tried using the react-spring library?
In the basics section of react-spring documentation (can be found here - https://www.react-spring.io/docs/hooks/basics) this code snippet is given:
const props = useSpring({ x: 100, from: { x: 0 } })
return (
<animated.svg strokeDashoffset={props.x}>
<path d="..." />
</animated.svg>
)
Using your example of your coffee mug, if you would render your mug separate to the coffee filling the mug, with the coffee stroke being upwards, then you could use this technique to animate the coffee height.
Edit:
As you are using a class component you will want to use the render props API for this - https://www.react-spring.io/docs/props/spring
<Spring
from={{ x: 100 }}
to={{ x: 0 }}>
{props => (
<svg strokeDashoffset={props.x}>
<path d="..." />
</svg>
)}
</Spring>

How to switch position of D3 SVG elements

I have some SVG elements on my page that I created with D3. All are children of one parent SVG. Each contains some other D3 elements like paths and text. On the click of a button, I want two of these child SVGs to switch positions, so they move up or down on the page (all are placed above/below each other).
I already tried creating groups ("g") instead of the child SVGs and accessing/changing their positions. However, I can't seem to access the y position of the element.
I also tried using "insertAfter" but this only works with divs, not with SVGs (however, I'm looking for a similar behaviour).
$(".move_up").click(function() {
var svg = $("#second_child"); //ID is actually retrieved from an attribute of the button
svg.insertBefore(svg.prev()); //does obviously not work
});
HTML for move up button (one per child SVG exists):
<a class="move_up">
<span class="grey glyphicon glyphicon-chevron-up" title="Move up"></span>
</a>
HTML for SVG:
<div>
<svg id="parent">
<svg id="first_child"
<path></path>
<rect></rect>
<text></text>
...
</svg>
<svg id="second_child"
<path></path>
<rect></rect>
<text></text>
...
</svg>
<rect></rect>
<text></text>
...
</svg>
</div>
I want the first and second child SVGs to switch positions, when the move up (or respectively a move down) button is used.
This is what I ended up doing:
let group = $("#first_group");
let next_group = $('#second_group");
let diff = 58; // Height of a group
let translate_y = 0;
let translate_y_next = 0;
if (next_group.offset()) {
if (group.attr("transform")) {
let string = group.attr("transform");
translate_y = parseInt(string.substring(string.indexOf("(")+1, string.indexOf(")")).split(",")[1]);
}
if (prev_group.attr("transform")) {
let string_next = prev_group.attr("transform");
translate_y_next = parseInt(string_next.substring(string_next.indexOf("(")+1, string_next.indexOf(")")).split(",")[1]);
}
group.attr("transform", `translate(0, ${translate_y + diff})`);
next_group.attr("transform", `translate(0, ${translate_y_next - diff})`);
}
Works similar for a "Move up" button. Just make sure to change the sign in the last two lines!
May not be super elegant, but does the job.
You are using an SVG as wrapper and the positions are different to html. In SVG You need to define the X and Y position.
let ids = ['ex1', 'ex2', 'ex3', 'ex4', 'ex5']
let btn = document.getElementById('move')
const sortArrayAsYouWish = (array) => {
array.sort(() => Math.random() - 0.5);
}
const changeOrder = () => {
let posY = 35
sortArrayAsYouWish(ids) // change order
ids.forEach((id, i) => {
let $el = document.getElementById(id)
$el.style.transform = `translate(0, ${posY*i}px)`
})
}
btn.onclick = changeOrder
changeOrder()
svg {
width: 500px;
height: 340px;
border: solid 1px #ccc;
}
g {
transition: transform 0.4s;
}
text {
fill: #fff;
text-anchor: middle;
}
#ex2 rect {
fill: blue;
}
#ex3 rect {
fill: yellow;
}
#ex4 rect {
fill: red;
}
#ex5 rect {
fill: cyan;
}
<div><button id="move">Move</button></div>
<svg>
<g id="ex1">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 1</text>
</g>
<g id="ex2">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 2</text>
</g>
<g id="ex3">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 3</text>
</g>
<g id="ex4">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 4</text>
</g>
<g id="ex5">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 5</text>
</g>
</svg>

SVG Knockout text styling issue: unexplained cropped bottom

I've tried to build a react component for knockout text and I run into a styling issue I can't explain to myself.
Here is my attempt.
const styles = {
container: {
backgroundSize: "cover",
backgroundImage: "url(http://brokensquare.com/Code/assets/landscape.jpg)",
padding: "20% 20%"
},
knockout: {
borderRadius: 200,
overflow: "hidden"
}
};
const Knockout = ({ text, style }) => {
const s = style || {};
return (
<div style={styles.knockout}>
<svg viewBox="0 0 200 25">
<rect
fill={s.backgroundColor || "rgba(0,0,0,0.6)"}
x="0"
y="0"
width="100%"
height="100%"
mask="url(#knockout-text)"
/>
<mask id="knockout-text">
<rect fill="#fff" x="0" y="0" width="100%" height="100%" />
<text y="70%" fill="#000" textAnchor="middle" x="50%">
{text}
</text>
</mask>
</svg>
</div>
);
};
const App = Radium(() => (
<div>
<div style={styles.container}>
<Knockout style={{}} text={"VERY INSPIRATION"} />
</div>
</div>
));
As you can see the bottom is cropped for some reason, instead of having the side completely rounded, drawing half a circle. Can anyone here see why and how I could fix this ? Thanks.
add this css, it will work
svg {
display: block
}
you can check below codesandbox
https://codesandbox.io/s/71qxyx6m86
I added styles as below
render(
<div>
<App />
<Style
rules={{
"*": {
margin: 0,
padding: 0,
boxSizing: "border-box"
},
svg: {
display: "block"
}
}}
/>
</div>,
document.getElementById("root")

Categories

Resources