rveciana / d3-composite-projections

Set of d3 projections for showing countries distant lands together
http://rveciana.github.io/d3-composite-projections/
Other
98 stars 22 forks source link

.scale ? #17

Closed joyguy55 closed 6 years ago

joyguy55 commented 6 years ago

I don't believe the scale works the way you imagined it would. Or maybe I'm confused about how you intended it to be called but it does not seem that clear.

   createMap = () => {
        const node = this.node;
        const { geojson } = this.state;
        const projection = d3Projections.albersUsa.scale(1000);
        const path = geoPath().projection(projection)

        console.log(d3Projections)
        select(node)
            .selectAll('path')
            .data(geojson)
            .enter()
            .append('path')
            .attr('d', path)
            .attr('class', 'state')
            .style('stroke', '#fff')
            .style('stroke-width', '1')
            .on('click', this.handleToolTip.bind(this))
            .on('mouseover', function(state) {
                select(this).style('fill', 'grey');
            })
            .on('mouseout', function(state) {
                select(this).style('fill', 'black');
            })

    }
rveciana commented 6 years ago

Hi, I don't understand your problem. I made this example showing how it works, and I think that is ok...

https://beta.observablehq.com/@rveciana/d3-composite-projections-issue17

joyguy55 commented 6 years ago
screen shot 2018-06-29 at 3 01 06 pm
joyguy55 commented 6 years ago

When I import albersUsa the appropriate paths come through but the scale function is undefined. in just looking at your node modules I see two definitions of GeoAlbers one with and one without scale. Is there a special way you are importing by any chance?

rveciana commented 6 years ago

OK, then your problem is that you aren't loading the library properly, that's why is undefined. If you are loading the library directly from the html page, like here, use:

const projection = d3Projections.albersUsa.scale(1000);

If you want to use browserify:

var d3_composite = require("d3-composite-projections"); var projection = d3_composite.geoAlbersUsa();

Does it work with that?

rveciana commented 6 years ago

Oh, now I see. I'll take a look.

rveciana commented 6 years ago

Can you please tell me where do you see the different definitions of AlbersUsa? The ones at the src directory have both the scale function... Are you using ES6 with imports or the traditional browser html including all the library in a script tag?

joyguy55 commented 6 years ago

I'm using ES6 with imports.

rveciana commented 6 years ago

OK, now I see. Change const projection = d3Projections.albersUsa.scale(1000); to const projection = d3Projections.geoAlbersUsa().scale(1000);

It's working now?

joyguy55 commented 6 years ago

d3Projections does not import like so

import { d3Projections } from 'd3-composite-projections';

It will always return undefined. I can however import like so

import { geoAlbersUsa } from 'd3-composite-projections';

However it will not render the map correctly it renders this weird box.

screen shot 2018-07-01 at 5 37 16 pm

The only import that renders the map properly is

import { albersUsa } from from 'd3-composite-projections';
rveciana commented 6 years ago

I made this example with react, like in this example, using the import style you said, and the result seems ok too... which is logical, because the functions are copies of the original d3 code. Also, the strange squares are not the ones after calling .getCompositionBorders(), right? Maybe if you put a larger part of the code...

screenshot-2018-7-2 react app

import logo from './logo.svg';
import { geoAlbersUsa } from 'd3-composite-projections';
import { geoPath } from "d3-geo"
import { feature } from "topojson-client"
import './App.css';

class App extends Component {
  constructor() {
    super()
    this.state = {
      worldData: [],
    }
  }
  projection() {
    return geoAlbersUsa()
      .scale(1000)
      .translate([ 800 / 2, 450 / 2 ])
}
  componentDidMount() {
    fetch("/us.json")
      .then(response => {
        if (response.status !== 200) {
          console.log(`There was a problem: ${response.status}`)
          return
        }
        response.json().then(worldData => {
          this.setState({
            worldData: feature(worldData, worldData.objects.states).features,
          })
        })
      })
}

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <svg width={ 800 } height={ 450 } viewBox="0 0 800 450">
        <g className="countries">
          {
            this.state.worldData.map((d,i) => (
              <path
                key={ `path-${ i }` }
                d={ geoPath().projection(this.projection())(d) }
                className="country"
                fill={ `rgba(38,50,56,${1 / this.state.worldData.length * i})` }
                stroke="#FFFFFF"
                strokeWidth={ 0.5 }
              />

            ))
          }

        </g>
        <g className="projBorders">
          <path fill='None' stroke='#333' d={this.projection().getCompositionBorders()}/>
        </g>

</svg>
      </div>
    );
  }
}

export default App;
joyguy55 commented 6 years ago
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import * as d3 from 'd3';
import { geoAlbersUsa } from 'd3-composite-projections';
import { geoPath } from "d3-geo"
import { feature } from 'topojson-client';
import { select } from 'd3-selection';
import ToolTip from './ToolTip'
import './UsMap.css';

