D3js with element base on data - javascript

I am drawing a graph where the elements may differ, for example:
[
{id: 1, type: "type1", name: "Some name" },
{id: 2, type: "type2", name: "Some name" },
{id: 3, type: "type2", name: "Some name" },
{id: 4, type: "type1", name: "Some name" }
]
Now if the element is of type=type1 would I like it to add
<g>
<rect width="10" height="10" fill="blue" />
<text x="0" y="0" fill="red">Some name</text>
</g>
if type=type2
<g>
<rect width="10" height="10" stroke="blue" />
<rect x="15" y="15" width="10" height="10" stroke="blue" />
<text x="0" y="0" fill="red">Some name</text>
</g>
How would I be able to do this using D3js?

I agree with Lars...but...if what you are really looking for is a cool outliner, look no more...I offer you the Kitchen Sink Outliner...with its own kitchen sink scale system ;-)
d1 = 30;
d2 = d1 + 5;
NOTE:...been working too hard...need some levity...

Related

Using SVG with IDs for subcomponents

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>

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>

How do I print a list in an SVG element in react?

I am trying to print a list inside of a rectangular element in React. Currently, the list is printing correctly (so I know am formatting everything correctly in regards to including li tags around the items, etc.) above the rectangle, and the rectangle prints below it. The text "Title Here" prints correctly inside the box. The problem is that the list must be represented by a state, and is of unknown size. How do I print this list inside the rectangle?
<div>
<ul>{this.state.ListText}</ul>
<svg width="100%" height="100%" viewBox="0 0 610 150" version="1.1">
<defs>
<linearGradient x1="51.7971499%" y1="47.5635228%" x2="52.4921324%" y2="48.1654036%" id="linearGradient-1">
<stop stopColor="#9198A1" offset="0%"></stop>
<stop stopColor="#888D95" offset="100%"></stop>
</linearGradient>
<rect id="path-2" x="0" y="0" width="610" height="150" rx="7.2"></rect>
</defs>
<g id="Patient-Page" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="Desktop-HD" transform="translate(-732.000000, -106.000000)">
<g id="Medications" transform="translate(732.000000, 106.000000)">
<g id="Rectangle-7">
<use fillOpacity="0.55" fill="url(#linearGradient-1)" fillRule="evenodd" xlinkHref="#path-2"></use>
</g>
<text fontFamily="Helvetica" fontSize="32" fontWeight="normal" fill="#000000">
<tspan x="165" y="39">Title Here</tspan>
</text>
</g>
</g>
</g>
</svg>
</div>
You can use a foreignObject to embed HTML in your SVG.
You can use jQuery to manipulate the foreignObject DOM properties and to append HTML such as your list.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<ul>{this.state.ListText}</ul>
<span id='cont'>
<svg width="100%" height="100%" viewBox="0 0 610 150" version="1.1">
<defs>
<linearGradient x1="51.7971499%" y1="47.5635228%" x2="52.4921324%" y2="48.1654036%" id="linearGradient-1">
<stop stopColor="#9198A1" offset="0%"></stop>
<stop stopColor="#888D95" offset="100%"></stop>
</linearGradient>
<rect id="path-2" x="0" y="0" width="610" height="150" rx="7.2"></rect>
</defs>
<g id="Patient-Page" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g id="Desktop-HD" transform="translate(-732.000000, -106.000000)">
<g id="Medications" transform="translate(732.000000, 106.000000)">
<g id="Rectangle-7">
<use fillOpacity="0.55" fill="url(#linearGradient-1)" fillRule="evenodd" xlinkHref="#path-2"></use>
</g>
<text fontFamily="Helvetica" fontSize="32" fontWeight="normal" fill="#000000" >
<tspan x="165" y="39">Title Here</tspan>
</text>
<foreignObject width="100" height="300"
requiredExtensions="http://www.w3.org/1999/xhtml">
<body xmlns="http://www.w3.org/1999/xhtml">
<p>Here is my list</p>
</body>
</foreignObject>
</g>
</g>
</g>
</svg>
</span>
</div>
<script>
var ul = $('<ul>');
["Item 1", "Item 2", "Item 3"].forEach(function(item) {
ul.append($("<li>").text(item));
});
$('foreignObject').append(ul);
$('foreignObject body').append(ul);
</script>

Captain America with 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>

jQuery-added svg elements do not show up

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.

Categories

Resources