Appending D3.js Elements to Shadow DOM of Polymer Element - d3.js

I'm working on a Polymer (v1) project and one of my custom polymer elements needs to contain a D3 (v4) chart. D3 seems to operate pretty heavily on appending to the DOM. Unfortunately, Polymer seems pretty strict about how DOM manipulation is performed.
I have created a very simple version of the D3 chart I want to implement:
index.html
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
.bar {
fill: #0198E1;
}
</style>
</head>
<body>
<svg></svg>
<script>
var data = [100, 120, 130, 110, 150, 90];
const CHART_WIDTH = 126;
const CHART_HEIGHT = 160;
svg = d3.select('svg')
.attr("width", CHART_WIDTH)
.attr("height", CHART_HEIGHT);
svg.selectAll('rect')
.data(data).enter().append('rect')
.attr("x", function(d, i) {return i * 21})
.attr("y", function(d) {return CHART_HEIGHT - d})
.attr("height", function(d) {return d})
.attr("width", 20)
.attr("class", "bar");
</script>
</body>
</html>
I have attempted two solutions with the following files.
index.html
<!DOCTYPE html>
<html>
<head>
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="./d3-chart.html">
</head>
<body>
<d3-chart></d3-chart>
</body>
</html>
d3-lib.html
<script src="https://d3js.org/d3.v4.min.js"></script>
Attempted Solution 1
Use a combination of D3 and Polymer to select the target svg element and then perform d3 appends.
d3-chart.html
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="./d3-lib.html">
<dom-module id="d3-chart">
<template>
<style>
.bar {
fill: #0198E1;
}
</style>
<svg id="svg"></svg>
</template>
<script>
Polymer({
is: 'd3-chart',
properties: {
data: {
Type: Array,
value: [100, 120, 130, 110, 150, 90]
}
},
ready: function() {
const CHART_WIDTH = 126;
const CHART_HEIGHT = 160;
var svg = d3.select(this.$.svg)
.attr("width", CHART_WIDTH)
.attr("height", CHART_HEIGHT);
svg.selectAll('rect')
.data(this.data).enter().append('rect')
.attr("x", function(d, i) {return i * 21})
.attr("y", function(d) {return CHART_HEIGHT - d})
.attr("height", function(d) {return d})
.attr("width", 20)
.attr("class", "bar");
}
});
</script>
</dom-module>
This successfully displays the chart but the css style doesn't apply. I assume this is because Polymer doesn't 'know' about the new elements that have been appended.
Attempted Solution 2
Use D3 to select a new svg element (not in the DOM), perform D3 appends on the element, and use Polymer to append it to the DOM.
d3-chart.html
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="./d3-lib.html">
<dom-module id="d3-chart">
<template>
<style>
.bar {
fill: #0198E1;
}
</style>
</template>
<script>
Polymer({
is: 'd3-chart',
properties: {
data: {
Type: Array,
value: [100, 120, 130, 110, 150, 90]
}
},
ready: function() {
const CHART_WIDTH = 126;
const CHART_HEIGHT = 160;
var newSvgElement = document.createElement("svg");
var svg = d3.select(newSvgElement)
.attr("width", CHART_WIDTH)
.attr("height", CHART_HEIGHT);
svg.selectAll('rect')
.data(this.data).enter().append('rect')
.attr("x", function(d, i) {return i * 21})
.attr("y", function(d) {return CHART_HEIGHT - d})
.attr("height", function(d) {return d})
.attr("width", 20)
.attr("class", "bar");
Polymer.dom(this.root).appendChild(newSvgElement);
}
});
</script>
</dom-module>
This code successfully appends all elements to the DOM but nothing is displayed.
What is the proper way to integrate Polymer with D3?

I found a solution. I just had to add the following line to the beginning of the ready function in 'Attempted Solution 1'.
this.scopeSubtree(this.$.svg, true);
For more information:
Why is my SVG rendered by D3 inside a Polymer component unstyled?

Related

Cannot get D3 .on('mouseover', ...) to work

