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

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));

Related

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;
}

How to Show D3.js Chart inside Liferay 7 MVC Portlet

I am very new to Liferay.
I would like to show my graph created by D3.js inside of a portlet.
I have created the default MVC portlet.
However, I have no idea how to show my graph inside a MVC portlet.
Can you please advise?
<!DOCTYPE html>
<html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
.chart div {
font: 10px sans-serif;
background-color: steelblue;
text-align: right;
padding: 3px;
margin: 1px;
color: white;
}
</style>
<body>
<div class="chart">
</div>
<script>
var data = [30, 86, 168, 281, 303, 365];
d3.select(".chart")
.selectAll("div")
.data(data)
.enter()
.append("div")
.style("width", function(d) { return d * 2 + "px"; })
.text(function(d) { return '$ ' + d; });
</script>
</body>
</html>

Appending D3.js Elements to Shadow DOM of Polymer Element

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?

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.)

d3 js circleMarker is not defined(…)

I'm trying to add markers to d3.js map.Unfortunately I get error after clicking button ' circleMarker is not defined(…)'.
Here's my 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 makeSomeMaps() {
map = d3.carto.map();
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>
I'm following example form here:
http://bl.ocks.org/emeeks/f8c0220c54ec8347ea95
Perhaps it doesn't work because I use different type of map than in example?
Any advice much appreciated.
Many thanks in advance.

Resources