Aligning text around center in pie chart using svg circle and text - svg

I've created a pie chart using react and svg circles. Now I want to position the text labels so that the text starts close to the middle of the circle and continue outwards, wheel of fortune style.
This is my code, rendering the svgs. In chrome dev tools I tried to apply tranform: rotate(45deg); and adjusting the number but the text keeps flyying away outside the circle.
<svg viewBox="0 0 32 32">
{elements.map((element, index) => (
<g>
<circle
key={_id}
r={16 / 1.0053}
cx={'50%'}
cy={'50%'}
style={{
strokeDasharray: `${percentage}, 100.53`,
strokeDashoffset: -offset,
stroke: segmentColor,
}}
textAnchor="middle"
></circle>
<text
textAnchor="middle"
x={'50%'}
y={'50%'}
fontFamily={'sans-serif'}
fontSize={'20px'}
fill="black"
textAnchor="start"
>
{title}
</text>
</g>
))}
</svg>
```

The issue is that rotation is about the origin, which is the top left of your image. The easiest way around this is to change the viewBox so the origin is in the center of your SVG: viewBox="-16 -16 32 32".
Then you calculate the angle based on the offset and the percentage.
I assume you have some elements like this:
const elements = [
{ percentage: 15, offset: 0, title: 'title', segmentColor: 'red' },
{ percentage: 40, offset: 15, title: 'another title', segmentColor: 'blue' },
{ percentage: 25, offset: 55, title: 'and another', segmentColor: 'green' },
{ percentage: 20, offset: 80, title: 'yet another', segmentColor: 'black' },
];
So something like this would work:
<svg viewBox="-16 -16 32 32">
{elements.map((element, index) => {
const angle = (element.offset + element.percentage / 2) * 360 / 100;
return <g key={index}>
<circle
r={15.5}
style={{
fill: 'none',
strokeDasharray: `${element.percentage}, 100.53`,
strokeDashoffset: -element.offset,
stroke: element.segmentColor,
}}
textAnchor="middle"
></circle>
<text
x="10%"
transform={`rotate(${angle})`}
fontFamily={'sans-serif'}
fontSize={'2px'}
fill="black"
textAnchor="start"
alignmentBaseline="middle"
>
{element.title}
</text>
</g>
})}
</svg>
Now the x value on the text determined how from the center the text starts.

Related

Changing single contour svg image fill color in QML

I have made a single contour .svg cursor (basically a triangle curved at the bottom) without any fill, and I want to be able to change its fill color. I can't just make few different svgs for this, due to fill color being picked by user.
QtGraphicalEffects library is not supported since Qt6, so all I could find on this topic was of little use. The solution for this might be to just drop my svg over a colored rectangle, but then I'll have to mask out the area outside of my cursor to make it transparent(or vice-versa), and I'm not sure if it's even possible. I also found some shader-based solutions but none of then seem to work for my case (might just be me being new to QML)
SVG:
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
<defs>
<style>
.cls-1 {
fill: none;
stroke: #414141;
stroke-width: 44px;
fill-rule: evenodd;
}
</style>
</defs>
<path id="Pointer_1" data-name="Pointer 1" class="cls-1" d="M300.291,48.248L556.347,560.4s-127.234-47.825-255.133-47.825c-128.157,0-256.979,47.825-256.979,47.825Z"/>
</svg>
[MULTIPLE EDITS]
Firstly, the latest builds of Qt6 do support QtGraphicalEffects via Qt5Compat.GraphicalEffects. https://doc.qt.io/qt-6/qtgraphicaleffects5-
The approach I prefer to use is to leverage the fact that most components have an icon property which has a built-in mechanism for recoloring an SVG. You set the following properties:
icon.source
icon.width
icon.height
icon.color
By creating two SVGs we can use the above approach to control the fill and stroke independently.
The typical component I use is Button, but, I strip off the default Button UI/UX and just leverage from the icon property as follows:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
SplitView {
anchors.fill: parent
orientation: Qt.Horizontal
Item {
SplitView.preferredWidth: parent.width / 2
SplitView.fillHeight: true
Item {
anchors.centerIn: parent
width: parent.width * 8 / 10
height: parent.height * 8 / 10
Button {
anchors.centerIn: parent
visible: fillCombo.currentText !== 'none'
background: Item { }
icon.source: "shape-fill.svg"
icon.width: Math.min(parent.width, parent.height)
icon.height: icon.width
icon.color: fillCombo.currentText
}
Button {
anchors.centerIn: parent
visible: strokeCombo.currentText !== 'none'
background: Item { }
icon.source: "shape-outline.svg"
icon.width: Math.min(parent.width, parent.height)
icon.height: icon.width
icon.color: strokeCombo.currentText
}
}
}
Item {
SplitView.fillWidth: true
SplitView.fillHeight: true
ColumnLayout {
anchors.centerIn: parent
Label {
text: qsTr("Stroke")
}
ComboBox {
id: strokeCombo
model: [ "lightsteelblue", "red", "orange", "yellow", "green", "blue" ]
}
Label {
Layout.topMargin: 20
text: qsTr("Fill")
}
ComboBox {
id: fillCombo
model: [ "none", "red", "orange", "yellow", "green", "blue" ]
}
}
}
}
}
// shape-outline.svg
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
<path stroke="red" stroke-width="44" fill="none" d="M300.291,48.248L556.347,560.4s-127.234-47.825-255.133-47.825c-128.157,0-256.979,47.825-256.979,47.825Z"/>
</svg>
// shape-fill.svg
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
<path stroke="none" fill="black" d="M300.291,48.248L556.347,560.4s-127.234-47.825-255.133-47.825c-128.157,0-256.979,47.825-256.979,47.825Z"/>
</svg>
You can Try it Online!
Alternatively, another approach is you can build your SVG as a data uri by adding a "data:image/svg+xml," prefix to your SVG string. Because you're using a data uri, you can make a function that generates the SVG based on use input:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
SplitView {
anchors.fill: parent
orientation: Qt.Horizontal
Item {
SplitView.preferredWidth: parent.width / 2
SplitView.fillHeight: true
Image {
anchors.centerIn: parent
width: parent.width * 8 / 10
height: parent.height * 8 / 10
source: todatauri(getsvg(strokeCombo.currentText, fillCombo.currentText))
fillMode: Image.PreserveAspectFit
}
}
Item {
SplitView.fillWidth: true
SplitView.fillHeight: true
ColumnLayout {
anchors.centerIn: parent
Label {
text: qsTr("Stroke")
}
ComboBox {
id: strokeCombo
model: [ "black", "red", "orange", "yellow", "green", "blue" ]
}
Label {
Layout.topMargin: 20
text: qsTr("Fill")
}
ComboBox {
id: fillCombo
model: [ "none", "red", "orange", "yellow", "green", "blue" ]
}
}
}
}
function todatauri(svg) {
return "data:image/svg+xml," + svg;
}
function getsvg(stroke, fill) {
return `<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
<defs>
<style>
.cls-1 {
fill: none;
stroke: #414141;
stroke-width: 44px;
fill-rule: evenodd;
}
</style>
</defs>
<path id="Pointer_1" data-name="Pointer 1" class="cls-1" stroke="${stroke}" stroke-width="44" fill="${fill}" d="M300.291,48.248L556.347,560.4s-127.234-47.825-255.133-47.825c-128.157,0-256.979,47.825-256.979,47.825Z"/>
</svg>
`;
}
}
You can Try it Online!

D3 and React Hooks: Move element along an arc

I am creating a gauge with a small marker which is a triangle, I already have it working here: https://codesandbox.io/s/blazing-sun-teo9oe?file=/src/Gauge.tsx
Except for the marker transition, I need it to move along the gauge arc, so when the component prop changes from 40 to 80 for example, the marker will move from 40 to 80, right now it always starts at 50 or the middle of the arc and then moves to the final value.
Feel free to change the Gauge prop value in the index.tsx file to see the problem. Transition duration is 10 seconds to see the problem easier.
D3 and React = 300 kB (minified!)
You can do it in native JavaScript:
customElements.define("svg-gauge", class extends HTMLElement {
connectedCallback() {
let arc = "m20 45a35 35 0 1170 0";
let col = this.getAttribute("color")||"green";
this.innerHTML =
`<input type="range" min="0" max="100" step="5" value="0"`+ // delete 2 lines
` oninput="this.parentNode.percent=this.value" /><br>`+ // just for demo
`<svg viewbox="0 0 100 100">
<path d="${arc}" stroke="grey" stroke-width="6" fill="none"
stroke-linecap="round"></path>
<path id="P" d="${arc}" stroke="${col}" stroke-width="8" pathLength="100"
fill="none" stroke-linecap="round" stroke-dasharray="75 35"></path>
<circle cx="0" cy="0" fill="#fff" r="4" stroke="${col}" stroke-width="3">
<animateMotion dur="0.5s" keyPoints="0;0.75"
fill="freeze" keyTimes="0;1" calcMode="linear" path="${arc}">
</animateMotion></circle>
<text x="55%" y="40%" text-anchor="middle"/>
</svg>`;
this.style.display='inline-block';
this.percent = this.getAttribute("value") || "50";
}
set percent(val = 0) {
this.setAttribute("value", val);
let dash = val + " " + (105 - val);
this.querySelector("#P").setAttribute('stroke-dasharray', dash);
this.querySelector("animateMotion").setAttribute('keyPoints', '0;'+val/100);
this.querySelector("text").innerHTML =`${val} %`;
this.querySelector("animateMotion").beginElement();
this.querySelector("input").value = val;
}
})
svg-gauge { width:180px }
<h3>Drag sliders:</h3>
<svg-gauge value="35" color="red"></svg-gauge>
<svg-gauge value="50" color="hotpink"></svg-gauge>
<svg-gauge value="75" color="blue"></svg-gauge>

Colored SVG icons in Leaflet

I have added a SVG icon to leaflet as follows:
var myIcon = L.icon({
iconUrl: `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 512 512"><path d="M224 387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.931 0 521.286 151.707 491.481 394.785 224 387.814z"/></svg>`,
});
this.myMarker = L.marker([50.505, 30.57], { icon: myIcon }).addTo(map);
It is showing up as expected:
Now, when you add color to the SVG as follows, the icon disappears in the map:
iconUrl: `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 512 512"><path d="M224 387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.931 0 521.286 151.707 491.481 394.785 224 387.814z" fill="#fdbf00"/></svg>`,
Any thought about this?
I prepared an example instead of iconUrl I used html Then you can easily use colors in hex ;)
// config map
let config = {
minZoom: 7,
maxZomm: 18,
};
// magnification with which the map will start
const zoom = 13;
// co-ordinates
const lat = 52.237049;
const lon = 21.017532;
// calling map
const map = L.map('map', config).setView([lat, lon], zoom);
// Used to load and display tile layers on the map
// Most tile servers require attribution, which you can set under `Layer`
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
const svgTemplate = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="marker">
<path fill-opacity=".25" d="M16 32s1.427-9.585 3.761-12.025c4.595-4.805 8.685-.99 8.685-.99s4.044 3.964-.526 8.743C25.514 30.245 16 32 16 32z"/>
<path fill="#F7FADA" stroke="#000" d="M15.938 32S6 17.938 6 11.938C6 .125 15.938 0 15.938 0S26 .125 26 11.875C26 18.062 15.938 32 15.938 32zM16 6a4 4 0 100 8 4 4 0 000-8z"/>
</svg>`;
const icon = L.divIcon({
className: "marker",
html: svgTemplate,
iconSize: [40, 40],
iconAnchor: [12, 24],
popupAnchor: [7, -16]
});
const marker = L.marker([lat, lon], {
icon: icon
})
.bindPopup('#F7FADA')
.addTo(map);
* {
margin: 0;
padding: 0
}
html {
height: 100%
}
body,
html,
#map {
height: 100%;
margin: 0;
padding: 0
}
body {
height: 100%;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.6.0/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet#1.6.0/dist/leaflet.js"></script>
<div id="map"></div>
Modified #IvanSanche example below
var map = new L.Map('leaflet', {
layers: [
new L.TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
'attribution': 'Map data © OpenStreetMap contributors'
})
],
center: [0, 0],
zoom: 0
});
var myIcon = L.icon({
iconUrl: `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 512 512"><path d="M224 387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.931 0 521.286 151.707 491.481 394.785 224 387.814z"/></svg>`,
});
var myIcon2 = L.icon({
iconUrl: `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 512 512"><path d="M224 387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.931 0 521.286 151.707 491.481 394.785 224 387.814z" fill="%23fdbf00" stroke="red" /></svg>`,
});
this.myMarker = L.marker([50.505, 30.57], { icon: myIcon }).addTo(map);
this.myMarker2 = L.marker([50.505, 130.57], { icon: myIcon2 }).addTo(map);
body {
margin: 0;
}
html, body, #leaflet {
height: 100%
}
<script src="https://unpkg.com/leaflet/dist/leaflet-src.js"></script>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" /><div id="leaflet"></div>