I'm learning D3 and I'm trying to display data infomation on a scatterplot by hovering on the SVG circles. I take the data from a csv file (data is on the Solar System, with planet names, masses and radiuses) and all the circles show up correctly but when I try to console.log the data information (for instance the mass) on mouseover it does not log anything. The mouseover action is functioning OK but the console tells me that the value requested is "undefined".
Where did I mess up? This is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<style type="text/css">
body{
background-color: black;
}
#chart{
background-color: black;
width: 800px;
height: 500px;
border: solid 1px white;
}
svg{
background-color: white;
}
.dot{
stroke: red;
stroke-width: 1px;
}
</style>
<script type="text/javascript" src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<div id="chart"></div>
<script type="text/javascript">
var width = 800;
var height = 500;
var circles;
var svg = d3.select('#chart')
.append('svg')
.attr('width', width + 'px')
.attr('height', height + 'px');
d3.csv('astro.csv').then(function(data){
xScale = d3.scaleLinear()
.domain([0,500])
.range([0, 800]);
circles = svg.selectAll('.dot')
.data(data)
.enter()
.append('circle')
.attr('class', '.dot')
.attr('cx', function(d){
return xScale(d.mass);
})
.attr('cy', 100)
.attr('r', 20)
.on('mouseover', function(d){
console.log(d.mass);
})
});
</script>
</body>
</html>
Since D3 V6, all mouse event handlers has an event passed as the first argument and data as the second. In the V5 or earlier, your code will work.
V6 or higher:
.on('mouseover', (event, d) => console.log(d.mass));
V5 or earlier:
.on('mouseover', d => console.log(d.mass));

Why are my axes not positioning over my bar chart in this d3.js graph?

I am working on a project to teach myself d3.js where I'm building a bar graph using an API of US GDP. However, now that I've added my axes, the axes place where I expected them to. However, they have pushed my bars down below them. Can anyone explain to me why my axes and bars are behaving this way, and what I can do to fix this?
let apiUrl = "https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json"
GDPGraph ()
function GDPGraph () {
fetch(apiUrl)
.then(response => response.json())
.then(data => {
// document.getElementById('GDP').innerHTML = JSON.stringify(data.data);
var width = document.documentElement.clientWidth,
height = document.documentElement.clientHeight * .8,
margin = 10;
var GDPbyQuarter = [],
yearQuarter =[]
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
for(i = 0; i < data.data.length; i++){
GDPbyQuarter.push(data.data[i][1])
}
for(i = 0; i < data.data.length; i++){
yearQuarter.push(data.data[i][0])
}
console.log(yearQuarter)
// console.log('calculated width:',((1/GDPbyQuarter.length)*width),'calculated legnth:', GDPbyQuarter.length, 'total width:', width)
const maxNumber = d3.max(GDPbyQuarter)
console.log(maxNumber);
const xScale = d3.scaleLinear()
.domain([0, GDPGraph.length])
.range([0, width]);
const yScale = d3.scaleLinear()
.domain([0, maxNumber])
.range([0, height]);
d3.select('body')
.selectAll('div')
.data(GDPbyQuarter)
.enter()
.append('svg')
.attr('class', 'bar')
.style('height', (d) => yScale(d)+'px')
.style('width', ((1/(GDPbyQuarter.length+margin)*width)+'px'))
.append('title')
.attr('class', 'title')
.text((d, i) => yearQuarter[i]+': ' + d)
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
svg.append("g")
.call(xAxis)
.attr("transform", "translate(0," + (height -100) + ")");
svg.append('g')
.attr('transform', 'translate(10,0)')
.call(yAxis)
})
}
.bar {
width: 25px;
height: 100px;
display: inline-block;
background-color: red;
}
.bar:hover {
background-color: blue;
}
.title {
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div>
<h1>US GDP By Quarter</h1>
<h3>FreeCodeCamp</h3>
</div>
<div id="GDP">
</div>
<script src='js/app.js'></script>
</body>
</html>
You are creating an svg node for each bar. Try inspecting the DOM using the Chrome Dev tools
The answer is that I had to fix my positioning. I ended up making the containing div position:relative; and then my elements within the div position:absolute;
#GDP {
position: relative;
}
#GDP > * {
position: absolute;
}
g {
position: absolute;
}

d3 js markers don't display on map