class WorldMap extends Component {
    constructor() {
        super();
        this.state = {
            geojson: [],
            toolTipIsOpen: false,
            selectedState: {},
        };
    }

    componentDidMount() {
        fetch('https://d3js.org/us-10m.v1.json').then(response => {
            if (response.status !== 200) {
                console.log(`There was a problem: ${response.status}`);
                return;
            }
            response
                .json()
                .then(usdata => {
                    this.setState({
                        geojson: feature(usdata, usdata.objects.states).features
                    });
                });
        });

        this.createMap()
    }

    componentDidUpdate() {
        this.createMap()
    }

    handleBlur = () => {
        console.log('blurHandled')
        this.setState({toolTipIsOpen: false})
    }

    handleToolTip = (state) => {
        const selectedState = this.props.electionData.find((data)=>{
            return data.State_GeoJson_ID === parseInt(state.id,0)
        })
        this.setState({ 
            top: d3.event.pageY - 250,
            left: d3.event.pageX > 700 ? d3.event.pageX - 513 : d3.event.pageX + 25,
            toolTipIsOpen: true,
            stateId: state.id,
            selectedState,
        })
    }

    createMap = () => {
        const node = this.node;
        const { geojson } = this.state;
        const projection = geoAlbersUsa().scale(1000)
        const path = geoPath().projection(projection)

        select(node)
            .selectAll('path')
            .data(geojson)
            .enter()
            .append('path')
            .attr('d', path)
            .attr('class', 'state')
            .style('stroke', '#fff')
            .style('stroke-width', '1')
            .on('click', this.handleToolTip.bind(this))
            .on('mouseover', function(state) {
                select(this).style('fill', 'grey');
            })
            .on('mouseout', function(state) {
                select(this).style('fill', 'black');
            })

    }

    // Can't use arrow functions on d3 events?

    render() {
        const { toolTipIsOpen, top, left, selectedState} = this.state
        return ( 
        <Fragment> 
            <svg
                id="map" 
                ref={ node => {this.node = node}}
                width={600}
                height={600}
            />
            { toolTipIsOpen ? 
            <ToolTip
                handleBlur={this.handleBlur}
                top={top} 
                left={left}
                selectedState={selectedState}
            /> : <span/> }
        </Fragment >
        );
    }
}
joyguy55 commented 6 years ago

I imagine that your method is probably preferred. I was passing a ref to a node which is used when you want to retain control of d3 transitions. But I don't imagine needing too many transitions with the map.

joyguy55 commented 6 years ago

Also you seem to be setting up a fetch method for getting your map from what appears to be a non usable url. Unless your fetching it locally? But then wouldn't you just import it directly? That might be why my map is rendering so funny if the JSON objects aren't identical.

joyguy55 commented 6 years ago

![Uploading screen_shot.png…]()

Don't know if this matters but I get this odd warning notice in Visual Studio code regarding the node module. Sorry for the weird picture had to take it with my phone as it only appears when I am scrolling over the package.

rveciana commented 6 years ago

I copied the example from this page and just changed the projection: https://medium.com/@zimrick/how-to-create-pure-react-svg-maps-with-topojson-and-d3-geo-e4a6b6848a98 So yes, many things could be different... I have never tried to do d3 transitions in react this way. The us.json file is stored in the public directory, but you are right. Actually, I saw some examples where the topojson is converted into an object and imported directly, since the data won't change. Here's another example that loads the file as in my case: https://medium.com/@zimrick/how-to-create-pure-react-svg-maps-with-topojson-and-d3-geo-e4a6b6848a98 Finally, I can't see any picture with the VS code warning! I understand that the projection worked well, by the way, ir you still have problems?

joyguy55 commented 6 years ago

Yeah the fetch doesn't work.

screen shot 2018-07-02 at 1 44 51 pm

    componentDidMount() {
        fetch("/us.json")
        .then(response => {
          if (response.status !== 200) {
            console.log(`There was a problem: ${response.status}`)
            return
          }
          response.json().then(worldData => {
            this.setState({
              worldData: feature(worldData, worldData.objects.states).features,
            })
          })
        })
    }
rveciana commented 6 years ago

I think that this is an issue with the JSON file. Have you looked inside? The one I'm using is this one: https://bl.ocks.org/rveciana/raw/ee2119324e835e1bad42d0e4c1b9ab0d/us.json The error message says that the first position in the json is a <, which is invalid and seems the beginning of an HTML file or similar...

joyguy55 commented 6 years ago

Ok I would suggest showing the source for that type of particular map. Because its obviously critical to your module I was using a completely different topojson map that wasn't working anyhow seems like a pretty critical point. Super thanks for you help.

rveciana commented 6 years ago

You are welcome! It's true that the docs should be better, and adapted to React and the new stuff people is using.