Leaflet : Easier to select Circle Markers

I work with a Leaflet map that displays multiple circle markers when page is loaded. But users have found it a bit difficult to click/touch on the circle markers on mobiles and Ipads. Is there a way to increase the select "area" for these circle markers?
This runnable code demonstrates the use of SVG to make the marker's icon that can be your solution:
var clatlng = [15, 100];
var zoom = 8;
var mymap = L.map("mapid").setView(clatlng, zoom);
//This map tiles is simple and no hassles.
L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "Map data © OpenStreetMap contributors"
}).addTo(mymap);
const svg_O =
'<svg width="100" height="100" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50" stroke="none" opacity="0.35" fill="yellow"/><circle cx="50" cy="50" r="15" stroke="blue" fill="green"/></svg>';
const svgpin_O = encodeURI("data:image/svg+xml;utf-8," + svg_O);
const icon_O = L.icon({
iconUrl: svgpin_O,
iconSize: [100, 100],
iconAnchor: [50, 50]
});
var marker1 = L.marker(clatlng, {
icon: icon_O,
draggable: true,
autoPan: true
}).addTo(mymap);
const svg_pin =
'<svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M12,11.5A2.5,2.5 0 0,1 9.5,9A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 14.5,9A2.5,2.5 0 0,1 12,11.5M12,2A7,7 0 0,0 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9A7,7 0 0,0 12,2Z" fill="firebrick"></path></svg>';
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
const svgpin_Icon = L.icon({
iconUrl: svgpin_Url,
iconSize: [24, 24],
iconAnchor: [12, 24],
popupAnchor: [0, -22]
});
var marker2 = L.marker(clatlng, {
icon: svgpin_Icon,
draggable: true,
autoPan: true
}).addTo(mymap);
#mapid {
height: 200px;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.5.1/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossorigin="" />
<script src="https://unpkg.com/leaflet#1.5.1/dist/leaflet.js"
integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og=="
crossorigin=""></script>
<div id="mapid"></div>

