I am trying (in javascript) to access parts of a list of SVG components in a little test page, but I am not sure I can achieve what I want this way. The main question is:
Can I have sub-components having the same id in the two SVG top components?
In the code hereafter I want to change the color inside the first disk and the first rectangle. Here is what I tried, but it is not working.
Any tip would be appreciated.
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta name=viewport content='width=device-width, initial-scale=1'>
<title>SVG-ID-Test</title>
</head>
<body>
<svg id="theSVGOne" width="200" height="300" fill="#d55">
<circle id="theCircle" cx="100" cy="75" r="50"
stroke="firebrick" fill="#ddd" stroke-width="3" />
<rect id="theRectangle" x="30" y="140" width="110" height="30"
stroke="black" fill="#ddd" stroke-width="3" />
</svg>
<svg id="theSVGTwo" width="200" height="200">
<circle id="theCircle" cx="100" cy="75" r="50"
stroke="firebrick" fill="#ddd" stroke-width="3" />
<rect id="theRectangle" x="30" y="140" width="110" height="30"
stroke="black" fill="#ddd" stroke-width="3" />
</svg>
<div id="status"></div>
<script type='text/javascript'>
let svgOne = document.getElementById('theSVGOne')
let svgTwo = document.getElementById('theSVGTwo')
let statusPane = document.getElementById('status')
statusPane.innerHTML = 'svgOne => '+svgOne.childElementCount.toString()
let circOne = svgOne.firstChild
let rctOne = svgOne.lastChild
circOne.setAttribute(('fill', '#ec3'))
rctOne.setAttribute(('fill', '#e3c'))
</script>
</body>
You can use a class instead of id on the child elements (you can have as many duplicate class attributes as you want in the document) and then use querySelector on each svg to target specific children.
let svgOne = document.getElementById('theSVGOne')
let svgTwo = document.getElementById('theSVGTwo')
let statusPane = document.getElementById('status')
statusPane.innerHTML = 'svgOne => '+svgOne.childElementCount.toString()
let circOne = svgOne.querySelector(".theCircle")
let rctOne = svgOne.querySelector(".theRectangle")
circOne.setAttribute('fill', '#ec3')
rctOne.setAttribute('fill', '#e3c')
let circTwo = svgTwo.querySelector(".theCircle")
let rctTwo = svgTwo.querySelector(".theRectangle")
circTwo.setAttribute('fill', 'blue')
rctTwo.setAttribute('fill', 'green')
<svg id="theSVGOne" width="200" height="300" fill="#d55">
<circle class="theCircle" cx="100" cy="75" r="50" stroke="firebrick" fill="#ddd" stroke-width="3" />
<rect class="theRectangle" x="30" y="140" width="110" height="30" stroke="black" fill="#ddd" stroke-width="3" />
</svg>
<svg id="theSVGTwo" width="200" height="200">
<circle class="theCircle" cx="100" cy="75" r="50" stroke="firebrick" fill="#ddd" stroke-width="3" />
<rect class="theRectangle" x="30" y="140" width="110" height="30" stroke="black" fill="#ddd" stroke-width="3" />
</svg>
<div id="status"></div>
Don't use IDs
While technically you can have duplicate ids in the DOM (and make it work), this is very bad practise. Id stands for identifier and should be unique to be able to "tell them apart". You can't do that with identical ids (which theCircle are we talking about?).
Problem is, html won't complain about duplicate ids (no errors are thrown).
To actually get feedback on right or wrong markup you can go to w3's validator and check your html there.
Eg. Error: Duplicate ID theCircle.
Working (but wrong) example using id:
let svgOne = document.getElementById('theSVGOne')
let svgTwo = document.getElementById('theSVGTwo')
let statusPane = document.getElementById('status')
statusPane.innerHTML = 'svgOne => '+svgOne.childElementCount.toString()
let circOne = svgOne.getElementById("theCircle")
let rctOne = svgOne.getElementById("theRectangle")
circOne.setAttribute('fill', '#ec3')
rctOne.setAttribute('fill', '#e3c')
let circTwo = svgTwo.getElementById("theCircle")
let rctTwo = svgTwo.getElementById("theRectangle")
circTwo.setAttribute('fill', 'pink')
rctTwo.setAttribute('fill', 'teal')
<svg id="theSVGOne" width="200" height="300" fill="#d55">
<circle id="theCircle" cx="100" cy="75" r="50" stroke="firebrick" fill="#ddd" stroke-width="3" />
<rect id="theRectangle" x="30" y="140" width="110" height="30" stroke="black" fill="#ddd" stroke-width="3" />
</svg>
<svg id="theSVGTwo" width="200" height="200">
<circle id="theCircle" cx="100" cy="75" r="50" stroke="firebrick" fill="#ddd" stroke-width="3" />
<rect id="theRectangle" x="30" y="140" width="110" height="30" stroke="black" fill="#ddd" stroke-width="3" />
</svg>
<div id="status"></div>
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>
How I can do this star right with svg? I must use svg and I try with points but not work. I mustn't use element path. Thanks.
<!DOCTYPE html>
<html>
<body>
<svg width="100" height="100">
<circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" />
<circle cx="50" cy="50" r="30" stroke="red" stroke-width="10" fill="blue" />
<polygon points="50,25 30,80 75,40 25,40 70,70" style="fill:white;"/>
</svg>
</body>
</html>
If you want to get the most accurate points for the pentastar, you can get them easily from the underlying pentagon.
A simple js function to obtain these points is something like this (REPL, which by the way you can use for polygons with any n edges):
var n = 5;
var points = [];
for (var i=0; i < n; i++) {
var x = 50;
var y = -50;
var r = 25;
points.push([x + r * Math.sin(2 * Math.PI * i / n),
y + r * Math.cos(2 * Math.PI * i / n)]);
}
Result is the pentagon points clock-wise, starting at the top (use all values as positive ones):
[ [ 50, -25 ],
[ 73.77641290737884, -42.27457514062631 ],
[ 64.69463130731182, -70.22542485937369 ],
[ 35.30536869268818, -70.22542485937369 ],
[ 26.22358709262116, -42.27457514062632 ] ]
Your order would be points[x], where x = 0, 3, 1, 4, 2.
And using them for your example rounded to the nearest pixel:
<!DOCTYPE html>
<html>
<body>
<svg width="100" height="100">
<circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" />
<circle cx="50" cy="50" r="30" stroke="red" stroke-width="10" fill="blue" />
<polygon points="50,25 35,70 73,42 26,42 65,70" style="fill:white;"/>
</svg>
</body>
</html>
This seems a bit closer.
<!DOCTYPE html>
<html>
<body>
<svg width="100" height="100">
<circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" />
<circle cx="50" cy="50" r="30" stroke="red" stroke-width="10" fill="blue" />
<polygon points="50,25 35,70 73,42 26,42 65,70" style="fill:white;"/>
</svg>
</body>
</html>
updated to use #frhd's number, make community wiki,
please see his answer for calculations
Here with path
<svg width="100" height="100">
<circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" />
<circle cx="50" cy="50" r="30" stroke="red" stroke-width="10" fill="blue" />
<!-- <polygon points="50,25 30,80 75,40 25,40 70,70" style="fill:white;"/> -->
<path fill="#fff" d="m50,25 5,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z" />
</svg>
Sorry if this has already been answered, I am new to SO.
I am trying to create svg elements using jquery, and I have this code as part of an HTML page:
<svg viewBox="0 0 1000 500">
<defs>
<clipPath id="clip">
<ellipse cx="100" cy="250" rx="200" ry="50" />
</clipPath>
</defs>
<g>
<path d="M 0,0 L 1000,0 1000,500 0,500"
fill="#9ADEFF" />
<path id="boat" stroke="none" fill="red"
d="M 100,175 L 300,175 300,325 100,325"
clip-path="url(#clip)" />
</g>
<g id="0002" width="100" height="100%"
transform="translate(1000)">
<line x1="50" y1="0" x2="50" y2="300"
stroke="green" stroke-width="100" />
</g>
</svg>
and this Javascript (with jQuery 1.9):
var id = 10000,
coinArray = []
function generateNextLine(type) {
$('svg').append($(type()))
return $('svg')[0]
}
function idNo() {
id++
return ((id-1)+"").substr(-4)
}
function random(x,y) {
if (!y) {
y=x
x=0
}
x=parseInt(x)
y=parseInt(y)
return (Math.floor(Math.random()*(y-x+1))+x)
}
function coins() {
coinArray[id%10000]=[]
var gID = idNo(), x,
g=$(document.createElement('g')).attr({
id: gID,
width: "100",
height: "100%"
})
while (3<=random(10)) {
var randomPos=random(50,450)
coinArray[(id-1)%10000][x] = randomPos
$(g).append(
$(document.createElement('circle'))
.attr({
cx: "50",
cy: randomPos,
r: "50",
fill: "yellow"
})
)
x++
}
return $(g)[0]
}
When I run generateNextLine(coins);, the svg adds this element:
<g id="0000" width="100" height="100%">
<circle cx="50" cy="90" r="50" fill="yellow"></circle>
</g>
However, the actual display of the svg doesn't change. If I add this code directly to the svg, it renders as I would expect, but running my javascript function does not seem to do anything to the display. I am using Chrome 28, on OS X Lion.
You must create SVG elements in the SVG namespace which means you can't do
document.createElement('g')
but in instead you must write
document.createElementNS('http://www.w3.org/2000/svg', 'g')
same for circle etc.