I have a problem with d3.js. Markers display below the map instead on it.
Here's the code:
<!DOCTYPE html>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="http://d3js.org/colorbrewer.v1.min.js"></script>
<script src="http://bl.ocks.org/emeeks/raw/f3105fda25ff785dc5ed/tile.js" type="text/javascript">
</script>
<script src="http://bl.ocks.org/emeeks/raw/f3105fda25ff785dc5ed/d3.quadtiles.js" type="text/javascript">
</script>
<script src="http://bl.ocks.org/emeeks/raw/f3105fda25ff785dc5ed/d3.geo.raster.js" type="text/javascript">
</script>
<script src="https://rawgit.com/emeeks/d3-carto-map/master/d3.carto.map.js" type="text/javascript">
</script>
<style>
.markerButton {
position: fixed;
top: 20px;
cursor: pointer;
z-index: 99;
}
body {
background: #fcfcfa;
}
.stroke {
fill: none;
stroke: #000;
stroke-width: 3px;
}
.fill {
fill: #fff;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
.land {
fill: #222;
}
.boundary {
fill: none;
stroke: #fff;
stroke-width: .5px;
}
</style>
<body onload="makeSomeMaps()">
<script>
var width = 960,
height = 500;
var projection = d3.geo.mollweide()
.scale(165)
.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("defs").append("path")
.datum({type: "Sphere"})
.attr("id", "sphere")
.attr("d", path);
svg.append("use")
.attr("class", "stroke")
.attr("xlink:href", "#sphere");
svg.append("use")
.attr("class", "fill")
.attr("xlink:href", "#sphere");
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("world-50m.json", function(error, world) {
if (error) throw error;
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
d3.select(self.frameElement).style("height", height + "px");
function circleMarker() {
var sizeScale = d3.scale.linear().domain([0,100,2000]).range([2,10,20]).clamp(true);
var randomDatapoint = "r" + Math.ceil(Math.random() * 7);
d3.selectAll("g.marker").selectAll("*").remove();
d3.selectAll("g.marker").append("circle")
.attr("class", "metro")
.attr("r", function(d) {return sizeScale(d[randomDatapoint])})
}
function makeSomeMaps() {
map = d3.carto.map();
d3.select("#map").call(map);
map.centerOn([-99,39],"latlong");
map.setScale(4);
map.refresh();
cityLayer = d3.carto.layer.csv();
cityLayer
.path("cities.csv")
.label("Metro Areas")
.cssClass("metro")
.renderMode("svg")
.x("x")
.y("y")
.clickableFeatures(true)
.on("load", function(){console.log(cityLayer.dataset())});
map.addCartoLayer(cityLayer);
}
</script>
<div id="map">
<button style="left: 340px;" class="markerButton" onclick="circleMarker();">Circle Marker</button>
</div>
</body>
</html>
Please find link to site live:
http://www.ewelinawoloszyn.com/mymap/d3_projection03.html
Perhaps it's something to do with map.centerOn?
Many thanks in advance for your help.
I suspect it's the asynchronous rendering nature of html/javascript. I had a similar issue in the past and I solved it by defining different layers/groups for the map and the markers. Something like:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var map = svg.append("g");
var markers = svg.append("g");
and then append the map and the markers in these predefined layers. The markers will appear on top of the map because you define their layer after the map layer. In your case you should probably define a layer for each element (graticule layer etc.)

group bar, from 3rd onwards coming separately

Here is my code.
http://jsfiddle.net/x8rax/9/
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title> - jsFiddle demo</title>
<script type='text/javascript' src='http://d3js.org/d3.v3.min.js'></script>
<link rel="stylesheet" type="text/css" href="/css/result-light.css">
<style type='text/css'>
.chart rect {
fill:#98abc5 ;
}
.chart2 rect {
fill:#8a89a6 ;
}
.chart3 rect {
fill:#7b6888 ;
}
.chart4 rect {
fill:#6b486b ;
}
.chart5 rect {
fill:#a05d56 ;
}
.chart6 rect {
fill:#d0743c ;
}
.chart7 rect {
fill:#ff8c00 ;
}
.chart {
position: absolute;
}
.chart text {
fill: white;
font: 10px sans-serif;
text-anchor: end;
}
</style>
<script type='text/javascript'>//<![CDATA[
window.onload=function(){
var data = [4, 8, 15, 78, 100, 90];
var width = 420,
barHeight = 80;
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, width]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight +")"; });
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 70);
var data = [10, 30, 20, 1000, 9, 500];
var width = 420,
barHeight = 80;
var chart = d3.select(".chart2")
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + (i * barHeight + barHeight - 70 ) +")"; });
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 70);
var data = [500, 100, 60, 20, 1000, 9];
var width = 420,
barHeight = 80;
var chart = d3.select(".chart3")
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + (i * barHeight + barHeight - 60 ) +")"; });
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 70);
}//]]>
</script>
</head>
<body>
<svg class="chart"></svg>
<svg class = "chart2"></svg>
<svg class = "chart3"></svg>
<svg class = "chart4"></svg>
<svg class = "chart5"></svg>
<svg class = "chart6"></svg>
<svg class = "chart7"></svg>
</body>
</html>
As you can see, 3rd onwards it's coming separately. I want to create the group bar chart with 7 different bars. I am right now stuck in 3rd.
It is coming separately because the svg tags with classes chart2 onwards are not positioned absolutely.
It works if you add a css rule as follows:
.chart2, .chart3, .chart4, .chart5, .chart6, .chart7{
position: absolute;
}
Please note however, that your current approach is not generic and you end up needing to create specific svg tags and repeating the bar chart creation code.
Please refer the grouped bar example at: http://bl.ocks.org/mbostock/3887051

