This is a weird one. It's also a bit long so apologies in advance. update - it ended up being 2 problems see my answer below.
Here's my error: EXCEPTION: this.svg.selectAll(...).data(...).enter is not a function
I have an angular-cli client and a node api server. I can retrieve a states.json file from a service using an observable (code below). d3 likes the file and displays the expected US map.
The moment I change the target of the service in my api server from a file to a bluemix-cloudant server I get the error above in my client.
When I console.log the output in a variation using ngOnInit, initially mapData prints as an empty array and the error gets thrown. This is the obvious source of the error since there's no data, but the Chrome debugger shows the get request pending. When the request completes, the data prints as expected in the console.
- angular-cli version 1.0.0-beta.26
- angular version ^2.3.1
- d3 version ^4.4.4
- rxjs version ^5.0.1
map.component.ts:
import { Component, ElementRef, Input } from '@angular/core';
import * as D3 from 'd3';
import '../rxjs-operators';
import { MapService } from '../map.service';
@Component({
selector: 'map-component',
templateUrl: './map.component.html',
styleUrls: ['./map.component.css']
})
export class MapComponent {
errorMessage: string;
height;
host;
htmlElement: HTMLElement;
mapData;
margin;
projection;
path;
svg;
width;
constructor (private _element: ElementRef, private _mapService: MapService) {
this.host = D3.select(this._element.nativeElement);
this.getMapData();
this.setup();
this.buildSVG();
}
getMapData() {
this._mapService.getMapData()
.subscribe(
mapData => this.setMap(mapData),
error => this.errorMessage = <any>error
)
}
setup() {
this.margin = {
top: 15,
right: 50,
bottom: 40,
left: 50
};
this.width = document.querySelector('#map').clientWidth - this.margin.left - this.margin.right;
this.height = this.width * 0.6 - this.margin.bottom - this.margin.top;
}
buildSVG() {
this.host.html('');
this.svg = this.host.append('svg')
.attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.top + this.margin.bottom)
.append('g')
.attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');
}
setMap(mapData) {
this.mapData = mapData;
this.projection = D3.geoAlbersUsa()
.translate([this.width /2 , this.height /2 ])
.scale(650);
this.path = D3.geoPath()
.projection(this.projection);
this.svg.selectAll('path')
.data(this.mapData.features)
.enter().append('path')
.attr('d', this.path)
.style('stroke', '#fff')
.style('stroke-width', '1')
.style('fill', 'lightgrey');
}
}
map.service.ts:
import { Http, Response } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class MapService {
private url = 'http://localhost:3000/api/mapData';
private socket;
constructor (private _http: Http) { }
getMapData(): Observable<any> {
return this._http.get(this.url)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body.data || {};
}
private handleError(error: any) {
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg);
return Promise.reject(errMsg);
}
}
Is this a function of being Async and the call to the data takes too long for d3?
I had hopes that this question Uncaught TypeError: canvas.selectAll(...).data(...).enter is not a function in d3 would offer some insight but I don't see any.
Any help or insight is greatly appreciated!
EDIT: Here's a screenshot of the headers section from Chrome per Marks request below. The response tab shows the data properly coming across as a GeoJSON object. I've also copied that response into a file locally and used it as a map source with positive results.
Data Tests so far: GeoJSON file (2.1mb)
- Local file, local server: Success (response time 54ms)
- Same file, remote server: D3 errors before data returned to browser (750ms)
- API call from remote server: D3 errors before data returned to browser (2.1 s)
mapData.features
? – Rentschler"features": [ { "type": "Feature", "properties": { "GEO_ID": "0400000US01", "STATE": "01", "NAME": "Alabama", "LSAD": "", "CENSUSAREA": 50645.326000 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -88.124658, 30.283640 ], [ -88.086812, 30.259864 ], [ -88.074854, 30.249119 ], [ -88.075856, 30.246139 ], [ -88.078786, 30.245039 ], ...},{next state and so on...}]
– Moribundthis.mapData.features
is what you expect (must be an array) inside thesetMap
function? – RentschlersetMap()
get called twice? Once with an empty array, and once with the expected array? – IvettsetMap(mapData)
only showed executing once. – Moribundangular
code below. My guess, though, is that your data coming from you API is somehow malformed, times out or is otherwise bogus. Can you post a screenshot of theheader
tab from the chrome network tool, when you make the request? – Sollows