CSS Sprites horrible slow using Edge or Internet Explorer 11

We have a Singe Page Application using AngularJS 1.5 and it works smoothly using Chrome but unfortunately we also support IE 11+ and probably Edge and using these browsers, the performance is close to horrible (more than two seconds delay). It seems that it has something to do with the way we are using css sprites because if I remove the background-image property everything works smoothly again. My experience on css is not very high thus I hope that somebody with a deeper insight has a hint about performance optimizations. The sprite svg file (325KB 2350px x 2340px) looks like this
<?xml version="1.0" encoding="utf-8"?>
<!-- SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2350 2340" enable-background="new 0 0 2350 2340" xml:space="preserve">
<symbol id="ic_x5F_website_x5F_blk" viewBox="0 -50 50 50">
...
<use xlink:href="#ic_x5F_website_x5F_blk" width="50" height="50" id="XMLID_496_" x="0" y="-50" transform="matrix(1 0 0 -1 284.9999 155)" overflow="visible"/>
...
</svg>
And the relevant css/ less styles are
.ico() {
&:extend(.ic-mixin);
}
.ic {
.ic-mixin;
}
.ic-mixin {
background-image: url('../icons/spritesheet.svg');
background-size: #spritesheet-width #spritesheet-height;
display: inline-block;
width: 50px;
height: 50px;
}
.icon(#column, #row, #color: blk, #offset: 0px, #offsetY: #offset) {
.createIcon(#column, #row, #color, #offset, #offsetY);
}
.createIcon(#column, #row, #color, #offsetX: 0, #offsetY: #offsetX) {
#positionX: #initIconGapX + #iconColumnWidth * (#column - 1);
#positionY: #initIconGapY + #iconOuterSize * (#row - 1);
& when (#color = wht) {
background-position: -(#positionX + #iconOuterSize + #offsetX) -(#positionY + #offsetY);
}
& when (#color = blu) {
background-position: -(#positionX + #iconOuterSize * 2 + #offsetX) -(#positionY + #offsetY);
}
}
.icon-virtual {
&:after {
.ico();
.icon(4, 23, wht, 15px);
content: ' ';
width: 20px;
height: 20px;
background-color: #green;
position: absolute;
top: 2px;
right: 2px;
}
}
Why is Chrome so much faster? Is this a cache problem? Is IE trying to fetch the huge file over and over again? Could there be a problem with the svg file itself? Or is the css to complicated? Are we missing some important properties? Is IE too slow finding the correct background position?
Thanks a lot for any hint!
UPDATE
Exchanging the big file with a different much smaller svg does not help in improving the performance.

Resources