D3 - how to include č,š,ž letters in D3

I'm beginner in D3. I would like to know how to include in text letters like č,š,ž..
I included line with utf-8:
<meta charset="utf-8">
I also tried this in the head:
<script type="text/javascript" charset="utf-8" src="D3/d3.js"></script>
Letters like č,š or ž are shown like �. That is the same with letters from .text("Število...") and also with words from loaded file *.csv.
I tried change letter č with code
č
but no success.
What can I do? Please help.
Here is my code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" >
<title>Slo</title>
<script type="text/javascript" src="D3/d3.js"></script>
<style type="text/css">
.naslov {
fill: #505050;
font: 18px sans-serif;}
.podnaslov {
fill: #777;
font: 12px sans-serif;}
.besedilo {
fill: #777;
font: 8px sans-serif;}
.land {
fill: #ccc;
stroke: #fff;
stroke-linejoin: round;
stroke-linecap: round;
stroke-width: .5;}
</style>
</head>
<body>
<script type="text/javascript">
var w = 1000;
var h = 450;
var formatNumber = d3.format(",.0f");
var radius = d3.scale.sqrt()
.domain([0, 1e6])
.range([1, 90]);
//Projekcija
var projection = d3.geo.mercator()
.scale(11000)
.translate([-2480,10270]);
//Path generator
var path = d3.geo.path()
.projection(projection);
//Create SVG element
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
//Napisi
var naslov = svg.append("text")
.text("PREBIVALSTVO V SLOVENIJI PO NASELJIH")
.attr("class", "naslov")
.attr("x",15).attr("y",30);
var podnaslov = svg.append("text")
.text("LETA 2002 in 2013")
.attr("class", "podnaslov")
.attr("x",15).attr("y",60);
var besedilo1 = svg.append("text")
.attr("class", "besedilo")
.text("Velikost kroga kaže na stevilo prebivalcev v naselju leta 2013.").attr("x",15).attr("dy",100);
var besedilo2 = svg.append("text")
.attr("class", "besedilo")
.text("Prikazani so podatki za naselja, ki so imela leta 2013 vec kot 1300 prebivalcev.").attr("x",15).attr("dy",112);
var besedilo3 = svg.append("text")
.attr("class", "besedilo")
.text("Zadrži miško na krogih in si oglej podatke.").attr("x",15).attr("dy",124);
//Load in GeoJSON data
d3.json("Obstara.geojson", function (data){
svg.selectAll("path")
.data(data.features)
.enter()
.append("path")
.attr("d", path)
.attr("class","land")
});
//Load mesta
d3.csv("Mesta_02_13.csv", function(error,podatki) {
if (error) return console.error(erorr);
svg.selectAll("circle")
.data(podatki)
.enter()
.append("circle")
.style("fill", function(d) {
if (d.trinajst-d.dva <= 0) {return "#9D5355"}
else { return "#006699"};})
.attr("cx", function(d) {return projection([d.lon, d.lat])[0];})
.attr("cy", function(d) {return projection([d.lon, d.lat])[1];})
.attr("r", function(d) { return radius(d.trinajst); })
.style("stroke","grey")
.style("stroke-width", '0.3px')
.style("opacity", 0.6)
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function(){d3.select(this).style("fill", function(d) {
if (d.trinajst-d.dva <= 0) {return "#9D5355"}
else { return "#006699" };});})
.append("title")
.text(function(d) { return d.name + "\n2002: " + d.dva + "\n2013: " + d.trinajst; })
});
</script>
</body>
I loaded csv file where are also included names with č,š,ž letters:
name,lat,lon,dva,trinajst
Ljubljana,46.0605,14.5166,258873,274826
Maribor,46.5620,15.6482,93847,94809
Celje,46.2524,15.2765,37834,37490
Mengeš,46.1686,14.5731,5557,6202
...
As may others have the same problem:
First you need to change the encoding of the document and save it once in UTF-8 format, and then put your UTF-8 texts inside that document.

Resources